From 559da882b0af8b67ebfe126de37db3e82ac0b305 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 12 Dec 2023 14:43:30 +0000 Subject: [PATCH 01/31] Add UDP Like transport mode to snowflake --- client/lib/packetIDConnClient.go | 109 +++++++++++++++++++++++++++++++ client/lib/snowflake.go | 12 +++- client/lib/webrtc.go | 14 +++- proxy/lib/snowflake.go | 7 ++ proxy/lib/webrtcconn.go | 10 +++ server/lib/http.go | 65 ++++++++++++++++++ server/lib/packetIDConnServer.go | 52 +++++++++++++++ server/lib/snowflake.go | 2 +- 8 files changed, 268 insertions(+), 3 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 00000000..15b6c51a --- /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 7d405cd5..72b7d4a1 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -332,6 +332,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 { @@ -356,7 +366,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 8ccc3d07..9e272040 100644 --- a/client/lib/webrtc.go +++ b/client/lib/webrtc.go @@ -43,6 +43,8 @@ type WebRTCPeer struct { bytesLogger bytesLogger eventsLogger event.SnowflakeEventReceiver proxy *url.URL + + activeTransportMode byte } // Deprecated: Use NewWebRTCPeerWithEventsAndProxy Instead. @@ -168,6 +170,7 @@ func (c *WebRTCPeer) checkForStaleness(timeout time.Duration) { // receive an answer from broker, and wait for data channel to open func (c *WebRTCPeer) connect(config *webrtc.Configuration, broker *BrokerChannel) error { log.Println(c.id, " connecting...") + c.activeTransportMode = 'u' err := c.preparePeerConnection(config) localDescription := c.pc.LocalDescription() c.eventsLogger.OnNewSnowflakeEvent(event.EventOnOfferCreated{ @@ -236,8 +239,17 @@ func (c *WebRTCPeer) preparePeerConnection(config *webrtc.Configuration) error { 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 dd53f19e..dbc2a530 100644 --- a/proxy/lib/snowflake.go +++ b/proxy/lib/snowflake.go @@ -371,6 +371,12 @@ func (sf *SnowflakeProxy) datachannelHandler(conn *webRTCConn, remoteAddr net.Ad log.Printf("no remote address given in websocket") } + if protocol := conn.GetConnectionProtocol(); protocol != "" { + q := u.Query() + q.Set("protocol", protocol) + u.RawQuery = q.Encode() + } + ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil) if err != nil { log.Printf("error dialing relay: %s = %s", u.String(), err) @@ -444,6 +450,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 9ad88b53..7d534f59 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 403aeb17..a667f7b3 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 00000000..feca1fa6 --- /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 bcf9dd68..d7d0c406 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) -- GitLab From 889009311a698fa093ed20390ec80734c7ffd083 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 12 Dec 2023 14:43:56 +0000 Subject: [PATCH 02/31] 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 6f4a0e27..10bacb66 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 dbc2a530..319c2c58 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" @@ -814,6 +815,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, false) if err != nil { return fmt.Errorf("Error parsing url: %w", err) -- GitLab From 9c1e733754c63a8b5ef0df6ccdf9d981b3f32a89 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 19 Dec 2023 14:59:45 +0000 Subject: [PATCH 03/31] 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 72b7d4a1..4f7140d1 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -32,6 +32,7 @@ import ( "math/rand" "net" "net/url" + "os" "strings" "time" @@ -378,6 +379,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 d7d0c406..b158f035 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) -- GitLab From 8d456f448a953d6725e3c4ac660ef9f5dc3756f1 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Mon, 29 Apr 2024 14:53:02 +0100 Subject: [PATCH 04/31] add client side support for extra data based client id --- client/lib/rendezvous.go | 21 ++++++++++++++++----- client/lib/snowflake.go | 18 +++++++++++++----- client/lib/webrtc.go | 20 +++++++++++++++----- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/client/lib/rendezvous.go b/client/lib/rendezvous.go index 91ba088f..a8d20536 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/v3" utls "github.com/refraction-networking/utls" @@ -186,8 +188,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 NewWebRTCDialerWithEventsAndProxy instead @@ -203,6 +206,13 @@ func NewWebRTCDialerWithEvents(broker *BrokerChannel, iceServers []webrtc.ICESer // NewWebRTCDialerWithEventsAndProxy constructs a new WebRTCDialer. func NewWebRTCDialerWithEventsAndProxy(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int, eventLogger event.SnowflakeEventReceiver, proxy *url.URL, +) *WebRTCDialer { + return NewWebRTCDialerWithEventsProxyAndClientID(broker, iceServers, max, eventLogger, proxy, turbotunnel.ClientID{}) +} + +// NewWebRTCDialerWithEventsProxyAndClientID constructs a new WebRTCDialer. +func NewWebRTCDialerWithEventsProxyAndClientID(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int, + eventLogger event.SnowflakeEventReceiver, proxy *url.URL, clientID turbotunnel.ClientID, ) *WebRTCDialer { config := webrtc.Configuration{ ICEServers: iceServers, @@ -213,8 +223,9 @@ func NewWebRTCDialerWithEventsAndProxy(broker *BrokerChannel, iceServers []webrt webrtcConfig: &config, max: max, - eventLogger: eventLogger, - proxy: proxy, + eventLogger: eventLogger, + proxy: proxy, + connectionID: clientID, } } @@ -222,7 +233,7 @@ func NewWebRTCDialerWithEventsAndProxy(broker *BrokerChannel, iceServers []webrt 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 NewWebRTCPeerWithEventsProxyAndClientID(w.webrtcConfig, w.BrokerChannel, w.eventLogger, w.proxy, w.connectionID) } // GetMax returns the maximum number of snowflakes to collect. diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index 4f7140d1..6e24494c 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. @@ -162,7 +164,11 @@ func NewSnowflakeClient(config ClientConfig) (*Transport, error) { max = config.Max } eventsLogger := event.NewSnowflakeEventDispatcher() - transport := &Transport{dialer: NewWebRTCDialerWithEventsAndProxy(broker, iceServers, max, eventsLogger, config.CommunicationProxy), eventDispatcher: eventsLogger} + clientID := turbotunnel.NewClientID() + transport := &Transport{ + dialer: NewWebRTCDialerWithEventsAndProxy(broker, iceServers, max, eventsLogger, config.CommunicationProxy), + eventDispatcher: eventsLogger, clientID: clientID, + } return transport, nil } @@ -196,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) + pconn, sess, err := newSession(snowflakes, t.clientID) if err != nil { return nil, err } @@ -317,8 +323,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 @@ -335,9 +344,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 9e272040..15bba2c5 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" ) // WebRTCPeer represents a WebRTC connection to a remote snowflake proxy. @@ -45,6 +46,7 @@ type WebRTCPeer struct { proxy *url.URL activeTransportMode byte + connectionID turbotunnel.ClientID } // Deprecated: Use NewWebRTCPeerWithEventsAndProxy Instead. @@ -62,14 +64,21 @@ func NewWebRTCPeerWithEvents( return NewWebRTCPeerWithEventsAndProxy(config, broker, eventsLogger, nil) } -// NewWebRTCPeerWithEventsAndProxy constructs a WebRTC PeerConnection to a snowflake proxy. +func NewWebRTCPeerWithEventsAndProxy(config *webrtc.Configuration, + broker *BrokerChannel, eventsLogger event.SnowflakeEventReceiver, proxy *url.URL, +) (*WebRTCPeer, error) { + return NewWebRTCPeerWithEventsProxyAndClientID(config, broker, eventsLogger, proxy, turbotunnel.ClientID{}) +} + +// NewWebRTCPeerWithEventsProxyAndClientID 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 NewWebRTCPeerWithEventsAndProxy( - config *webrtc.Configuration, broker *BrokerChannel, - eventsLogger event.SnowflakeEventReceiver, proxy *url.URL, +// connectionID is the hinted ID for the connection. +func NewWebRTCPeerWithEventsProxyAndClientID(config *webrtc.Configuration, + broker *BrokerChannel, eventsLogger event.SnowflakeEventReceiver, proxy *url.URL, + clientID turbotunnel.ClientID, ) (*WebRTCPeer, error) { if eventsLogger == nil { eventsLogger = event.NewSnowflakeEventDispatcher() @@ -93,6 +102,7 @@ func NewWebRTCPeerWithEventsAndProxy( connection.eventsLogger = eventsLogger connection.proxy = proxy + connection.connectionID = clientID err := connection.connect(config, broker) if err != nil { @@ -245,7 +255,7 @@ func (c *WebRTCPeer) preparePeerConnection(config *webrtc.Configuration) error { 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, -- GitLab From 50625abeb61838491ea2b23107a76f12b7bb1776 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 2 May 2024 11:12:08 +0100 Subject: [PATCH 05/31] 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 a667f7b3..c003d973 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) -- GitLab From 14bf23175c83c113c13187b1dae249403fa27cce Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Mon, 13 May 2024 13:27:52 +0100 Subject: [PATCH 06/31] 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 319c2c58..95c51fa4 100644 --- a/proxy/lib/snowflake.go +++ b/proxy/lib/snowflake.go @@ -463,7 +463,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()) if sf.OutboundAddress != "" { selectedCandidatePair, err := pc.SCTP().Transport().ICETransport().GetSelectedCandidatePair() -- GitLab From 9faedb7219790017a200681235f7e4330d67d358 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Mon, 13 May 2024 13:49:01 +0100 Subject: [PATCH 07/31] 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 6e24494c..2f4b2cf0 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -166,7 +166,7 @@ func NewSnowflakeClient(config ClientConfig) (*Transport, error) { eventsLogger := event.NewSnowflakeEventDispatcher() clientID := turbotunnel.NewClientID() transport := &Transport{ - dialer: NewWebRTCDialerWithEventsAndProxy(broker, iceServers, max, eventsLogger, config.CommunicationProxy), + dialer: NewWebRTCDialerWithEventsProxyAndClientID(broker, iceServers, max, eventsLogger, config.CommunicationProxy, clientID), eventDispatcher: eventsLogger, clientID: clientID, } -- GitLab From 8d9589720b98bb7add81d7284f856d45347a64dc Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Mon, 13 May 2024 16:02:01 +0100 Subject: [PATCH 08/31] 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 c003d973..70ffa16c 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) -- GitLab From 0474d1eaccf79f2d847e75e2ca4c4d58b7685dff Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Mon, 13 May 2024 16:09:10 +0100 Subject: [PATCH 09/31] 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 15b6c51a..00000000 --- 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 feca1fa6..00000000 --- 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 -} -- GitLab From f1df19c33292c3c98990f933c032368bb0f0898e Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 14 May 2024 11:13:24 +0100 Subject: [PATCH 10/31] 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 00000000..ca96a24f --- /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 +} -- GitLab From c685f21afb47e2cf041fd143eef6693fb4e13a17 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 14 May 2024 11:50:56 +0100 Subject: [PATCH 11/31] 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 a8d20536..37d36f31 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/v3" 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" ) -- GitLab From a4b5a93e0f6f5112361a88115d6fefc8e68abf89 Mon Sep 17 00:00:00 2001 From: David Fifield Date: Thu, 1 Aug 2024 21:38:48 +0000 Subject: [PATCH 12/31] =?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 37d36f31..26118d09 100644 --- a/client/lib/rendezvous.go +++ b/client/lib/rendezvous.go @@ -187,9 +187,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 NewWebRTCDialerWithEventsAndProxy instead @@ -222,9 +222,9 @@ func NewWebRTCDialerWithEventsProxyAndClientID(broker *BrokerChannel, iceServers webrtcConfig: &config, max: max, - eventLogger: eventLogger, - proxy: proxy, - connectionID: clientID, + eventLogger: eventLogger, + proxy: proxy, + clientID: clientID, } } @@ -232,7 +232,7 @@ func NewWebRTCDialerWithEventsProxyAndClientID(broker *BrokerChannel, iceServers func (w WebRTCDialer) Catch() (*WebRTCPeer, error) { // TODO: [#25591] Fetch ICE server information from Broker. // TODO: [#25596] Consider TURN servers here too. - return NewWebRTCPeerWithEventsProxyAndClientID(w.webrtcConfig, w.BrokerChannel, w.eventLogger, w.proxy, w.connectionID) + return NewWebRTCPeerWithEventsProxyAndClientID(w.webrtcConfig, w.BrokerChannel, 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 15bba2c5..70c9e851 100644 --- a/client/lib/webrtc.go +++ b/client/lib/webrtc.go @@ -46,7 +46,7 @@ type WebRTCPeer struct { proxy *url.URL activeTransportMode byte - connectionID turbotunnel.ClientID + clientID turbotunnel.ClientID } // Deprecated: Use NewWebRTCPeerWithEventsAndProxy Instead. @@ -75,7 +75,7 @@ func NewWebRTCPeerWithEventsAndProxy(config *webrtc.Configuration, // 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 NewWebRTCPeerWithEventsProxyAndClientID(config *webrtc.Configuration, broker *BrokerChannel, eventsLogger event.SnowflakeEventReceiver, proxy *url.URL, clientID turbotunnel.ClientID, @@ -102,7 +102,7 @@ func NewWebRTCPeerWithEventsProxyAndClientID(config *webrtc.Configuration, connection.eventsLogger = eventsLogger connection.proxy = proxy - connection.connectionID = clientID + connection.clientID = clientID err := connection.connect(config, broker) if err != nil { @@ -255,7 +255,7 @@ func (c *WebRTCPeer) preparePeerConnection(config *webrtc.Configuration) error { 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, -- GitLab From a7f497053e6e3d1486aec3a658a56fb3dc562cf3 Mon Sep 17 00:00:00 2001 From: David Fifield Date: Fri, 2 Aug 2024 02:58:59 +0000 Subject: [PATCH 13/31] 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 2f4b2cf0..260c6581 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -324,11 +324,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 @@ -342,26 +337,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 - } - // 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 + packetConnWrapper := &packetConnWrapper{ + ReadWriter: conn, + remoteAddr: dummyAddr{}, + localAddr: dummyAddr{}, } - 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 70c9e851..a1163a58 100644 --- a/client/lib/webrtc.go +++ b/client/lib/webrtc.go @@ -45,8 +45,7 @@ type WebRTCPeer struct { eventsLogger event.SnowflakeEventReceiver proxy *url.URL - activeTransportMode byte - clientID turbotunnel.ClientID + clientID turbotunnel.ClientID } // Deprecated: Use NewWebRTCPeerWithEventsAndProxy Instead. @@ -180,7 +179,6 @@ func (c *WebRTCPeer) checkForStaleness(timeout time.Duration) { // receive an answer from broker, and wait for data channel to open func (c *WebRTCPeer) connect(config *webrtc.Configuration, broker *BrokerChannel) error { log.Println(c.id, " connecting...") - c.activeTransportMode = 'u' err := c.preparePeerConnection(config) localDescription := c.pc.LocalDescription() c.eventsLogger.OnNewSnowflakeEvent(event.EventOnOfferCreated{ @@ -248,18 +246,13 @@ func (c *WebRTCPeer) preparePeerConnection(config *webrtc.Configuration) error { 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 70ffa16c..e143b662 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) } -- GitLab From 5ae520893fef06818389f1635f03b427926b0aa9 Mon Sep 17 00:00:00 2001 From: David Fifield Date: Fri, 2 Aug 2024 03:33:56 +0000 Subject: [PATCH 14/31] 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 e143b662..0d2e8203 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 } -- GitLab From 3f721ed32f1ccd7363eca27d0a989fae268f1394 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 11 Sep 2024 13:34:12 +0100 Subject: [PATCH 15/31] 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 ca96a24f..0eb8d987 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 } -- GitLab From be7e12aa745d09e8ea39de8b0545f845f9061e9c Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 11 Sep 2024 13:39:16 +0100 Subject: [PATCH 16/31] 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 0eb8d987..eadba2a5 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 260c6581..e7f0baa3 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -337,11 +337,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) -- GitLab From 06f9d9727f608402cf1654f196df038c22e30a12 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 11 Sep 2024 13:51:43 +0100 Subject: [PATCH 17/31] 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 eadba2a5..828557c3 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 { -- GitLab From 9eee1e54abf0cbad8cc0adddd562b374c5be572a Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 11 Sep 2024 14:24:26 +0100 Subject: [PATCH 18/31] 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 828557c3..882dd43a 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 e7f0baa3..9b037961 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -338,7 +338,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) -- GitLab From fc22e472e4578ea6e6030c874712e3db7f0fe384 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 11 Sep 2024 14:51:08 +0100 Subject: [PATCH 19/31] 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 9b037961..15de87c8 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -352,7 +352,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 -- GitLab From 36854ea4d6df1f20d04b84acceb71b5b61a7fceb Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Wed, 11 Sep 2024 15:00:30 +0100 Subject: [PATCH 20/31] 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 15de87c8..0a22cf62 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -352,8 +352,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) -- GitLab From d75525722ecd857d5546ff3be7005095bd4a9c85 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 12 Sep 2024 13:14:22 +0100 Subject: [PATCH 21/31] 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 882dd43a..0db93249 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 { -- GitLab From d4e0189796b80c772c9b2de542fa850fc1d26c29 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 12 Sep 2024 14:22:15 +0100 Subject: [PATCH 22/31] 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 0a22cf62..ebbd027e 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. @@ -167,7 +165,7 @@ func NewSnowflakeClient(config ClientConfig) (*Transport, error) { clientID := turbotunnel.NewClientID() transport := &Transport{ dialer: NewWebRTCDialerWithEventsProxyAndClientID(broker, iceServers, max, eventsLogger, config.CommunicationProxy, clientID), - eventDispatcher: eventsLogger, clientID: clientID, + eventDispatcher: eventsLogger, } return transport, nil @@ -202,7 +200,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 } @@ -323,7 +321,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 -- GitLab From 06573f8f0f4019572dd8f9b1c72b81a299c2c1fb Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 12 Sep 2024 14:23:36 +0100 Subject: [PATCH 23/31] initialize client id with valid value --- client/lib/rendezvous.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/rendezvous.go b/client/lib/rendezvous.go index 26118d09..2d8e1826 100644 --- a/client/lib/rendezvous.go +++ b/client/lib/rendezvous.go @@ -206,7 +206,7 @@ func NewWebRTCDialerWithEvents(broker *BrokerChannel, iceServers []webrtc.ICESer func NewWebRTCDialerWithEventsAndProxy(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int, eventLogger event.SnowflakeEventReceiver, proxy *url.URL, ) *WebRTCDialer { - return NewWebRTCDialerWithEventsProxyAndClientID(broker, iceServers, max, eventLogger, proxy, turbotunnel.ClientID{}) + return NewWebRTCDialerWithEventsProxyAndClientID(broker, iceServers, max, eventLogger, proxy, turbotunnel.NewClientID()) } // NewWebRTCDialerWithEventsProxyAndClientID constructs a new WebRTCDialer. -- GitLab From 9c6f022e29481a4d51c9bdf7e6faa5edfc44f87c Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 12 Sep 2024 14:35:03 +0100 Subject: [PATCH 24/31] 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 2d8e1826..517320c9 100644 --- a/client/lib/rendezvous.go +++ b/client/lib/rendezvous.go @@ -206,11 +206,11 @@ func NewWebRTCDialerWithEvents(broker *BrokerChannel, iceServers []webrtc.ICESer func NewWebRTCDialerWithEventsAndProxy(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int, eventLogger event.SnowflakeEventReceiver, proxy *url.URL, ) *WebRTCDialer { - return NewWebRTCDialerWithEventsProxyAndClientID(broker, iceServers, max, eventLogger, proxy, turbotunnel.NewClientID()) + return newWebRTCDialerWithEventsProxyAndClientID(broker, iceServers, max, eventLogger, proxy, turbotunnel.NewClientID()) } // NewWebRTCDialerWithEventsProxyAndClientID constructs a new WebRTCDialer. -func NewWebRTCDialerWithEventsProxyAndClientID(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int, +func newWebRTCDialerWithEventsProxyAndClientID(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int, eventLogger event.SnowflakeEventReceiver, proxy *url.URL, clientID turbotunnel.ClientID, ) *WebRTCDialer { config := webrtc.Configuration{ diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index ebbd027e..ffd4fc84 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -162,9 +162,8 @@ func NewSnowflakeClient(config ClientConfig) (*Transport, error) { max = config.Max } eventsLogger := event.NewSnowflakeEventDispatcher() - clientID := turbotunnel.NewClientID() transport := &Transport{ - dialer: NewWebRTCDialerWithEventsProxyAndClientID(broker, iceServers, max, eventsLogger, config.CommunicationProxy, clientID), + dialer: NewWebRTCDialerWithEventsAndProxy(broker, iceServers, max, eventsLogger, config.CommunicationProxy), eventDispatcher: eventsLogger, } -- GitLab From e62a3009ecc077764eebe3c4f9f08aaae2ba6ba6 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 17 Sep 2024 12:11:36 +0100 Subject: [PATCH 25/31] Remove WebRTCPeer constructor without client ID --- client/lib/webrtc.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/client/lib/webrtc.go b/client/lib/webrtc.go index a1163a58..da30c23f 100644 --- a/client/lib/webrtc.go +++ b/client/lib/webrtc.go @@ -48,22 +48,23 @@ type WebRTCPeer struct { clientID turbotunnel.ClientID } -// Deprecated: Use NewWebRTCPeerWithEventsAndProxy Instead. -func NewWebRTCPeer( +// Deprecated: Use NewWebRTCPeerWithEventsProxyAndClientID Instead. +func newWebRTCPeer( config *webrtc.Configuration, broker *BrokerChannel, ) (*WebRTCPeer, error) { - return NewWebRTCPeerWithEventsAndProxy(config, broker, nil, nil) + return newWebRTCPeerWithEventsAndProxy(config, broker, nil, nil) } -// Deprecated: Use NewWebRTCPeerWithEventsAndProxy Instead. -func NewWebRTCPeerWithEvents( +// Deprecated: Use NewWebRTCPeerWithEventsProxyAndClientID Instead. +func newWebRTCPeerWithEvents( config *webrtc.Configuration, broker *BrokerChannel, eventsLogger event.SnowflakeEventReceiver, ) (*WebRTCPeer, error) { - return NewWebRTCPeerWithEventsAndProxy(config, broker, eventsLogger, nil) + return newWebRTCPeerWithEventsAndProxy(config, broker, eventsLogger, nil) } -func NewWebRTCPeerWithEventsAndProxy(config *webrtc.Configuration, +// Deprecated: Use NewWebRTCPeerWithEventsProxyAndClientID Instead. +func newWebRTCPeerWithEventsAndProxy(config *webrtc.Configuration, broker *BrokerChannel, eventsLogger event.SnowflakeEventReceiver, proxy *url.URL, ) (*WebRTCPeer, error) { return NewWebRTCPeerWithEventsProxyAndClientID(config, broker, eventsLogger, proxy, turbotunnel.ClientID{}) -- GitLab From b093afc586d949c231b39cdde293beb19077f164 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 17 Sep 2024 12:44:49 +0100 Subject: [PATCH 26/31] set protocol query for connection with server unconditionally --- proxy/lib/snowflake.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proxy/lib/snowflake.go b/proxy/lib/snowflake.go index 95c51fa4..f7858a17 100644 --- a/proxy/lib/snowflake.go +++ b/proxy/lib/snowflake.go @@ -372,7 +372,8 @@ func (sf *SnowflakeProxy) datachannelHandler(conn *webRTCConn, remoteAddr net.Ad log.Printf("no remote address given in websocket") } - if protocol := conn.GetConnectionProtocol(); protocol != "" { + { + protocol := conn.GetConnectionProtocol() q := u.Query() q.Set("protocol", protocol) u.RawQuery = q.Encode() -- GitLab From 2055c82afb212dd367f0808507a1cd220a740dbc Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 17 Sep 2024 15:39:11 +0100 Subject: [PATCH 27/31] 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 f7858a17..382e65a1 100644 --- a/proxy/lib/snowflake.go +++ b/proxy/lib/snowflake.go @@ -451,8 +451,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 7d534f59..8a1693f5 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 } -- GitLab From 98829e546bbf052beac6ce9b6a563b71792877f2 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 17 Sep 2024 16:25:51 +0100 Subject: [PATCH 28/31] 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 da6359e5..9e7a2359 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 +} -- GitLab From 8f64ca0a2d8070ca482ba7a397ed10655c052fde Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Tue, 17 Sep 2024 16:53:26 +0100 Subject: [PATCH 29/31] use protocol field encoder --- client/lib/webrtc.go | 8 +++++++- common/messages/client.go | 2 +- server/lib/http.go | 8 ++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/client/lib/webrtc.go b/client/lib/webrtc.go index da30c23f..14e11fd7 100644 --- a/client/lib/webrtc.go +++ b/client/lib/webrtc.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "errors" "fmt" + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" "io" "log" "net/url" @@ -249,7 +250,12 @@ func (c *WebRTCPeer) preparePeerConnection(config *webrtc.Configuration) error { } 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 9e7a2359..5d1f0d64 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 0d2e8203..6da2d0fd 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 -- GitLab From 842e9b7f55c44a78b146b1881d57007a94d36259 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 19 Sep 2024 11:57:11 +0100 Subject: [PATCH 30/31] 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 ffd4fc84..88936182 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -323,9 +323,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. -- GitLab From d22977742130564923c5205f8197e4259406a3c5 Mon Sep 17 00:00:00 2001 From: Shelikhoo Date: Thu, 19 Sep 2024 12:31:37 +0100 Subject: [PATCH 31/31] 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 8a1693f5..d75578db 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 } -- GitLab