aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCecylia Bocovich <cohosh@torproject.org>2023-09-23 10:46:46 -0400
committerCecylia Bocovich <cohosh@torproject.org>2023-10-05 17:51:56 -0400
commit9fdfb3d1b53e9113422a7a2816b2a9af4450b4ac (patch)
tree786e2832356f5653f2da68699eeab692c8959928
parent5cdf52c8138c691d25a37e58b521ddc57f1faa97 (diff)
downloadsnowflake-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.go8
-rw-r--r--client/lib/rendezvous_ampcache.go20
-rw-r--r--client/lib/rendezvous_http.go20
-rw-r--r--client/lib/rendezvous_test.go44
-rw-r--r--client/lib/snowflake.go10
-rw-r--r--client/snowflake.go7
-rw-r--r--client/torrc4
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 &ampCacheRendezvous{
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