Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Aug 30, 2024. It is now read-only.
/coder-v1-cliPublic archive

Commit0e66c05

Browse files
authored
feat: Enable TURN proxying over WebSocket (#384)
* feat: Enable TURN proxying over WebSocket* Clean up API* Add net dependency* Close ws body* Add nop credentials to TURN candidate* Fix body close* Rename conn to dataChannelConn* Wrap websocket* Don't dial bad candidate* Refactor API* Fix deadline exceeding* Add comments* Try listing failed files* Organize imports* Fix test* Cleanup turnProxyConn impl
1 parenta8443d0 commit0e66c05

File tree

11 files changed

+179
-106
lines changed

11 files changed

+179
-106
lines changed

‎ci/scripts/files_changed.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ cd "$(git rev-parse --show-toplevel)"
66

77
if [[$(git ls-files --other --modified --exclude-standard) ]];then
88
echo"Files have changed:"
9+
git ls-files --other --modified --exclude-standard
910
git -c color.ui=never status
1011
exit 1
1112
fi

‎go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
github.com/pkg/browserv0.0.0-20180916011732-0a3d74bf9ce4
2424
github.com/rjeczalik/notifyv0.9.2
2525
github.com/spf13/cobrav1.2.1
26+
golang.org/x/netv0.0.0-20210614182718-04defd469f4e
2627
golang.org/x/syncv0.0.0-20210220032951-036812b2e83c
2728
golang.org/x/sysv0.0.0-20210514084401-e8d321eab015
2829
golang.org/x/termv0.0.0-20201126162022-7de9c90e9dd1

‎internal/cmd/agent.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ coder agent start --coder-url https://my-coder.com --token xxxx-xxxx
7373
}
7474
}
7575

76-
listener,err:=wsnet.Listen(context.Background(),wsnet.ListenEndpoint(u,token))
76+
listener,err:=wsnet.Listen(context.Background(),wsnet.ListenEndpoint(u,token),wsnet.TURNProxyWebSocket(u,token))
7777
iferr!=nil {
7878
returnxerrors.Errorf("listen: %w",err)
7979
}

‎internal/cmd/tunnel.go

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package cmd
22

