Skip to content

Commit

Permalink
Add experimental support for ICE TCP
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremija committed Jul 15, 2020
1 parent fe4c819 commit 2236dde
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 13 deletions.
22 changes: 22 additions & 0 deletions examples/ice-tcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# ice-tcp
ice-tcp demonstrates Pion WebRTC's ICE TCP abilities.

## Instructions

### Download ice-tcp
This example requires you to clone the repo since it is serving static HTML.

```
mkdir -p $GOPATH/src/github.com/pion
cd $GOPATH/src/github.com/pion
git clone https://github.com/pion/webrtc.git
cd webrtc/examples/ice-tcp
```

### Run ice-tcp
Execute `go run *.go`

### Open the Web UI
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.

Congrats, you have used Pion WebRTC! Now start building something cool
54 changes: 54 additions & 0 deletions examples/ice-tcp/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<html>
<head>
<title>ice-tcp</title>
</head>

<body>
<button onclick="window.doSignaling(true)"> ICE TCP </button><br />


<h3> ICE Connection States </h3>
<div id="iceConnectionStates"></div> <br />

<h3> Inbound DataChannel Messages </h3>
<div id="inboundDataChannelMessages"></div>
</body>

<script>
let pc = new RTCPeerConnection()
let dc = pc.createDataChannel('data')

dc.onmessage = event => {
let el = document.createElement('p')
el.appendChild(document.createTextNode(event.data))

document.getElementById('inboundDataChannelMessages').appendChild(el);
}

pc.oniceconnectionstatechange = () => {
let el = document.createElement('p')
el.appendChild(document.createTextNode(pc.iceConnectionState))

document.getElementById('iceConnectionStates').appendChild(el);
}

pc.createOffer()
.then(offer => {
pc.setLocalDescription(offer)

return fetch(`/doSignaling`, {
method: 'post',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: JSON.stringify(offer)
})
})
.then(res => res.json())
.then(res => {
pc.setRemoteDescription(res)
})
.catch(alert)
</script>
</html>
97 changes: 97 additions & 0 deletions examples/ice-tcp/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package main

import (
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/pion/webrtc/v3"
)

var peerConnection *webrtc.PeerConnection //nolint

func doSignaling(w http.ResponseWriter, r *http.Request) {
var err error

if peerConnection == nil {
m := webrtc.MediaEngine{}
m.RegisterDefaultCodecs()

settingEngine := webrtc.SettingEngine{}

// Enable support only for TCP ICE candidates.
settingEngine.SetNetworkTypes([]webrtc.NetworkType{
webrtc.NetworkTypeTCP4,
webrtc.NetworkTypeTCP6,
})
settingEngine.SetICETCPPort(8443)

api := webrtc.NewAPI(
webrtc.WithMediaEngine(m),
webrtc.WithSettingEngine(settingEngine),
)
if peerConnection, err = api.NewPeerConnection(webrtc.Configuration{}); err != nil {
panic(err)
}

// Set the handler for ICE connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
})

// Send the current time via a DataChannel to the remote peer every 3 seconds
peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
d.OnOpen(func() {
for range time.Tick(time.Second * 3) {
if err = d.SendText(time.Now().String()); err != nil {
panic(err)
}
}
})
})
}

var offer webrtc.SessionDescription
if err = json.NewDecoder(r.Body).Decode(&offer); err != nil {
panic(err)
}

if err = peerConnection.SetRemoteDescription(offer); err != nil {
panic(err)
}

// Create channel that is blocked until ICE Gathering is complete
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)

answer, err := peerConnection.CreateAnswer(nil)
if err != nil {
panic(err)
} else if err = peerConnection.SetLocalDescription(answer); err != nil {
panic(err)
}

// Block until ICE Gathering is complete, disabling trickle ICE
// we do this because we only can exchange one signaling message
// in a production application you should exchange ICE Candidates via OnICECandidate
<-gatherComplete

response, err := json.Marshal(*peerConnection.LocalDescription())
if err != nil {
panic(err)
}

