4
4
package magicsock
5
5
6
6
import (
7
+ "bufio"
7
8
"bytes"
8
9
"context"
9
10
crand "crypto/rand"
@@ -12,6 +13,7 @@ import (
12
13
"errors"
13
14
"fmt"
14
15
"io"
16
+ "log"
15
17
"math/rand"
16
18
"net"
17
19
"net/http"
@@ -37,6 +39,7 @@ import (
37
39
"golang.org/x/net/icmp"
38
40
"golang.org/x/net/ipv4"
39
41
"golang.org/x/net/ipv6"
42
+ "nhooyr.io/websocket"
40
43
"tailscale.com/cmd/testwrapper/flakytest"
41
44
"tailscale.com/derp"
42
45
"tailscale.com/derp/derphttp"
@@ -48,6 +51,7 @@ import (
48
51
"tailscale.com/net/ping"
49
52
"tailscale.com/net/stun/stuntest"
50
53
"tailscale.com/net/tstun"
54
+ "tailscale.com/net/wsconn"
51
55
"tailscale.com/tailcfg"
52
56
"tailscale.com/tstest"
53
57
"tailscale.com/tstest/natlab"
@@ -161,7 +165,16 @@ func newMagicStack(t testing.TB, logf logger.Logf, l nettype.PacketListener, der
161
165
return newMagicStackWithKey (t , logf , l , derpMap , privateKey )
162
166
}
163
167
168
+ func newMagicStackFunc (t testing.TB , logf logger.Logf , l nettype.PacketListener , derpMap * tailcfg.DERPMap , updateConnFunc func (ms * Conn )) * magicStack {
169
+ privateKey := key .NewNode ()
170
+ return newMagicStackWithKeyFunc (t , logf , l , derpMap , privateKey , updateConnFunc )
171
+ }
172
+
164
173
func newMagicStackWithKey (t testing.TB , logf logger.Logf , l nettype.PacketListener , derpMap * tailcfg.DERPMap , privateKey key.NodePrivate ) * magicStack {
174
+ return newMagicStackWithKeyFunc (t , logf , l , derpMap , privateKey , nil )
175
+ }
176
+
177
+ func newMagicStackWithKeyFunc (t testing.TB , logf logger.Logf , l nettype.PacketListener , derpMap * tailcfg.DERPMap , privateKey key.NodePrivate , updateConnFunc func (ms * Conn )) * magicStack {
165
178
t .Helper ()
166
179
167
180
epCh := make (chan []tailcfg.Endpoint , 100 ) // arbitrary
@@ -186,6 +199,11 @@ func newMagicStackWithKey(t testing.TB, logf logger.Logf, l nettype.PacketListen
186
199
187
200
wgLogger := wglog .NewLogger (logf )
188
201
dev := wgcfg .NewDevice (tsTun , conn .Bind (), wgLogger .DeviceLogger )
202
+
203
+ if updateConnFunc != nil {
204
+ updateConnFunc (conn )
205
+ }
206
+
189
207
dev .Up ()
190
208
191
209
// Wait for magicsock to connect up to DERP.
@@ -2876,3 +2894,109 @@ func TestAddrForSendLockedForWireGuardOnly(t *testing.T) {
2876
2894
}
2877
2895
}
2878
2896
}
2897
+
2898
+ // Copied from cmd/derper
2899
+ func addWebSocketSupport (s * derp.Server , base http.Handler ) http.Handler {
2900
+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
2901
+ up := strings .ToLower (r .Header .Get ("Upgrade" ))
2902
+
2903
+ // Very early versions of Tailscale set "Upgrade: WebSocket" but didn't actually
2904
+ // speak WebSockets (they still assumed DERP's binary framing). So to distinguish
2905
+ // clients that actually want WebSockets, look for an explicit "derp" subprotocol.
2906
+ if up != "websocket" || ! strings .Contains (r .Header .Get ("Sec-Websocket-Protocol" ), "derp" ) {
2907
+ base .ServeHTTP (w , r )
2908
+ return
2909
+ }
2910
+
2911
+ c , err := websocket .Accept (w , r , & websocket.AcceptOptions {
2912
+ Subprotocols : []string {"derp" },
2913
+ OriginPatterns : []string {"*" },
2914
+ // Disable compression because we transmit WireGuard messages that
2915
+ // are not compressible.
2916
+ // Additionally, Safari has a broken implementation of compression
2917
+ // (see https://github.com/nhooyr/websocket/issues/218) that makes
2918
+ // enabling it actively harmful.
2919
+ CompressionMode : websocket .CompressionDisabled ,
2920
+ })
2921
+ if err != nil {
2922
+ log .Printf ("websocket.Accept: %v" , err )
2923
+ return
2924
+ }
2925
+ defer c .Close (websocket .StatusInternalError , "closing" )
2926
+ if c .Subprotocol () != "derp" {
2927
+ c .Close (websocket .StatusPolicyViolation , "client must speak the derp subprotocol" )
2928
+ return
2929
+ }
2930
+ wc := wsconn .NetConn (r .Context (), c , websocket .MessageBinary )
2931
+ brw := bufio .NewReadWriter (bufio .NewReader (wc ), bufio .NewWriter (wc ))
2932
+ s .Accept (r .Context (), wc , brw , r .RemoteAddr )
2933
+ })
2934
+ }
2935
+
2936
+ func TestDERPForceWebsockets (t * testing.T ) {
2937
+ logf , closeLogf := logger .LogfCloser (t .Logf )
2938
+ defer closeLogf ()
2939
+
2940
+ // Create a DERP server manually, without a STUN server and with a custom
2941
+ // handler.
2942
+ derpServer := derp .NewServer (key .NewNode (), logf )
2943
+ derpHandler := derphttp .Handler (derpServer )
2944
+ derpHandler = addWebSocketSupport (derpServer , derpHandler )
2945
+
2946
+ var upgradeCount int64
2947
+ httpsrv := httptest .NewUnstartedServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
2948
+ up := r .Header .Get ("Upgrade" )
2949
+ if up != "" {
2950
+ if up != "websocket" {
2951
+ t .Errorf ("unexpected upgrade header: %q" , up )
2952
+ } else {
2953
+ atomic .AddInt64 (& upgradeCount , 1 )
2954
+ }
2955
+ }
2956
+
2957
+ derpHandler .ServeHTTP (w , r )
2958
+ }))
2959
+ httpsrv .Config .ErrorLog = logger .StdLogger (logf )
2960
+ httpsrv .Config .TLSNextProto = make (map [string ]func (* http.Server , * tls.Conn , http.Handler ))
2961
+ httpsrv .StartTLS ()
2962
+ t .Cleanup (func () {
2963
+ httpsrv .CloseClientConnections ()
2964
+ httpsrv .Close ()
2965
+ derpServer .Close ()
2966
+ })
2967
+
2968
+ derpMap := & tailcfg.DERPMap {
2969
+ Regions : map [int ]* tailcfg.DERPRegion {
2970
+ 1 : {
2971
+ RegionID : 1 ,
2972
+ RegionCode : "test" ,
2973
+ Nodes : []* tailcfg.DERPNode {
2974
+ {
2975
+ Name : "t1" ,
2976
+ RegionID : 1 ,
2977
+ HostName : "test-node.unused" ,
2978
+ IPv4 : "127.0.0.1" ,
2979
+ IPv6 : "" ,
2980
+ STUNPort : - 1 ,
2981
+ DERPPort : httpsrv .Listener .Addr ().(* net.TCPAddr ).Port ,
2982
+ InsecureForTests : true ,
2983
+ },
2984
+ },
2985
+ },
2986
+ },
2987
+ }
2988
+
2989
+ m := & natlab.Machine {Name : "m1" }
2990
+ ms := newMagicStackFunc (t , logger .WithPrefix (logf , "conn1: " ), m , derpMap , func (ms * Conn ) {
2991
+ ms .SetDERPForceWebsockets (true )
2992
+ })
2993
+ defer ms .Close ()
2994
+
2995
+ if len (ms .conn .activeDerp ) == 0 {
2996
+ t .Errorf ("unexpected DERP empty got: %v want: >0" , len (ms .conn .activeDerp ))
2997
+ }
2998
+
2999
+ if atomic .LoadInt64 (& upgradeCount ) == 0 {
3000
+ t .Errorf ("no websocket upgrade requests seen" )
3001
+ }
3002
+ }
0 commit comments