33
import (
44
"context"
5-
"errors"
65
"fmt"
76
"io"
87
"net"
@@ -12,7 +11,6 @@ import (
1211

1312
"cdr.dev/slog"
1413
"cdr.dev/slog/sloggers/sloghuman"
15-
"github.com/pion/webrtc/v3"
1614
"github.com/spf13/cobra"
1715
"golang.org/x/xerrors"
1816

@@ -104,30 +102,14 @@ type tunnneler struct {
104102
}
105103

106104
func (c*tunnneler)start(ctx context.Context)error {
107-
username,password,err:=wsnet.TURNCredentials(c.token)
108-
iferr!=nil {
109-
returnxerrors.Errorf("failed to parse credentials from token")
110-
}
111-
server:= webrtc.ICEServer{
112-
URLs: []string{wsnet.TURNEndpoint(c.brokerAddr)},
113-
Username:username,
114-
Credential:password,
115-
CredentialType:webrtc.ICECredentialTypePassword,
116-
}
117-
118-
err=wsnet.DialICE(server,nil)
119-
iferrors.Is(err,wsnet.ErrInvalidCredentials) {
120-
returnxerrors.Errorf("failed to authenticate your user for this workspace")
121-
}
122-
iferrors.Is(err,wsnet.ErrMismatchedProtocol) {
123-
returnxerrors.Errorf("your TURN server is configured incorrectly. check TLS settings")
124-
}
125-
iferr!=nil {
126-
returnxerrors.Errorf("dial ice: %w",err)
127-
}
128-
129105
c.log.Debug(ctx,"Connecting to workspace...")
130-
wd,err:=wsnet.DialWebsocket(ctx,wsnet.ConnectEndpoint(c.brokerAddr,c.workspaceID,c.token), []webrtc.ICEServer{server})
106+
wd,err:=wsnet.DialWebsocket(
107+
ctx,
108+
wsnet.ConnectEndpoint(c.brokerAddr,c.workspaceID,c.token),
109+
&wsnet.DialOptions{
110+
TURNProxy:wsnet.TURNProxyWebSocket(c.brokerAddr,c.token),
111+
},
112+
)
131113
iferr!=nil {
132114
returnxerrors.Errorf("creating workspace dialer: %w",err)
133115
}

‎wsnet/auth.go

Lines changed: 0 additions & 22 deletions
This file was deleted.

‎wsnet/conn.go

Lines changed: 89 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
package wsnet
22

33
import (
4+
"context"
45
"fmt"
56
"net"
7+
"net/http"
68
"net/url"
79
"sync"
810
"time"
911

1012
"github.com/pion/datachannel"
1113
"github.com/pion/webrtc/v3"
14+
"golang.org/x/net/proxy"
15+
"nhooyr.io/websocket"
16+
17+
"cdr.dev/coder-cli/coder-sdk"
1218
)
1319

1420
const (
@@ -22,16 +28,6 @@ const (
2228
maxMessageLength=32*1024// 32 KB
2329
)
2430

25-
// TURNEndpoint returns the TURN address for a Coder baseURL.
26-
funcTURNEndpoint(baseURL*url.URL)string {
27-
turnScheme:="turns"
28-
ifbaseURL.Scheme==httpScheme {
29-
turnScheme="turn"
30-
}
31-
32-
returnfmt.Sprintf("%s:%s:5349?transport=tcp",turnScheme,baseURL.Hostname())
33-
}
34-
3531
// ListenEndpoint returns the Coder endpoint to listen for workspace connections.
3632
funcListenEndpoint(baseURL*url.URL,tokenstring)string {
3733
wsScheme:="wss"
@@ -50,7 +46,80 @@ func ConnectEndpoint(baseURL *url.URL, workspace, token string) string {
5046
returnfmt.Sprintf("%s://%s%s%s%s%s",wsScheme,baseURL.Host,"/api/private/envagent/",workspace,"/connect?session_token=",token)
5147
}
5248

53-
typeconnstruct {
49+
// TURNWebSocketICECandidate returns a valid relay ICEServer that can be used to
50+
// trigger a TURNWebSocketDialer.
51+
funcTURNProxyICECandidate() webrtc.ICEServer {
52+
return webrtc.ICEServer{
53+
URLs: []string{"turn:127.0.0.1:3478?transport=tcp"},
54+
Username:"~magicalusername~",
55+
Credential:"~magicalpassword~",
56+
CredentialType:webrtc.ICECredentialTypePassword,
57+
}
58+
}
59+
60+
// TURNWebSocketDialer proxies all TURN traffic through a WebSocket.
61+
funcTURNProxyWebSocket(baseURL*url.URL,tokenstring) proxy.Dialer {
62+
return&turnProxyDialer{
63+
baseURL:baseURL,
64+
token:token,
65+
}
66+
}
67+
68+
// Proxies all TURN ICEServer traffic through this dialer.
69+
// References Coder APIs with a specific token.
70+
typeturnProxyDialerstruct {
71+
baseURL*url.URL
72+
tokenstring
73+
}
74+
75+
func (t*turnProxyDialer)Dial(network,addrstring) (c net.Conn,errerror) {
76+
headers:= http.Header{}
77+
headers.Set("Session-Token",t.token)
78+
79+
ctx,cancel:=context.WithTimeout(context.Background(),time.Second*15)
80+
defercancel()
81+
82+
// Copy the baseURL so we can adjust path.
83+
url:=*t.baseURL
84+
url.Scheme="wss"
85+
ifurl.Scheme==httpScheme {
86+
url.Scheme="ws"
87+
}
88+
url.Path="/api/private/turn"
89+
conn,resp,err:=websocket.Dial(ctx,url.String(),&websocket.DialOptions{
90+
HTTPHeader:headers,
91+
})
92+
iferr!=nil {
93+
ifresp!=nil {
94+
deferresp.Body.Close()
95+
returnnil,coder.NewHTTPError(resp)
96+
}
97+
returnnil,fmt.Errorf("dial: %w",err)
98+
}
99+
100+
return&turnProxyConn{
101+
websocket.NetConn(context.Background(),conn,websocket.MessageBinary),
102+
},nil
103+
}
104+
105+
// turnProxyConn is a net.Conn wrapper that returns a TCPAddr for the
106+
// LocalAddr function. pion/ice unsafely checks the types. See:
107+
// https://github.com/pion/ice/blob/e78f26fb435987420546c70369ade5d713beca39/gather.go#L448
108+
typeturnProxyConnstruct {
109+
net.Conn
110+
}
111+
112+
// The LocalAddr specified here doesn't really matter,
113+
// it just has to be of type "TCPAddr".
114+
func (*turnProxyConn)LocalAddr() net.Addr {
115+
return&net.TCPAddr{
116+
IP:net.IPv4(127,0,0,1),
117+
Port:0,
118+
}
119+
}
120+
121+
// Properly buffers data for data channel connections.
122+
typedataChannelConnstruct {
54123
addr*net.UnixAddr
55124
dc*webrtc.DataChannel
56125
rw datachannel.ReadWriteCloser
@@ -62,7 +131,7 @@ type conn struct {
62131
writeMutex sync.Mutex
63132
}
64133

65-
func (c*conn)init() {
134+
func (c*dataChannelConn)init() {
66135
c.sendMore=make(chanstruct{},1)
67136
c.dc.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold)
68137
c.dc.OnBufferedAmountLow(func() {
@@ -78,11 +147,11 @@ func (c *conn) init() {
78147
})
79148
}
80149

81-
func (c*conn)Read(b []byte) (nint,errerror) {
150+
func (c*dataChannelConn)Read(b []byte) (nint,errerror) {
82151
returnc.rw.Read(b)
83152
}
84153

85-
func (c*conn)Write(b []byte) (nint,errerror) {
154+
func (c*dataChannelConn)Write(b []byte) (nint,errerror) {
86155
c.writeMutex.Lock()
87156
deferc.writeMutex.Unlock()
88157
iflen(b)>maxMessageLength {
@@ -101,7 +170,7 @@ func (c *conn) Write(b []byte) (n int, err error) {
101170
returnc.rw.Write(b)
102171
}
103172

104-
func (c*conn)Close()error {
173+
func (c*dataChannelConn)Close()error {
105174
c.closedMutex.Lock()
106175
deferc.closedMutex.Unlock()
107176
if!c.closed {
@@ -111,22 +180,22 @@ func (c *conn) Close() error {
111180
returnc.dc.Close()
112181
}
113182

114-
func (c*conn)LocalAddr() net.Addr {
183+
func (c*dataChannelConn)LocalAddr() net.Addr {
115184
returnc.addr
116185
}
117186

118-
func (c*conn)RemoteAddr() net.Addr {
187+
func (c*dataChannelConn)RemoteAddr() net.Addr {
119188
returnc.addr
120189
}
121190

122-
func (c*conn)SetDeadline(t time.Time)error {
191+
func (c*dataChannelConn)SetDeadline(t time.Time)error {
123192
returnnil
124193
}
125194

126-
func (c*conn)SetReadDeadline(t time.Time)error {
195+
func (c*dataChannelConn)SetReadDeadline(t time.Time)error {
127196
returnnil
128197
}
129198

130-
func (c*conn)SetWriteDeadline(t time.Time)error {
199+
func (c*dataChannelConn)SetWriteDeadline(t time.Time)error {
131200
returnnil
132201
}

‎wsnet/dial.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,26 @@ import (
1212

1313
"github.com/pion/datachannel"
1414
"github.com/pion/webrtc/v3"
15+
"golang.org/x/net/proxy"
1516
"nhooyr.io/websocket"
1617

1718
"cdr.dev/coder-cli/coder-sdk"
1819
)
1920

21+
// DialOptions are configurable options for a wsnet connection.
22+
typeDialOptionsstruct {
23+
// ICEServers is an array of STUN or TURN servers to use for negotiation purposes.
24+
// See: https://developer.mozilla.org/en-US/docs/Web/API/RTCConfiguration/iceServers
25+
ICEServers []webrtc.ICEServer
26+
27+
// TURNProxy is a function used to proxy all TURN traffic.
28+
// If specified without ICEServers, `TURNProxyICECandidate`
29+
// will be used.
30+
TURNProxy proxy.Dialer
31+
}
32+
2033
// DialWebsocket dials the broker with a WebSocket and negotiates a connection.
21-
funcDialWebsocket(ctx context.Context,brokerstring,iceServers []webrtc.ICEServer) (*Dialer,error) {
34+
funcDialWebsocket(ctx context.Context,brokerstring,options*DialOptions) (*Dialer,error) {
2235
conn,resp,err:=websocket.Dial(ctx,broker,nil)
2336
iferr!=nil {
2437
ifresp!=nil {
@@ -35,16 +48,24 @@ func DialWebsocket(ctx context.Context, broker string, iceServers []webrtc.ICESe
3548
// We should close the socket intentionally.
3649
_=conn.Close(websocket.StatusInternalError,"an error occurred")
3750
}()
38-
returnDial(nconn,iceServers)
51+
returnDial(nconn,options)
3952
}
4053

4154
// Dial negotiates a connection to a listener.
42-
funcDial(conn net.Conn,iceServers []webrtc.ICEServer) (*Dialer,error) {
43-
ificeServers==nil {
44-
iceServers= []webrtc.ICEServer{}
55+
funcDial(conn net.Conn,options*DialOptions) (*Dialer,error) {
56+
ifoptions==nil {
57+
options=&DialOptions{}
58+
}
59+
ifoptions.ICEServers==nil {
60+
options.ICEServers= []webrtc.ICEServer{}
61+
}
62+
// If the TURNProxy is specified and ICEServers aren't,
63+
// it's safe to assume we can inject the default proxy candidate.
64+
iflen(options.ICEServers)==0&&options.TURNProxy!=nil {
65+
options.ICEServers= []webrtc.ICEServer{TURNProxyICECandidate()}
4566
}
4667

47-
rtc,err:=newPeerConnection(iceServers)
68+
rtc,err:=newPeerConnection(options.ICEServers,options.TURNProxy)
4869
iferr!=nil {
4970
returnnil,fmt.Errorf("create peer connection: %w",err)
5071
}
@@ -70,7 +91,7 @@ func Dial(conn net.Conn, iceServers []webrtc.ICEServer) (*Dialer, error) {
7091

7192
offerMessage,err:=json.Marshal(&BrokerMessage{
7293
Offer:&offer,
73-
Servers:iceServers,
94+
Servers:options.ICEServers,
7495
})
7596
iferr!=nil {
7697
returnnil,fmt.Errorf("marshal offer message: %w",err)
@@ -287,7 +308,7 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.
287308
returnnil,ctx.Err()
288309
}
289310

290-
c:=&conn{
311+
c:=&dataChannelConn{
291312
addr:&net.UnixAddr{
292313
Name:address,
293314
Net:network,

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp