Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit deae9e9

Browse files
committed
Add turnconn
1 parent e8b3101 commit deae9e9

File tree

2 files changed

+280
-0
lines changed

2 files changed

+280
-0
lines changed

coderd/turnconn/turnconn.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package turnconn
2+
3+
import (
4+
"io"
5+
"net"
6+
"sync"
7+
8+
"github.com/pion/logging"
9+
"github.com/pion/turn/v2"
10+
"github.com/pion/webrtc/v3"
11+
"golang.org/x/net/proxy"
12+
"golang.org/x/xerrors"
13+
)
14+
15+
var (
16+
reservedHost = "coder"
17+
18+
// Proxy is a an ICE Server that uses a special hostname
19+
// to indicate traffic should be proxied.
20+
Proxy = webrtc.ICEServer{
21+
URLs: []string{"turns:" + reservedHost},
22+
Username: "coder",
23+
Credential: "coder",
24+
}
25+
)
26+
27+
// New constructs a new TURN server binding to the relay address provided.
28+
// The relay address is used to broadcast the location of an accepted connection.
29+
func New(relayAddress *turn.RelayAddressGeneratorStatic) (*Server, error) {
30+
if relayAddress == nil {
31+
// Default to localhost.
32+
relayAddress = &turn.RelayAddressGeneratorStatic{
33+
RelayAddress: net.IP{127, 0, 0, 1},
34+
Address: "127.0.0.1",
35+
}
36+
}
37+
logger := logging.NewDefaultLoggerFactory()
38+
logger.DefaultLogLevel = logging.LogLevelDisabled
39+
server := &Server{
40+
conns: make(chan net.Conn, 1),
41+
closed: make(chan struct{}),
42+
}
43+
server.listener = &listener{
44+
srv: server,
45+
}
46+
var err error
47+
server.turn, err = turn.NewServer(turn.ServerConfig{
48+
AuthHandler: func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) {
49+
return turn.GenerateAuthKey(Proxy.Username, "", Proxy.Credential.(string)), true
50+
},
51+
ListenerConfigs: []turn.ListenerConfig{{
52+
Listener: server.listener,
53+
RelayAddressGenerator: relayAddress,
54+
}},
55+
LoggerFactory: logger,
56+
})
57+
if err != nil {
58+
return nil, xerrors.Errorf("create server: %w", err)
59+
}
60+
61+
return server, nil
62+
}
63+
64+
// ProxyDialer accepts a proxy function that's called when the connection
65+
// address matches the reserved host in the "Proxy" ICE server.
66+
//
67+
// This should be passed to WebRTC connections as an ICE dialer.
68+
func ProxyDialer(proxyFunc func() (c net.Conn, err error)) proxy.Dialer {
69+
return dialer(func(network, addr string) (net.Conn, error) {
70+
host, _, err := net.SplitHostPort(addr)
71+
if err != nil {
72+
return nil, err
73+
}
74+
if host != reservedHost {
75+
return proxy.Direct.Dial(network, addr)
76+
}
77+
netConn, err := proxyFunc()
78+
if err != nil {
79+
return nil, err
80+
}
81+
return &conn{
82+
localAddress: &net.TCPAddr{
83+
IP: net.IPv4(127, 0, 0, 1),
84+
},
85+
Conn: netConn,
86+
}, nil
87+
})
88+
}
89+
90+
// Server accepts and connects TURN allocations.
91+
//
92+
// This is a thin wrapper around pion/turn that pipes
93+
// connections directly to the in-memory handler.
94+
type Server struct {
95+
listener *listener
96+
turn *turn.Server
97+
98+
closeMutex sync.Mutex
99+
closed chan (struct{})
100+
conns chan (net.Conn)
101+
}
102+
103+
// Accept consumes a new connection into the TURN server.
104+
// A unique remote address must exist per-connection.
105+
// pion/turn indexes allocations based on the address.
106+
func (s *Server) Accept(nc net.Conn, remoteAddress *net.TCPAddr) {
107+
s.conns <- &conn{
108+
Conn: nc,
109+
remoteAddress: remoteAddress,
110+
}
111+
}
112+
113+
// Close ends the TURN server.
114+
func (s *Server) Close() error {
115+
s.closeMutex.Lock()
116+
defer s.closeMutex.Unlock()
117+
if s.isClosed() {
118+
return nil
119+
}
120+
defer close(s.closed)
121+
close(s.conns)
122+
return s.turn.Close()
123+
}
124+
125+
func (s *Server) isClosed() bool {
126+
select {
127+
case <-s.closed:
128+
return true
129+
default:
130+
return false
131+
}
132+
}
133+
134+
// listener implements net.Listener for the TURN
135+
// server to consume.
136+
type listener struct {
137+
srv *Server
138+
}
139+
140+
func (l *listener) Accept() (net.Conn, error) {
141+
conn, ok := <-l.srv.conns
142+
if !ok {
143+
return nil, io.EOF
144+
}
145+
return conn, nil
146+
}
147+
148+
func (*listener) Close() error {
149+
return nil
150+
}
151+
152+
func (*listener) Addr() net.Addr {
153+
return nil
154+
}
155+
156+
type conn struct {
157+
net.Conn
158+
localAddress *net.TCPAddr
159+
remoteAddress *net.TCPAddr
160+
}
161+
162+
func (t *conn) LocalAddr() net.Addr {
163+
return t.localAddress
164+
}
165+
166+
func (t *conn) RemoteAddr() net.Addr {
167+
return t.remoteAddress
168+
}
169+
170+
type dialer func(network, addr string) (c net.Conn, err error)
171+
172+
func (d dialer) Dial(network, addr string) (c net.Conn, err error) {
173+
return d(network, addr)
174+
}

