diff options
author | Cecylia Bocovich <cohosh@torproject.org> | 2023-09-23 10:46:46 -0400 |
---|---|---|
committer | Cecylia Bocovich <cohosh@torproject.org> | 2023-10-05 17:51:56 -0400 |
commit | 9fdfb3d1b53e9113422a7a2816b2a9af4450b4ac (patch) | |
tree | 786e2832356f5653f2da68699eeab692c8959928 | |
parent | 5cdf52c8138c691d25a37e58b521ddc57f1faa97 (diff) | |
download | snowflake-9fdfb3d1b53e9113422a7a2816b2a9af4450b4ac.tar.gz snowflake-9fdfb3d1b53e9113422a7a2816b2a9af4450b4ac.zip |
Randomly select front domain from comma-separated list
This commmit changes the command-line and Bridge line arguments to take
a comma-separated list of front domains. The change is backwards
compatible with old Bridge and ClientTransportPlugin lines. At
rendezvous time, a front domain will be randomly chosen from the list.
-rw-r--r-- | client/lib/rendezvous.go | 8 | ||||
-rw-r--r-- | client/lib/rendezvous_ampcache.go | 20 | ||||
-rw-r--r-- | client/lib/rendezvous_http.go | 20 | ||||
-rw-r--r-- | client/lib/rendezvous_test.go | 44 | ||||
-rw-r--r-- | client/lib/snowflake.go | 10 | ||||
-rw-r--r-- | client/snowflake.go | 7 | ||||
-rw-r--r-- | client/torrc | 4 |
7 files changed, 65 insertions, 48 deletions
diff --git a/client/lib/rendezvous.go b/client/lib/rendezvous.go index eeb4638..ea6ab39 100644 --- a/client/lib/rendezvous.go +++ b/client/lib/rendezvous.go @@ -64,8 +64,8 @@ func createBrokerTransport() http.RoundTripper { func newBrokerChannelFromConfig(config ClientConfig) (*BrokerChannel, error) { log.Println("Rendezvous using Broker at:", config.BrokerURL) - if config.FrontDomain != "" { - log.Println("Domain fronting using:", config.FrontDomain) + if len(config.FrontDomains) != 0 { + log.Printf("Domain fronting using a randomly selected domain from: %v", config.FrontDomains) } brokerTransport := createBrokerTransport() @@ -86,11 +86,11 @@ func newBrokerChannelFromConfig(config ClientConfig) (*BrokerChannel, error) { if config.AmpCacheURL != "" { log.Println("Through AMP cache at:", config.AmpCacheURL) rendezvous, err = newAMPCacheRendezvous( - config.BrokerURL, config.AmpCacheURL, config.FrontDomain, + config.BrokerURL, config.AmpCacheURL, config.FrontDomains, brokerTransport) } else { rendezvous, err = newHTTPRendezvous( - config.BrokerURL, config.FrontDomain, brokerTransport) + config.BrokerURL, config.FrontDomains, brokerTransport) } if err != nil { return nil, err diff --git a/client/lib/rendezvous_ampcache.go b/client/lib/rendezvous_ampcache.go index 593904e..b6fa2c7 100644 --- a/client/lib/rendezvous_ampcache.go +++ b/client/lib/rendezvous_ampcache.go @@ -5,8 +5,10 @@ import ( "io" "io/ioutil" "log" + "math/rand" "net/http" "net/url" + "time" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/amp" ) @@ -17,7 +19,7 @@ import ( type ampCacheRendezvous struct { brokerURL *url.URL cacheURL *url.URL // Optional AMP cache URL. - front string // Optional front domain to replace url.Host in requests. + fronts []string // Optional front domains to replace url.Host in requests. transport http.RoundTripper // Used to make all requests. } @@ -25,7 +27,7 @@ type ampCacheRendezvous struct { // broker at the given URL, optionally proxying through an AMP cache, and with // an optional front domain. transport is the http.RoundTripper used to make all // requests. -func newAMPCacheRendezvous(broker, cache, front string, transport http.RoundTripper) (*ampCacheRendezvous, error) { +func newAMPCacheRendezvous(broker, cache string, fronts []string, transport http.RoundTripper) (*ampCacheRendezvous, error) { brokerURL, err := url.Parse(broker) if err != nil { return nil, err @@ -41,7 +43,7 @@ func newAMPCacheRendezvous(broker, cache, front string, transport http.RoundTrip return &CacheRendezvous{ brokerURL: brokerURL, cacheURL: cacheURL, - front: front, + fronts: fronts, transport: transport, }, nil } @@ -50,7 +52,6 @@ func (r *ampCacheRendezvous) Exchange(encPollReq []byte) ([]byte, error) { log.Println("Negotiating via AMP cache rendezvous...") log.Println("Broker URL:", r.brokerURL) log.Println("AMP cache URL:", r.cacheURL) - log.Println("Front domain:", r.front) // We cannot POST a body through an AMP cache, so instead we GET and // encode the client poll request message into the URL. @@ -72,11 +73,14 @@ func (r *ampCacheRendezvous) Exchange(encPollReq []byte) ([]byte, error) { return nil, err } - if r.front != "" { - // Do domain fronting. Replace the domain in the URL's with the - // front, and store the original domain the HTTP Host header. + if len(r.fronts) != 0 { + // Do domain fronting. Replace the domain in the URL's with a randomly + // selected front, and store the original domain the HTTP Host header. + rand.Seed(time.Now().UnixNano()) + front := r.fronts[rand.Intn(len(r.fronts))] + log.Println("Front domain:", front) req.Host = req.URL.Host - req.URL.Host = r.front + req.URL.Host = front } resp, err := r.transport.RoundTrip(req) diff --git a/client/lib/rendezvous_http.go b/client/lib/rendezvous_http.go index fd80e7f..26733bb 100644 --- a/client/lib/rendezvous_http.go +++ b/client/lib/rendezvous_http.go @@ -6,29 +6,31 @@ import ( "io" "io/ioutil" "log" + "math/rand" "net/http" "net/url" + "time" ) // httpRendezvous is a RendezvousMethod that communicates with the .../client // route of the broker over HTTP or HTTPS, with optional domain fronting. type httpRendezvous struct { brokerURL *url.URL - front string // Optional front domain to replace url.Host in requests. + fronts []string // Optional front domain to replace url.Host in requests. transport http.RoundTripper // Used to make all requests. } // newHTTPRendezvous creates a new httpRendezvous that contacts the broker at // the given URL, with an optional front domain. transport is the // http.RoundTripper used to make all requests. -func newHTTPRendezvous(broker, front string, transport http.RoundTripper) (*httpRendezvous, error) { +func newHTTPRendezvous(broker string, fronts []string, transport http.RoundTripper) (*httpRendezvous, error) { brokerURL, err := url.Parse(broker) if err != nil { return nil, err } return &httpRendezvous{ brokerURL: brokerURL, - front: front, + fronts: fronts, transport: transport, }, nil } @@ -36,7 +38,6 @@ func newHTTPRendezvous(broker, front string, transport http.RoundTripper) (*http func (r *httpRendezvous) Exchange(encPollReq []byte) ([]byte, error) { log.Println("Negotiating via HTTP rendezvous...") log.Println("Target URL: ", r.brokerURL.Host) - log.Println("Front URL: ", r.front) // Suffix the path with the broker's client registration handler. reqURL := r.brokerURL.ResolveReference(&url.URL{Path: "client"}) @@ -45,11 +46,14 @@ func (r *httpRendezvous) Exchange(encPollReq []byte) ([]byte, error) { return nil, err } - if r.front != "" { - // Do domain fronting. Replace the domain in the URL's with the - // front, and store the original domain the HTTP Host header. + if len(r.fronts) != 0 { + // Do domain fronting. Replace the domain in the URL's with a randomly + // selected front, and store the original domain the HTTP Host header. + rand.Seed(time.Now().UnixNano()) + front := r.fronts[rand.Intn(len(r.fronts))] + log.Println("Front URL: ", front) req.Host = req.URL.Host - req.URL.Host = r.front + req.URL.Host = front } resp, err := r.transport.RoundTrip(req) diff --git a/client/lib/rendezvous_test.go b/client/lib/rendezvous_test.go index 918d723..227d43c 100644 --- a/client/lib/rendezvous_test.go +++ b/client/lib/rendezvous_test.go @@ -71,21 +71,21 @@ func TestHTTPRendezvous(t *testing.T) { Convey("HTTP rendezvous", t, func() { Convey("Construct httpRendezvous with no front domain", func() { transport := &mockTransport{http.StatusOK, []byte{}} - rend, err := newHTTPRendezvous("http://test.broker", "", transport) + rend, err := newHTTPRendezvous("http://test.broker", []string{}, transport) So(err, ShouldBeNil) So(rend.brokerURL, ShouldNotBeNil) So(rend.brokerURL.Host, ShouldResemble, "test.broker") - So(rend.front, ShouldResemble, "") + So(rend.fronts, ShouldEqual, []string{}) So(rend.transport, ShouldEqual, transport) }) Convey("Construct httpRendezvous *with* front domain", func() { transport := &mockTransport{http.StatusOK, []byte{}} - rend, err := newHTTPRendezvous("http://test.broker", "front", transport) + rend, err := newHTTPRendezvous("http://test.broker", []string{"front"}, transport) So(err, ShouldBeNil) So(rend.brokerURL, ShouldNotBeNil) So(rend.brokerURL.Host, ShouldResemble, "test.broker") - So(rend.front, ShouldResemble, "front") + So(rend.fronts, ShouldContain, "front") So(rend.transport, ShouldEqual, transport) }) @@ -94,7 +94,7 @@ func TestHTTPRendezvous(t *testing.T) { `{"answer": "{\"type\":\"answer\",\"sdp\":\"fake\"}" }`, "", ) - rend, err := newHTTPRendezvous("http://test.broker", "", + rend, err := newHTTPRendezvous("http://test.broker", []string{}, &mockTransport{http.StatusOK, fakeEncPollResp}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) @@ -107,7 +107,7 @@ func TestHTTPRendezvous(t *testing.T) { "", `{"error": "no snowflake proxies currently available"}`, ) - rend, err := newHTTPRendezvous("http://test.broker", "", + rend, err := newHTTPRendezvous("http://test.broker", []string{}, &mockTransport{http.StatusOK, fakeEncPollResp}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) @@ -116,7 +116,7 @@ func TestHTTPRendezvous(t *testing.T) { }) Convey("httpRendezvous.Exchange fails with unexpected HTTP status code", func() { - rend, err := newHTTPRendezvous("http://test.broker", "", + rend, err := newHTTPRendezvous("http://test.broker", []string{}, &mockTransport{http.StatusInternalServerError, []byte{}}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) @@ -127,7 +127,7 @@ func TestHTTPRendezvous(t *testing.T) { Convey("httpRendezvous.Exchange fails with error", func() { transportErr := errors.New("error") - rend, err := newHTTPRendezvous("http://test.broker", "", + rend, err := newHTTPRendezvous("http://test.broker", []string{}, &errorTransport{err: transportErr}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) @@ -136,7 +136,7 @@ func TestHTTPRendezvous(t *testing.T) { }) Convey("httpRendezvous.Exchange fails with large read", func() { - rend, err := newHTTPRendezvous("http://test.broker", "", + rend, err := newHTTPRendezvous("http://test.broker", []string{}, &mockTransport{http.StatusOK, make([]byte, readLimit+1)}) So(err, ShouldBeNil) _, err = rend.Exchange(fakeEncPollReq) @@ -166,47 +166,47 @@ func TestAMPCacheRendezvous(t *testing.T) { Convey("AMP cache rendezvous", t, func() { Convey("Construct ampCacheRendezvous with no cache and no front domain", func() { transport := &mockTransport{http.StatusOK, []byte{}} - rend, err := newAMPCacheRendezvous("http://test.broker", "", "", transport) + rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{}, transport) So(err, ShouldBeNil) So(rend.brokerURL, ShouldNotBeNil) So(rend.brokerURL.String(), ShouldResemble, "http://test.broker") So(rend.cacheURL, ShouldBeNil) - So(rend.front, ShouldResemble, "") + So(rend.fronts, ShouldResemble, []string{}) So(rend.transport, ShouldEqual, transport) }) Convey("Construct ampCacheRendezvous with cache and no front domain", func() { transport := &mockTransport{http.StatusOK, []byte{}} - rend, err := newAMPCacheRendezvous("http://test.broker", "https://amp.cache/", "", transport) + rend, err := newAMPCacheRendezvous("http://test.broker", "https://amp.cache/", []string{}, transport) So(err, ShouldBeNil) So(rend.brokerURL, ShouldNotBeNil) So(rend.brokerURL.String(), ShouldResemble, "http://test.broker") So(rend.cacheURL, ShouldNotBeNil) So(rend.cacheURL.String(), ShouldResemble, "https://amp.cache/") - So(rend.front, ShouldResemble, "") + So(rend.fronts, ShouldResemble, []string{}) So(rend.transport, ShouldEqual, transport) }) Convey("Construct ampCacheRendezvous with no cache and front domain", func() { transport := &mockTransport{http.StatusOK, []byte{}} - rend, err := newAMPCacheRendezvous("http://test.broker", "", "front", transport) + rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{"front"}, transport) So(err, ShouldBeNil) So(rend.brokerURL, ShouldNotBeNil) So(rend.brokerURL.String(), ShouldResemble, "http://test.broker") So(rend.cacheURL, ShouldBeNil) - So(rend.front, ShouldResemble, "front") + So(rend.fronts, ShouldContain, "front") So(rend.transport, ShouldEqual, transport) }) Convey("Construct ampCacheRendezvous with cache and front domain", func() { transport := &mockTransport{http.StatusOK, []byte{}} - rend, err := newAMPCacheRendezvous("http://test.broker", "https://amp.cache/", "front", transport) + rend, err := newAMPCacheRendezvous("http://test.broker", "https://amp.cache/", []string{"front"}, transport) So(err, ShouldBeNil) So(rend.brokerURL, ShouldNotBeNil) So(rend.brokerURL.String(), ShouldResemble, "http://test.broker") So(rend.cacheURL, ShouldNotBeNil) So(rend.cacheURL.String(), ShouldResemble, "https://amp.cache/") - So(rend.front, ShouldResemble, "front") + So(rend.fronts, ShouldContain, "front") So(rend.transport, ShouldEqual, transport) }) @@ -215,7 +215,7 @@ func TestAMPCacheRendezvous(t *testing.T) { `{"answer": "{\"type\":\"answer\",\"sdp\":\"fake\"}" }`, "", ) - rend, err := newAMPCacheRendezvous("http://test.broker", "", "", + rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{}, &mockTransport{http.StatusOK, ampArmorEncode(fakeEncPollResp)}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) @@ -228,7 +228,7 @@ func TestAMPCacheRendezvous(t *testing.T) { "", `{"error": "no snowflake proxies currently available"}`, ) - rend, err := newAMPCacheRendezvous("http://test.broker", "", "", + rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{}, &mockTransport{http.StatusOK, ampArmorEncode(fakeEncPollResp)}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) @@ -237,7 +237,7 @@ func TestAMPCacheRendezvous(t *testing.T) { }) Convey("ampCacheRendezvous.Exchange fails with unexpected HTTP status code", func() { - rend, err := newAMPCacheRendezvous("http://test.broker", "", "", + rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{}, &mockTransport{http.StatusInternalServerError, []byte{}}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) @@ -248,7 +248,7 @@ func TestAMPCacheRendezvous(t *testing.T) { Convey("ampCacheRendezvous.Exchange fails with error", func() { transportErr := errors.New("error") - rend, err := newAMPCacheRendezvous("http://test.broker", "", "", + rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{}, &errorTransport{err: transportErr}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) @@ -261,7 +261,7 @@ func TestAMPCacheRendezvous(t *testing.T) { // encoded bytes. Encode readLimit bytes—the encoded // size will be larger—and try to read the body. It // should fail. - rend, err := newAMPCacheRendezvous("http://test.broker", "", "", + rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{}, &mockTransport{http.StatusOK, ampArmorEncode(make([]byte, readLimit))}) So(err, ShouldBeNil) _, err = rend.Exchange(fakeEncPollReq) diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go index bcfadb3..453b42b 100644 --- a/client/lib/snowflake.go +++ b/client/lib/snowflake.go @@ -85,11 +85,14 @@ type ClientConfig struct { // AmpCacheURL is the full URL of a valid AMP cache. A nonzero value indicates // that AMP cache will be used as the rendezvous method with the broker. AmpCacheURL string - // FrontDomain is a the full URL of an optional front domain that can be used with either + // FrontDomain is the full URL of an optional front domain that can be used with either // the AMP cache or HTTP domain fronting rendezvous method. FrontDomain string // ICEAddresses are a slice of ICE server URLs that will be used for NAT traversal and // the creation of the client's WebRTC SDP offer. + FrontDomains []string + // ICEAddresses are a slice of ICE server URLs that will be used for NAT traversal and + // the creation of the client's WebRTC SDP offer. ICEAddresses []string // KeepLocalAddresses is an optional setting that will prevent the removal of local or // invalid addresses from the client's SDP offer. This is useful for local deployments @@ -134,6 +137,11 @@ func NewSnowflakeClient(config ClientConfig) (*Transport, error) { log.Printf("url: %v", strings.Join(server.URLs, " ")) } + // Maintain backwards compatability with old FrontDomain field of ClientConfig + if (len(config.FrontDomains) == 0) && (config.FrontDomain != "") { + config.FrontDomains = []string{config.FrontDomain} + } + // Rendezvous with broker using the given parameters. broker, err := newBrokerChannelFromConfig(config) if err != nil { diff --git a/client/snowflake.go b/client/snowflake.go index cb6fa82..d72e33f 100644 --- a/client/snowflake.go +++ b/client/snowflake.go @@ -81,7 +81,7 @@ func socksAcceptLoop(ln *pt.SocksListener, config sf.ClientConfig, shutdown chan config.AmpCacheURL = arg } if arg, ok := conn.Req.Args.Get("front"); ok { - config.FrontDomain = arg + config.FrontDomains = strings.Split(strings.TrimSpace(arg), ",") } if arg, ok := conn.Req.Args.Get("ice"); ok { config.ICEAddresses = strings.Split(strings.TrimSpace(arg), ",") @@ -151,7 +151,7 @@ func socksAcceptLoop(ln *pt.SocksListener, config sf.ClientConfig, shutdown chan func main() { iceServersCommas := flag.String("ice", "", "comma-separated list of ICE servers") brokerURL := flag.String("url", "", "URL of signaling broker") - frontDomain := flag.String("front", "", "front domain") + frontDomainsCommas := flag.String("front", "", "comma-separated list of front domains") ampCacheURL := flag.String("ampcache", "", "URL of AMP cache to use as a proxy for signaling") logFilename := flag.String("log", "", "name of log file") logToStateDir := flag.Bool("log-to-state-dir", false, "resolve the log file relative to tor's pt state dir") @@ -206,11 +206,12 @@ func main() { log.Printf("snowflake-client %s\n", version.GetVersion()) iceAddresses := strings.Split(strings.TrimSpace(*iceServersCommas), ",") + frontDomains := strings.Split(strings.TrimSpace(*frontDomainsCommas), ",") config := sf.ClientConfig{ BrokerURL: *brokerURL, AmpCacheURL: *ampCacheURL, - FrontDomain: *frontDomain, + FrontDomains: frontDomains, ICEAddresses: iceAddresses, KeepLocalAddresses: *keepLocalAddresses || *oldKeepLocalAddresses, Max: *max, diff --git a/client/torrc b/client/torrc index 00f17a2..6f09bfe 100644 --- a/client/torrc +++ b/client/torrc @@ -3,7 +3,7 @@ DataDirectory datadir ClientTransportPlugin snowflake exec ./client -log snowflake.log -Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=cdn.sstatic.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn -Bridge snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=cdn.sstatic.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn +Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=foursquare.com,github.githubassets.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn +Bridge snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=foursquare.com,github.githubassets.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn SocksPort auto |