mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-13 20:11:19 -04:00
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
This commit is contained in:
parent
82d7f16bab
commit
ae0643320e
2 changed files with 159 additions and 4 deletions
|
@ -13,6 +13,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -38,6 +39,25 @@ var (
|
||||||
client http.Client
|
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 {
|
type webRTCConn struct {
|
||||||
dc *webrtc.DataChannel
|
dc *webrtc.DataChannel
|
||||||
pc *webrtc.PeerConnection
|
pc *webrtc.PeerConnection
|
||||||
|
@ -64,8 +84,13 @@ func (c *webRTCConn) LocalAddr() net.Addr {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *webRTCConn) RemoteAddr() net.Addr {
|
func (c *webRTCConn) RemoteAddr() net.Addr {
|
||||||
|
//Parse Remote SDP offer and extract client IP
|
||||||
|
clientIP := remoteIPFromSDP(c.pc.RemoteDescription().Sdp)
|
||||||
|
if clientIP == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return &net.IPAddr{clientIP, ""}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *webRTCConn) SetDeadline(t time.Time) error {
|
func (c *webRTCConn) SetDeadline(t time.Time) error {
|
||||||
return fmt.Errorf("SetDeadline not implemented")
|
return fmt.Errorf("SetDeadline not implemented")
|
||||||
|
@ -181,11 +206,31 @@ func CopyLoopTimeout(c1 net.Conn, c2 net.Conn, timeout time.Duration) {
|
||||||
wg.Wait()
|
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 conn.Close()
|
||||||
defer retToken()
|
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 {
|
if err != nil {
|
||||||
log.Printf("error dialing relay: %s", err)
|
log.Printf("error dialing relay: %s", err)
|
||||||
return
|
return
|
||||||
|
@ -237,8 +282,9 @@ func makePeerConnectionFromOffer(sdp *webrtc.SessionDescription, config *webrtc.
|
||||||
panic("short write")
|
panic("short write")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := &webRTCConn{pc: pc, dc: dc, pr: pr}
|
conn := &webRTCConn{pc: pc, dc: dc, pr: pr}
|
||||||
go datachannelHandler(conn)
|
go datachannelHandler(conn, conn.RemoteAddr())
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pc.SetRemoteDescription(sdp)
|
err = pc.SetRemoteDescription(sdp)
|
||||||
|
|
109
proxy-go/webrtc_test.go
Normal file
109
proxy-go/webrtc_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue