mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-13 20:11:19 -04:00
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.
This commit is contained in:
parent
5cdf52c813
commit
9fdfb3d1b5
7 changed files with 65 additions and 48 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue