commit 0fae4ee8ea487c3b4384217e193e5b9a9088e7de Author: Arlo Breault arlolra@gmail.com Date: Fri Jan 31 00:17:50 2020 -0500
Remove local LAN address ICE candidates
Unfortunately, the "public" RTCIceTransportPolicy was removed.
https://developer.mozilla.org/en-US/docs/Web/API/RTCConfiguration#RTCIceTran...
Trac: 19026 --- client/lib/lib_test.go | 35 +++++++++++++++++++++------ client/lib/rendezvous.go | 63 ++++++++++++++++++++++++++++++++++++++++++++---- client/snowflake.go | 3 ++- client/torrc-localhost | 1 + 4 files changed, 89 insertions(+), 13 deletions(-)
diff --git a/client/lib/lib_test.go b/client/lib/lib_test.go index 91a9809..adfc9ec 100644 --- a/client/lib/lib_test.go +++ b/client/lib/lib_test.go @@ -288,7 +288,7 @@ func TestSnowflakeClient(t *testing.T) { fakeOffer := deserializeSessionDescription(`{"type":"offer","sdp":"test"}`)
Convey("Construct BrokerChannel with no front domain", func() { - b, err := NewBrokerChannel("test.broker", "", transport) + b, err := NewBrokerChannel("test.broker", "", transport, false) So(b.url, ShouldNotBeNil) So(err, ShouldBeNil) So(b.url.Path, ShouldResemble, "test.broker") @@ -296,7 +296,7 @@ func TestSnowflakeClient(t *testing.T) { })
Convey("Construct BrokerChannel *with* front domain", func() { - b, err := NewBrokerChannel("test.broker", "front", transport) + b, err := NewBrokerChannel("test.broker", "front", transport, false) So(b.url, ShouldNotBeNil) So(err, ShouldBeNil) So(b.url.Path, ShouldResemble, "test.broker") @@ -305,7 +305,7 @@ func TestSnowflakeClient(t *testing.T) { })
Convey("BrokerChannel.Negotiate responds with answer", func() { - b, err := NewBrokerChannel("test.broker", "", transport) + b, err := NewBrokerChannel("test.broker", "", transport, false) So(err, ShouldBeNil) answer, err := b.Negotiate(fakeOffer) So(err, ShouldBeNil) @@ -315,7 +315,8 @@ func TestSnowflakeClient(t *testing.T) {
Convey("BrokerChannel.Negotiate fails with 503", func() { b, err := NewBrokerChannel("test.broker", "", - &MockTransport{http.StatusServiceUnavailable, []byte("\n")}) + &MockTransport{http.StatusServiceUnavailable, []byte("\n")}, + false) So(err, ShouldBeNil) answer, err := b.Negotiate(fakeOffer) So(err, ShouldNotBeNil) @@ -325,7 +326,8 @@ func TestSnowflakeClient(t *testing.T) {
Convey("BrokerChannel.Negotiate fails with 400", func() { b, err := NewBrokerChannel("test.broker", "", - &MockTransport{http.StatusBadRequest, []byte("\n")}) + &MockTransport{http.StatusBadRequest, []byte("\n")}, + false) So(err, ShouldBeNil) answer, err := b.Negotiate(fakeOffer) So(err, ShouldNotBeNil) @@ -335,7 +337,8 @@ func TestSnowflakeClient(t *testing.T) {
Convey("BrokerChannel.Negotiate fails with large read", func() { b, err := NewBrokerChannel("test.broker", "", - &MockTransport{http.StatusOK, make([]byte, 100001, 100001)}) + &MockTransport{http.StatusOK, make([]byte, 100001, 100001)}, + false) So(err, ShouldBeNil) answer, err := b.Negotiate(fakeOffer) So(err, ShouldNotBeNil) @@ -345,7 +348,7 @@ func TestSnowflakeClient(t *testing.T) {
Convey("BrokerChannel.Negotiate fails with unexpected error", func() { b, err := NewBrokerChannel("test.broker", "", - &MockTransport{123, []byte("")}) + &MockTransport{123, []byte("")}, false) So(err, ShouldBeNil) answer, err := b.Negotiate(fakeOffer) So(err, ShouldNotBeNil) @@ -353,4 +356,22 @@ func TestSnowflakeClient(t *testing.T) { So(err.Error(), ShouldResemble, BrokerErrorUnexpected) }) }) + + Convey("Strip", t, func() { + const offerStart = `{"type":"offer","sdp":"v=0\r\no=- 4358805017720277108 2 IN IP4 8.8.8.8\r\ns=-\r\nt=0 0\r\na=group:BUNDLE data\r\na=msid-semantic: WMS\r\nm=application 56688 DTLS/SCTP 5000\r\nc=IN IP4 8.8.8.8\r\n` + const goodCandidate = `a=candidate:3769337065 1 udp 2122260223 8.8.8.8 56688 typ host generation 0 network-id 1 network-cost 50\r\n` + const offerEnd = `a=ice-ufrag:aMAZ\r\na=ice-pwd:jcHb08Jjgrazp2dzjdrvPPvV\r\na=ice-options:trickle\r\na=fingerprint:sha-256 C8:88:EE:B9:E7:02:2E:21:37:ED:7A:D1:EB:2B:A3:15:A2:3B:5B:1C:3D:D4:D5:1F:06:CF:52:40:03:F8:DD:66\r\na=setup:actpass\r\na=mid:data\r\na=sctpmap:5000 webrtc-datachannel 1024\r\n"}` + + offer := offerStart + goodCandidate + + `a=candidate:3769337065 1 udp 2122260223 192.168.0.100 56688 typ host generation 0 network-id 1 network-cost 50\r\n` + // IsLocal IPv4 + `a=candidate:3769337065 1 udp 2122260223 fdf8:f53b:82e4::53 56688 typ host generation 0 network-id 1 network-cost 50\r\n` + // IsLocal IPv6 + `a=candidate:3769337065 1 udp 2122260223 0.0.0.0 56688 typ host generation 0 network-id 1 network-cost 50\r\n` + // IsUnspecified IPv4 + `a=candidate:3769337065 1 udp 2122260223 :: 56688 typ host generation 0 network-id 1 network-cost 50\r\n` + // IsUnspecified IPv6 + `a=candidate:3769337065 1 udp 2122260223 127.0.0.1 56688 typ host generation 0 network-id 1 network-cost 50\r\n` + // IsLoopback IPv4 + `a=candidate:3769337065 1 udp 2122260223 ::1 56688 typ host generation 0 network-id 1 network-cost 50\r\n` + // IsLoopback IPv6 + offerEnd + + So(stripLocalAddresses(offer), ShouldEqual, offerStart+goodCandidate+offerEnd) + }) + } diff --git a/client/lib/rendezvous.go b/client/lib/rendezvous.go index d117ebc..bd8ff00 100644 --- a/client/lib/rendezvous.go +++ b/client/lib/rendezvous.go @@ -14,9 +14,12 @@ import ( "io" "io/ioutil" "log" + "net" "net/http" "net/url" + "regexp"
+ "github.com/pion/sdp" "github.com/pion/webrtc" )
@@ -31,9 +34,10 @@ const ( type BrokerChannel struct { // The Host header to put in the HTTP request (optional and may be // different from the host name in URL). - Host string - url *url.URL - transport http.RoundTripper // Used to make all requests. + Host string + url *url.URL + transport http.RoundTripper // Used to make all requests. + keepLocalAddresses bool }
// We make a copy of DefaultTransport because we want the default Dial @@ -48,7 +52,7 @@ func CreateBrokerTransport() http.RoundTripper { // Construct a new BrokerChannel, where: // |broker| is the full URL of the facilitating program which assigns proxies // to clients, and |front| is the option fronting domain. -func NewBrokerChannel(broker string, front string, transport http.RoundTripper) (*BrokerChannel, error) { +func NewBrokerChannel(broker string, front string, transport http.RoundTripper, keepLocalAddresses bool) (*BrokerChannel, error) { targetURL, err := url.Parse(broker) if err != nil { return nil, err @@ -63,6 +67,7 @@ func NewBrokerChannel(broker string, front string, transport http.RoundTripper) }
bc.transport = transport + bc.keepLocalAddresses = keepLocalAddresses return bc, nil }
@@ -76,6 +81,41 @@ func limitedRead(r io.Reader, limit int64) ([]byte, error) { return p, err }
+// Stolen from https://github.com/golang/go/pull/30278 +func IsLocal(ip net.IP) bool { + if ip4 := ip.To4(); ip4 != nil { + // Local IPv4 addresses are defined in https://tools.ietf.org/html/rfc1918 + return ip4[0] == 10 || + (ip4[0] == 172 && ip4[1]&0xf0 == 16) || + (ip4[0] == 192 && ip4[1] == 168) + } + // Local IPv6 addresses are defined in https://tools.ietf.org/html/rfc4193 + return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc +} + +// Removes local LAN address ICE candidates +func stripLocalAddresses(str string) string { + re := regexp.MustCompile(`a=candidate:.*?\r\n`) + return re.ReplaceAllStringFunc(str, func(s string) string { + t := s[len("a=candidate:") : len(s)-len("\r\n")] + var ice sdp.ICECandidate + err := ice.Unmarshal(t) + if err != nil { + return s + } + if ice.Typ == "host" { + ip := net.ParseIP(ice.Address) + if ip == nil { + return s + } + if IsLocal(ip) || ip.IsUnspecified() || ip.IsLoopback() { + return "" + } + } + return s + }) +} + // Roundtrip HTTP POST using WebRTC SessionDescriptions. // // Send an SDP offer to the broker, which assigns a proxy and responds @@ -84,7 +124,20 @@ func (bc *BrokerChannel) Negotiate(offer *webrtc.SessionDescription) ( *webrtc.SessionDescription, error) { log.Println("Negotiating via BrokerChannel...\nTarget URL: ", bc.Host, "\nFront URL: ", bc.url.Host) - data := bytes.NewReader([]byte(serializeSessionDescription(offer))) + str := serializeSessionDescription(offer) + // Ideally, we could specify an `RTCIceTransportPolicy` that would handle + // this for us. However, "public" was removed from the draft spec. + // See https://developer.mozilla.org/en-US/docs/Web/API/RTCConfiguration#RTCIceTran... + // + // FIXME: We are stripping local addresses from the JSON serialized string, + // which is expedient but unsatisfying. We could advocate upstream to + // implement a non-standard ICE transport policy, or to somehow alter + // APIs to avoid adding the undesirable candidates or a method to filter + // them from the marshalled session description. + if !bc.keepLocalAddresses { + str = stripLocalAddresses(str) + } + data := bytes.NewReader([]byte(str)) // Suffix with broker's client registration handler. clientURL := bc.url.ResolveReference(&url.URL{Path: "client"}) request, err := http.NewRequest("POST", clientURL.String(), data) diff --git a/client/snowflake.go b/client/snowflake.go index edcbd4a..7cf8a9d 100644 --- a/client/snowflake.go +++ b/client/snowflake.go @@ -90,6 +90,7 @@ func main() { frontDomain := flag.String("front", "", "front domain") logFilename := flag.String("log", "", "name of log file") logToStateDir := flag.Bool("logToStateDir", false, "resolve the log file relative to tor's pt state dir") + keepLocalAddresses := flag.Bool("keepLocalAddresses", false, "keep local LAN address ICE candidates") max := flag.Int("max", DefaultSnowflakeCapacity, "capacity for number of multiplexed WebRTC peers") flag.Parse() @@ -133,7 +134,7 @@ func main() { snowflakes := sf.NewPeers(*max)
// Use potentially domain-fronting broker to rendezvous. - broker, err := sf.NewBrokerChannel(*brokerURL, *frontDomain, sf.CreateBrokerTransport()) + broker, err := sf.NewBrokerChannel(*brokerURL, *frontDomain, sf.CreateBrokerTransport(), *keepLocalAddresses) if err != nil { log.Fatalf("parsing broker URL: %v", err) } diff --git a/client/torrc-localhost b/client/torrc-localhost index 7d539fb..9afb033 100644 --- a/client/torrc-localhost +++ b/client/torrc-localhost @@ -3,5 +3,6 @@ DataDirectory datadir
ClientTransportPlugin snowflake exec ./client \ -url http://localhost:8080/ \ +-keepLocalAddresses
Bridge snowflake 0.0.3.0:1
tor-commits@lists.torproject.org