From 64a2cf0c473c3e860728f9aa5be6c9f64bb162fc Mon Sep 17 00:00:00 2001 From: Assad Obaid Date: Thu, 8 Oct 2020 10:51:05 +0200 Subject: [PATCH] Add proxy dialer interface Enable user to implement the golang.org/x/net/proxy dialer interface in order to have a customized dialer. The customized dialer could be one that connect through a corporate HTTP/HTTPS proxy. Resolves #284 --- README.md | 1 + agent.go | 4 +++ agent_config.go | 5 ++++ agent_test.go | 3 +- gather.go | 12 ++++++++ gather_test.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 96 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dcb8853..f28eda0 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contribu * [JooYoung Lim](https://github.com/DevRockstarZ) * [Kory Miller](https://github.com/korymiller1489) * [ZHENK](https://github.com/scorpionknifes) +* [Assad Obaid](https://github.com/assadobaid) ### License MIT License - see [LICENSE](LICENSE) for full text diff --git a/agent.go b/agent.go index c9788b1..3d49b93 100644 --- a/agent.go +++ b/agent.go @@ -15,6 +15,7 @@ import ( "github.com/pion/stun" "github.com/pion/transport/packetio" "github.com/pion/transport/vnet" + "golang.org/x/net/proxy" ) type bindingRequest struct { @@ -126,6 +127,8 @@ type Agent struct { interfaceFilter func(string) bool insecureSkipVerify bool + + proxyDialer proxy.Dialer } type task struct { @@ -292,6 +295,7 @@ func NewAgent(config *AgentConfig) (*Agent, error) { //nolint:gocognit loggerFactory: loggerFactory, log: log, net: config.Net, + proxyDialer: config.ProxyDialer, mDNSMode: mDNSMode, mDNSName: mDNSName, diff --git a/agent_config.go b/agent_config.go index 1523d12..e09ad76 100644 --- a/agent_config.go +++ b/agent_config.go @@ -5,6 +5,7 @@ import ( "github.com/pion/logging" "github.com/pion/transport/vnet" + "golang.org/x/net/proxy" ) const ( @@ -143,6 +144,10 @@ type AgentConfig struct { // Currently only passive candidates are supported. This functionality is // experimental and the API might change in the future. TCPMux TCPMux + + // Proxy Dialer is a dialer that should be implemented by the user based on golang.org/x/net/proxy + // dial interface in order to support corporate proxies + ProxyDialer proxy.Dialer } // initWithDefaults populates an agent and falls back to defaults if fields are unset diff --git a/agent_test.go b/agent_test.go index 39da38c..b635cd6 100644 --- a/agent_test.go +++ b/agent_test.go @@ -19,8 +19,7 @@ import ( "github.com/stretchr/testify/require" ) -type mockPacketConn struct { -} +type mockPacketConn struct{} func (m *mockPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { return 0, nil, nil } func (m *mockPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { return 0, nil } diff --git a/gather.go b/gather.go index 1db2732..3bc3d77 100644 --- a/gather.go +++ b/gather.go @@ -369,6 +369,18 @@ func (a *Agent) gatherCandidatesRelay(ctx context.Context, urls []*URL) { //noli RelAddr = locConn.LocalAddr().(*net.UDPAddr).IP.String() RelPort = locConn.LocalAddr().(*net.UDPAddr).Port + case a.proxyDialer != nil && url.Proto == ProtoTypeTCP && + (url.Scheme == SchemeTypeTURN || url.Scheme == SchemeTypeTURNS): + conn, connectErr := a.proxyDialer.Dial(NetworkTypeTCP4.String(), TURNServerAddr) + if connectErr != nil { + a.log.Warnf("Failed to Dial TCP Addr %s via proxy dialer: %v\n", TURNServerAddr, connectErr) + return + } + + RelAddr = conn.LocalAddr().(*net.TCPAddr).IP.String() + RelPort = conn.LocalAddr().(*net.TCPAddr).Port + locConn = turn.NewSTUNConn(conn) + case url.Proto == ProtoTypeTCP && url.Scheme == SchemeTypeTURN: tcpAddr, connectErr := net.ResolveTCPAddr(NetworkTypeTCP4.String(), TURNServerAddr) if connectErr != nil { diff --git a/gather_test.go b/gather_test.go index 8e953b6..e69d987 100644 --- a/gather_test.go +++ b/gather_test.go @@ -5,7 +5,9 @@ package ice import ( "context" "crypto/tls" + "io" "net" + "net/url" "reflect" "sort" "strconv" @@ -19,6 +21,7 @@ import ( "github.com/pion/turn/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/net/proxy" ) func TestListenUDP(t *testing.T) { @@ -197,7 +200,7 @@ func TestTURNConcurrency(t *testing.T) { Username: "username", Password: "password", Proto: protocol, - Port: serverPort + 1, + Port: serverPort + 1 + i, }) } urls = append(urls, &URL{ @@ -412,3 +415,72 @@ func TestCloseConnLog(t *testing.T) { assert.NoError(t, a.Close()) } + +type mockProxy struct { + proxyWasDialed func() +} + +type mockConn struct{} + +func (m *mockConn) Read(b []byte) (n int, err error) { return 0, io.EOF } +func (m *mockConn) Write(b []byte) (int, error) { return 0, io.EOF } +func (m *mockConn) Close() error { return io.EOF } +func (m *mockConn) LocalAddr() net.Addr { return &net.TCPAddr{} } +func (m *mockConn) RemoteAddr() net.Addr { return &net.TCPAddr{} } +func (m *mockConn) SetDeadline(t time.Time) error { return io.EOF } +func (m *mockConn) SetReadDeadline(t time.Time) error { return io.EOF } +func (m *mockConn) SetWriteDeadline(t time.Time) error { return io.EOF } + +func (m *mockProxy) Dial(network, addr string) (net.Conn, error) { + m.proxyWasDialed() + return &mockConn{}, nil +} + +func TestTURNProxyDialer(t *testing.T) { + report := test.CheckRoutines(t) + defer report() + + lim := test.TimeOut(time.Second * 30) + defer lim.Stop() + + proxyWasDialed, proxyWasDialedFunc := context.WithCancel(context.Background()) + proxy.RegisterDialerType("tcp", func(*url.URL, proxy.Dialer) (proxy.Dialer, error) { + return &mockProxy{proxyWasDialedFunc}, nil + }) + + tcpProxyURI, err := url.Parse("tcp://fakeproxy:3128") + assert.NoError(t, err) + + proxyDialer, err := proxy.FromURL(tcpProxyURI, proxy.Direct) + assert.NoError(t, err) + + a, err := NewAgent(&AgentConfig{ + CandidateTypes: []CandidateType{CandidateTypeRelay}, + NetworkTypes: supportedNetworkTypes(), + Urls: []*URL{ + { + Scheme: SchemeTypeTURN, + Host: "127.0.0.1", + Username: "username", + Password: "password", + Proto: ProtoTypeTCP, + Port: 5000, + }, + }, + ProxyDialer: proxyDialer, + }) + assert.NoError(t, err) + + candidateGatherFinish, candidateGatherFinishFunc := context.WithCancel(context.Background()) + assert.NoError(t, a.OnCandidate(func(c Candidate) { + if c == nil { + candidateGatherFinishFunc() + } + })) + + assert.NoError(t, a.GatherCandidates()) + <-candidateGatherFinish.Done() + <-proxyWasDialed.Done() + + assert.NoError(t, a.Close()) +}