coderd/turnconn/turnconn_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package turnconn_test
2+
3+
import (
4+
"net"
5+
"sync"
6+
"testing"
7+
8+
"github.com/pion/webrtc/v3"
9+
"github.com/stretchr/testify/require"
10+
"go.uber.org/goleak"
11+
12+
"cdr.dev/slog"
13+
"cdr.dev/slog/sloggers/slogtest"
14+
"github.com/coder/coder/coderd/turnconn"
15+
"github.com/coder/coder/peer"
16+
)
17+
18+
func TestMain(m *testing.M) {
19+
goleak.VerifyTestMain(m)
20+
}
21+
22+
func TestTURNConn(t *testing.T) {
23+
t.Parallel()
24+
turnServer, err := turnconn.New(nil)
25+
require.NoError(t, err)
26+
defer turnServer.Close()
27+
28+
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
29+
30+
clientDialer, clientTURN := net.Pipe()
31+
turnServer.Accept(clientTURN, &net.TCPAddr{
32+
IP: net.IPv4(127, 0, 0, 1),
33+
Port: 1,
34+
})
35+
require.NoError(t, err)
36+
clientSettings := webrtc.SettingEngine{}
37+
clientSettings.SetNetworkTypes([]webrtc.NetworkType{webrtc.NetworkTypeTCP4, webrtc.NetworkTypeTCP6})
38+
clientSettings.SetRelayAcceptanceMinWait(0)
39+
clientSettings.SetICEProxyDialer(turnconn.ProxyDialer(func() (net.Conn, error) {
40+
return clientDialer, nil
41+
}))
42+
client, err := peer.Client([]webrtc.ICEServer{turnconn.Proxy}, &peer.ConnOptions{
43+
SettingEngine: clientSettings,
44+
Logger: logger.Named("client"),
45+
})
46+
require.NoError(t, err)
47+
48+
serverDialer, serverTURN := net.Pipe()
49+
turnServer.Accept(serverTURN, &net.TCPAddr{
50+
IP: net.IPv4(127, 0, 0, 1),
51+
Port: 2,
52+
})
53+
require.NoError(t, err)
54+
serverSettings := webrtc.SettingEngine{}
55+
serverSettings.SetNetworkTypes([]webrtc.NetworkType{webrtc.NetworkTypeTCP4, webrtc.NetworkTypeTCP6})
56+
serverSettings.SetRelayAcceptanceMinWait(0)
57+
serverSettings.SetICEProxyDialer(turnconn.ProxyDialer(func() (net.Conn, error) {
58+
return serverDialer, nil
59+
}))
60+
server, err := peer.Server([]webrtc.ICEServer{turnconn.Proxy}, &peer.ConnOptions{
61+
SettingEngine: serverSettings,
62+
Logger: logger.Named("server"),
63+
})
64+
require.NoError(t, err)
65+
exchange(t, client, server)
66+
67+
_, err = client.Ping()
68+
require.NoError(t, err)
69+
}
70+
71+
func exchange(t *testing.T, client, server *peer.Conn) {
72+
var wg sync.WaitGroup
73+
wg.Add(2)
74+
t.Cleanup(func() {
75+
_ = client.Close()
76+
_ = server.Close()
77+
78+
wg.Wait()
79+
})
80+
go func() {
81+
defer wg.Done()
82+
for {
83+
select {
84+
case c := <-server.LocalCandidate():
85+
client.AddRemoteCandidate(c)
86+
case c := <-server.LocalSessionDescription():
87+
client.SetRemoteSessionDescription(c)
88+
case <-server.Closed():
89+
return
90+
}
91+
}
92+
}()
93+
go func() {
94+
defer wg.Done()
95+
for {
96+
select {
97+
case c := <-client.LocalCandidate():
98+
server.AddRemoteCandidate(c)
99+
case c := <-client.LocalSessionDescription():
100+
server.SetRemoteSessionDescription(c)
101+
case <-client.Closed():
102+
return
103+
}
104+
}
105+
}()
106+
}

0 commit comments

Comments
 (0)