diff --git a/agent.go b/agent.go index f1152ad..d2dff26 100644 --- a/agent.go +++ b/agent.go @@ -156,6 +156,8 @@ type Agent struct { net *vnet.Net interfaceFilter func(string) bool + + insecureSkipVerify bool } func (a *Agent) ok() error { @@ -268,6 +270,10 @@ type AgentConfig struct { // InterfaceFilter is a function that you can use in order to whitelist or blacklist // the interfaces which are used to gather ICE candidates. InterfaceFilter func(string) bool + + // InsecureSkipVerify controls if self-signed certificates are accepted when connecting + // to TURN servers via TLS or DTLS + InsecureSkipVerify bool } func containsCandidateType(candidateType CandidateType, candidateTypeList []CandidateType) bool { @@ -406,6 +412,8 @@ func NewAgent(config *AgentConfig) (*Agent, error) { forceCandidateContact: make(chan bool, 1), interfaceFilter: config.InterfaceFilter, + + insecureSkipVerify: config.InsecureSkipVerify, } a.haveStarted.Store(false) diff --git a/gather.go b/gather.go index 0887bad..ece63eb 100644 --- a/gather.go +++ b/gather.go @@ -1,11 +1,13 @@ package ice import ( + "crypto/tls" "fmt" "net" "sync" "time" + "github.com/pion/dtls/v2" "github.com/pion/logging" "github.com/pion/turn/v2" ) @@ -26,6 +28,25 @@ func closeConnAndLog(c closeable, log logging.LeveledLogger, msg string) { } } +// fakePacketConn wraps a net.Conn and emulates net.PacketConn +type fakePacketConn struct { + nextConn net.Conn +} + +func (f *fakePacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + n, err = f.nextConn.Read(p) + addr = f.nextConn.RemoteAddr() + return +} +func (f *fakePacketConn) Close() error { return f.nextConn.Close() } +func (f *fakePacketConn) LocalAddr() net.Addr { return f.nextConn.LocalAddr() } +func (f *fakePacketConn) SetDeadline(t time.Time) error { return f.nextConn.SetDeadline(t) } +func (f *fakePacketConn) SetReadDeadline(t time.Time) error { return f.nextConn.SetReadDeadline(t) } +func (f *fakePacketConn) SetWriteDeadline(t time.Time) error { return f.nextConn.SetWriteDeadline(t) } +func (f *fakePacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + return f.nextConn.Write(p) +} + // GatherCandidates initiates the trickle based gathering process. func (a *Agent) GatherCandidates() error { gatherErrChan := make(chan error, 1) @@ -264,7 +285,7 @@ func (a *Agent) gatherCandidatesRelay(urls []*URL) error { network := NetworkTypeUDP4.String() // TODO IPv6 for i := range urls { switch { - case urls[i].Scheme != SchemeTypeTURN: + case urls[i].Scheme != SchemeTypeTURN && urls[i].Scheme != SchemeTypeTURNS: continue case urls[i].Username == "": return ErrUsernameEmpty @@ -283,36 +304,63 @@ func (a *Agent) gatherCandidatesRelay(urls []*URL) error { RelPort int ) - if url.Proto == ProtoTypeUDP { - locConn, err = a.net.ListenPacket(network, "0.0.0.0:0") - if err != nil { + switch { + case url.Proto == ProtoTypeUDP && url.Scheme == SchemeTypeTURN: + if locConn, err = a.net.ListenPacket(network, "0.0.0.0:0"); err != nil { a.log.Warnf("Failed to listen %s: %v\n", network, err) return } RelAddr = locConn.LocalAddr().(*net.UDPAddr).IP.String() RelPort = locConn.LocalAddr().(*net.UDPAddr).Port - } else { - var ( - tcpAddr *net.TCPAddr - tcpConn *net.TCPConn - ) - - tcpAddr, err = net.ResolveTCPAddr(NetworkTypeTCP4.String(), TURNServerAddr) - if err != nil { - a.log.Warnf("Failed to resolve TCP Addr %s: %v\n", TURNServerAddr, err) + case url.Proto == ProtoTypeTCP && url.Scheme == SchemeTypeTURN: + tcpAddr, connectErr := net.ResolveTCPAddr(NetworkTypeTCP4.String(), TURNServerAddr) + if connectErr != nil { + a.log.Warnf("Failed to resolve TCP Addr %s: %v\n", TURNServerAddr, connectErr) return } - tcpConn, err = net.DialTCP(NetworkTypeTCP4.String(), nil, tcpAddr) - if err != nil { - a.log.Warnf("Failed to Dial TCP Addr %s: %v\n", TURNServerAddr, err) + conn, connectErr := net.DialTCP(NetworkTypeTCP4.String(), nil, tcpAddr) + if connectErr != nil { + a.log.Warnf("Failed to Dial TCP Addr %s: %v\n", TURNServerAddr, connectErr) return } - RelAddr = tcpConn.LocalAddr().(*net.TCPAddr).IP.String() - RelPort = tcpConn.LocalAddr().(*net.TCPAddr).Port - locConn = turn.NewSTUNConn(tcpConn) + RelAddr = conn.LocalAddr().(*net.TCPAddr).IP.String() + RelPort = conn.LocalAddr().(*net.TCPAddr).Port + locConn = turn.NewSTUNConn(conn) + case url.Proto == ProtoTypeUDP && url.Scheme == SchemeTypeTURNS: + udpAddr, connectErr := net.ResolveUDPAddr(network, TURNServerAddr) + if connectErr != nil { + a.log.Warnf("Failed to resolve UDP Addr %s: %v\n", TURNServerAddr, connectErr) + return + } + + conn, connectErr := dtls.Dial(network, udpAddr, &dtls.Config{ + InsecureSkipVerify: a.insecureSkipVerify, //nolint:gosec + }) + if connectErr != nil { + a.log.Warnf("Failed to Dial DTLS Addr %s: %v\n", TURNServerAddr, connectErr) + return + } + + RelAddr = conn.LocalAddr().(*net.UDPAddr).IP.String() + RelPort = conn.LocalAddr().(*net.UDPAddr).Port + locConn = &fakePacketConn{conn} + case url.Proto == ProtoTypeTCP && url.Scheme == SchemeTypeTURNS: + conn, connectErr := tls.Dial(NetworkTypeTCP4.String(), TURNServerAddr, &tls.Config{ + InsecureSkipVerify: a.insecureSkipVerify, //nolint:gosec + }) + if connectErr != nil { + a.log.Warnf("Failed to Dial TLS Addr %s: %v\n", TURNServerAddr, connectErr) + return + } + RelAddr = conn.LocalAddr().(*net.TCPAddr).IP.String() + RelPort = conn.LocalAddr().(*net.TCPAddr).Port + locConn = turn.NewSTUNConn(conn) + default: + a.log.Warnf("Unable to handle URL in gatherCandidatesRelay %v\n", url) + return } client, err := turn.NewClient(&turn.ClientConfig{ diff --git a/gather_test.go b/gather_test.go index 96e855e..bbf9878 100644 --- a/gather_test.go +++ b/gather_test.go @@ -4,6 +4,7 @@ package ice import ( "context" + "crypto/tls" "net" "reflect" "sort" @@ -11,6 +12,8 @@ import ( "testing" "time" + "github.com/pion/dtls/v2" + "github.com/pion/dtls/v2/pkg/crypto/selfsign" "github.com/pion/transport/test" "github.com/pion/turn/v2" "github.com/stretchr/testify/assert" @@ -189,10 +192,11 @@ func TestTURNConcurrency(t *testing.T) { }) a, err := NewAgent(&AgentConfig{ - NetworkTypes: supportedNetworkTypes, - Trickle: true, - Urls: urls, - CandidateTypes: []CandidateType{CandidateTypeRelay}, + CandidateTypes: []CandidateType{CandidateTypeRelay}, + InsecureSkipVerify: true, + NetworkTypes: supportedNetworkTypes, + Trickle: true, + Urls: urls, }) assert.NoError(t, err) @@ -218,11 +222,37 @@ func TestTURNConcurrency(t *testing.T) { runTest(ProtoTypeUDP, SchemeTypeTURN, serverListener, nil, serverPort) }) - t.Run("TURN Relay", func(t *testing.T) { + t.Run("TCP Relay", func(t *testing.T) { serverPort := randomPort(t) serverListener, err := net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(serverPort)) assert.NoError(t, err) runTest(ProtoTypeTCP, SchemeTypeTURN, nil, serverListener, serverPort) }) + + t.Run("TLS Relay", func(t *testing.T) { + certificate, genErr := selfsign.GenerateSelfSigned() + assert.NoError(t, genErr) + + serverPort := randomPort(t) + serverListener, err := tls.Listen("tcp", "127.0.0.1:"+strconv.Itoa(serverPort), &tls.Config{ + Certificates: []tls.Certificate{certificate}, + }) + assert.NoError(t, err) + + runTest(ProtoTypeTCP, SchemeTypeTURNS, nil, serverListener, serverPort) + }) + + t.Run("DTLS Relay", func(t *testing.T) { + certificate, genErr := selfsign.GenerateSelfSigned() + assert.NoError(t, genErr) + + serverPort := randomPort(t) + serverListener, err := dtls.Listen("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: serverPort}, &dtls.Config{ + Certificates: []tls.Certificate{certificate}, + }) + assert.NoError(t, err) + + runTest(ProtoTypeUDP, SchemeTypeTURNS, nil, serverListener, serverPort) + }) } diff --git a/go.mod b/go.mod index e2907ef..9d73f5f 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/pion/ice go 1.13 require ( + github.com/pion/dtls/v2 v2.0.0-rc.7 github.com/pion/logging v0.2.2 github.com/pion/mdns v0.0.4 github.com/pion/stun v0.3.3 diff --git a/go.sum b/go.sum index 7cf6da1..9846fcf 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pion/dtls/v2 v2.0.0-rc.7 h1:LDAIQDt1pcuAIJs7Q2EZ3PSl8MseCFA2nCW0YYSYCx0= +github.com/pion/dtls/v2 v2.0.0-rc.7/go.mod h1:U199DvHpRBN0muE9+tVN4TMy1jvEhZIZ63lk4xkvVSk= 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= @@ -23,15 +23,20 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=