w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(response); err != nil {
panic(err)
}
}

func main() {
http.Handle("/", http.FileServer(http.Dir(".")))
http.HandleFunc("/doSignaling", doSignaling)

fmt.Println("Open http://localhost:8080 to access this demo")
panic(http.ListenAndServe(":8080", nil))
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.12
require (
github.com/pion/datachannel v1.4.17
github.com/pion/dtls/v2 v2.0.1
github.com/pion/ice/v2 v2.0.0-rc.5
github.com/pion/ice/v2 v2.0.0-rc.6
github.com/pion/logging v0.2.2
github.com/pion/quic v0.1.1
github.com/pion/randutil v0.1.0
Expand Down
16 changes: 13 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,22 @@ github.com/pion/datachannel v1.4.17 h1:8CChK5VrJoGrwKCysoTscoWvshCAFpUkgY11Tqgz5
github.com/pion/datachannel v1.4.17/go.mod h1:+vPQfypU9vSsyPXogYj1hBThWQ6MNXEQoQAzxoPvjYM=
github.com/pion/dtls/v2 v2.0.1 h1:ddE7+V0faYRbyh4uPsRZ2vLdRrjVZn+wmCfI7jlBfaA=
github.com/pion/dtls/v2 v2.0.1/go.mod h1:uMQkz2W0cSqY00xav7WByQ4Hb+18xeQh2oH2fRezr5U=
github.com/pion/ice/v2 v2.0.0-rc.5 h1:HSSnTn3QEBgRgxwGj/kiI4iBgseNWpTQpb9GZZfLBcY=
github.com/pion/ice/v2 v2.0.0-rc.5/go.mod h1:qfkp2BfgVTocUA3C9W559kFzW3IeCZxGplCIHAMyBZs=
github.com/pion/ice/v2 v2.0.0-rc.3 h1:GvQ6nMGIGz7GltCUC9EU0m9JyQMan2vbifO4i8Y6T6A=
github.com/pion/ice/v2 v2.0.0-rc.3/go.mod h1:5sP3yQ8Kd/azvPS4UrVTSgs/p5jfXMy3Ft2dQZBWyI8=
github.com/pion/ice/v2 v2.0.0-rc.6 h1:Jz88W1iXzHBYJG6I5QbRTm+xuKD1vbgUW+NP1MtUPAg=
github.com/pion/ice/v2 v2.0.0-rc.6/go.mod h1:VvpoDXwdierv9sPB8LAV3+T33ncCt0IG2NeI+CZYmTg=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0=
github.com/pion/quic v0.1.1 h1:D951FV+TOqI9A0rTF7tHx0Loooqz+nyzjEyj8o3PuMA=
github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k=
github.com/pion/randutil v0.0.0 h1:aLWLVhTG2jzoD25F0OlW6nXvXrjoGwiXq2Sz7j7NzL0=
github.com/pion/randutil v0.0.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.3 h1:2wrhKnqgSz91Q5nzYTO07mQXztYPtxL8a0XOss4rJqA=
github.com/pion/rtcp v1.2.3/go.mod h1:zGhIv0RPRF0Z1Wiij22pUt5W/c9fevqSzT4jje/oK7I=
github.com/pion/rtp v1.5.4/go.mod h1:bg60AL5GotNOlYZsqycbhDtEV3TkfbpXG0KBiUq29Mg=
github.com/pion/rtp v1.5.5 h1:WTqWdmBuIj+luh8Wg6XVX+w7OytZHAIgtC7uSvgEl9Y=
github.com/pion/rtp v1.5.5/go.mod h1:bg60AL5GotNOlYZsqycbhDtEV3TkfbpXG0KBiUq29Mg=
github.com/pion/rtp v1.6.0 h1:4Ssnl/T5W2LzxHj9ssYpGVEQh3YYhQFNVmSWO88MMwk=
Expand All @@ -53,10 +55,15 @@ github.com/pion/sctp v1.7.6 h1:8qZTdJtbKfAns/Hv5L0PAj8FyXcsKhMH1pKUCGisQg4=
github.com/pion/sctp v1.7.6/go.mod h1:ichkYQ5tlgCQwEwvgfdcAolqx1nHbYCxo4D7zK/K0X8=
github.com/pion/sctp v1.7.7 h1:6KVHBstRFV9+2si2B8H39CUpNn03oQ9yk/3dJ1TnkOs=
github.com/pion/sctp v1.7.7/go.mod h1:E0K0acHLowZ2Ua21lHlQe4pHJoRzMU0HXqZVQEk061k=
github.com/pion/sdp/v2 v2.3.9 h1:KQMzypCMOcbHnx20t2r/Kuh9rKqWBa7RVy2tZ8Zk2MA=
github.com/pion/sdp/v2 v2.3.9/go.mod h1:sbxACjjlmwAgXMk0Qqw9uzFaazLIdPv4m0mIreLzPVk=
github.com/pion/sdp/v2 v2.4.0 h1:luUtaETR5x2KNNpvEMv/r4Y+/kzImzbz4Lm1z8eQNQI=
github.com/pion/sdp/v2 v2.4.0/go.mod h1:L2LxrOpSTJbAns244vfPChbciR/ReU1KWfG04OpkR7E=
github.com/pion/srtp v1.3.4 h1:idh+9/W7tLOsHjcYYketIPSShb9k2Dz+RVrqyCm2LQE=
github.com/pion/srtp v1.3.4/go.mod h1:M3+LQiqLfVcV/Jo46KYJ3z9PP8DjmGPW8fUOQrF6q/M=
github.com/pion/srtp v1.4.0 h1:Qg/RYeCOY59fpjaHgAaybj+Wdu7EBSmrqWqlb0hjrdE=
github.com/pion/srtp v1.4.0/go.mod h1:LSHkbwXr484DujfzX9bY1PsoQbAqDO+WMKd1KBq5yW0=
github.com/pion/stun v0.3.3/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M=
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
Expand All @@ -65,8 +72,11 @@ github.com/pion/transport v0.10.0 h1:9M12BSneJm6ggGhJyWpDveFOstJsTiQjkLf4M44rm80
github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM=
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
github.com/pion/turn/v2 v2.0.3 h1:SJUUIbcPoehlyZgMyIUbBBDhI03sBx32x3JuSIBKBWA=
github.com/pion/turn/v2 v2.0.3/go.mod h1:kl1hmT3NxcLynpXVnwJgObL8C9NaCyPTeqI2DcCpSZs=
github.com/pion/turn/v2 v2.0.4 h1:oDguhEv2L/4rxwbL9clGLgtzQPjtuZwCdoM7Te8vQVk=
github.com/pion/turn/v2 v2.0.4/go.mod h1:1812p4DcGVbYVBTiraUmP50XoKye++AMkbfp+N27mog=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
43 changes: 34 additions & 9 deletions icecandidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type ICECandidate struct {
Component uint16 `json:"component"`
RelatedAddress string `json:"relatedAddress"`
RelatedPort uint16 `json:"relatedPort"`
TCPType string `json:"tcpType"`
}

// Conversion for package ice
Expand Down Expand Up @@ -56,6 +57,7 @@ func newICECandidateFromICE(i ice.Candidate) (ICECandidate, error) {
Port: uint16(i.Port()),
Component: i.Component(),
Typ: typ,
TCPType: i.TCPType().String(),
}

if i.RelatedAddress() != nil {
Expand All @@ -76,6 +78,7 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
Address: c.Address,
Port: int(c.Port),
Component: c.Component,
TCPType: ice.NewTCPType(c.TCPType),
}
return ice.NewCandidateHost(&config)
case ICECandidateTypeSrflx:
Expand Down Expand Up @@ -140,16 +143,26 @@ func (c ICECandidate) String() string {
}

func iceCandidateToSDP(c ICECandidate) sdp.ICECandidate {
var extensions []sdp.ICECandidateAttribute

if c.Protocol == ICEProtocolTCP && c.TCPType != "" {
extensions = append(extensions, sdp.ICECandidateAttribute{
Key: "tcptype",
Value: c.TCPType,
})
}

return sdp.ICECandidate{
Foundation: c.Foundation,
Priority: c.Priority,
Address: c.Address,
Protocol: c.Protocol.String(),
Port: c.Port,
Component: c.Component,
Typ: c.Typ.String(),
RelatedAddress: c.RelatedAddress,
RelatedPort: c.RelatedPort,
Foundation: c.Foundation,
Priority: c.Priority,
Address: c.Address,
Protocol: c.Protocol.String(),
Port: c.Port,
Component: c.Component,
Typ: c.Typ.String(),
RelatedAddress: c.RelatedAddress,
RelatedPort: c.RelatedPort,
ExtensionAttributes: extensions,
}
}

Expand All @@ -162,6 +175,17 @@ func newICECandidateFromSDP(c sdp.ICECandidate) (ICECandidate, error) {
if err != nil {
return ICECandidate{}, err
}

var tcpType string
if protocol == ICEProtocolTCP {
for _, attr := range c.ExtensionAttributes {
if attr.Key == "tcptype" {
tcpType = attr.Value
break
}
}
}

return ICECandidate{
Foundation: c.Foundation,
Priority: c.Priority,
Expand All @@ -172,6 +196,7 @@ func newICECandidateFromSDP(c sdp.ICECandidate) (ICECandidate, error) {
Typ: typ,
RelatedAddress: c.RelatedAddress,
RelatedPort: c.RelatedPort,
TCPType: tcpType,
}, nil
}

Expand Down
43 changes: 43 additions & 0 deletions icecandidate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"testing"

"github.com/pion/ice/v2"
"github.com/pion/sdp/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestICECandidate_Convert(t *testing.T) {
Expand Down Expand Up @@ -129,6 +131,47 @@ func TestICECandidate_Convert(t *testing.T) {
}
}

func TestICECandidate_ConvertTCP(t *testing.T) {
candidate := ICECandidate{
Foundation: "foundation",
Priority: 128,
Address: "1.0.0.1",
Protocol: ICEProtocolTCP,
Port: 1234,
Typ: ICECandidateTypeHost,
Component: 1,
TCPType: "passive",
}

got, err := candidate.toICE()
require.NoError(t, err)

want, err := ice.NewCandidateHost(&ice.CandidateHostConfig{
CandidateID: got.ID(),
Address: "1.0.0.1",
Component: 1,
Network: "tcp",
Port: 1234,
TCPType: ice.TCPTypePassive,
})
require.NoError(t, err)

assert.Equal(t, want, got)

sdpCandidate := iceCandidateToSDP(candidate)
assert.Equal(t, []sdp.ICECandidateAttribute{
{
Key: "tcptype",
Value: "passive",
},
}, sdpCandidate.ExtensionAttributes)

candidate2, err := newICECandidateFromSDP(sdpCandidate)
require.NoError(t, err)

assert.Equal(t, candidate, candidate2)
}

func TestConvertTypeFromICE(t *testing.T) {
t.Run("host", func(t *testing.T) {
ct, err := convertTypeFromICE(ice.CandidateTypeHost)
Expand Down
1 change: 1 addition & 0 deletions icegatherer.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func (g *ICEGatherer) createAgent() error {
MulticastDNSHostName: g.api.settingEngine.candidates.MulticastDNSHostName,
LocalUfrag: g.api.settingEngine.candidates.UsernameFragment,
LocalPwd: g.api.settingEngine.candidates.Password,
TCPListenPort: g.api.settingEngine.iceTCPPort,
}

requestedNetworkTypes := g.api.settingEngine.candidates.ICENetworkTypes
Expand Down
Loading

0 comments on commit 2236dde

Please sign in to comment.