From 457c4fbf15a287645ab8af4d17c81b7e17ac2f44 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 12 Dec 2023 14:43:30 +0000 Subject: [PATCH 01/44] Add UDP Like transport mode to snowflake --- client/lib/packetIDConnClient.go | 109 +++++++++++++++++++++++++++++++ client/lib/snowflake.go | 12 +++- client/lib/webrtc.go | 15 ++++- proxy/lib/snowflake.go | 15 ++++- proxy/lib/webrtcconn.go | 10 +++ server/lib/http.go | 65 ++++++++++++++++++ server/lib/packetIDConnServer.go | 52 +++++++++++++++ server/lib/snowflake.go | 2 +- 8 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 client/lib/packetIDConnClient.go create mode 100644 server/lib/packetIDConnServer.go diff --git a/client/lib/packetIDConnClient.go b/client/lib/packetIDConnClient.go new file mode 100644 index 0000000..15b6c51 --- /dev/null +++ b/client/lib/packetIDConnClient.go @@ -0,0 +1,109 @@ +package snowflake_client + +import ( + "io" + "log" + "net" + "time" + + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" +) + +const ( + packetClientIDConn_StateNew = iota + packetClientIDConn_StateConnectionIDAcknowledged +) + +type ClientID = turbotunnel.ClientID + +func newPacketClientIDConn(ClientID ClientID, transport io.ReadWriter) *packetClientIDConn { + return &packetClientIDConn{ + state: packetClientIDConn_StateNew, + ConnID: ClientID, + transport: transport, + } +} + +type packetClientIDConn struct { + state int + ConnID ClientID + transport io.ReadWriter +} + +func (c *packetClientIDConn) Write(p []byte) (int, error) { + switch c.state { + case packetClientIDConn_StateConnectionIDAcknowledged: + packet := make([]byte, len(p)+1) + packet[0] = 0xff + copy(packet[1:], p) + _, err := c.transport.Write(packet) + if err != nil { + return 0, err + } + return len(p), nil + case packetClientIDConn_StateNew: + packet := make([]byte, len(p)+1+len(c.ConnID)) + packet[0] = 0xfe + copy(packet[1:], c.ConnID[:]) + copy(packet[1+len(c.ConnID):], p) + _, err := c.transport.Write(packet) + if err != nil { + return 0, err + } + return len(p), nil + default: + panic("invalid state") + } +} + +func (c *packetClientIDConn) Read(p []byte) (int, error) { + n, err := c.transport.Read(p) + if err != nil { + return 0, err + } + if p[0] == 0xff { + c.state = packetClientIDConn_StateConnectionIDAcknowledged + return copy(p, p[1:n]), nil + } else { + log.Println("discarded unknown packet") + } + return 0, nil +} + +type packetConnWrapper struct { + io.ReadWriter + remoteAddr net.Addr + localAddr net.Addr +} + +func (pcw *packetConnWrapper) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + n, err = pcw.Read(p) + if err != nil { + return 0, nil, err + } + return n, pcw.remoteAddr, nil +} + +func (pcw *packetConnWrapper) WriteTo(p []byte, addr net.Addr) (n int, err error) { + return pcw.Write(p) +} + +func (pcw *packetConnWrapper) Close() error { + return nil +} + +func (pcw *packetConnWrapper) LocalAddr() net.Addr { + return pcw.localAddr +} + +func (pcw *packetConnWrapper) SetDeadline(t time.Time) error { + return nil +} + +func (pcw *packetConnWrapper) SetReadDeadline(t time.Time) error { + return nil +} + +func (pcw *packetConnWrapper) SetWriteDeadline(t time.Time) error { + return nil +} diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index f1a3bad..7442ce9 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -339,6 +339,16 @@ func newSession(snowflakes SnowflakeCollector) (net.PacketConn, *smux.Session, e return nil, errors.New("handler: Received invalid Snowflake") } log.Println("---- Handler: snowflake assigned ----") + log.Printf("activeTransportMode = %c \n", conn.activeTransportMode) + if conn.activeTransportMode == 'u' { + packetIDConn := newPacketClientIDConn(clientID, conn) + packetConnWrapper := &packetConnWrapper{ + ReadWriter: packetIDConn, + remoteAddr: dummyAddr{}, + localAddr: dummyAddr{}, + } + return packetConnWrapper, nil + } // Send the magic Turbo Tunnel token. _, err := conn.Write(turbotunnel.Token[:]) if err != nil { @@ -363,7 +373,7 @@ func newSession(snowflakes SnowflakeCollector) (net.PacketConn, *smux.Session, e return nil, nil, err } // Permit coalescing the payloads of consecutive sends. - conn.SetStreamMode(true) + conn.SetStreamMode(false) // Set the maximum send and receive window sizes to a high number // Removes KCP bottlenecks: https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/issues/40026 conn.SetWindowSize(WindowSize, WindowSize) diff --git a/client/lib/webrtc.go b/client/lib/webrtc.go index 9d803a2..85b18c9 100644 --- a/client/lib/webrtc.go +++ b/client/lib/webrtc.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "encoding/hex" "errors" + "fmt" "io" "log" "net" @@ -43,6 +44,8 @@ type WebRTCPeer struct { bytesLogger bytesLogger eventsLogger event.SnowflakeEventReceiver proxy *url.URL + + activeTransportMode byte } // Deprecated: Use NewWebRTCPeerWithNatPolicyAndEventsAndProxy Instead. @@ -191,6 +194,7 @@ func (c *WebRTCPeer) connect( ) error { log.Println(c.id, " connecting...") + c.activeTransportMode = 'u' err := c.preparePeerConnection(config, broker.keepLocalAddresses) localDescription := c.pc.LocalDescription() c.eventsLogger.OnNewSnowflakeEvent(event.EventOnOfferCreated{ @@ -297,8 +301,17 @@ func (c *WebRTCPeer) preparePeerConnection( return err } ordered := true + var maxRetransmission *uint16 + if c.activeTransportMode == 'u' { + ordered = false + maxRetransmissionVal := uint16(0) + maxRetransmission = &maxRetransmissionVal + } + protocol := fmt.Sprintf("%c", c.activeTransportMode) dataChannelOptions := &webrtc.DataChannelInit{ - Ordered: &ordered, + Ordered: &ordered, + Protocol: &protocol, + MaxRetransmits: maxRetransmission, } // We must create the data channel before creating an offer // https://github.com/pion/webrtc/wiki/Release-WebRTC@v3.0.0#a-data-channel-is-no-longer-implicitly-created-with-a-peerconnection diff --git a/proxy/lib/snowflake.go b/proxy/lib/snowflake.go index 7bd1aaf..c6c667b 100644 --- a/proxy/lib/snowflake.go +++ b/proxy/lib/snowflake.go @@ -343,7 +343,7 @@ func (sf *SnowflakeProxy) datachannelHandler(conn *webRTCConn, remoteAddr net.Ad relayURL = sf.RelayURL } - wsConn, err := connectToRelay(relayURL, remoteAddr) + wsConn, err := connectToRelay(relayURL, remoteAddr, conn.GetConnectionProtocol()) if err != nil { log.Print(err) return @@ -354,7 +354,11 @@ func (sf *SnowflakeProxy) datachannelHandler(conn *webRTCConn, remoteAddr net.Ad log.Printf("datachannelHandler ends") } -func connectToRelay(relayURL string, remoteAddr net.Addr) (*websocketconn.Conn, error) { +func connectToRelay( + relayURL string, + remoteAddr net.Addr, + webrtcConnProtocol string, +) (*websocketconn.Conn, error) { u, err := url.Parse(relayURL) if err != nil { return nil, fmt.Errorf("invalid relay url: %s", err) @@ -370,6 +374,12 @@ func connectToRelay(relayURL string, remoteAddr net.Addr) (*websocketconn.Conn, log.Printf("no remote address given in websocket") } + if webrtcConnProtocol != "" { + q := u.Query() + q.Set("protocol", webrtcConnProtocol) + u.RawQuery = q.Encode() + } + ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil) if err != nil { return nil, fmt.Errorf("error dialing relay: %s = %s", u.String(), err) @@ -451,6 +461,7 @@ func (sf *SnowflakeProxy) makePeerConnectionFromOffer( pr, pw := io.Pipe() conn := newWebRTCConn(pc, dc, pr, sf.bytesLogger) + conn.SetConnectionProtocol(dc.Protocol()) dc.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold) diff --git a/proxy/lib/webrtcconn.go b/proxy/lib/webrtcconn.go index b09ff3d..f2381bf 100644 --- a/proxy/lib/webrtcconn.go +++ b/proxy/lib/webrtcconn.go @@ -41,6 +41,8 @@ type webRTCConn struct { cancelTimeoutLoop context.CancelFunc bytesLogger bytesLogger + + protocol string } func newWebRTCConn(pc *webrtc.PeerConnection, dc *webrtc.DataChannel, pr *io.PipeReader, bytesLogger bytesLogger) *webRTCConn { @@ -137,6 +139,14 @@ func (c *webRTCConn) SetWriteDeadline(t time.Time) error { return fmt.Errorf("SetWriteDeadline not implemented") } +func (c *webRTCConn) SetConnectionProtocol(protocol string) { + c.protocol = protocol +} + +func (c *webRTCConn) GetConnectionProtocol() string { + return c.protocol +} + func remoteIPFromSDP(str string) net.IP { // Look for remote IP in "a=candidate" attribute fields // https://tools.ietf.org/html/rfc5245#section-15.1 diff --git a/server/lib/http.go b/server/lib/http.go index 403aeb1..a667f7b 100644 --- a/server/lib/http.go +++ b/server/lib/http.go @@ -108,6 +108,16 @@ func (handler *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Pass the address of client as the remote address of incoming connection clientIPParam := r.URL.Query().Get("client_ip") addr := clientAddr(clientIPParam) + clientTransport := r.URL.Query().Get("protocol") + + if clientTransport == "u" { + err = handler.turboTunnelUDPLikeMode(conn, addr) + if err != nil && err != io.EOF { + log.Println(err) + return + } + return + } var token [len(turbotunnel.Token)]byte _, err = io.ReadFull(conn, token[:]) @@ -221,6 +231,61 @@ func (handler *httpHandler) turbotunnelMode(conn net.Conn, addr net.Addr) error return nil } +func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr) error { + packetConnIDCon := packetConnIDConnServer{Conn: conn} + var packet [1600]byte + n, err := packetConnIDCon.Read(packet[:]) + if err != nil { + return fmt.Errorf("reading ClientID: %v", err) + } + clientID, err := packetConnIDCon.GetClientID() + if err != nil { + return fmt.Errorf("reading ClientID: %v", err) + } + clientIDAddrMap.Set(clientID, addr) + + pconn := handler.lookupPacketConn(clientID) + pconn.QueueIncoming(packet[:n], clientID) + var wg sync.WaitGroup + wg.Add(2) + done := make(chan struct{}) + go func() { + defer wg.Done() + defer close(done) // Signal the write loop to finish + for { + n, err := packetConnIDCon.Read(packet[:]) + if err != nil { + log.Println(err) + return + } + pconn.QueueIncoming(packet[:n], clientID) + } + }() + go func() { + defer wg.Done() + defer conn.Close() // Signal the read loop to finish + for { + select { + case <-done: + return + case p, ok := <-pconn.OutgoingQueue(clientID): + if !ok { + return + } + _, err := packetConnIDCon.Write(p) + pconn.Restore(p) + if err != nil { + log.Println(err) + return + } + } + } + }() + + wg.Wait() + return nil +} + // ClientMapAddr is a string that represents a connecting client. type ClientMapAddr string diff --git a/server/lib/packetIDConnServer.go b/server/lib/packetIDConnServer.go new file mode 100644 index 0000000..feca1fa --- /dev/null +++ b/server/lib/packetIDConnServer.go @@ -0,0 +1,52 @@ +package snowflake_server + +import ( + "errors" + "net" + + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" +) + +type ConnID = turbotunnel.ClientID + +type packetConnIDConnServer struct { + // This net.Conn must preserve message boundaries. + net.Conn + connID ConnID + clientIDReceived bool +} + +var ErrClientIDNotReceived = errors.New("ClientID not received") + +func (p *packetConnIDConnServer) GetClientID() (ConnID, error) { + if !p.clientIDReceived { + return p.connID, ErrClientIDNotReceived + } + return p.connID, nil +} + +func (p *packetConnIDConnServer) Read(buf []byte) (n int, err error) { + n, err = p.Conn.Read(buf) + if err != nil { + return + } + switch buf[0] { + case 0xfe: + p.clientIDReceived = true + copy(p.connID[:], buf[1:9]) + copy(buf[0:], buf[9:]) + return n - 9, nil + case 0xff: + copy(buf[0:], buf[1:]) + return n - 1, nil + } + return 0, nil +} + +func (p *packetConnIDConnServer) Write(buf []byte) (n int, err error) { + n, err = p.Conn.Write(append([]byte{0xff}, buf...)) + if err != nil { + return 0, err + } + return len(buf) - 1, nil +} diff --git a/server/lib/snowflake.go b/server/lib/snowflake.go index bcf9dd6..d7d0c40 100644 --- a/server/lib/snowflake.go +++ b/server/lib/snowflake.go @@ -253,7 +253,7 @@ func (l *SnowflakeListener) acceptSessions(ln *kcp.Listener) error { return err } // Permit coalescing the payloads of consecutive sends. - conn.SetStreamMode(true) + conn.SetStreamMode(false) // Set the maximum send and receive window sizes to a high number // Removes KCP bottlenecks: https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/issues/40026 conn.SetWindowSize(WindowSize, WindowSize) From 93d303b47b72227a68efa7379519dc65b6c9962d Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 12 Dec 2023 14:43:56 +0000 Subject: [PATCH 02/44] Add testing environment helpers --- client/snowflake.go | 6 +++++- proxy/lib/snowflake.go | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/client/snowflake.go b/client/snowflake.go index 648481f..fb6bbb7 100644 --- a/client/snowflake.go +++ b/client/snowflake.go @@ -271,7 +271,11 @@ func main() { switch methodName { case "snowflake": // TODO: Be able to recover when SOCKS dies. - ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") + listenAddr := "127.0.0.1:0" + if forcedListenAddr := os.Getenv("SNOWFLAKE_TEST_FORCELISTENADDR"); forcedListenAddr != "" { + listenAddr = forcedListenAddr + } + ln, err := pt.ListenSocks("tcp", listenAddr) if err != nil { pt.CmethodError(methodName, err.Error()) break diff --git a/proxy/lib/snowflake.go b/proxy/lib/snowflake.go index c6c667b..114f517 100644 --- a/proxy/lib/snowflake.go +++ b/proxy/lib/snowflake.go @@ -35,6 +35,7 @@ import ( "net" "net/http" "net/url" + "os" "strings" "sync" "time" @@ -849,6 +850,11 @@ func (sf *SnowflakeProxy) Stop() { func (sf *SnowflakeProxy) checkNATType(config webrtc.Configuration, probeURL string) error { log.Printf("Checking our NAT type, contacting NAT check probe server at \"%v\"...", probeURL) + if os.Getenv("SNOWFLAKE_TEST_ASSUMEUNRESTRICTED") != "" { + currentNATType = NATUnrestricted + return nil + } + probe, err := newSignalingServer(probeURL) if err != nil { return fmt.Errorf("Error parsing url: %w", err) From ad5edd3f010e9c0a34cd1d33dbe28c7ba554c874 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 19 Dec 2023 14:59:45 +0000 Subject: [PATCH 03/44] add kcp setting adjustment SNOWFLAKE_TEST_KCP_FAST3MODE --- client/lib/snowflake.go | 9 +++++++++ server/lib/snowflake.go | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index 7442ce9..77c784e 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -32,6 +32,7 @@ import ( "math/rand" "net" "net/url" + "os" "strings" "time" @@ -385,6 +386,14 @@ func newSession(snowflakes SnowflakeCollector) (net.PacketConn, *smux.Session, e 0, // default resend 1, // nc=1 => congestion window off ) + if os.Getenv("SNOWFLAKE_TEST_KCP_FAST3MODE") == "1" { + conn.SetNoDelay( + 1, + 10, + 2, + 1, + ) + } // On the KCP connection we overlay an smux session and stream. smuxConfig := smux.DefaultConfig() smuxConfig.Version = 2 diff --git a/server/lib/snowflake.go b/server/lib/snowflake.go index d7d0c40..b158f03 100644 --- a/server/lib/snowflake.go +++ b/server/lib/snowflake.go @@ -41,6 +41,7 @@ import ( "log" "net" "net/http" + "os" "sync" "time" @@ -265,6 +266,14 @@ func (l *SnowflakeListener) acceptSessions(ln *kcp.Listener) error { 0, // default resend 1, // nc=1 => congestion window off ) + if os.Getenv("SNOWFLAKE_TEST_KCP_FAST3MODE") == "1" { + conn.SetNoDelay( + 1, + 10, + 2, + 1, + ) + } go func() { defer conn.Close() err := l.acceptStreams(conn) From bf165264b18fd600335e5e5bdb2d37978b057667 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Mon, 29 Apr 2024 14:53:02 +0100 Subject: [PATCH 04/44] add client side support for extra data based client id --- client/lib/rendezvous.go | 38 ++++++++++++++++++++++++++++++-------- client/lib/snowflake.go | 18 +++++++++++++----- client/lib/webrtc.go | 26 ++++++++++++++++++++++---- 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/client/lib/rendezvous.go b/client/lib/rendezvous.go index bf1a48c..bb344ec 100644 --- a/client/lib/rendezvous.go +++ b/client/lib/rendezvous.go @@ -13,6 +13,8 @@ import ( "sync" "time" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" + "github.com/pion/webrtc/v4" utls "github.com/refraction-networking/utls" @@ -249,8 +251,9 @@ type WebRTCDialer struct { webrtcConfig *webrtc.Configuration max int - eventLogger event.SnowflakeEventReceiver - proxy *url.URL + eventLogger event.SnowflakeEventReceiver + proxy *url.URL + connectionID turbotunnel.ClientID } // Deprecated: Use NewWebRTCDialerWithNatPolicyAndEventsAndProxy instead @@ -281,7 +284,6 @@ func NewWebRTCDialerWithEventsAndProxy(broker *BrokerChannel, iceServers []webrt ) } -// NewWebRTCDialerWithNatPolicyAndEventsAndProxy constructs a new WebRTCDialer. func NewWebRTCDialerWithNatPolicyAndEventsAndProxy( broker *BrokerChannel, natPolicy *NATPolicy, @@ -289,6 +291,27 @@ func NewWebRTCDialerWithNatPolicyAndEventsAndProxy( max int, eventLogger event.SnowflakeEventReceiver, proxy *url.URL, +) *WebRTCDialer { + return NewWebRTCDialerWithNatPolicyAndEventsAndProxyAndClientID( + broker, + natPolicy, + iceServers, + max, + eventLogger, + proxy, + turbotunnel.NewClientID(), + ) +} + +// NewWebRTCDialerWithNatPolicyAndEventsAndProxy constructs a new WebRTCDialer. +func NewWebRTCDialerWithNatPolicyAndEventsAndProxyAndClientID( + broker *BrokerChannel, + natPolicy *NATPolicy, + iceServers []webrtc.ICEServer, + max int, + eventLogger event.SnowflakeEventReceiver, + proxy *url.URL, + clientID turbotunnel.ClientID, ) *WebRTCDialer { config := webrtc.Configuration{ ICEServers: iceServers, @@ -300,8 +323,9 @@ func NewWebRTCDialerWithNatPolicyAndEventsAndProxy( webrtcConfig: &config, max: max, - eventLogger: eventLogger, - proxy: proxy, + eventLogger: eventLogger, + proxy: proxy, + connectionID: clientID, } } @@ -309,9 +333,7 @@ func NewWebRTCDialerWithNatPolicyAndEventsAndProxy( func (w WebRTCDialer) Catch() (*WebRTCPeer, error) { // TODO: [#25591] Fetch ICE server information from Broker. // TODO: [#25596] Consider TURN servers here too. - return NewWebRTCPeerWithNatPolicyAndEventsAndProxy( - w.webrtcConfig, w.BrokerChannel, w.natPolicy, w.eventLogger, w.proxy, - ) + return NewWebRTCPeerWithEventsAndProxy(w.webrtcConfig, w.BrokerChannel, w.eventLogger, w.proxy) } // GetMax returns the maximum number of snowflakes to collect. diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index 77c784e..af00bc9 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -78,6 +78,8 @@ type Transport struct { // EventDispatcher is the event bus for snowflake events. // When an important event happens, it will be distributed here. eventDispatcher event.SnowflakeEventDispatcher + + clientID turbotunnel.ClientID } // ClientConfig defines how the SnowflakeClient will connect to the broker and Snowflake proxies. @@ -164,7 +166,11 @@ func NewSnowflakeClient(config ClientConfig) (*Transport, error) { max = config.Max } eventsLogger := event.NewSnowflakeEventDispatcher() - transport := &Transport{dialer: NewWebRTCDialerWithNatPolicyAndEventsAndProxy(broker, natPolicy, iceServers, max, eventsLogger, config.CommunicationProxy), eventDispatcher: eventsLogger} + clientID := turbotunnel.NewClientID() + transport := &Transport{ + dialer: NewWebRTCDialerWithNatPolicyAndEventsAndProxy(broker, natPolicy, iceServers, max, eventsLogger, config.CommunicationProxy), + eventDispatcher: eventsLogger, clientID: clientID, + } return transport, nil } @@ -198,7 +204,7 @@ func (t *Transport) Dial() (net.Conn, error) { // Create a new smux session log.Printf("---- SnowflakeConn: starting a new session ---") - pconn, sess, err := newSession(snowflakes) + pconn, sess, err := newSession(snowflakes, t.clientID) if err != nil { return nil, err } @@ -324,8 +330,11 @@ func parseIceServers(addresses []string) []webrtc.ICEServer { // newSession returns a new smux.Session and the net.PacketConn it is running // over. The net.PacketConn successively connects through Snowflake proxies // pulled from snowflakes. -func newSession(snowflakes SnowflakeCollector) (net.PacketConn, *smux.Session, error) { +func newSession(snowflakes SnowflakeCollector, clientIDCandid turbotunnel.ClientID) (net.PacketConn, *smux.Session, error) { clientID := turbotunnel.NewClientID() + if clientIDCandid != (turbotunnel.ClientID{}) { + clientID = clientIDCandid + } // We build a persistent KCP session on a sequence of ephemeral WebRTC // connections. This dialContext tells RedialPacketConn how to get a new @@ -342,9 +351,8 @@ func newSession(snowflakes SnowflakeCollector) (net.PacketConn, *smux.Session, e log.Println("---- Handler: snowflake assigned ----") log.Printf("activeTransportMode = %c \n", conn.activeTransportMode) if conn.activeTransportMode == 'u' { - packetIDConn := newPacketClientIDConn(clientID, conn) packetConnWrapper := &packetConnWrapper{ - ReadWriter: packetIDConn, + ReadWriter: conn, remoteAddr: dummyAddr{}, localAddr: dummyAddr{}, } diff --git a/client/lib/webrtc.go b/client/lib/webrtc.go index 85b18c9..a78c264 100644 --- a/client/lib/webrtc.go +++ b/client/lib/webrtc.go @@ -19,6 +19,7 @@ import ( "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/event" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/proxy" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/util" ) @@ -46,6 +47,7 @@ type WebRTCPeer struct { proxy *url.URL activeTransportMode byte + connectionID turbotunnel.ClientID } // Deprecated: Use NewWebRTCPeerWithNatPolicyAndEventsAndProxy Instead. @@ -77,15 +79,30 @@ func NewWebRTCPeerWithEventsAndProxy( ) } +func NewWebRTCPeerWithNatPolicyAndEventsAndProxy( + config *webrtc.Configuration, + broker *BrokerChannel, natPolicy *NATPolicy, eventsLogger event.SnowflakeEventReceiver, proxy *url.URL, +) (*WebRTCPeer, error) { + return NewWebRTCPeerWithNatPolicyAndEventsProxyAndClientID( + config, + broker, + natPolicy, + eventsLogger, + proxy, + turbotunnel.ClientID{}, + ) +} + // NewWebRTCPeerWithNatPolicyAndEventsAndProxy constructs // a WebRTC PeerConnection to a snowflake proxy. // // The creation of the peer handles the signaling to the Snowflake broker, including // the exchange of SDP information, the creation of a PeerConnection, and the establishment // of a DataChannel to the Snowflake proxy. -func NewWebRTCPeerWithNatPolicyAndEventsAndProxy( - config *webrtc.Configuration, broker *BrokerChannel, natPolicy *NATPolicy, - eventsLogger event.SnowflakeEventReceiver, proxy *url.URL, +// connectionID is the hinted ID for the connection. +func NewWebRTCPeerWithNatPolicyAndEventsProxyAndClientID(config *webrtc.Configuration, + broker *BrokerChannel, natPolicy *NATPolicy, eventsLogger event.SnowflakeEventReceiver, proxy *url.URL, + clientID turbotunnel.ClientID, ) (*WebRTCPeer, error) { if eventsLogger == nil { eventsLogger = event.NewSnowflakeEventDispatcher() @@ -109,6 +126,7 @@ func NewWebRTCPeerWithNatPolicyAndEventsAndProxy( connection.eventsLogger = eventsLogger connection.proxy = proxy + connection.connectionID = clientID err := connection.connect(config, broker, natPolicy) if err != nil { @@ -307,7 +325,7 @@ func (c *WebRTCPeer) preparePeerConnection( maxRetransmissionVal := uint16(0) maxRetransmission = &maxRetransmissionVal } - protocol := fmt.Sprintf("%c", c.activeTransportMode) + protocol := fmt.Sprintf("%c %s", c.activeTransportMode, c.connectionID.String()) dataChannelOptions := &webrtc.DataChannelInit{ Ordered: &ordered, Protocol: &protocol, From c7ccaa38f9e31b492efd01d4852e5e0482d3452e Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 2 May 2024 11:12:08 +0100 Subject: [PATCH 05/44] add server side support for extra data based client id --- server/lib/http.go | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/server/lib/http.go b/server/lib/http.go index a667f7b..c003d97 100644 --- a/server/lib/http.go +++ b/server/lib/http.go @@ -7,11 +7,13 @@ import ( "crypto/rand" "crypto/sha256" "encoding/binary" + "encoding/hex" "fmt" "io" "log" "net" "net/http" + "strings" "sync" "time" @@ -108,10 +110,16 @@ func (handler *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Pass the address of client as the remote address of incoming connection clientIPParam := r.URL.Query().Get("client_ip") addr := clientAddr(clientIPParam) - clientTransport := r.URL.Query().Get("protocol") + protocol := r.URL.Query().Get("protocol") + + clientTransport := "t" + + if protocol != "" { + clientTransport = fmt.Sprintf("%c", protocol[0]) + } if clientTransport == "u" { - err = handler.turboTunnelUDPLikeMode(conn, addr) + err = handler.turboTunnelUDPLikeMode(conn, addr, protocol) if err != nil && err != io.EOF { log.Println(err) return @@ -231,21 +239,19 @@ func (handler *httpHandler) turbotunnelMode(conn net.Conn, addr net.Addr) error return nil } -func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr) error { - packetConnIDCon := packetConnIDConnServer{Conn: conn} +func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr, protocol string) error { var packet [1600]byte - n, err := packetConnIDCon.Read(packet[:]) - if err != nil { - return fmt.Errorf("reading ClientID: %v", err) - } - clientID, err := packetConnIDCon.GetClientID() + + clientID := turbotunnel.ClientID{} + compoments := strings.Split(protocol, " ") + _, err := hex.Decode(clientID[:], []byte(compoments[1])) if err != nil { return fmt.Errorf("reading ClientID: %v", err) } + clientIDAddrMap.Set(clientID, addr) pconn := handler.lookupPacketConn(clientID) - pconn.QueueIncoming(packet[:n], clientID) var wg sync.WaitGroup wg.Add(2) done := make(chan struct{}) @@ -253,7 +259,7 @@ func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr) defer wg.Done() defer close(done) // Signal the write loop to finish for { - n, err := packetConnIDCon.Read(packet[:]) + n, err := conn.Read(packet[:]) if err != nil { log.Println(err) return @@ -272,7 +278,7 @@ func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr) if !ok { return } - _, err := packetConnIDCon.Write(p) + _, err := conn.Write(p) pconn.Restore(p) if err != nil { log.Println(err) From 2606075bdf6c668c620edf6fdd21df4223f8b2dd Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Mon, 13 May 2024 13:27:52 +0100 Subject: [PATCH 06/44] add debug protocol output --- proxy/lib/snowflake.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/lib/snowflake.go b/proxy/lib/snowflake.go index 114f517..9f5ee5a 100644 --- a/proxy/lib/snowflake.go +++ b/proxy/lib/snowflake.go @@ -474,7 +474,7 @@ func (sf *SnowflakeProxy) makePeerConnectionFromOffer( }) dc.OnOpen(func() { - log.Printf("Data Channel %s-%d open\n", dc.Label(), dc.ID()) + log.Printf("Data Channel %s-%d;%s open\n", dc.Label(), dc.ID(), dc.Protocol()) sf.EventDispatcher.OnNewSnowflakeEvent(event.EventOnProxyClientConnected{}) if sf.OutboundAddress != "" { From c0acdd7d2a1f519a6d734497cc04e8ec1fb4bfaf Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Mon, 13 May 2024 13:49:01 +0100 Subject: [PATCH 07/44] fix pass client id to webrtc dialer --- client/lib/snowflake.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index af00bc9..436d55b 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -168,7 +168,7 @@ func NewSnowflakeClient(config ClientConfig) (*Transport, error) { eventsLogger := event.NewSnowflakeEventDispatcher() clientID := turbotunnel.NewClientID() transport := &Transport{ - dialer: NewWebRTCDialerWithNatPolicyAndEventsAndProxy(broker, natPolicy, iceServers, max, eventsLogger, config.CommunicationProxy), + dialer: NewWebRTCDialerWithNatPolicyAndEventsAndProxyAndClientID(broker, natPolicy, iceServers, max, eventsLogger, config.CommunicationProxy, clientID), eventDispatcher: eventsLogger, clientID: clientID, } From 15189aa5aceda63ec06f57dc5ce630ada3c318a9 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Mon, 13 May 2024 16:02:01 +0100 Subject: [PATCH 08/44] fix checking number of arg before accessing it --- server/lib/http.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/lib/http.go b/server/lib/http.go index c003d97..70ffa16 100644 --- a/server/lib/http.go +++ b/server/lib/http.go @@ -244,6 +244,9 @@ func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr, clientID := turbotunnel.ClientID{} compoments := strings.Split(protocol, " ") + if len(compoments) != 2 { + return fmt.Errorf("invalid protocol: %s", protocol) + } _, err := hex.Decode(clientID[:], []byte(compoments[1])) if err != nil { return fmt.Errorf("reading ClientID: %v", err) From ecfd0f3f76be0044c59c4640da5ffa28c7e222ec Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Mon, 13 May 2024 16:09:10 +0100 Subject: [PATCH 09/44] delete expired connection wrapper --- client/lib/packetIDConnClient.go | 109 ------------------------------- server/lib/packetIDConnServer.go | 52 --------------- 2 files changed, 161 deletions(-) delete mode 100644 client/lib/packetIDConnClient.go delete mode 100644 server/lib/packetIDConnServer.go diff --git a/client/lib/packetIDConnClient.go b/client/lib/packetIDConnClient.go deleted file mode 100644 index 15b6c51..0000000 --- a/client/lib/packetIDConnClient.go +++ /dev/null @@ -1,109 +0,0 @@ -package snowflake_client - -import ( - "io" - "log" - "net" - "time" - - "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" -) - -const ( - packetClientIDConn_StateNew = iota - packetClientIDConn_StateConnectionIDAcknowledged -) - -type ClientID = turbotunnel.ClientID - -func newPacketClientIDConn(ClientID ClientID, transport io.ReadWriter) *packetClientIDConn { - return &packetClientIDConn{ - state: packetClientIDConn_StateNew, - ConnID: ClientID, - transport: transport, - } -} - -type packetClientIDConn struct { - state int - ConnID ClientID - transport io.ReadWriter -} - -func (c *packetClientIDConn) Write(p []byte) (int, error) { - switch c.state { - case packetClientIDConn_StateConnectionIDAcknowledged: - packet := make([]byte, len(p)+1) - packet[0] = 0xff - copy(packet[1:], p) - _, err := c.transport.Write(packet) - if err != nil { - return 0, err - } - return len(p), nil - case packetClientIDConn_StateNew: - packet := make([]byte, len(p)+1+len(c.ConnID)) - packet[0] = 0xfe - copy(packet[1:], c.ConnID[:]) - copy(packet[1+len(c.ConnID):], p) - _, err := c.transport.Write(packet) - if err != nil { - return 0, err - } - return len(p), nil - default: - panic("invalid state") - } -} - -func (c *packetClientIDConn) Read(p []byte) (int, error) { - n, err := c.transport.Read(p) - if err != nil { - return 0, err - } - if p[0] == 0xff { - c.state = packetClientIDConn_StateConnectionIDAcknowledged - return copy(p, p[1:n]), nil - } else { - log.Println("discarded unknown packet") - } - return 0, nil -} - -type packetConnWrapper struct { - io.ReadWriter - remoteAddr net.Addr - localAddr net.Addr -} - -func (pcw *packetConnWrapper) ReadFrom(p []byte) (n int, addr net.Addr, err error) { - n, err = pcw.Read(p) - if err != nil { - return 0, nil, err - } - return n, pcw.remoteAddr, nil -} - -func (pcw *packetConnWrapper) WriteTo(p []byte, addr net.Addr) (n int, err error) { - return pcw.Write(p) -} - -func (pcw *packetConnWrapper) Close() error { - return nil -} - -func (pcw *packetConnWrapper) LocalAddr() net.Addr { - return pcw.localAddr -} - -func (pcw *packetConnWrapper) SetDeadline(t time.Time) error { - return nil -} - -func (pcw *packetConnWrapper) SetReadDeadline(t time.Time) error { - return nil -} - -func (pcw *packetConnWrapper) SetWriteDeadline(t time.Time) error { - return nil -} diff --git a/server/lib/packetIDConnServer.go b/server/lib/packetIDConnServer.go deleted file mode 100644 index feca1fa..0000000 --- a/server/lib/packetIDConnServer.go +++ /dev/null @@ -1,52 +0,0 @@ -package snowflake_server - -import ( - "errors" - "net" - - "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" -) - -type ConnID = turbotunnel.ClientID - -type packetConnIDConnServer struct { - // This net.Conn must preserve message boundaries. - net.Conn - connID ConnID - clientIDReceived bool -} - -var ErrClientIDNotReceived = errors.New("ClientID not received") - -func (p *packetConnIDConnServer) GetClientID() (ConnID, error) { - if !p.clientIDReceived { - return p.connID, ErrClientIDNotReceived - } - return p.connID, nil -} - -func (p *packetConnIDConnServer) Read(buf []byte) (n int, err error) { - n, err = p.Conn.Read(buf) - if err != nil { - return - } - switch buf[0] { - case 0xfe: - p.clientIDReceived = true - copy(p.connID[:], buf[1:9]) - copy(buf[0:], buf[9:]) - return n - 9, nil - case 0xff: - copy(buf[0:], buf[1:]) - return n - 1, nil - } - return 0, nil -} - -func (p *packetConnIDConnServer) Write(buf []byte) (n int, err error) { - n, err = p.Conn.Write(append([]byte{0xff}, buf...)) - if err != nil { - return 0, err - } - return len(buf) - 1, nil -} From f521b180f5b0e144e731c4a08b876ce2d47c9f30 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 14 May 2024 11:13:24 +0100 Subject: [PATCH 10/44] add connwrapper --- client/lib/connwrapper.go | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 client/lib/connwrapper.go diff --git a/client/lib/connwrapper.go b/client/lib/connwrapper.go new file mode 100644 index 0000000..ca96a24 --- /dev/null +++ b/client/lib/connwrapper.go @@ -0,0 +1,45 @@ +package snowflake_client + +import ( + "io" + "net" + "time" +) + +type packetConnWrapper struct { + io.ReadWriter + remoteAddr net.Addr + localAddr net.Addr +} + +func (pcw *packetConnWrapper) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + n, err = pcw.Read(p) + if err != nil { + return 0, nil, err + } + return n, pcw.remoteAddr, nil +} + +func (pcw *packetConnWrapper) WriteTo(p []byte, addr net.Addr) (n int, err error) { + return pcw.Write(p) +} + +func (pcw *packetConnWrapper) Close() error { + return nil +} + +func (pcw *packetConnWrapper) LocalAddr() net.Addr { + return pcw.localAddr +} + +func (pcw *packetConnWrapper) SetDeadline(t time.Time) error { + return nil +} + +func (pcw *packetConnWrapper) SetReadDeadline(t time.Time) error { + return nil +} + +func (pcw *packetConnWrapper) SetWriteDeadline(t time.Time) error { + return nil +} From a601c2b1fa64246559cf7eb8a9a2c6977664949a Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 14 May 2024 11:50:56 +0100 Subject: [PATCH 11/44] fix coding style issue --- client/lib/rendezvous.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/lib/rendezvous.go b/client/lib/rendezvous.go index bb344ec..770cc9b 100644 --- a/client/lib/rendezvous.go +++ b/client/lib/rendezvous.go @@ -13,8 +13,6 @@ import ( "sync" "time" - "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" - "github.com/pion/webrtc/v4" utls "github.com/refraction-networking/utls" @@ -22,6 +20,7 @@ import ( "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/event" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/nat" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/util" utlsutil "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/utls" ) From 568a0999d2d995ff595c325ff0819663cda57c02 Mon Sep 17 00:00:00 2001 From: David Fifield Date: Thu, 1 Aug 2024 21:38:48 +0000 Subject: [PATCH 12/44] =?UTF-8?q?connectionID=20=E2=86=92=20clientID.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/lib/rendezvous.go | 14 +++++++------- client/lib/webrtc.go | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/lib/rendezvous.go b/client/lib/rendezvous.go index 770cc9b..b961f28 100644 --- a/client/lib/rendezvous.go +++ b/client/lib/rendezvous.go @@ -250,9 +250,9 @@ type WebRTCDialer struct { webrtcConfig *webrtc.Configuration max int - eventLogger event.SnowflakeEventReceiver - proxy *url.URL - connectionID turbotunnel.ClientID + eventLogger event.SnowflakeEventReceiver + proxy *url.URL + clientID turbotunnel.ClientID } // Deprecated: Use NewWebRTCDialerWithNatPolicyAndEventsAndProxy instead @@ -322,9 +322,9 @@ func NewWebRTCDialerWithNatPolicyAndEventsAndProxyAndClientID( webrtcConfig: &config, max: max, - eventLogger: eventLogger, - proxy: proxy, - connectionID: clientID, + eventLogger: eventLogger, + proxy: proxy, + clientID: clientID, } } @@ -332,7 +332,7 @@ func NewWebRTCDialerWithNatPolicyAndEventsAndProxyAndClientID( func (w WebRTCDialer) Catch() (*WebRTCPeer, error) { // TODO: [#25591] Fetch ICE server information from Broker. // TODO: [#25596] Consider TURN servers here too. - return NewWebRTCPeerWithEventsAndProxy(w.webrtcConfig, w.BrokerChannel, w.eventLogger, w.proxy) + return NewWebRTCPeerWithNatPolicyAndEventsProxyAndClientID(w.webrtcConfig, w.BrokerChannel, w.natPolicy, w.eventLogger, w.proxy, w.clientID) } // GetMax returns the maximum number of snowflakes to collect. diff --git a/client/lib/webrtc.go b/client/lib/webrtc.go index a78c264..a2fbb40 100644 --- a/client/lib/webrtc.go +++ b/client/lib/webrtc.go @@ -47,7 +47,7 @@ type WebRTCPeer struct { proxy *url.URL activeTransportMode byte - connectionID turbotunnel.ClientID + clientID turbotunnel.ClientID } // Deprecated: Use NewWebRTCPeerWithNatPolicyAndEventsAndProxy Instead. @@ -99,7 +99,7 @@ func NewWebRTCPeerWithNatPolicyAndEventsAndProxy( // The creation of the peer handles the signaling to the Snowflake broker, including // the exchange of SDP information, the creation of a PeerConnection, and the establishment // of a DataChannel to the Snowflake proxy. -// connectionID is the hinted ID for the connection. +// clientID is the hinted ID for the connection. func NewWebRTCPeerWithNatPolicyAndEventsProxyAndClientID(config *webrtc.Configuration, broker *BrokerChannel, natPolicy *NATPolicy, eventsLogger event.SnowflakeEventReceiver, proxy *url.URL, clientID turbotunnel.ClientID, @@ -126,7 +126,7 @@ func NewWebRTCPeerWithNatPolicyAndEventsProxyAndClientID(config *webrtc.Configur connection.eventsLogger = eventsLogger connection.proxy = proxy - connection.connectionID = clientID + connection.clientID = clientID err := connection.connect(config, broker, natPolicy) if err != nil { @@ -325,7 +325,7 @@ func (c *WebRTCPeer) preparePeerConnection( maxRetransmissionVal := uint16(0) maxRetransmission = &maxRetransmissionVal } - protocol := fmt.Sprintf("%c %s", c.activeTransportMode, c.connectionID.String()) + protocol := fmt.Sprintf("%c %s", c.activeTransportMode, c.clientID.String()) dataChannelOptions := &webrtc.DataChannelInit{ Ordered: &ordered, Protocol: &protocol, From 490502691dffbeb5e370a7b0d2c3951617645901 Mon Sep 17 00:00:00 2001 From: David Fifield Date: Fri, 2 Aug 2024 02:58:59 +0000 Subject: [PATCH 13/44] Remove WebRTCPeer.activeTransportMode. Make "u" mode the assumed default. The WebRTC data channel protocol contains just the hex clientID. --- client/lib/snowflake.go | 29 +++++------------------- client/lib/webrtc.go | 17 +++++--------- server/lib/http.go | 49 +++-------------------------------------- 3 files changed, 13 insertions(+), 82 deletions(-) diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index 436d55b..f4ad630 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -331,11 +331,6 @@ func parseIceServers(addresses []string) []webrtc.ICEServer { // over. The net.PacketConn successively connects through Snowflake proxies // pulled from snowflakes. func newSession(snowflakes SnowflakeCollector, clientIDCandid turbotunnel.ClientID) (net.PacketConn, *smux.Session, error) { - clientID := turbotunnel.NewClientID() - if clientIDCandid != (turbotunnel.ClientID{}) { - clientID = clientIDCandid - } - // We build a persistent KCP session on a sequence of ephemeral WebRTC // connections. This dialContext tells RedialPacketConn how to get a new // WebRTC connection when the previous one dies. Inside each WebRTC @@ -349,26 +344,12 @@ func newSession(snowflakes SnowflakeCollector, clientIDCandid turbotunnel.Client return nil, errors.New("handler: Received invalid Snowflake") } log.Println("---- Handler: snowflake assigned ----") - log.Printf("activeTransportMode = %c \n", conn.activeTransportMode) - if conn.activeTransportMode == 'u' { - packetConnWrapper := &packetConnWrapper{ - ReadWriter: conn, - remoteAddr: dummyAddr{}, - localAddr: dummyAddr{}, - } - return packetConnWrapper, nil + packetConnWrapper := &packetConnWrapper{ + ReadWriter: conn, + remoteAddr: dummyAddr{}, + localAddr: dummyAddr{}, } - // Send the magic Turbo Tunnel token. - _, err := conn.Write(turbotunnel.Token[:]) - if err != nil { - return nil, err - } - // Send ClientID prefix. - _, err = conn.Write(clientID[:]) - if err != nil { - return nil, err - } - return newEncapsulationPacketConn(dummyAddr{}, dummyAddr{}, conn), nil + return packetConnWrapper, nil } pconn := turbotunnel.NewRedialPacketConn(dummyAddr{}, dummyAddr{}, dialContext) diff --git a/client/lib/webrtc.go b/client/lib/webrtc.go index a2fbb40..334f6ab 100644 --- a/client/lib/webrtc.go +++ b/client/lib/webrtc.go @@ -46,8 +46,7 @@ type WebRTCPeer struct { eventsLogger event.SnowflakeEventReceiver proxy *url.URL - activeTransportMode byte - clientID turbotunnel.ClientID + clientID turbotunnel.ClientID } // Deprecated: Use NewWebRTCPeerWithNatPolicyAndEventsAndProxy Instead. @@ -212,7 +211,6 @@ func (c *WebRTCPeer) connect( ) error { log.Println(c.id, " connecting...") - c.activeTransportMode = 'u' err := c.preparePeerConnection(config, broker.keepLocalAddresses) localDescription := c.pc.LocalDescription() c.eventsLogger.OnNewSnowflakeEvent(event.EventOnOfferCreated{ @@ -318,18 +316,13 @@ func (c *WebRTCPeer) preparePeerConnection( log.Printf("NewPeerConnection ERROR: %s", err) return err } - ordered := true - var maxRetransmission *uint16 - if c.activeTransportMode == 'u' { - ordered = false - maxRetransmissionVal := uint16(0) - maxRetransmission = &maxRetransmissionVal - } - protocol := fmt.Sprintf("%c %s", c.activeTransportMode, c.clientID.String()) + ordered := false + var maxRetransmission uint16 = 0 + protocol := fmt.Sprintf("%s", c.clientID.String()) dataChannelOptions := &webrtc.DataChannelInit{ Ordered: &ordered, Protocol: &protocol, - MaxRetransmits: maxRetransmission, + MaxRetransmits: &maxRetransmission, } // We must create the data channel before creating an offer // https://github.com/pion/webrtc/wiki/Release-WebRTC@v3.0.0#a-data-channel-is-no-longer-implicitly-created-with-a-peerconnection diff --git a/server/lib/http.go b/server/lib/http.go index 70ffa16..e143b66 100644 --- a/server/lib/http.go +++ b/server/lib/http.go @@ -2,7 +2,6 @@ package snowflake_server import ( "bufio" - "bytes" "crypto/hmac" "crypto/rand" "crypto/sha256" @@ -13,7 +12,6 @@ import ( "log" "net" "net/http" - "strings" "sync" "time" @@ -112,45 +110,8 @@ func (handler *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { addr := clientAddr(clientIPParam) protocol := r.URL.Query().Get("protocol") - clientTransport := "t" - - if protocol != "" { - clientTransport = fmt.Sprintf("%c", protocol[0]) - } - - if clientTransport == "u" { - err = handler.turboTunnelUDPLikeMode(conn, addr, protocol) - if err != nil && err != io.EOF { - log.Println(err) - return - } - return - } - - var token [len(turbotunnel.Token)]byte - _, err = io.ReadFull(conn, token[:]) - if err != nil { - // Don't bother logging EOF: that happens with an unused - // connection, which clients make frequently as they maintain a - // pool of proxies. - if err != io.EOF { - log.Printf("reading token: %v", err) - } - return - } - - switch { - case bytes.Equal(token[:], turbotunnel.Token[:]): - err = handler.turbotunnelMode(conn, addr) - default: - // We didn't find a matching token, which means that we are - // dealing with a client that doesn't know about such things. - // Close the conn as we no longer support the old - // one-session-per-WebSocket mode. - log.Println("Received unsupported oneshot connection") - return - } - if err != nil { + err = handler.turboTunnelUDPLikeMode(conn, addr, protocol) + if err != nil && err != io.EOF { log.Println(err) return } @@ -243,11 +204,7 @@ func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr, var packet [1600]byte clientID := turbotunnel.ClientID{} - compoments := strings.Split(protocol, " ") - if len(compoments) != 2 { - return fmt.Errorf("invalid protocol: %s", protocol) - } - _, err := hex.Decode(clientID[:], []byte(compoments[1])) + _, err := hex.Decode(clientID[:], []byte(protocol)) if err != nil { return fmt.Errorf("reading ClientID: %v", err) } From 2e4139ea979d2a671b9e90b8a8bbacaf10820a0c Mon Sep 17 00:00:00 2001 From: David Fifield Date: Fri, 2 Aug 2024 03:33:56 +0000 Subject: [PATCH 14/44] Remove turbotunnelMode. Replace it with turboTunnelUDPLikeMode, copying comments etc. to make the changes easier to see. --- server/lib/http.go | 88 +++++++--------------------------------------- 1 file changed, 12 insertions(+), 76 deletions(-) diff --git a/server/lib/http.go b/server/lib/http.go index e143b66..0d2e820 100644 --- a/server/lib/http.go +++ b/server/lib/http.go @@ -1,7 +1,6 @@ package snowflake_server import ( - "bufio" "crypto/hmac" "crypto/rand" "crypto/sha256" @@ -17,7 +16,6 @@ import ( "github.com/gorilla/websocket" - "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/encapsulation" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/websocketconn" ) @@ -32,7 +30,7 @@ const requestTimeout = 10 * time.Second const clientMapTimeout = 1 * time.Minute // How big to make the map of ClientIDs to IP addresses. The map is used in -// turbotunnelMode to store a reasonable IP address for a client session that +// turboTunnelUDPLikeMode to store a reasonable IP address for a client session that // may outlive any single WebSocket connection. const clientIDAddrMapCapacity = 98304 @@ -117,14 +115,12 @@ func (handler *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } -// turbotunnelMode handles clients that sent turbotunnel.Token at the start of -// their stream. These clients expect to send and receive encapsulated packets, -// with a long-lived session identified by ClientID. -func (handler *httpHandler) turbotunnelMode(conn net.Conn, addr net.Addr) error { - // Read the ClientID prefix. Every packet encapsulated in this WebSocket - // connection pertains to the same ClientID. - var clientID turbotunnel.ClientID - _, err := io.ReadFull(conn, clientID[:]) +func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr, protocol string) error { + // Read the ClientID from the WebRTC data channel protocol string. Every + // packet received on this WebSocket connection pertains to the same + // ClientID. + clientID := turbotunnel.ClientID{} + _, err := hex.Decode(clientID[:], []byte(protocol)) if err != nil { return fmt.Errorf("reading ClientID: %w", err) } @@ -146,8 +142,8 @@ func (handler *httpHandler) turbotunnelMode(conn net.Conn, addr net.Addr) error wg.Add(2) done := make(chan struct{}) - // The remainder of the WebSocket stream consists of encapsulated - // packets. We read them one by one and feed them into the + // The remainder of the WebSocket stream consists of packets, one packet + // per WebSocket message. We read them one by one and feed them into the // QueuePacketConn on which kcp.ServeConn was set up, which eventually // leads to KCP-level sessions in the acceptSessions function. go func() { @@ -155,11 +151,9 @@ func (handler *httpHandler) turbotunnelMode(conn net.Conn, addr net.Addr) error defer close(done) // Signal the write loop to finish var p [2048]byte for { - n, err := encapsulation.ReadData(conn, p[:]) - if err == io.ErrShortBuffer { - err = nil - } + n, err := conn.Read(p[:]) if err != nil { + log.Println(err) return } pconn.QueueIncoming(p[:n], clientID) @@ -168,65 +162,6 @@ func (handler *httpHandler) turbotunnelMode(conn net.Conn, addr net.Addr) error // At the same time, grab packets addressed to this ClientID and // encapsulate them into the downstream. - go func() { - defer wg.Done() - defer conn.Close() // Signal the read loop to finish - - // Buffer encapsulation.WriteData operations to keep length - // prefixes in the same send as the data that follows. - bw := bufio.NewWriter(conn) - for { - select { - case <-done: - return - case p, ok := <-pconn.OutgoingQueue(clientID): - if !ok { - return - } - _, err := encapsulation.WriteData(bw, p) - pconn.Restore(p) - if err == nil { - err = bw.Flush() - } - if err != nil { - return - } - } - } - }() - - wg.Wait() - - return nil -} - -func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr, protocol string) error { - var packet [1600]byte - - clientID := turbotunnel.ClientID{} - _, err := hex.Decode(clientID[:], []byte(protocol)) - if err != nil { - return fmt.Errorf("reading ClientID: %v", err) - } - - clientIDAddrMap.Set(clientID, addr) - - pconn := handler.lookupPacketConn(clientID) - var wg sync.WaitGroup - wg.Add(2) - done := make(chan struct{}) - go func() { - defer wg.Done() - defer close(done) // Signal the write loop to finish - for { - n, err := conn.Read(packet[:]) - if err != nil { - log.Println(err) - return - } - pconn.QueueIncoming(packet[:n], clientID) - } - }() go func() { defer wg.Done() defer conn.Close() // Signal the read loop to finish @@ -249,6 +184,7 @@ func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr, }() wg.Wait() + return nil } From 85b716bb40246d687d5f98d28b95eef0b019e31c Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 11 Sep 2024 13:34:12 +0100 Subject: [PATCH 15/44] return an error for unimplemented packetConnWrapper feature --- client/lib/connwrapper.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/lib/connwrapper.go b/client/lib/connwrapper.go index ca96a24..0eb8d98 100644 --- a/client/lib/connwrapper.go +++ b/client/lib/connwrapper.go @@ -1,11 +1,14 @@ package snowflake_client import ( + "errors" "io" "net" "time" ) +var errENOSYS = errors.New("not implemented") + type packetConnWrapper struct { io.ReadWriter remoteAddr net.Addr @@ -33,13 +36,13 @@ func (pcw *packetConnWrapper) LocalAddr() net.Addr { } func (pcw *packetConnWrapper) SetDeadline(t time.Time) error { - return nil + return errENOSYS } func (pcw *packetConnWrapper) SetReadDeadline(t time.Time) error { - return nil + return errENOSYS } func (pcw *packetConnWrapper) SetWriteDeadline(t time.Time) error { - return nil + return errENOSYS } From f10dc3d619e1fb3aa7b219abddb47faef65200a9 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 11 Sep 2024 13:39:16 +0100 Subject: [PATCH 16/44] use a constructor for PacketConnWrapper --- client/lib/connwrapper.go | 8 ++++++++ client/lib/snowflake.go | 7 ++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/client/lib/connwrapper.go b/client/lib/connwrapper.go index 0eb8d98..eadba2a 100644 --- a/client/lib/connwrapper.go +++ b/client/lib/connwrapper.go @@ -9,6 +9,14 @@ import ( var errENOSYS = errors.New("not implemented") +func newPacketConnWrapper(localAddr, remoteAddr net.Addr, rw io.ReadWriter) net.PacketConn { + return &packetConnWrapper{ + ReadWriter: rw, + remoteAddr: remoteAddr, + localAddr: localAddr, + } +} + type packetConnWrapper struct { io.ReadWriter remoteAddr net.Addr diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index f4ad630..e31c06c 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -344,11 +344,8 @@ func newSession(snowflakes SnowflakeCollector, clientIDCandid turbotunnel.Client return nil, errors.New("handler: Received invalid Snowflake") } log.Println("---- Handler: snowflake assigned ----") - packetConnWrapper := &packetConnWrapper{ - ReadWriter: conn, - remoteAddr: dummyAddr{}, - localAddr: dummyAddr{}, - } + + packetConnWrapper := newPacketConnWrapper(dummyAddr{}, dummyAddr{}, conn) return packetConnWrapper, nil } pconn := turbotunnel.NewRedialPacketConn(dummyAddr{}, dummyAddr{}, dialContext) From 0d9bef77944b8d419976b219f94e90a80adc847c Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 11 Sep 2024 13:51:43 +0100 Subject: [PATCH 17/44] use a propagate close for PacketConnWrapper --- client/lib/connwrapper.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/lib/connwrapper.go b/client/lib/connwrapper.go index eadba2a..828557c 100644 --- a/client/lib/connwrapper.go +++ b/client/lib/connwrapper.go @@ -9,16 +9,16 @@ import ( var errENOSYS = errors.New("not implemented") -func newPacketConnWrapper(localAddr, remoteAddr net.Addr, rw io.ReadWriter) net.PacketConn { +func newPacketConnWrapper(localAddr, remoteAddr net.Addr, rwc io.ReadWriteCloser) net.PacketConn { return &packetConnWrapper{ - ReadWriter: rw, - remoteAddr: remoteAddr, - localAddr: localAddr, + ReadWriteCloser: rwc, + remoteAddr: remoteAddr, + localAddr: localAddr, } } type packetConnWrapper struct { - io.ReadWriter + io.ReadWriteCloser remoteAddr net.Addr localAddr net.Addr } @@ -36,7 +36,7 @@ func (pcw *packetConnWrapper) WriteTo(p []byte, addr net.Addr) (n int, err error } func (pcw *packetConnWrapper) Close() error { - return nil + return pcw.ReadWriteCloser.Close() } func (pcw *packetConnWrapper) LocalAddr() net.Addr { From 3a2b2fc969f6bd5d0d446249eb747fcfb74a1f82 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 11 Sep 2024 14:24:26 +0100 Subject: [PATCH 18/44] add confirmation to ReadWriteCloser should preserve message boundary --- client/lib/connwrapper.go | 25 ++++++++++++++++++++----- client/lib/snowflake.go | 2 +- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/client/lib/connwrapper.go b/client/lib/connwrapper.go index 828557c..882dd43 100644 --- a/client/lib/connwrapper.go +++ b/client/lib/connwrapper.go @@ -7,18 +7,33 @@ import ( "time" ) +type ReadWriteCloserPreservesBoundary interface { + io.ReadWriteCloser + MessageBoundaryPreserved() +} + +func ConfirmsReadWriteCloserPreservesMessageBoundary(rwc io.ReadWriteCloser) ReadWriteCloserPreservesBoundary { + return &messageBoundaryPreservedReadWriteCloser{rwc} +} + +type messageBoundaryPreservedReadWriteCloser struct { + io.ReadWriteCloser +} + +func (m *messageBoundaryPreservedReadWriteCloser) MessageBoundaryPreserved() {} + var errENOSYS = errors.New("not implemented") -func newPacketConnWrapper(localAddr, remoteAddr net.Addr, rwc io.ReadWriteCloser) net.PacketConn { +func newPacketConnWrapper(localAddr, remoteAddr net.Addr, rwc ReadWriteCloserPreservesBoundary) net.PacketConn { return &packetConnWrapper{ - ReadWriteCloser: rwc, - remoteAddr: remoteAddr, - localAddr: localAddr, + ReadWriteCloserPreservesBoundary: rwc, + remoteAddr: remoteAddr, + localAddr: localAddr, } } type packetConnWrapper struct { - io.ReadWriteCloser + ReadWriteCloserPreservesBoundary remoteAddr net.Addr localAddr net.Addr } diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index e31c06c..910e4c7 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -345,7 +345,7 @@ func newSession(snowflakes SnowflakeCollector, clientIDCandid turbotunnel.Client } log.Println("---- Handler: snowflake assigned ----") - packetConnWrapper := newPacketConnWrapper(dummyAddr{}, dummyAddr{}, conn) + packetConnWrapper := newPacketConnWrapper(dummyAddr{}, dummyAddr{}, ConfirmsReadWriteCloserPreservesMessageBoundary(conn)) return packetConnWrapper, nil } pconn := turbotunnel.NewRedialPacketConn(dummyAddr{}, dummyAddr{}, dialContext) From 69045b5fbbc30600e11aa6899332a637069fdb5b Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 11 Sep 2024 14:51:08 +0100 Subject: [PATCH 19/44] update comment on conn.SetStreamMode --- client/lib/snowflake.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index 910e4c7..29041f0 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -359,7 +359,7 @@ func newSession(snowflakes SnowflakeCollector, clientIDCandid turbotunnel.Client pconn.Close() return nil, nil, err } - // Permit coalescing the payloads of consecutive sends. + // Disallow coalescing the payloads of consecutive sends. conn.SetStreamMode(false) // Set the maximum send and receive window sizes to a high number // Removes KCP bottlenecks: https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/issues/40026 From 37f2f42ea0ed4d834253977af7b3f75cacc5ef03 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 11 Sep 2024 15:00:30 +0100 Subject: [PATCH 20/44] revert change on SetStreamMode --- client/lib/snowflake.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index 29041f0..a3f3fec 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -359,8 +359,8 @@ func newSession(snowflakes SnowflakeCollector, clientIDCandid turbotunnel.Client pconn.Close() return nil, nil, err } - // Disallow coalescing the payloads of consecutive sends. - conn.SetStreamMode(false) + // Permit coalescing the payloads of consecutive sends. + conn.SetStreamMode(true) // Set the maximum send and receive window sizes to a high number // Removes KCP bottlenecks: https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/issues/40026 conn.SetWindowSize(WindowSize, WindowSize) From 1da7fecbed406591b1041ed61f8eb48f9a64b2c7 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 12 Sep 2024 13:14:22 +0100 Subject: [PATCH 21/44] fix rwcrb close --- client/lib/connwrapper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/connwrapper.go b/client/lib/connwrapper.go index 882dd43..0db9324 100644 --- a/client/lib/connwrapper.go +++ b/client/lib/connwrapper.go @@ -51,7 +51,7 @@ func (pcw *packetConnWrapper) WriteTo(p []byte, addr net.Addr) (n int, err error } func (pcw *packetConnWrapper) Close() error { - return pcw.ReadWriteCloser.Close() + return pcw.ReadWriteCloserPreservesBoundary.Close() } func (pcw *packetConnWrapper) LocalAddr() net.Addr { From c7b163de57ebef3bb9e24fd6cd450c8633a6ca88 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 12 Sep 2024 14:22:15 +0100 Subject: [PATCH 22/44] Remove clientID from unused branch --- client/lib/snowflake.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index a3f3fec..30a204c 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -78,8 +78,6 @@ type Transport struct { // EventDispatcher is the event bus for snowflake events. // When an important event happens, it will be distributed here. eventDispatcher event.SnowflakeEventDispatcher - - clientID turbotunnel.ClientID } // ClientConfig defines how the SnowflakeClient will connect to the broker and Snowflake proxies. @@ -169,7 +167,7 @@ func NewSnowflakeClient(config ClientConfig) (*Transport, error) { clientID := turbotunnel.NewClientID() transport := &Transport{ dialer: NewWebRTCDialerWithNatPolicyAndEventsAndProxyAndClientID(broker, natPolicy, iceServers, max, eventsLogger, config.CommunicationProxy, clientID), - eventDispatcher: eventsLogger, clientID: clientID, + eventDispatcher: eventsLogger, } return transport, nil @@ -204,7 +202,7 @@ func (t *Transport) Dial() (net.Conn, error) { // Create a new smux session log.Printf("---- SnowflakeConn: starting a new session ---") - pconn, sess, err := newSession(snowflakes, t.clientID) + pconn, sess, err := newSession(snowflakes) if err != nil { return nil, err } @@ -330,7 +328,7 @@ func parseIceServers(addresses []string) []webrtc.ICEServer { // newSession returns a new smux.Session and the net.PacketConn it is running // over. The net.PacketConn successively connects through Snowflake proxies // pulled from snowflakes. -func newSession(snowflakes SnowflakeCollector, clientIDCandid turbotunnel.ClientID) (net.PacketConn, *smux.Session, error) { +func newSession(snowflakes SnowflakeCollector) (net.PacketConn, *smux.Session, error) { // We build a persistent KCP session on a sequence of ephemeral WebRTC // connections. This dialContext tells RedialPacketConn how to get a new // WebRTC connection when the previous one dies. Inside each WebRTC From c322e8b7f50c69e112a9e079b2fd0ff589787ec8 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 12 Sep 2024 14:35:03 +0100 Subject: [PATCH 23/44] always let WebRTCDialer constructor decide the clientID --- client/lib/rendezvous.go | 4 ++-- client/lib/snowflake.go | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/client/lib/rendezvous.go b/client/lib/rendezvous.go index b961f28..cf44362 100644 --- a/client/lib/rendezvous.go +++ b/client/lib/rendezvous.go @@ -291,7 +291,7 @@ func NewWebRTCDialerWithNatPolicyAndEventsAndProxy( eventLogger event.SnowflakeEventReceiver, proxy *url.URL, ) *WebRTCDialer { - return NewWebRTCDialerWithNatPolicyAndEventsAndProxyAndClientID( + return newWebRTCDialerWithNatPolicyAndEventsAndProxyAndClientID( broker, natPolicy, iceServers, @@ -303,7 +303,7 @@ func NewWebRTCDialerWithNatPolicyAndEventsAndProxy( } // NewWebRTCDialerWithNatPolicyAndEventsAndProxy constructs a new WebRTCDialer. -func NewWebRTCDialerWithNatPolicyAndEventsAndProxyAndClientID( +func newWebRTCDialerWithNatPolicyAndEventsAndProxyAndClientID( broker *BrokerChannel, natPolicy *NATPolicy, iceServers []webrtc.ICEServer, diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index 30a204c..82573c9 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -164,9 +164,8 @@ func NewSnowflakeClient(config ClientConfig) (*Transport, error) { max = config.Max } eventsLogger := event.NewSnowflakeEventDispatcher() - clientID := turbotunnel.NewClientID() transport := &Transport{ - dialer: NewWebRTCDialerWithNatPolicyAndEventsAndProxyAndClientID(broker, natPolicy, iceServers, max, eventsLogger, config.CommunicationProxy, clientID), + dialer: NewWebRTCDialerWithNatPolicyAndEventsAndProxy(broker, natPolicy, iceServers, max, eventsLogger, config.CommunicationProxy), eventDispatcher: eventsLogger, } From 11442c8857f769be87f2013eb77d4734cd0ab9c4 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 17 Sep 2024 12:11:36 +0100 Subject: [PATCH 24/44] Remove WebRTCPeer constructor without client ID --- client/lib/webrtc.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/client/lib/webrtc.go b/client/lib/webrtc.go index 334f6ab..bca262d 100644 --- a/client/lib/webrtc.go +++ b/client/lib/webrtc.go @@ -50,35 +50,35 @@ type WebRTCPeer struct { } // Deprecated: Use NewWebRTCPeerWithNatPolicyAndEventsAndProxy Instead. -func NewWebRTCPeer( +func newWebRTCPeer( config *webrtc.Configuration, broker *BrokerChannel, ) (*WebRTCPeer, error) { - return NewWebRTCPeerWithNatPolicyAndEventsAndProxy( + return newWebRTCPeerWithNatPolicyAndEventsAndProxy( config, broker, nil, nil, nil, ) } // Deprecated: Use NewWebRTCPeerWithNatPolicyAndEventsAndProxy Instead. -func NewWebRTCPeerWithEvents( +func newWebRTCPeerWithEvents( config *webrtc.Configuration, broker *BrokerChannel, eventsLogger event.SnowflakeEventReceiver, ) (*WebRTCPeer, error) { - return NewWebRTCPeerWithNatPolicyAndEventsAndProxy( + return newWebRTCPeerWithNatPolicyAndEventsAndProxy( config, broker, nil, eventsLogger, nil, ) } // Deprecated: Use NewWebRTCPeerWithNatPolicyAndEventsAndProxy Instead. -func NewWebRTCPeerWithEventsAndProxy( +func newWebRTCPeerWithEventsAndProxy( config *webrtc.Configuration, broker *BrokerChannel, eventsLogger event.SnowflakeEventReceiver, proxy *url.URL, ) (*WebRTCPeer, error) { - return NewWebRTCPeerWithNatPolicyAndEventsAndProxy( + return newWebRTCPeerWithNatPolicyAndEventsAndProxy( config, broker, nil, eventsLogger, proxy, ) } -func NewWebRTCPeerWithNatPolicyAndEventsAndProxy( +func newWebRTCPeerWithNatPolicyAndEventsAndProxy( config *webrtc.Configuration, broker *BrokerChannel, natPolicy *NATPolicy, eventsLogger event.SnowflakeEventReceiver, proxy *url.URL, ) (*WebRTCPeer, error) { From 80262c9e4f4c1b1eae45da2184537052a014152e Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 17 Sep 2024 12:44:49 +0100 Subject: [PATCH 25/44] set protocol query for connection with server unconditionally --- proxy/lib/snowflake.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/lib/snowflake.go b/proxy/lib/snowflake.go index 9f5ee5a..2d715a9 100644 --- a/proxy/lib/snowflake.go +++ b/proxy/lib/snowflake.go @@ -375,7 +375,7 @@ func connectToRelay( log.Printf("no remote address given in websocket") } - if webrtcConnProtocol != "" { + { q := u.Query() q.Set("protocol", webrtcConnProtocol) u.RawQuery = q.Encode() From b2605b7961c1d3a52789e91d4e12c12f168a6cd7 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 17 Sep 2024 15:39:11 +0100 Subject: [PATCH 26/44] add protocol setting to newWebRTCConn --- proxy/lib/snowflake.go | 3 +-- proxy/lib/webrtcconn.go | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/proxy/lib/snowflake.go b/proxy/lib/snowflake.go index 2d715a9..34ed9bf 100644 --- a/proxy/lib/snowflake.go +++ b/proxy/lib/snowflake.go @@ -461,8 +461,7 @@ func (sf *SnowflakeProxy) makePeerConnectionFromOffer( close(dataChan) pr, pw := io.Pipe() - conn := newWebRTCConn(pc, dc, pr, sf.bytesLogger) - conn.SetConnectionProtocol(dc.Protocol()) + conn := newWebRTCConn(pc, dc, pr, sf.bytesLogger, dc.Protocol()) dc.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold) diff --git a/proxy/lib/webrtcconn.go b/proxy/lib/webrtcconn.go index f2381bf..42fb54b 100644 --- a/proxy/lib/webrtcconn.go +++ b/proxy/lib/webrtcconn.go @@ -45,7 +45,7 @@ type webRTCConn struct { protocol string } -func newWebRTCConn(pc *webrtc.PeerConnection, dc *webrtc.DataChannel, pr *io.PipeReader, bytesLogger bytesLogger) *webRTCConn { +func newWebRTCConn(pc *webrtc.PeerConnection, dc *webrtc.DataChannel, pr *io.PipeReader, bytesLogger bytesLogger, protocol string) *webRTCConn { conn := &webRTCConn{pc: pc, dc: dc, pr: pr, bytesLogger: bytesLogger} conn.isClosing = false conn.activity = make(chan struct{}, 100) @@ -53,6 +53,7 @@ func newWebRTCConn(pc *webrtc.PeerConnection, dc *webrtc.DataChannel, pr *io.Pip conn.inactivityTimeout = 30 * time.Second ctx, cancel := context.WithCancel(context.Background()) conn.cancelTimeoutLoop = cancel + conn.protocol = protocol go conn.timeoutLoop(ctx) return conn } @@ -139,10 +140,6 @@ func (c *webRTCConn) SetWriteDeadline(t time.Time) error { return fmt.Errorf("SetWriteDeadline not implemented") } -func (c *webRTCConn) SetConnectionProtocol(protocol string) { - c.protocol = protocol -} - func (c *webRTCConn) GetConnectionProtocol() string { return c.protocol } From fb57ad79f540b70de7d6dda14f28944f8b4805c0 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 17 Sep 2024 16:25:51 +0100 Subject: [PATCH 27/44] add protocol field encoder --- common/messages/client.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/common/messages/client.go b/common/messages/client.go index da6359e..9e7a235 100644 --- a/common/messages/client.go +++ b/common/messages/client.go @@ -5,6 +5,7 @@ package messages import ( "bytes" + "encoding/base64" "encoding/json" "fmt" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint" @@ -149,3 +150,31 @@ func DecodeClientPollResponse(data []byte) (*ClientPollResponse, error) { return &message, nil } + +type ClientConnectionMetadata struct { + ClientID string `json:"client_id"` +} + +func (meta *ClientConnectionMetadata) EncodeConnectionMetadata() (string, error) { + jsonData, err := json.Marshal(meta) + if err != nil { + return "", err + } + + return base64.RawURLEncoding.EncodeToString(jsonData), nil +} + +func DecodeConnectionMetadata(data string) (*ClientConnectionMetadata, error) { + decodedData, err := base64.RawURLEncoding.DecodeString(data) + if err != nil { + return nil, err + } + + var meta ClientConnectionMetadata + err = json.Unmarshal(decodedData, &meta) + if err != nil { + return nil, err + } + + return &meta, nil +} From 00da3c95ed5474c105cdf481da8c87eb1c813b6e Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 17 Sep 2024 16:53:26 +0100 Subject: [PATCH 28/44] use protocol field encoder --- client/lib/webrtc.go | 9 +++++++-- common/messages/client.go | 2 +- server/lib/http.go | 8 ++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/client/lib/webrtc.go b/client/lib/webrtc.go index bca262d..7af1186 100644 --- a/client/lib/webrtc.go +++ b/client/lib/webrtc.go @@ -4,7 +4,7 @@ import ( "crypto/rand" "encoding/hex" "errors" - "fmt" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" "io" "log" "net" @@ -318,7 +318,12 @@ func (c *WebRTCPeer) preparePeerConnection( } ordered := false var maxRetransmission uint16 = 0 - protocol := fmt.Sprintf("%s", c.clientID.String()) + connectionMetadata := messages.ClientConnectionMetadata{ClientID: c.clientID[:]} + encodedMetadata, err := connectionMetadata.EncodeConnectionMetadata() + if err != nil { + return err + } + protocol := encodedMetadata dataChannelOptions := &webrtc.DataChannelInit{ Ordered: &ordered, Protocol: &protocol, diff --git a/common/messages/client.go b/common/messages/client.go index 9e7a235..5d1f0d6 100644 --- a/common/messages/client.go +++ b/common/messages/client.go @@ -152,7 +152,7 @@ func DecodeClientPollResponse(data []byte) (*ClientPollResponse, error) { } type ClientConnectionMetadata struct { - ClientID string `json:"client_id"` + ClientID []byte `json:"client_id"` } func (meta *ClientConnectionMetadata) EncodeConnectionMetadata() (string, error) { diff --git a/server/lib/http.go b/server/lib/http.go index 0d2e820..6da2d0f 100644 --- a/server/lib/http.go +++ b/server/lib/http.go @@ -5,8 +5,7 @@ import ( "crypto/rand" "crypto/sha256" "encoding/binary" - "encoding/hex" - "fmt" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" "io" "log" "net" @@ -120,10 +119,11 @@ func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr, // packet received on this WebSocket connection pertains to the same // ClientID. clientID := turbotunnel.ClientID{} - _, err := hex.Decode(clientID[:], []byte(protocol)) + metaData, err := messages.DecodeConnectionMetadata(protocol) if err != nil { - return fmt.Errorf("reading ClientID: %w", err) + return err } + copy(clientID[:], metaData.ClientID[:]) // Store a short-term mapping from the ClientID to the client IP // address attached to this WebSocket connection. tor will want us to From de9fede4ac775293727abbb15f6c7662c8e7a3e4 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 19 Sep 2024 11:57:11 +0100 Subject: [PATCH 29/44] update comment for newSession in client --- client/lib/snowflake.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index 82573c9..35b85ae 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -330,9 +330,12 @@ func parseIceServers(addresses []string) []webrtc.ICEServer { func newSession(snowflakes SnowflakeCollector) (net.PacketConn, *smux.Session, error) { // We build a persistent KCP session on a sequence of ephemeral WebRTC // connections. This dialContext tells RedialPacketConn how to get a new - // WebRTC connection when the previous one dies. Inside each WebRTC - // connection, we use encapsulationPacketConn to encode packets into a + // WebRTC connection when the previous one dies. + // If Stream based transport are used, inside each WebRTC connection, + // we use encapsulationPacketConn to encode packets into a // stream. + // If Packet based transport are used, inside each WebRTC connection, + // packets are sent directly over unreliable data channel. dialContext := func(ctx context.Context) (net.PacketConn, error) { log.Printf("redialing on same connection") // Obtain an available WebRTC remote. May block. From 0d3b5f02b01a519b79786fe03bb0e17c72db9a8f Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 19 Sep 2024 12:31:37 +0100 Subject: [PATCH 30/44] update comment for protocol in proxy/webRTCConn --- proxy/lib/webrtcconn.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proxy/lib/webrtcconn.go b/proxy/lib/webrtcconn.go index 42fb54b..1c8bf08 100644 --- a/proxy/lib/webrtcconn.go +++ b/proxy/lib/webrtcconn.go @@ -42,6 +42,9 @@ type webRTCConn struct { bytesLogger bytesLogger + // protocol reflect the protocol field in the channel opening + // message of Data Channel Establishment Protocol. + // In snowflake it is used to transmit connection metadata. protocol string } From c1ac2aa577362d35c5bbf014d1ef8c50dba26d3b Mon Sep 17 00:00:00 2001 From: David Fifield Date: Thu, 12 Dec 2024 06:38:50 +0000 Subject: [PATCH 31/44] Update comment not to refer to a Stream transport. Stream is not part of this branch. --- client/lib/snowflake.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index 35b85ae..ef04f2a 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -330,12 +330,9 @@ func parseIceServers(addresses []string) []webrtc.ICEServer { func newSession(snowflakes SnowflakeCollector) (net.PacketConn, *smux.Session, error) { // We build a persistent KCP session on a sequence of ephemeral WebRTC // connections. This dialContext tells RedialPacketConn how to get a new - // WebRTC connection when the previous one dies. - // If Stream based transport are used, inside each WebRTC connection, - // we use encapsulationPacketConn to encode packets into a - // stream. - // If Packet based transport are used, inside each WebRTC connection, - // packets are sent directly over unreliable data channel. + // WebRTC connection when the previous one dies. Inside each WebRTC + // connection, KCP packets are sent and received, one-to-one, in data + // channel messages. dialContext := func(ctx context.Context) (net.PacketConn, error) { log.Printf("redialing on same connection") // Obtain an available WebRTC remote. May block. From 9e1cc3587842966af5aac51d45c889b88b329c8b Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 28 Jan 2025 14:03:59 +0000 Subject: [PATCH 32/44] Add packet padding container for packet length shaping --- common/packetPaddingContainer/container.go | 47 ++++++++ .../packetPaddingContainer/containerIfce.go | 34 ++++++ .../packetPaddingContainer/container_test.go | 113 ++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 common/packetPaddingContainer/container.go create mode 100644 common/packetPaddingContainer/containerIfce.go create mode 100644 common/packetPaddingContainer/container_test.go diff --git a/common/packetPaddingContainer/container.go b/common/packetPaddingContainer/container.go new file mode 100644 index 0000000..acf5137 --- /dev/null +++ b/common/packetPaddingContainer/container.go @@ -0,0 +1,47 @@ +package packetPaddingContainer + +import "encoding/binary" + +func New() PacketPaddingContainer { + return packetPaddingContainer{} +} + +type packetPaddingContainer struct { +} + +func (c packetPaddingContainer) Pack(data_OWNERSHIP_RELINQUISHED []byte, padding int) []byte { + data := append(data_OWNERSHIP_RELINQUISHED, make([]byte, padding)...) + data_length := len(data_OWNERSHIP_RELINQUISHED) + data = append(data, byte(data_length>>8), byte(data_length)) + return data +} + +func (c packetPaddingContainer) Pad(padding int) []byte { + if assertPaddingLengthIsNotNegative := padding < 0; assertPaddingLengthIsNotNegative { + return nil + } + switch padding { + case 0: + return []byte{} + case 1: + return []byte{0} + case 2: + return []byte{0, 0} + default: + return append(make([]byte, padding-2), byte(padding>>8), byte(padding)) + } + +} + +func (c packetPaddingContainer) Unpack(wrappedData_OWNERSHIP_RELINQUISHED []byte) ([]byte, int) { + if len(wrappedData_OWNERSHIP_RELINQUISHED) < 2 { + return nil, len(wrappedData_OWNERSHIP_RELINQUISHED) + } + wrappedData_tail := wrappedData_OWNERSHIP_RELINQUISHED[len(wrappedData_OWNERSHIP_RELINQUISHED)-2:] + dataLength := int(binary.BigEndian.Uint16(wrappedData_tail)) + paddingLength := len(wrappedData_OWNERSHIP_RELINQUISHED) - dataLength - 2 + if paddingLength < 0 { + return nil, paddingLength + } + return wrappedData_OWNERSHIP_RELINQUISHED[:dataLength], paddingLength +} diff --git a/common/packetPaddingContainer/containerIfce.go b/common/packetPaddingContainer/containerIfce.go new file mode 100644 index 0000000..31cf317 --- /dev/null +++ b/common/packetPaddingContainer/containerIfce.go @@ -0,0 +1,34 @@ +package packetPaddingContainer + +// PacketPaddingContainer is an interface that defines methods to pad packets +// with a given number of bytes, and to unpack the padding from a padded packet. +// The packet format is as follows if the desired output length is greater than +// 2 bytes: +// | data | padding | data length | +// The data length is a 16-bit big-endian integer that represents the length of +// the data in bytes. +// If the desired output length is 2 bytes or less, the packet format is as +// follows: +// | padding | +// No payload will be included in the packet. +type PacketPaddingContainer interface { + // Pack pads the given data with the given number of bytes, and appends the + // length of the data to the end of the data. The returned byte slice + // contains the padded data. + // This generates a packet with a length of + // len(data_OWNERSHIP_RELINQUISHED) + padding + 2 + // @param data_OWNERSHIP_RELINQUISHED - The payload, this reference is consumed and should not be used after this call. + // @param padding - The number of padding bytes to add to the data. + Pack(data_OWNERSHIP_RELINQUISHED []byte, padding int) []byte + + // Unpack extracts the data and padding from the given padded data. It + // returns the data and the number of padding bytes. + // the data may be nil. + // @param wrappedData_OWNERSHIP_RELINQUISHED - The packet, this reference is consumed and should not be used after this call. + Unpack(wrappedData_OWNERSHIP_RELINQUISHED []byte) ([]byte, int) + + // Pad returns a padding packet of padding length. + // If the padding length is less than 0, nil is returned. + // @param padding - The number of padding bytes to add to the data. + Pad(padding int) []byte +} diff --git a/common/packetPaddingContainer/container_test.go b/common/packetPaddingContainer/container_test.go new file mode 100644 index 0000000..06e72fe --- /dev/null +++ b/common/packetPaddingContainer/container_test.go @@ -0,0 +1,113 @@ +package packetPaddingContainer_test + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/packetPaddingContainer" +) + +func TestPacketPaddingContainer(t *testing.T) { + Convey("Given a PacketPaddingContainer", t, func() { + container := packetPaddingContainer.New() + + Convey("When packing data with padding", func() { + data := []byte("testdata") + padding := 4 + packedData := container.Pack(data, padding) + + Convey("The packed data should have the correct length", func() { + expectedLength := len(data) + padding + 2 + So(len(packedData), ShouldEqual, expectedLength) + }) + + Convey("When unpacking the packed data", func() { + unpackedData, unpackedPadding := container.Unpack(packedData) + + Convey("The unpacked data should match the original data", func() { + So(string(unpackedData), ShouldEqual, string(data)) + }) + + Convey("The unpacked padding should match the original padding", func() { + So(unpackedPadding, ShouldEqual, padding) + }) + }) + }) + + Convey("When packing empty data with padding", func() { + data := []byte("") + padding := 4 + packedData := container.Pack(data, padding) + + Convey("The packed data should have the correct length", func() { + expectedLength := len(data) + padding + 2 + So(len(packedData), ShouldEqual, expectedLength) + }) + + Convey("When unpacking the packed data", func() { + unpackedData, unpackedPadding := container.Unpack(packedData) + + Convey("The unpacked data should match the original data", func() { + So(string(unpackedData), ShouldEqual, string(data)) + }) + + Convey("The unpacked padding should match the original padding", func() { + So(unpackedPadding, ShouldEqual, padding) + }) + }) + }) + + Convey("When packing data with zero padding", func() { + data := []byte("testdata") + padding := 0 + packedData := container.Pack(data, padding) + + Convey("The packed data should have the correct length", func() { + expectedLength := len(data) + padding + 2 + So(len(packedData), ShouldEqual, expectedLength) + }) + + Convey("When unpacking the packed data", func() { + unpackedData, unpackedPadding := container.Unpack(packedData) + + Convey("The unpacked data should match the original data", func() { + So(string(unpackedData), ShouldEqual, string(data)) + }) + + Convey("The unpacked padding should match the original padding", func() { + So(unpackedPadding, ShouldEqual, padding) + }) + }) + }) + + Convey("When padding data", func() { + Convey("With a positive padding length", func() { + padLength := 3 + padData := container.Pad(padLength) + + Convey("The padded data should have the correct length", func() { + So(len(padData), ShouldEqual, padLength) + }) + }) + + Convey("With a zero padding length", func() { + padLength := 0 + padData := container.Pad(padLength) + + Convey("The padded data should be empty", func() { + So(len(padData), ShouldEqual, 0) + }) + }) + + Convey("With a negative padding length", func() { + padLength := -1 + padData := container.Pad(padLength) + + Convey("The padded data should be nil", func() { + So(padData, ShouldBeNil) + }) + }) + }) + }) +} From 1689279e95719532ad4c79adb547953b709ef138 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 28 Jan 2025 14:08:03 +0000 Subject: [PATCH 33/44] Add packet padding container for packet length shaping: refactor --- common/packetPaddingContainer/container.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/common/packetPaddingContainer/container.go b/common/packetPaddingContainer/container.go index acf5137..87f547d 100644 --- a/common/packetPaddingContainer/container.go +++ b/common/packetPaddingContainer/container.go @@ -11,8 +11,8 @@ type packetPaddingContainer struct { func (c packetPaddingContainer) Pack(data_OWNERSHIP_RELINQUISHED []byte, padding int) []byte { data := append(data_OWNERSHIP_RELINQUISHED, make([]byte, padding)...) - data_length := len(data_OWNERSHIP_RELINQUISHED) - data = append(data, byte(data_length>>8), byte(data_length)) + dataLength := len(data_OWNERSHIP_RELINQUISHED) + data = binary.BigEndian.AppendUint16(data, uint16(dataLength)) return data } @@ -34,14 +34,16 @@ func (c packetPaddingContainer) Pad(padding int) []byte { } func (c packetPaddingContainer) Unpack(wrappedData_OWNERSHIP_RELINQUISHED []byte) ([]byte, int) { - if len(wrappedData_OWNERSHIP_RELINQUISHED) < 2 { - return nil, len(wrappedData_OWNERSHIP_RELINQUISHED) + dataLength := len(wrappedData_OWNERSHIP_RELINQUISHED) + if dataLength < 2 { + return nil, dataLength } - wrappedData_tail := wrappedData_OWNERSHIP_RELINQUISHED[len(wrappedData_OWNERSHIP_RELINQUISHED)-2:] - dataLength := int(binary.BigEndian.Uint16(wrappedData_tail)) - paddingLength := len(wrappedData_OWNERSHIP_RELINQUISHED) - dataLength - 2 + + dataLen := int(binary.BigEndian.Uint16(wrappedData_OWNERSHIP_RELINQUISHED[dataLength-2:])) + paddingLength := dataLength - dataLen - 2 if paddingLength < 0 { return nil, paddingLength } - return wrappedData_OWNERSHIP_RELINQUISHED[:dataLength], paddingLength + + return wrappedData_OWNERSHIP_RELINQUISHED[:dataLen], paddingLength } From fbcb9bc8638a70754390a886ed2f626839b1b2e9 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 28 Jan 2025 14:19:20 +0000 Subject: [PATCH 34/44] Add packet padding container for packet length shaping: refactor arg names --- common/packetPaddingContainer/container.go | 12 +++---- .../packetPaddingContainer/containerIfce.go | 4 +-- .../packetPaddingContainer/container_test.go | 36 +++++++++---------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/common/packetPaddingContainer/container.go b/common/packetPaddingContainer/container.go index 87f547d..3ac912c 100644 --- a/common/packetPaddingContainer/container.go +++ b/common/packetPaddingContainer/container.go @@ -9,18 +9,18 @@ func New() PacketPaddingContainer { type packetPaddingContainer struct { } -func (c packetPaddingContainer) Pack(data_OWNERSHIP_RELINQUISHED []byte, padding int) []byte { - data := append(data_OWNERSHIP_RELINQUISHED, make([]byte, padding)...) +func (c packetPaddingContainer) Pack(data_OWNERSHIP_RELINQUISHED []byte, paddingLength int) []byte { + data := append(data_OWNERSHIP_RELINQUISHED, make([]byte, paddingLength)...) dataLength := len(data_OWNERSHIP_RELINQUISHED) data = binary.BigEndian.AppendUint16(data, uint16(dataLength)) return data } -func (c packetPaddingContainer) Pad(padding int) []byte { - if assertPaddingLengthIsNotNegative := padding < 0; assertPaddingLengthIsNotNegative { +func (c packetPaddingContainer) Pad(paddingLength int) []byte { + if assertPaddingLengthIsNotNegative := paddingLength < 0; assertPaddingLengthIsNotNegative { return nil } - switch padding { + switch paddingLength { case 0: return []byte{} case 1: @@ -28,7 +28,7 @@ func (c packetPaddingContainer) Pad(padding int) []byte { case 2: return []byte{0, 0} default: - return append(make([]byte, padding-2), byte(padding>>8), byte(padding)) + return append(make([]byte, paddingLength-2), byte(paddingLength>>8), byte(paddingLength)) } } diff --git a/common/packetPaddingContainer/containerIfce.go b/common/packetPaddingContainer/containerIfce.go index 31cf317..b66ac9d 100644 --- a/common/packetPaddingContainer/containerIfce.go +++ b/common/packetPaddingContainer/containerIfce.go @@ -19,7 +19,7 @@ type PacketPaddingContainer interface { // len(data_OWNERSHIP_RELINQUISHED) + padding + 2 // @param data_OWNERSHIP_RELINQUISHED - The payload, this reference is consumed and should not be used after this call. // @param padding - The number of padding bytes to add to the data. - Pack(data_OWNERSHIP_RELINQUISHED []byte, padding int) []byte + Pack(data_OWNERSHIP_RELINQUISHED []byte, paddingLength int) []byte // Unpack extracts the data and padding from the given padded data. It // returns the data and the number of padding bytes. @@ -30,5 +30,5 @@ type PacketPaddingContainer interface { // Pad returns a padding packet of padding length. // If the padding length is less than 0, nil is returned. // @param padding - The number of padding bytes to add to the data. - Pad(padding int) []byte + Pad(paddingLength int) []byte } diff --git a/common/packetPaddingContainer/container_test.go b/common/packetPaddingContainer/container_test.go index 06e72fe..f616528 100644 --- a/common/packetPaddingContainer/container_test.go +++ b/common/packetPaddingContainer/container_test.go @@ -14,69 +14,69 @@ func TestPacketPaddingContainer(t *testing.T) { Convey("When packing data with padding", func() { data := []byte("testdata") - padding := 4 - packedData := container.Pack(data, padding) + paddingLength := 4 + packedData := container.Pack(data, paddingLength) Convey("The packed data should have the correct length", func() { - expectedLength := len(data) + padding + 2 + expectedLength := len(data) + paddingLength + 2 So(len(packedData), ShouldEqual, expectedLength) }) Convey("When unpacking the packed data", func() { - unpackedData, unpackedPadding := container.Unpack(packedData) + unpackedData, unpackedPaddingLength := container.Unpack(packedData) Convey("The unpacked data should match the original data", func() { So(string(unpackedData), ShouldEqual, string(data)) }) - Convey("The unpacked padding should match the original padding", func() { - So(unpackedPadding, ShouldEqual, padding) + Convey("The unpacked padding length should match the original padding length", func() { + So(unpackedPaddingLength, ShouldEqual, paddingLength) }) }) }) Convey("When packing empty data with padding", func() { data := []byte("") - padding := 4 - packedData := container.Pack(data, padding) + paddingLength := 4 + packedData := container.Pack(data, paddingLength) Convey("The packed data should have the correct length", func() { - expectedLength := len(data) + padding + 2 + expectedLength := len(data) + paddingLength + 2 So(len(packedData), ShouldEqual, expectedLength) }) Convey("When unpacking the packed data", func() { - unpackedData, unpackedPadding := container.Unpack(packedData) + unpackedData, unpackedPaddingLength := container.Unpack(packedData) Convey("The unpacked data should match the original data", func() { So(string(unpackedData), ShouldEqual, string(data)) }) - Convey("The unpacked padding should match the original padding", func() { - So(unpackedPadding, ShouldEqual, padding) + Convey("The unpacked padding length should match the original padding length", func() { + So(unpackedPaddingLength, ShouldEqual, paddingLength) }) }) }) Convey("When packing data with zero padding", func() { data := []byte("testdata") - padding := 0 - packedData := container.Pack(data, padding) + paddingLength := 0 + packedData := container.Pack(data, paddingLength) Convey("The packed data should have the correct length", func() { - expectedLength := len(data) + padding + 2 + expectedLength := len(data) + paddingLength + 2 So(len(packedData), ShouldEqual, expectedLength) }) Convey("When unpacking the packed data", func() { - unpackedData, unpackedPadding := container.Unpack(packedData) + unpackedData, unpackedPaddingLength := container.Unpack(packedData) Convey("The unpacked data should match the original data", func() { So(string(unpackedData), ShouldEqual, string(data)) }) - Convey("The unpacked padding should match the original padding", func() { - So(unpackedPadding, ShouldEqual, padding) + Convey("The unpacked padding length should match the original padding length", func() { + So(unpackedPaddingLength, ShouldEqual, paddingLength) }) }) }) From 9e4577217720c1622dc8891cee65e85c81ea9149 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 29 Jan 2025 11:22:30 +0000 Subject: [PATCH 35/44] rename to common/packetpadding --- .../{packetPaddingContainer => packetpadding}/container.go | 2 +- .../containerIfce.go | 2 +- .../container_test.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename common/{packetPaddingContainer => packetpadding}/container.go (97%) rename common/{packetPaddingContainer => packetpadding}/containerIfce.go (98%) rename common/{packetPaddingContainer => packetpadding}/container_test.go (96%) diff --git a/common/packetPaddingContainer/container.go b/common/packetpadding/container.go similarity index 97% rename from common/packetPaddingContainer/container.go rename to common/packetpadding/container.go index 3ac912c..c2a5bb3 100644 --- a/common/packetPaddingContainer/container.go +++ b/common/packetpadding/container.go @@ -1,4 +1,4 @@ -package packetPaddingContainer +package packetpadding import "encoding/binary" diff --git a/common/packetPaddingContainer/containerIfce.go b/common/packetpadding/containerIfce.go similarity index 98% rename from common/packetPaddingContainer/containerIfce.go rename to common/packetpadding/containerIfce.go index b66ac9d..84fac0c 100644 --- a/common/packetPaddingContainer/containerIfce.go +++ b/common/packetpadding/containerIfce.go @@ -1,4 +1,4 @@ -package packetPaddingContainer +package packetpadding // PacketPaddingContainer is an interface that defines methods to pad packets // with a given number of bytes, and to unpack the padding from a padded packet. diff --git a/common/packetPaddingContainer/container_test.go b/common/packetpadding/container_test.go similarity index 96% rename from common/packetPaddingContainer/container_test.go rename to common/packetpadding/container_test.go index f616528..e68dcc8 100644 --- a/common/packetPaddingContainer/container_test.go +++ b/common/packetpadding/container_test.go @@ -1,16 +1,16 @@ -package packetPaddingContainer_test +package packetpadding_test import ( "testing" . "github.com/smartystreets/goconvey/convey" - "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/packetPaddingContainer" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/packetpadding" ) func TestPacketPaddingContainer(t *testing.T) { Convey("Given a PacketPaddingContainer", t, func() { - container := packetPaddingContainer.New() + container := packetpadding.New() Convey("When packing data with padding", func() { data := []byte("testdata") From 53172a588bddb7c459a7dbf1da15c62631afa878 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 29 Jan 2025 11:29:52 +0000 Subject: [PATCH 36/44] add paddable connection --- common/packetpadding/conn.go | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 common/packetpadding/conn.go diff --git a/common/packetpadding/conn.go b/common/packetpadding/conn.go new file mode 100644 index 0000000..6ecbf51 --- /dev/null +++ b/common/packetpadding/conn.go @@ -0,0 +1,44 @@ +package packetpadding + +import "io" + +type ReadWriteCloserPreservesBoundary interface { + io.ReadWriteCloser + MessageBoundaryPreserved() +} + +type PaddableConnection interface { + ReadWriteCloserPreservesBoundary +} + +func NewPaddableConnection(rwc ReadWriteCloserPreservesBoundary, padding PacketPaddingContainer) PaddableConnection { + return &paddableConnection{ + ReadWriteCloserPreservesBoundary: rwc, + padding: padding, + } +} + +type paddableConnection struct { + ReadWriteCloserPreservesBoundary + padding PacketPaddingContainer +} + +func (c *paddableConnection) Write(p []byte) (n int, err error) { + dataLen := len(p) + if _, err = c.ReadWriteCloserPreservesBoundary.Write(c.padding.Pack(p, 0)); err != nil { + return 0, err + } + return dataLen, nil +} + +func (c *paddableConnection) Read(p []byte) (n int, err error) { + if n, err = c.ReadWriteCloserPreservesBoundary.Read(p); err != nil { + return 0, err + } + + payload, _ := c.padding.Unpack(p[:n]) + if payload != nil { + copy(p, payload) + } + return len(payload), nil +} From 0aa1470e01356e61501306b6f3b367646d7c1913 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 29 Jan 2025 11:46:33 +0000 Subject: [PATCH 37/44] add connection padding on server side --- client/lib/snowflake.go | 7 ++++++- common/packetpadding/conn.go | 11 +++++++++++ server/lib/http.go | 10 +++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index ef04f2a..92c36b4 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -28,6 +28,7 @@ package snowflake_client import ( "context" "errors" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/packetpadding" "log" "math/rand" "net" @@ -342,7 +343,11 @@ func newSession(snowflakes SnowflakeCollector) (net.PacketConn, *smux.Session, e } log.Println("---- Handler: snowflake assigned ----") - packetConnWrapper := newPacketConnWrapper(dummyAddr{}, dummyAddr{}, ConfirmsReadWriteCloserPreservesMessageBoundary(conn)) + packetConnWrapper := newPacketConnWrapper(dummyAddr{}, dummyAddr{}, + packetpadding.NewPaddableConnection( + ConfirmsReadWriteCloserPreservesMessageBoundary(conn), + packetpadding.New())) + return packetConnWrapper, nil } pconn := turbotunnel.NewRedialPacketConn(dummyAddr{}, dummyAddr{}, dialContext) diff --git a/common/packetpadding/conn.go b/common/packetpadding/conn.go index 6ecbf51..8993745 100644 --- a/common/packetpadding/conn.go +++ b/common/packetpadding/conn.go @@ -7,6 +7,17 @@ type ReadWriteCloserPreservesBoundary interface { MessageBoundaryPreserved() } +type messageBoundaryPreservedReadWriteCloser struct { + io.ReadWriteCloser +} + +func (m *messageBoundaryPreservedReadWriteCloser) MessageBoundaryPreserved() { +} + +func ConfirmsReadWriteCloserPreservesMessageBoundary(rwc io.ReadWriteCloser) ReadWriteCloserPreservesBoundary { + return &messageBoundaryPreservedReadWriteCloser{rwc} +} + type PaddableConnection interface { ReadWriteCloserPreservesBoundary } diff --git a/server/lib/http.go b/server/lib/http.go index 6da2d0f..39eac9b 100644 --- a/server/lib/http.go +++ b/server/lib/http.go @@ -5,7 +5,7 @@ import ( "crypto/rand" "crypto/sha256" "encoding/binary" - "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/packetpadding" "io" "log" "net" @@ -15,6 +15,7 @@ import ( "github.com/gorilla/websocket" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/websocketconn" ) @@ -142,6 +143,9 @@ func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr, wg.Add(2) done := make(chan struct{}) + connPaddable := packetpadding.NewPaddableConnection( + packetpadding.ConfirmsReadWriteCloserPreservesMessageBoundary(conn), packetpadding.New()) + // The remainder of the WebSocket stream consists of packets, one packet // per WebSocket message. We read them one by one and feed them into the // QueuePacketConn on which kcp.ServeConn was set up, which eventually @@ -151,7 +155,7 @@ func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr, defer close(done) // Signal the write loop to finish var p [2048]byte for { - n, err := conn.Read(p[:]) + n, err := connPaddable.Read(p[:]) if err != nil { log.Println(err) return @@ -173,7 +177,7 @@ func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr, if !ok { return } - _, err := conn.Write(p) + _, err := connPaddable.Write(p) pconn.Restore(p) if err != nil { log.Println(err) From 5daf971ea15f8a9cac8b988eb6168a7ca6204f4a Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 28 Jan 2025 14:26:46 +0000 Subject: [PATCH 38/44] update golang testing setting in CI --- .gitlab-ci.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4fd85e0..c7fddef 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -143,7 +143,14 @@ android: - gomobile bind -v -target=android $REPRODUCIBLE_FLAGS . go-1.21: - image: golang:1.21-$DEBIAN_STABLE + image: containers.torproject.org/tpo/anti-censorship/duplicatedcontainerimages:golang-1.21-$DEBIAN_STABLE + <<: *golang-docker-debian-template + <<: *test-template + script: + - *go-test + +go-1.23: + image: containers.torproject.org/tpo/anti-censorship/duplicatedcontainerimages:golang-1.23-$DEBIAN_STABLE <<: *golang-docker-debian-template <<: *test-template script: From 51eb36051cdd7838c636f91512e6eeefa2f4e9ff Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 29 Jan 2025 12:25:57 +0000 Subject: [PATCH 39/44] fix coding style issue in common/message --- common/messages/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/messages/client.go b/common/messages/client.go index 5d1f0d6..637e771 100644 --- a/common/messages/client.go +++ b/common/messages/client.go @@ -8,8 +8,8 @@ import ( "encoding/base64" "encoding/json" "fmt" - "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/nat" ) From 6e0bc74fb82382394e45c140b7557c3c805040f4 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 29 Jan 2025 13:07:50 +0000 Subject: [PATCH 40/44] add comment for ClientConnectionMetadata --- common/messages/client.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/messages/client.go b/common/messages/client.go index 637e771..bde9781 100644 --- a/common/messages/client.go +++ b/common/messages/client.go @@ -151,6 +151,9 @@ func DecodeClientPollResponse(data []byte) (*ClientPollResponse, error) { return &message, nil } +// ClientConnectionMetadata is a struct that contains metadata about a snowflake connection between client and server +// It will be sent from the client to the proxy in WebRTC data channel protocol string +// The proxy will then send the metadata to the server in the protocol get parameter of the WebSocket connection type ClientConnectionMetadata struct { ClientID []byte `json:"client_id"` } From 33b38916484efb26adff45fb44200531ee9a6b08 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 29 Jan 2025 13:16:21 +0000 Subject: [PATCH 41/44] use cbor instead of json to avoid double base64 --- common/messages/client.go | 6 ++++-- go.mod | 2 ++ go.sum | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/common/messages/client.go b/common/messages/client.go index bde9781..030e052 100644 --- a/common/messages/client.go +++ b/common/messages/client.go @@ -9,6 +9,8 @@ import ( "encoding/json" "fmt" + "github.com/fxamacker/cbor" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/nat" ) @@ -159,7 +161,7 @@ type ClientConnectionMetadata struct { } func (meta *ClientConnectionMetadata) EncodeConnectionMetadata() (string, error) { - jsonData, err := json.Marshal(meta) + jsonData, err := cbor.Marshal(meta, cbor.CanonicalEncOptions()) if err != nil { return "", err } @@ -174,7 +176,7 @@ func DecodeConnectionMetadata(data string) (*ClientConnectionMetadata, error) { } var meta ClientConnectionMetadata - err = json.Unmarshal(decodedData, &meta) + err = cbor.Unmarshal(decodedData, &meta) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index 74e28d4..fe1153a 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fxamacker/cbor v1.5.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect @@ -77,6 +78,7 @@ require ( github.com/tjfoc/gmsm v1.4.1 // indirect github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect github.com/wlynxg/anet v0.0.5 // indirect + github.com/x448/float16 v0.8.4 // indirect golang.org/x/mod v0.18.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/text v0.21.0 // indirect diff --git a/go.sum b/go.sum index 6d3d29a..6ab0f6c 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg= +github.com/fxamacker/cbor v1.5.1/go.mod h1:3aPGItF174ni7dDzd6JZ206H8cmr4GDNBGpPa971zsU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= @@ -162,6 +164,8 @@ github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 h1:d/Wr/Vl/wiJHc github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301/go.mod h1:ntmMHL/xPq1WLeKiw8p/eRATaae6PiVRNipHFJxI8PM= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xtaci/kcp-go/v5 v5.6.8 h1:jlI/0jAyjoOjT/SaGB58s4bQMJiNS41A2RKzR6TMWeI= github.com/xtaci/kcp-go/v5 v5.6.8/go.mod h1:oE9j2NVqAkuKO5o8ByKGch3vgVX3BNf8zqP8JiGq0bM= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= From 1ba58c864d5b288047b7dbaa17b6369c4e77d1af Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 30 Jan 2025 13:35:36 +0000 Subject: [PATCH 42/44] refactor message boundary constrains --- client/lib/connwrapper.go | 10 ---------- client/lib/snowflake.go | 3 +-- client/lib/webrtc.go | 2 ++ common/packetpadding/conn.go | 15 +++------------ common/websocketconn/websocketconn.go | 2 ++ server/lib/http.go | 5 ++--- 6 files changed, 10 insertions(+), 27 deletions(-) diff --git a/client/lib/connwrapper.go b/client/lib/connwrapper.go index 0db9324..f8d0614 100644 --- a/client/lib/connwrapper.go +++ b/client/lib/connwrapper.go @@ -12,16 +12,6 @@ type ReadWriteCloserPreservesBoundary interface { MessageBoundaryPreserved() } -func ConfirmsReadWriteCloserPreservesMessageBoundary(rwc io.ReadWriteCloser) ReadWriteCloserPreservesBoundary { - return &messageBoundaryPreservedReadWriteCloser{rwc} -} - -type messageBoundaryPreservedReadWriteCloser struct { - io.ReadWriteCloser -} - -func (m *messageBoundaryPreservedReadWriteCloser) MessageBoundaryPreserved() {} - var errENOSYS = errors.New("not implemented") func newPacketConnWrapper(localAddr, remoteAddr net.Addr, rwc ReadWriteCloserPreservesBoundary) net.PacketConn { diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index 92c36b4..0ede459 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -344,8 +344,7 @@ func newSession(snowflakes SnowflakeCollector) (net.PacketConn, *smux.Session, e log.Println("---- Handler: snowflake assigned ----") packetConnWrapper := newPacketConnWrapper(dummyAddr{}, dummyAddr{}, - packetpadding.NewPaddableConnection( - ConfirmsReadWriteCloserPreservesMessageBoundary(conn), + packetpadding.NewPaddableConnection(conn, packetpadding.New())) return packetConnWrapper, nil diff --git a/client/lib/webrtc.go b/client/lib/webrtc.go index 7af1186..ed745d9 100644 --- a/client/lib/webrtc.go +++ b/client/lib/webrtc.go @@ -412,3 +412,5 @@ func (c *WebRTCPeer) cleanup() { } } } + +func (c *WebRTCPeer) MessageBoundaryPreserved() {} diff --git a/common/packetpadding/conn.go b/common/packetpadding/conn.go index 8993745..b214e70 100644 --- a/common/packetpadding/conn.go +++ b/common/packetpadding/conn.go @@ -1,23 +1,14 @@ package packetpadding -import "io" +import ( + "io" +) type ReadWriteCloserPreservesBoundary interface { io.ReadWriteCloser MessageBoundaryPreserved() } -type messageBoundaryPreservedReadWriteCloser struct { - io.ReadWriteCloser -} - -func (m *messageBoundaryPreservedReadWriteCloser) MessageBoundaryPreserved() { -} - -func ConfirmsReadWriteCloserPreservesMessageBoundary(rwc io.ReadWriteCloser) ReadWriteCloserPreservesBoundary { - return &messageBoundaryPreservedReadWriteCloser{rwc} -} - type PaddableConnection interface { ReadWriteCloserPreservesBoundary } diff --git a/common/websocketconn/websocketconn.go b/common/websocketconn/websocketconn.go index e5256df..0178f3d 100644 --- a/common/websocketconn/websocketconn.go +++ b/common/websocketconn/websocketconn.go @@ -41,6 +41,8 @@ func (conn *Conn) SetDeadline(t time.Time) error { return err } +func (conn *Conn) MessageBoundaryPreserved() {} + func readLoop(w io.Writer, ws *websocket.Conn) error { var buf [2048]byte for { diff --git a/server/lib/http.go b/server/lib/http.go index 39eac9b..89c8a6b 100644 --- a/server/lib/http.go +++ b/server/lib/http.go @@ -115,7 +115,7 @@ func (handler *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } -func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr, protocol string) error { +func (handler *httpHandler) turboTunnelUDPLikeMode(conn *websocketconn.Conn, addr net.Addr, protocol string) error { // Read the ClientID from the WebRTC data channel protocol string. Every // packet received on this WebSocket connection pertains to the same // ClientID. @@ -143,8 +143,7 @@ func (handler *httpHandler) turboTunnelUDPLikeMode(conn net.Conn, addr net.Addr, wg.Add(2) done := make(chan struct{}) - connPaddable := packetpadding.NewPaddableConnection( - packetpadding.ConfirmsReadWriteCloserPreservesMessageBoundary(conn), packetpadding.New()) + connPaddable := packetpadding.NewPaddableConnection(conn, packetpadding.New()) // The remainder of the WebSocket stream consists of packets, one packet // per WebSocket message. We read them one by one and feed them into the From 94eddc6391440c4757c264510060355eb1296938 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 30 Jan 2025 13:37:22 +0000 Subject: [PATCH 43/44] refactor message boundary constrains: coding style --- client/lib/snowflake.go | 2 +- server/lib/http.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index 0ede459..939e593 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -28,7 +28,6 @@ package snowflake_client import ( "context" "errors" - "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/packetpadding" "log" "math/rand" "net" @@ -44,6 +43,7 @@ import ( "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/event" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/nat" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/packetpadding" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" ) diff --git a/server/lib/http.go b/server/lib/http.go index 89c8a6b..26ecaab 100644 --- a/server/lib/http.go +++ b/server/lib/http.go @@ -5,7 +5,6 @@ import ( "crypto/rand" "crypto/sha256" "encoding/binary" - "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/packetpadding" "io" "log" "net" @@ -16,6 +15,7 @@ import ( "github.com/gorilla/websocket" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/packetpadding" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/websocketconn" ) From 182fb83d98aa9397e9130c25a0cf4f84e869385c Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 4 Feb 2025 11:19:44 +0000 Subject: [PATCH 44/44] reject message with 2047 bytes to reserve higher bits --- common/packetpadding/container.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/packetpadding/container.go b/common/packetpadding/container.go index c2a5bb3..318ba2e 100644 --- a/common/packetpadding/container.go +++ b/common/packetpadding/container.go @@ -40,6 +40,9 @@ func (c packetPaddingContainer) Unpack(wrappedData_OWNERSHIP_RELINQUISHED []byte } dataLen := int(binary.BigEndian.Uint16(wrappedData_OWNERSHIP_RELINQUISHED[dataLength-2:])) + if dataLen > 2047 { + return nil, 0 + } paddingLength := dataLength - dataLen - 2 if paddingLength < 0 { return nil, paddingLength