Add experimental support for ICE TCP

See pion/ice issue and PR:

- https://github.com/pion/ice/tree/issue-196
- https://github.com/pion/ice/pull/226
This commit is contained in:
Jerko Steiner
2020-07-07 22:05:06 +02:00
parent fe4c819802
commit 2236ddeafd
9 changed files with 272 additions and 13 deletions

View File

@@ -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

View File

@@ -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
examples/ice-tcp/main.go Normal file
View File

@@ -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
go.mod
View File

@@ -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

16
go.sum
View File

@@ -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=
@@ -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=
@@ -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=

View File

@@ -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
@@ -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 {
@@ -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:
@@ -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,
}
}
@@ -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,
@@ -172,6 +196,7 @@ func newICECandidateFromSDP(c sdp.ICECandidate) (ICECandidate, error) {
Typ: typ,
RelatedAddress: c.RelatedAddress,
RelatedPort: c.RelatedPort,
TCPType: tcpType,
}, nil
}

View File

@@ -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) {
@@ -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)

View File

@@ -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

View File

@@ -56,6 +56,7 @@ type SettingEngine struct {
disableSRTCPReplayProtection bool
vnet *vnet.Net
LoggerFactory logging.LoggerFactory
iceTCPPort int
}
// DetachDataChannels enables detaching data channels. When enabled
@@ -242,6 +243,12 @@ func (e *SettingEngine) SetSDPMediaLevelFingerprints(sdpMediaLevelFingerprints b
e.sdpMediaLevelFingerprints = sdpMediaLevelFingerprints
}
// SetICETCPPort to a non-zero value enables ICE-TCP listener. This API is experimental and
// is likely to change in the future.
func (e *SettingEngine) SetICETCPPort(port int) {
e.iceTCPPort = port
}
// AddSDPExtensions adds available and offered extensions for media type.
//
// Ext IDs are optional and generated if you do not provide them