Skip to content

Commit 2236dde

Browse files
committed
Add experimental support for ICE TCP
See pion/ice issue and PR: - https://github.com/pion/ice/tree/issue-196 - pion/ice#226
1 parent fe4c819 commit 2236dde

File tree

9 files changed

+272
-13
lines changed

9 files changed

+272
-13
lines changed

examples/ice-tcp/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# ice-tcp
2+
ice-tcp demonstrates Pion WebRTC's ICE TCP abilities.
3+
4+
## Instructions
5+
6+
### Download ice-tcp
7+
This example requires you to clone the repo since it is serving static HTML.
8+
9+
```
10+
mkdir -p $GOPATH/src/github.com/pion
11+
cd $GOPATH/src/github.com/pion
12+
git clone https://github.com/pion/webrtc.git
13+
cd webrtc/examples/ice-tcp
14+
```
15+
16+
### Run ice-tcp
17+
Execute `go run *.go`
18+
19+
### Open the Web UI
20+
Open [http://localhost:8080](http://localhost:8080). This will automatically start a PeerConnection. This page will now prints stats about the PeerConnection. The UDP candidates will be filtered out from the SDP.
21+
22+
Congrats, you have used Pion WebRTC! Now start building something cool

examples/ice-tcp/index.html

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<html>
2+
<head>
3+
<title>ice-tcp</title>
4+
</head>
5+
6+
<body>
7+
<button onclick="window.doSignaling(true)"> ICE TCP </button><br />
8+
9+
10+
<h3> ICE Connection States </h3>
11+
<div id="iceConnectionStates"></div> <br />
12+
13+
<h3> Inbound DataChannel Messages </h3>
14+
<div id="inboundDataChannelMessages"></div>
15+
</body>
16+
17+
<script>
18+
let pc = new RTCPeerConnection()
19+
let dc = pc.createDataChannel('data')
20+
21+
dc.onmessage = event => {
22+
let el = document.createElement('p')
23+
el.appendChild(document.createTextNode(event.data))
24+
25+
document.getElementById('inboundDataChannelMessages').appendChild(el);
26+
}
27+
28+
pc.oniceconnectionstatechange = () => {
29+
let el = document.createElement('p')
30+
el.appendChild(document.createTextNode(pc.iceConnectionState))
31+
32+
document.getElementById('iceConnectionStates').appendChild(el);
33+
}
34+
35+
pc.createOffer()
36+
.then(offer => {
37+
pc.setLocalDescription(offer)
38+
39+
return fetch(`/doSignaling`, {
40+
method: 'post',
41+
headers: {
42+
'Accept': 'application/json, text/plain, */*',
43+
'Content-Type': 'application/json'
44+
},
45+
body: JSON.stringify(offer)
46+
})
47+
})
48+
.then(res => res.json())
49+
.then(res => {
50+
pc.setRemoteDescription(res)
51+
})
52+
.catch(alert)
53+
</script>
54+
</html>

examples/ice-tcp/main.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"time"
8+
9+
"github.com/pion/webrtc/v3"
10+
)
11+
12+
var peerConnection *webrtc.PeerConnection //nolint
13+
14+
func doSignaling(w http.ResponseWriter, r *http.Request) {
15+
var err error
16+
17+
if peerConnection == nil {
18+
m := webrtc.MediaEngine{}
19+
m.RegisterDefaultCodecs()
20+
21+
settingEngine := webrtc.SettingEngine{}
22+
23+
// Enable support only for TCP ICE candidates.
24+
settingEngine.SetNetworkTypes([]webrtc.NetworkType{
25+
webrtc.NetworkTypeTCP4,
26+
webrtc.NetworkTypeTCP6,
27+
})
28+
settingEngine.SetICETCPPort(8443)
29+
30+
api := webrtc.NewAPI(
31+
webrtc.WithMediaEngine(m),
32+
webrtc.WithSettingEngine(settingEngine),
33+
)
34+
if peerConnection, err = api.NewPeerConnection(webrtc.Configuration{}); err != nil {
35+
panic(err)
36+
}
37+
38+
// Set the handler for ICE connection state
39+
// This will notify you when the peer has connected/disconnected
40+
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
41+
fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
42+
})
43+
44+
// Send the current time via a DataChannel to the remote peer every 3 seconds
45+
peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
46+
d.OnOpen(func() {
47+
for range time.Tick(time.Second * 3) {
48+
if err = d.SendText(time.Now().String()); err != nil {
49+
panic(err)
50+
}
51+
}
52+
})
53+
})
54+
}
55+
56+
var offer webrtc.SessionDescription
57+
if err = json.NewDecoder(r.Body).Decode(&offer); err != nil {
58+
panic(err)
59+
}
60+
61+
if err = peerConnection.SetRemoteDescription(offer); err != nil {
62+
panic(err)
63+
}
64+
65+
// Create channel that is blocked until ICE Gathering is complete
66+
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
67+
68+
answer, err := peerConnection.CreateAnswer(nil)
69+
if err != nil {
70+
panic(err)
71+
} else if err = peerConnection.SetLocalDescription(answer); err != nil {
72+
panic(err)
73+
}
74+
75+
// Block until ICE Gathering is complete, disabling trickle ICE
76+
// we do this because we only can exchange one signaling message
77+
// in a production application you should exchange ICE Candidates via OnICECandidate
78+
<-gatherComplete
79+
80+
response, err := json.Marshal(*peerConnection.LocalDescription())
81+
if err != nil {
82+
panic(err)
83+
}
84+
85+
w.Header().Set("Content-Type", "application/json")
86+
if _, err := w.Write(response); err != nil {
87+
panic(err)
88+
}
89+
}
90+
91+
func main() {
92+
http.Handle("/", http.FileServer(http.Dir(".")))
93+
http.HandleFunc("/doSignaling", doSignaling)
94+
95+
fmt.Println("Open http://localhost:8080 to access this demo")
96+
panic(http.ListenAndServe(":8080", nil))
97+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.12
55
require (
66
github.com/pion/datachannel v1.4.17
77
github.com/pion/dtls/v2 v2.0.1
8-
github.com/pion/ice/v2 v2.0.0-rc.5
8+
github.com/pion/ice/v2 v2.0.0-rc.6
99
github.com/pion/logging v0.2.2
1010
github.com/pion/quic v0.1.1
1111
github.com/pion/randutil v0.1.0

go.sum

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,22 @@ github.com/pion/datachannel v1.4.17 h1:8CChK5VrJoGrwKCysoTscoWvshCAFpUkgY11Tqgz5
3131
github.com/pion/datachannel v1.4.17/go.mod h1:+vPQfypU9vSsyPXogYj1hBThWQ6MNXEQoQAzxoPvjYM=
3232
github.com/pion/dtls/v2 v2.0.1 h1:ddE7+V0faYRbyh4uPsRZ2vLdRrjVZn+wmCfI7jlBfaA=
3333
github.com/pion/dtls/v2 v2.0.1/go.mod h1:uMQkz2W0cSqY00xav7WByQ4Hb+18xeQh2oH2fRezr5U=
34-
github.com/pion/ice/v2 v2.0.0-rc.5 h1:HSSnTn3QEBgRgxwGj/kiI4iBgseNWpTQpb9GZZfLBcY=
35-
github.com/pion/ice/v2 v2.0.0-rc.5/go.mod h1:qfkp2BfgVTocUA3C9W559kFzW3IeCZxGplCIHAMyBZs=
34+
github.com/pion/ice/v2 v2.0.0-rc.3 h1:GvQ6nMGIGz7GltCUC9EU0m9JyQMan2vbifO4i8Y6T6A=
35+
github.com/pion/ice/v2 v2.0.0-rc.3/go.mod h1:5sP3yQ8Kd/azvPS4UrVTSgs/p5jfXMy3Ft2dQZBWyI8=
36+
github.com/pion/ice/v2 v2.0.0-rc.6 h1:Jz88W1iXzHBYJG6I5QbRTm+xuKD1vbgUW+NP1MtUPAg=
37+
github.com/pion/ice/v2 v2.0.0-rc.6/go.mod h1:VvpoDXwdierv9sPB8LAV3+T33ncCt0IG2NeI+CZYmTg=
3638
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
3739
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
3840
github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
3941
github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0=
4042
github.com/pion/quic v0.1.1 h1:D951FV+TOqI9A0rTF7tHx0Loooqz+nyzjEyj8o3PuMA=
4143
github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k=
42-
github.com/pion/randutil v0.0.0 h1:aLWLVhTG2jzoD25F0OlW6nXvXrjoGwiXq2Sz7j7NzL0=
4344
github.com/pion/randutil v0.0.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
4445
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
4546
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
4647
github.com/pion/rtcp v1.2.3 h1:2wrhKnqgSz91Q5nzYTO07mQXztYPtxL8a0XOss4rJqA=
4748
github.com/pion/rtcp v1.2.3/go.mod h1:zGhIv0RPRF0Z1Wiij22pUt5W/c9fevqSzT4jje/oK7I=
49+
github.com/pion/rtp v1.5.4/go.mod h1:bg60AL5GotNOlYZsqycbhDtEV3TkfbpXG0KBiUq29Mg=
4850
github.com/pion/rtp v1.5.5 h1:WTqWdmBuIj+luh8Wg6XVX+w7OytZHAIgtC7uSvgEl9Y=
4951
github.com/pion/rtp v1.5.5/go.mod h1:bg60AL5GotNOlYZsqycbhDtEV3TkfbpXG0KBiUq29Mg=
5052
github.com/pion/rtp v1.6.0 h1:4Ssnl/T5W2LzxHj9ssYpGVEQh3YYhQFNVmSWO88MMwk=
@@ -53,10 +55,15 @@ github.com/pion/sctp v1.7.6 h1:8qZTdJtbKfAns/Hv5L0PAj8FyXcsKhMH1pKUCGisQg4=
5355
github.com/pion/sctp v1.7.6/go.mod h1:ichkYQ5tlgCQwEwvgfdcAolqx1nHbYCxo4D7zK/K0X8=
5456
github.com/pion/sctp v1.7.7 h1:6KVHBstRFV9+2si2B8H39CUpNn03oQ9yk/3dJ1TnkOs=
5557
github.com/pion/sctp v1.7.7/go.mod h1:E0K0acHLowZ2Ua21lHlQe4pHJoRzMU0HXqZVQEk061k=
58+
github.com/pion/sdp/v2 v2.3.9 h1:KQMzypCMOcbHnx20t2r/Kuh9rKqWBa7RVy2tZ8Zk2MA=
59+
github.com/pion/sdp/v2 v2.3.9/go.mod h1:sbxACjjlmwAgXMk0Qqw9uzFaazLIdPv4m0mIreLzPVk=
5660
github.com/pion/sdp/v2 v2.4.0 h1:luUtaETR5x2KNNpvEMv/r4Y+/kzImzbz4Lm1z8eQNQI=
5761
github.com/pion/sdp/v2 v2.4.0/go.mod h1:L2LxrOpSTJbAns244vfPChbciR/ReU1KWfG04OpkR7E=
62+
github.com/pion/srtp v1.3.4 h1:idh+9/W7tLOsHjcYYketIPSShb9k2Dz+RVrqyCm2LQE=
63+
github.com/pion/srtp v1.3.4/go.mod h1:M3+LQiqLfVcV/Jo46KYJ3z9PP8DjmGPW8fUOQrF6q/M=
5864
github.com/pion/srtp v1.4.0 h1:Qg/RYeCOY59fpjaHgAaybj+Wdu7EBSmrqWqlb0hjrdE=
5965
github.com/pion/srtp v1.4.0/go.mod h1:LSHkbwXr484DujfzX9bY1PsoQbAqDO+WMKd1KBq5yW0=
66+
github.com/pion/stun v0.3.3/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M=
6067
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
6168
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
6269
github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
@@ -65,8 +72,11 @@ github.com/pion/transport v0.10.0 h1:9M12BSneJm6ggGhJyWpDveFOstJsTiQjkLf4M44rm80
6572
github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
6673
github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM=
6774
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
75+
github.com/pion/turn/v2 v2.0.3 h1:SJUUIbcPoehlyZgMyIUbBBDhI03sBx32x3JuSIBKBWA=
76+
github.com/pion/turn/v2 v2.0.3/go.mod h1:kl1hmT3NxcLynpXVnwJgObL8C9NaCyPTeqI2DcCpSZs=
6877
github.com/pion/turn/v2 v2.0.4 h1:oDguhEv2L/4rxwbL9clGLgtzQPjtuZwCdoM7Te8vQVk=
6978
github.com/pion/turn/v2 v2.0.4/go.mod h1:1812p4DcGVbYVBTiraUmP50XoKye++AMkbfp+N27mog=
79+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
7080
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
7181
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
7282
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

icecandidate.go

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type ICECandidate struct {
1919
Component uint16 `json:"component"`
2020
RelatedAddress string `json:"relatedAddress"`
2121
RelatedPort uint16 `json:"relatedPort"`
22+
TCPType string `json:"tcpType"`
2223
}
2324

2425
// Conversion for package ice
@@ -56,6 +57,7 @@ func newICECandidateFromICE(i ice.Candidate) (ICECandidate, error) {
5657
Port: uint16(i.Port()),
5758
Component: i.Component(),
5859
Typ: typ,
60+
TCPType: i.TCPType().String(),
5961
}
6062

6163
if i.RelatedAddress() != nil {
@@ -76,6 +78,7 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
7678
Address: c.Address,
7779
Port: int(c.Port),
7880
Component: c.Component,
81+
TCPType: ice.NewTCPType(c.TCPType),
7982
}
8083
return ice.NewCandidateHost(&config)
8184
case ICECandidateTypeSrflx:
@@ -140,16 +143,26 @@ func (c ICECandidate) String() string {
140143
}
141144

142145
func iceCandidateToSDP(c ICECandidate) sdp.ICECandidate {
146+
var extensions []sdp.ICECandidateAttribute
147+
148+
if c.Protocol == ICEProtocolTCP && c.TCPType != "" {
149+
extensions = append(extensions, sdp.ICECandidateAttribute{
150+
Key: "tcptype",
151+
Value: c.TCPType,
152+
})
153+
}
154+
143155
return sdp.ICECandidate{
144-
Foundation: c.Foundation,
145-
Priority: c.Priority,
146-
Address: c.Address,
147-
Protocol: c.Protocol.String(),
148-
Port: c.Port,
149-
Component: c.Component,
150-
Typ: c.Typ.String(),
151-
RelatedAddress: c.RelatedAddress,
152-
RelatedPort: c.RelatedPort,
156+
Foundation: c.Foundation,
157+
Priority: c.Priority,
158+
Address: c.Address,
159+
Protocol: c.Protocol.String(),
160+
Port: c.Port,
161+
Component: c.Component,
162+
Typ: c.Typ.String(),
163+
RelatedAddress: c.RelatedAddress,
164+
RelatedPort: c.RelatedPort,
165+
ExtensionAttributes: extensions,
153166
}
154167
}
155168

@@ -162,6 +175,17 @@ func newICECandidateFromSDP(c sdp.ICECandidate) (ICECandidate, error) {
162175
if err != nil {
163176
return ICECandidate{}, err
164177
}
178+
179+
var tcpType string
180+
if protocol == ICEProtocolTCP {
181+
for _, attr := range c.ExtensionAttributes {
182+
if attr.Key == "tcptype" {
183+
tcpType = attr.Value
184+
break
185+
}
186+
}
187+
}
188+
165189
return ICECandidate{
166190
Foundation: c.Foundation,
167191
Priority: c.Priority,
@@ -172,6 +196,7 @@ func newICECandidateFromSDP(c sdp.ICECandidate) (ICECandidate, error) {
172196
Typ: typ,
173197
RelatedAddress: c.RelatedAddress,
174198
RelatedPort: c.RelatedPort,
199+
TCPType: tcpType,
175200
}, nil
176201
}
177202

icecandidate_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"testing"
55

66
"github.com/pion/ice/v2"
7+
"github.com/pion/sdp/v2"
78
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
810
)
911

1012
func TestICECandidate_Convert(t *testing.T) {
@@ -129,6 +131,47 @@ func TestICECandidate_Convert(t *testing.T) {
129131
}
130132
}
131133

134+
func TestICECandidate_ConvertTCP(t *testing.T) {
135+
candidate := ICECandidate{
136+
Foundation: "foundation",
137+
Priority: 128,
138+
Address: "1.0.0.1",
139+
Protocol: ICEProtocolTCP,
140+
Port: 1234,
141+
Typ: ICECandidateTypeHost,
142+
Component: 1,
143+
TCPType: "passive",
144+
}
145+
146+
got, err := candidate.toICE()
147+
require.NoError(t, err)
148+
149+
want, err := ice.NewCandidateHost(&ice.CandidateHostConfig{
150+
CandidateID: got.ID(),
151+
Address: "1.0.0.1",
152+
Component: 1,
153+
Network: "tcp",
154+
Port: 1234,
155+
TCPType: ice.TCPTypePassive,
156+
})
157+
require.NoError(t, err)
158+
159+
assert.Equal(t, want, got)
160+
161+
sdpCandidate := iceCandidateToSDP(candidate)
162+
assert.Equal(t, []sdp.ICECandidateAttribute{
163+
{
164+
Key: "tcptype",
165+
Value: "passive",
166+
},
167+
}, sdpCandidate.ExtensionAttributes)
168+
169+
candidate2, err := newICECandidateFromSDP(sdpCandidate)
170+
require.NoError(t, err)
171+
172+
assert.Equal(t, candidate, candidate2)
173+
}
174+
132175
func TestConvertTypeFromICE(t *testing.T) {
133176
t.Run("host", func(t *testing.T) {
134177
ct, err := convertTypeFromICE(ice.CandidateTypeHost)

icegatherer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ func (g *ICEGatherer) createAgent() error {
109109
MulticastDNSHostName: g.api.settingEngine.candidates.MulticastDNSHostName,
110110
LocalUfrag: g.api.settingEngine.candidates.UsernameFragment,
111111
LocalPwd: g.api.settingEngine.candidates.Password,
112+
TCPListenPort: g.api.settingEngine.iceTCPPort,
112113
}
113114

114115
requestedNetworkTypes := g.api.settingEngine.candidates.ICENetworkTypes

0 commit comments

Comments
 (0)