[tor-commits] [snowflake/master] Pass client IP from proxy-go to server by parsing SDP

dcf at torproject.org dcf at torproject.org
Sat Oct 14 19:12:34 UTC 2017


commit ae0643320e30fb1d9b825c779fdfa0ae2d2ac190
Author: Hooman <hoomanm at princeton.edu>
Date:   Thu Jul 20 16:39:28 2017 -0700

    Pass client IP from proxy-go to server by parsing SDP
    
    Call conn.RemoteAddr() before entering the datachannelHandler goroutine.
    This is a workaround for the hang described at
    https://bugs.torproject.org/18628#comment:8
---
 proxy-go/snowflake.go   |  54 ++++++++++++++++++++++--
 proxy-go/webrtc_test.go | 109 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 159 insertions(+), 4 deletions(-)

diff --git a/proxy-go/snowflake.go b/proxy-go/snowflake.go
index 33becb3..1273474 100644
--- a/proxy-go/snowflake.go
+++ b/proxy-go/snowflake.go
@@ -13,6 +13,7 @@ import (
 	"net/http"
 	"net/url"
 	"os"
+	"regexp"
 	"strings"
 	"sync"
 	"time"
@@ -38,6 +39,25 @@ var (
 	client http.Client
 )
 
+var remoteIPPatterns = []*regexp.Regexp{
+	/* IPv4 */
+	regexp.MustCompile(`(?m)^c=IN IP4 ([\d.]+)(?:(?:\/\d+)?\/\d+)?(:? |\r?\n)`),
+	/* IPv6 */
+	regexp.MustCompile(`(?m)^c=IN IP6 ([0-9A-Fa-f:.]+)(?:\/\d+)?(:? |\r?\n)`),
+}
+
+// https://tools.ietf.org/html/rfc4566#section-5.7
+func remoteIPFromSDP(sdp string) net.IP {
+	for _, pattern := range remoteIPPatterns {
+		m := pattern.FindStringSubmatch(sdp)
+		if m != nil {
+			// Ignore parsing errors, ParseIP returns nil.
+			return net.ParseIP(m[1])
+		}
+	}
+	return nil
+}
+
 type webRTCConn struct {
 	dc *webrtc.DataChannel
 	pc *webrtc.PeerConnection
@@ -64,7 +84,12 @@ func (c *webRTCConn) LocalAddr() net.Addr {
 }
 
 func (c *webRTCConn) RemoteAddr() net.Addr {
-	return nil
+	//Parse Remote SDP offer and extract client IP
+	clientIP := remoteIPFromSDP(c.pc.RemoteDescription().Sdp)
+	if clientIP == nil {
+		return nil
+	}
+	return &net.IPAddr{clientIP, ""}
 }
 
 func (c *webRTCConn) SetDeadline(t time.Time) error {
@@ -181,11 +206,31 @@ func CopyLoopTimeout(c1 net.Conn, c2 net.Conn, timeout time.Duration) {
 	wg.Wait()
 }
 
-func datachannelHandler(conn *webRTCConn) {
+// We pass conn.RemoteAddr() as an additional parameter, rather than calling
+// conn.RemoteAddr() inside this function, as a workaround for a hang that
+// otherwise occurs inside of conn.pc.RemoteDescription() (called by
+// RemoteAddr). https://bugs.torproject.org/18628#comment:8
+func datachannelHandler(conn *webRTCConn, remoteAddr net.Addr) {
 	defer conn.Close()
 	defer retToken()
 
-	wsConn, err := websocket.Dial(relayURL, "", relayURL)
+	u, err := url.Parse(relayURL)
+	if err != nil {
+		log.Fatalf("invalid relay url: %s", err)
+	}
+
+	// Retrieve client IP address
+	if remoteAddr != nil {
+		// Encode client IP address in relay URL
+		q := u.Query()
+		clientIP := remoteAddr.String()
+		q.Set("client_ip", clientIP)
+		u.RawQuery = q.Encode()
+	} else {
+		log.Printf("no remote address given in websocket")
+	}
+
+	wsConn, err := websocket.Dial(u.String(), "", relayURL)
 	if err != nil {
 		log.Printf("error dialing relay: %s", err)
 		return
@@ -237,8 +282,9 @@ func makePeerConnectionFromOffer(sdp *webrtc.SessionDescription, config *webrtc.
 				panic("short write")
 			}
 		}
+
 		conn := &webRTCConn{pc: pc, dc: dc, pr: pr}
-		go datachannelHandler(conn)
+		go datachannelHandler(conn, conn.RemoteAddr())
 	}
 
 	err = pc.SetRemoteDescription(sdp)
diff --git a/proxy-go/webrtc_test.go b/proxy-go/webrtc_test.go
new file mode 100644
index 0000000..2413207
--- /dev/null
+++ b/proxy-go/webrtc_test.go
@@ -0,0 +1,109 @@
+package main
+
+import (
+	"net"
+	"strings"
+	"testing"
+)
+
+func TestRemoteIPFromSDP(t *testing.T) {
+	tests := []struct {
+		sdp      string
+		expected net.IP
+	}{
+		// https://tools.ietf.org/html/rfc4566#section-5
+		{`v=0
+o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5
+s=SDP Seminar
+i=A Seminar on the session description protocol
+u=http://www.example.com/seminars/sdp.pdf
+e=j.doe at example.com (Jane Doe)
+c=IN IP4 224.2.17.12/127
+t=2873397496 2873404696
+a=recvonly
+m=audio 49170 RTP/AVP 0
+m=video 51372 RTP/AVP 99
+a=rtpmap:99 h263-1998/90000
+`, net.ParseIP("224.2.17.12")},
+		// Missing c= line
+		{`v=0
+o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5
+s=SDP Seminar
+i=A Seminar on the session description protocol
+u=http://www.example.com/seminars/sdp.pdf
+e=j.doe at example.com (Jane Doe)
+t=2873397496 2873404696
+a=recvonly
+m=audio 49170 RTP/AVP 0
+m=video 51372 RTP/AVP 99
+a=rtpmap:99 h263-1998/90000
+`, nil},
+		// Single line, IP address only
+		{`c=IN IP4 224.2.1.1
+`, net.ParseIP("224.2.1.1")},
+		// Same, with TTL
+		{`c=IN IP4 224.2.1.1/127
+`, net.ParseIP("224.2.1.1")},
+		// Same, with TTL and multicast addresses
+		{`c=IN IP4 224.2.1.1/127/3
+`, net.ParseIP("224.2.1.1")},
+		// IPv6, address only
+		{`c=IN IP6 FF15::101
+`, net.ParseIP("ff15::101")},
+		// Same, with multicast addresses
+		{`c=IN IP6 FF15::101/3
+`, net.ParseIP("ff15::101")},
+		// Multiple c= lines
+		{`c=IN IP4 1.2.3.4
+c=IN IP4 5.6.7.8
+`, net.ParseIP("1.2.3.4")},
+		// Modified from SDP sent by snowflake-client.
+		{`v=0
+o=- 7860378660295630295 2 IN IP4 127.0.0.1
+s=-
+t=0 0
+a=group:BUNDLE data
+a=msid-semantic: WMS
+m=application 54653 DTLS/SCTP 5000
+c=IN IP4 1.2.3.4
+a=candidate:3581707038 1 udp 2122260223 192.168.0.1 54653 typ host generation 0 network-id 1 network-cost 50
+a=candidate:2617212910 1 tcp 1518280447 192.168.0.1 59673 typ host tcptype passive generation 0 network-id 1 network-cost 50
+a=candidate:2082671819 1 udp 1686052607 1.2.3.4 54653 typ srflx raddr 192.168.0.1 rport 54653 generation 0 network-id 1 network-cost 50
+a=ice-ufrag:IBdf
+a=ice-pwd:G3lTrrC9gmhQx481AowtkhYz
+a=fingerprint:sha-256 53:F8:84:D9:3C:1F:A0:44:AA:D6:3C:65:80:D3:CB:6F:23:90:17:41:06:F9:9C:10:D8:48:4A:A8:B6:FA:14:A1
+a=setup:actpass
+a=mid:data
+a=sctpmap:5000 webrtc-datachannel 1024
+`, net.ParseIP("1.2.3.4")},
+		// Improper character within IPv4
+		{`c=IN IP4 224.2z.1.1
+`, nil},
+		// Improper character within IPv6
+		{`c=IN IP6 ff15:g::101
+`, nil},
+		// Bogus "IP7" addrtype
+		{`c=IN IP7 1.2.3.4
+`, nil},
+	}
+
+	for _, test := range tests {
+		// https://tools.ietf.org/html/rfc4566#section-5: "The sequence
+		// CRLF (0x0d0a) is used to end a record, although parsers
+		// SHOULD be tolerant and also accept records terminated with a
+		// single newline character." We represent the test cases with
+		// LF line endings for convenience, and test them both that way
+		// and with CRLF line endings.
+		lfSDP := test.sdp
+		crlfSDP := strings.Replace(lfSDP, "\n", "\r\n", -1)
+
+		ip := remoteIPFromSDP(lfSDP)
+		if !ip.Equal(test.expected) {
+			t.Errorf("expected %q, got %q from %q", test.expected, ip, lfSDP)
+		}
+		ip = remoteIPFromSDP(crlfSDP)
+		if !ip.Equal(test.expected) {
+			t.Errorf("expected %q, got %q from %q", test.expected, ip, crlfSDP)
+		}
+	}
+}





More information about the tor-commits mailing list