Remove local LAN address ICE candidates

Unfortunately, the "public" RTCIceTransportPolicy was removed.

https://developer.mozilla.org/en-US/docs/Web/API/RTCConfiguration#RTCIceTransportPolicy_enum

Trac: 19026
This commit is contained in:
Arlo Breault 2020-01-31 00:17:50 -05:00
parent 28cf70bb44
commit 0fae4ee8ea
4 changed files with 89 additions and 13 deletions

View file

@ -288,7 +288,7 @@ func TestSnowflakeClient(t *testing.T) {
fakeOffer := deserializeSessionDescription(`{"type":"offer","sdp":"test"}`) fakeOffer := deserializeSessionDescription(`{"type":"offer","sdp":"test"}`)
Convey("Construct BrokerChannel with no front domain", func() { 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(b.url, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(b.url.Path, ShouldResemble, "test.broker") So(b.url.Path, ShouldResemble, "test.broker")
@ -296,7 +296,7 @@ func TestSnowflakeClient(t *testing.T) {
}) })
Convey("Construct BrokerChannel *with* front domain", func() { 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(b.url, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(b.url.Path, ShouldResemble, "test.broker") So(b.url.Path, ShouldResemble, "test.broker")
@ -305,7 +305,7 @@ func TestSnowflakeClient(t *testing.T) {
}) })
Convey("BrokerChannel.Negotiate responds with answer", func() { Convey("BrokerChannel.Negotiate responds with answer", func() {
b, err := NewBrokerChannel("test.broker", "", transport) b, err := NewBrokerChannel("test.broker", "", transport, false)
So(err, ShouldBeNil) So(err, ShouldBeNil)
answer, err := b.Negotiate(fakeOffer) answer, err := b.Negotiate(fakeOffer)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -315,7 +315,8 @@ func TestSnowflakeClient(t *testing.T) {
Convey("BrokerChannel.Negotiate fails with 503", func() { Convey("BrokerChannel.Negotiate fails with 503", func() {
b, err := NewBrokerChannel("test.broker", "", b, err := NewBrokerChannel("test.broker", "",
&MockTransport{http.StatusServiceUnavailable, []byte("\n")}) &MockTransport{http.StatusServiceUnavailable, []byte("\n")},
false)
So(err, ShouldBeNil) So(err, ShouldBeNil)
answer, err := b.Negotiate(fakeOffer) answer, err := b.Negotiate(fakeOffer)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
@ -325,7 +326,8 @@ func TestSnowflakeClient(t *testing.T) {
Convey("BrokerChannel.Negotiate fails with 400", func() { Convey("BrokerChannel.Negotiate fails with 400", func() {
b, err := NewBrokerChannel("test.broker", "", b, err := NewBrokerChannel("test.broker", "",
&MockTransport{http.StatusBadRequest, []byte("\n")}) &MockTransport{http.StatusBadRequest, []byte("\n")},
false)
So(err, ShouldBeNil) So(err, ShouldBeNil)
answer, err := b.Negotiate(fakeOffer) answer, err := b.Negotiate(fakeOffer)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
@ -335,7 +337,8 @@ func TestSnowflakeClient(t *testing.T) {
Convey("BrokerChannel.Negotiate fails with large read", func() { Convey("BrokerChannel.Negotiate fails with large read", func() {
b, err := NewBrokerChannel("test.broker", "", b, err := NewBrokerChannel("test.broker", "",
&MockTransport{http.StatusOK, make([]byte, 100001, 100001)}) &MockTransport{http.StatusOK, make([]byte, 100001, 100001)},
false)
So(err, ShouldBeNil) So(err, ShouldBeNil)
answer, err := b.Negotiate(fakeOffer) answer, err := b.Negotiate(fakeOffer)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
@ -345,7 +348,7 @@ func TestSnowflakeClient(t *testing.T) {
Convey("BrokerChannel.Negotiate fails with unexpected error", func() { Convey("BrokerChannel.Negotiate fails with unexpected error", func() {
b, err := NewBrokerChannel("test.broker", "", b, err := NewBrokerChannel("test.broker", "",
&MockTransport{123, []byte("")}) &MockTransport{123, []byte("")}, false)
So(err, ShouldBeNil) So(err, ShouldBeNil)
answer, err := b.Negotiate(fakeOffer) answer, err := b.Negotiate(fakeOffer)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
@ -353,4 +356,22 @@ func TestSnowflakeClient(t *testing.T) {
So(err.Error(), ShouldResemble, BrokerErrorUnexpected) 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)
})
} }

View file

@ -14,9 +14,12 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"net"
"net/http" "net/http"
"net/url" "net/url"
"regexp"
"github.com/pion/sdp"
"github.com/pion/webrtc" "github.com/pion/webrtc"
) )
@ -31,9 +34,10 @@ const (
type BrokerChannel struct { type BrokerChannel struct {
// The Host header to put in the HTTP request (optional and may be // The Host header to put in the HTTP request (optional and may be
// different from the host name in URL). // different from the host name in URL).
Host string Host string
url *url.URL url *url.URL
transport http.RoundTripper // Used to make all requests. transport http.RoundTripper // Used to make all requests.
keepLocalAddresses bool
} }
// We make a copy of DefaultTransport because we want the default Dial // 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: // Construct a new BrokerChannel, where:
// |broker| is the full URL of the facilitating program which assigns proxies // |broker| is the full URL of the facilitating program which assigns proxies
// to clients, and |front| is the option fronting domain. // 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) targetURL, err := url.Parse(broker)
if err != nil { if err != nil {
return nil, err return nil, err
@ -63,6 +67,7 @@ func NewBrokerChannel(broker string, front string, transport http.RoundTripper)
} }
bc.transport = transport bc.transport = transport
bc.keepLocalAddresses = keepLocalAddresses
return bc, nil return bc, nil
} }
@ -76,6 +81,41 @@ func limitedRead(r io.Reader, limit int64) ([]byte, error) {
return p, err 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. // Roundtrip HTTP POST using WebRTC SessionDescriptions.
// //
// Send an SDP offer to the broker, which assigns a proxy and responds // 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) { *webrtc.SessionDescription, error) {
log.Println("Negotiating via BrokerChannel...\nTarget URL: ", log.Println("Negotiating via BrokerChannel...\nTarget URL: ",
bc.Host, "\nFront URL: ", bc.url.Host) 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#RTCIceTransportPolicy_enum
//
// 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. // Suffix with broker's client registration handler.
clientURL := bc.url.ResolveReference(&url.URL{Path: "client"}) clientURL := bc.url.ResolveReference(&url.URL{Path: "client"})
request, err := http.NewRequest("POST", clientURL.String(), data) request, err := http.NewRequest("POST", clientURL.String(), data)

View file

@ -90,6 +90,7 @@ func main() {
frontDomain := flag.String("front", "", "front domain") frontDomain := flag.String("front", "", "front domain")
logFilename := flag.String("log", "", "name of log file") logFilename := flag.String("log", "", "name of log file")
logToStateDir := flag.Bool("logToStateDir", false, "resolve the log file relative to tor's pt state dir") 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, max := flag.Int("max", DefaultSnowflakeCapacity,
"capacity for number of multiplexed WebRTC peers") "capacity for number of multiplexed WebRTC peers")
flag.Parse() flag.Parse()
@ -133,7 +134,7 @@ func main() {
snowflakes := sf.NewPeers(*max) snowflakes := sf.NewPeers(*max)
// Use potentially domain-fronting broker to rendezvous. // 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 { if err != nil {
log.Fatalf("parsing broker URL: %v", err) log.Fatalf("parsing broker URL: %v", err)
} }

View file

@ -3,5 +3,6 @@ DataDirectory datadir
ClientTransportPlugin snowflake exec ./client \ ClientTransportPlugin snowflake exec ./client \
-url http://localhost:8080/ \ -url http://localhost:8080/ \
-keepLocalAddresses
Bridge snowflake 0.0.3.0:1 Bridge snowflake 0.0.3.0:1