commit ae0643320e30fb1d9b825c779fdfa0ae2d2ac190 Author: Hooman hoomanm@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@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@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) + } + } +}