pax_global_header00006660000000000000000000000064141141035130014503gustar00rootroot0000000000000052 comment=b85259916339daf0dbac7d2edc8bdb650554e341 snowflake-1.1.0/000077500000000000000000000000001411410351300134735ustar00rootroot00000000000000snowflake-1.1.0/.gitignore000066400000000000000000000002131411410351300154570ustar00rootroot00000000000000*.swp *.swo *.swn *.swm .DS_Store datadir/ broker/broker client/client server/server proxy/proxy probetest/probetest snowflake.log ignore/ snowflake-1.1.0/.gitlab-ci.yml000066400000000000000000000077341411410351300161420ustar00rootroot00000000000000 # Set things up to use the OS-native packages for Go. Anything that # is downloaded by go during the `go fmt` stage is not coming from the # Debian/Ubuntu repo. So those would need to be packaged for this to # make it into Debian and/or Ubuntu. .debian-native-template: &debian-native-template variables: DEBIAN_FRONTEND: noninteractive GOPATH: /usr/share/gocode before_script: - apt-get -qy update - apt-get -qy install --no-install-recommends build-essential ca-certificates git golang golang-github-cheekybits-genny-dev golang-github-jtolds-gls-dev golang-github-klauspost-reedsolomon-dev golang-github-lucas-clemente-quic-go-dev golang-github-smartystreets-assertions-dev golang-github-smartystreets-goconvey-dev golang-github-tjfoc-gmsm-dev golang-github-xtaci-kcp-dev golang-github-xtaci-smux-dev golang-golang-x-crypto-dev golang-golang-x-net-dev golang-golang-x-sys-dev golang-golang-x-text-dev golang-golang-x-xerrors-dev lbzip2 # use Go installed as part of the official, Debian-based Docker images .golang-docker-debian-template: &golang-docker-debian-template variables: DEBIAN_FRONTEND: noninteractive before_script: - apt-get -qy update - apt-get -qy install --no-install-recommends ca-certificates git lbzip2 .go_test: &go-test - test -z "$(go fmt ./...)" - go vet ./... - go test -v -race ./... - cd $CI_PROJECT_DIR/client/ - go get - go build .test-template: &test-template artifacts: name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}" paths: - client/*.aar - client/*.jar - client/client expire_in: 1 day when: on_success after_script: - echo "Download debug artifacts from https://gitlab.com/${CI_PROJECT_PATH}/-/jobs" # -- jobs ------------------------------------------------------------ android: image: registry.gitlab.com/fdroid/ci-images-client variables: GOPATH: "/go" ANDROID_VERSION: 29 cache: paths: - .gradle/wrapper - .gradle/caches before_script: - apt-get -qy update - apt-get -qy install --no-install-recommends build-essential gnupg wget - cd /usr/local - export gotarball="go1.15.10.linux-amd64.tar.gz" - wget -q https://dl.google.com/go/${gotarball} - wget -q https://dl.google.com/go/${gotarball}.asc - curl https://dl.google.com/linux/linux_signing_key.pub | gpg --import - gpg --verify ${gotarball}.asc - echo "4aa1267517df32f2bf1cc3d55dfc27d0c6b2c2b0989449c96dd19273ccca051d ${gotarball}" | sha256sum -c - tar -xzf ${gotarball} - export PATH="/usr/local/go/bin:$GOPATH/bin:$PATH" # putting this in 'variables:' cause weird runner errors - cd $CI_PROJECT_DIR script: - *go-test - export GRADLE_USER_HOME=$PWD/.gradle - go version - go env - go get golang.org/x/mobile/cmd/gomobile - go get golang.org/x/mobile/cmd/gobind - go install golang.org/x/mobile/cmd/gomobile - go install golang.org/x/mobile/cmd/gobind - echo y | $ANDROID_HOME/tools/bin/sdkmanager 'ndk-bundle' > /dev/null - echo y | $ANDROID_HOME/tools/bin/sdkmanager "platforms;android-${ANDROID_VERSION}" > /dev/null - gomobile init - git -C $CI_PROJECT_DIR reset --hard - git -C $CI_PROJECT_DIR clean -fdx - cd $CI_PROJECT_DIR/client # gomobile builds a shared library not a CLI executable - sed -i 's,^package main$,package snowflakeclient,' snowflake.go - gomobile bind -v -target=android . <<: *test-template go-1.13: image: golang:1.13-stretch <<: *golang-docker-debian-template <<: *test-template script: - *go-test go-1.14: image: golang:1.14-stretch <<: *golang-docker-debian-template <<: *test-template script: - *go-test debian-testing: image: debian:testing <<: *debian-native-template <<: *test-template script: - *go-test snowflake-1.1.0/.gitmodules000066400000000000000000000000001411410351300156360ustar00rootroot00000000000000snowflake-1.1.0/.travis.yml000066400000000000000000000003151411410351300156030ustar00rootroot00000000000000language: go dist: xenial go_import_path: git.torproject.org/pluggable-transports/snowflake.git go: - 1.13.x script: - test -z "$(go fmt ./...)" - go vet ./... - go test -v -race ./... snowflake-1.1.0/CONTRIBUTING.md000066400000000000000000000002451411410351300157250ustar00rootroot00000000000000 - When editing Go, please run `go fmt` before every commit. - You may run tests locally with either `go test` or `npm test` for Go and JavaScript, respectively. snowflake-1.1.0/ChangeLog000066400000000000000000000007611411410351300152510ustar00rootroot00000000000000Changes in version v1.1.0 - 2021-07-13 - Refactors of the Snowflake broker code - Refactors of the Snowflake proxy code - Issue 40048: assign proxies based on self-reported client load - Issue 40052: fixed a memory leak in the server accept loop - Version bump of kcp and smux libraries - Bug fix to pass the correct client address to the Snowflake bridge metrics counter - Bug fixes to prevent race conditions in the Snowflake client Changes in version v1.0.0 - 2021-06-07 - Initial release. snowflake-1.1.0/LICENSE000066400000000000000000000034361411410351300145060ustar00rootroot00000000000000 This file contains the license for "Snowflake" a free software project which provides a WebRTC pluggable transport. ================================================================================ Copyright (c) 2016, Serene Han, Arlo Breault Copyright (c) 2019-2020, The Tor Project, Inc Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the names of the copyright owners nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================================================ snowflake-1.1.0/README.md000066400000000000000000000061741411410351300147620ustar00rootroot00000000000000# Snowflake [![Build Status](https://travis-ci.org/keroserene/snowflake.svg?branch=master)](https://travis-ci.org/keroserene/snowflake) Pluggable Transport using WebRTC, inspired by Flashproxy. **Table of Contents** - [Usage](#usage) - [Dependencies](#dependencies) - [More Info](#more-info) - [Building](#building) - [Test Environment](#test-environment) - [FAQ](#faq) - [Appendix](#appendix) - [-- Testing with Standalone Proxy --](#---testing-with-standalone-proxy---) ### Usage ``` cd client/ go get go build tor -f torrc ``` This should start the client plugin, bootstrapping to 100% using WebRTC. #### Dependencies Client: - [pion/webrtc](https://github.com/pion/webrtc) - Go 1.13+ --- #### More Info Tor can plug in the Snowflake client via a correctly configured `torrc`. For example: ``` ClientTransportPlugin snowflake exec ./client \ -url https://snowflake-broker.azureedge.net/ \ -front ajax.aspnetcdn.com \ -ice stun:stun.l.google.com:19302 -max 3 ``` The flags `-url` and `-front` allow the Snowflake client to speak to the Broker, in order to get connected with some volunteer's browser proxy. `-ice` is a comma-separated list of ICE servers, which are required for NAT traversal. For logging, run `tail -F snowflake.log` in a second terminal. You can modify the `torrc` to use your own broker: ``` ClientTransportPlugin snowflake exec ./client --meek ``` #### Test Environment There is a Docker-based test environment at https://github.com/cohosh/snowbox. ### FAQ **Q: How does it work?** In the Tor use-case: 1. Volunteers visit websites which host the "snowflake" proxy. (just like flashproxy) 2. Tor clients automatically find available browser proxies via the Broker (the domain fronted signaling channel). 3. Tor client and browser proxy establish a WebRTC peer connection. 4. Proxy connects to some relay. 5. Tor occurs. More detailed information about how clients, snowflake proxies, and the Broker fit together on the way... **Q: What are the benefits of this PT compared with other PTs?** Snowflake combines the advantages of flashproxy and meek. Primarily: - It has the convenience of Meek, but can support magnitudes more users with negligible CDN costs. (Domain fronting is only used for brief signalling / NAT-piercing to setup the P2P WebRTC DataChannels which handle the actual traffic.) - Arbitrarily high numbers of volunteer proxies are possible like in flashproxy, but NATs are no longer a usability barrier - no need for manual port forwarding! **Q: Why is this called Snowflake?** It utilizes the "ICE" negotiation via WebRTC, and also involves a great abundance of ephemeral and short-lived (and special!) volunteer proxies... ### Appendix ##### -- Testing with Standalone Proxy -- ``` cd proxy go build ./proxy ``` More documentation on the way. Also available at: [torproject.org/pluggable-transports/snowflake](https://gitweb.torproject.org/pluggable-transports/snowflake.git/) snowflake-1.1.0/broker/000077500000000000000000000000001411410351300147575ustar00rootroot00000000000000snowflake-1.1.0/broker/README.md000066400000000000000000000035051411410351300162410ustar00rootroot00000000000000This is the Broker component of Snowflake. ### Overview The Broker handles the rendezvous by matching Snowflake Clients with Proxies, and passing their WebRTC Session Descriptions (the "signaling" step). This allows Clients and Proxies to establish a Peer connection. It is analogous to Flashproxy's [Facilitator](https://trac.torproject.org/projects/tor/wiki/FlashProxyFAQ), but bidirectional and domain-fronted. The Broker expects: - Clients to send their SDP offer in a POST request, which will then block until the Broker responds with the answer of the matched Proxy. - Proxies to announce themselves with a POST request, to which the Broker responds with some Client's SDP offer. The Proxy should then send a second POST request soon after containing its SDP answer, which the Broker passes back to the same Client. ### Running your own The server uses TLS by default. There is a `--disable-tls` option for testing purposes, but you should use TLS in production. The server automatically fetches certificates from [Let's Encrypt](https://en.wikipedia.org/wiki/Let's_Encrypt) as needed. Use the `--acme-hostnames` option to tell the server what hostnames it may request certificates for. You can optionally provide a contact email address, using the `--acme-email` option, so that Let's Encrypt can inform you of any problems. In order to fetch certificates automatically, the server needs to open an additional HTTP listener on port 80. On Linux, you can use the `setcap` program, part of libcap2, to enable the broker to bind to low-numbered ports without having to run as root: ``` setcap 'cap_net_bind_service=+ep' /usr/local/bin/broker ``` You can control the listening broker port with the --addr option. Port 443 is the default. You'll need to provide the URL of the custom broker to the client plugin using the `--url $URL` flag. snowflake-1.1.0/broker/broker.go000066400000000000000000000225021411410351300165730ustar00rootroot00000000000000/* Broker acts as the HTTP signaling channel. It matches clients and snowflake proxies by passing corresponding SessionDescriptions in order to negotiate a WebRTC connection. */ package main import ( "container/heap" "crypto/tls" "flag" "io" "log" "net/http" "os" "os/signal" "strings" "sync" "syscall" "time" "git.torproject.org/pluggable-transports/snowflake.git/common/safelog" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "golang.org/x/crypto/acme/autocert" ) type BrokerContext struct { snowflakes *SnowflakeHeap restrictedSnowflakes *SnowflakeHeap // Maps keeping track of snowflakeIDs required to match SDP answers from // the second http POST. Restricted snowflakes can only be matched up with // clients behind an unrestricted NAT. idToSnowflake map[string]*Snowflake // Synchronization for the snowflake map and heap snowflakeLock sync.Mutex proxyPolls chan *ProxyPoll metrics *Metrics } func NewBrokerContext(metricsLogger *log.Logger) *BrokerContext { snowflakes := new(SnowflakeHeap) heap.Init(snowflakes) rSnowflakes := new(SnowflakeHeap) heap.Init(rSnowflakes) metrics, err := NewMetrics(metricsLogger) if err != nil { panic(err.Error()) } if metrics == nil { panic("Failed to create metrics") } return &BrokerContext{ snowflakes: snowflakes, restrictedSnowflakes: rSnowflakes, idToSnowflake: make(map[string]*Snowflake), proxyPolls: make(chan *ProxyPoll), metrics: metrics, } } // Proxies may poll for client offers concurrently. type ProxyPoll struct { id string proxyType string natType string clients int offerChannel chan *ClientOffer } // Registers a Snowflake and waits for some Client to send an offer, // as part of the polling logic of the proxy handler. func (ctx *BrokerContext) RequestOffer(id string, proxyType string, natType string, clients int) *ClientOffer { request := new(ProxyPoll) request.id = id request.proxyType = proxyType request.natType = natType request.clients = clients request.offerChannel = make(chan *ClientOffer) ctx.proxyPolls <- request // Block until an offer is available, or timeout which sends a nil offer. offer := <-request.offerChannel return offer } // goroutine which matches clients to proxies and sends SDP offers along. // Safely processes proxy requests, responding to them with either an available // client offer or nil on timeout / none are available. func (ctx *BrokerContext) Broker() { for request := range ctx.proxyPolls { snowflake := ctx.AddSnowflake(request.id, request.proxyType, request.natType, request.clients) // Wait for a client to avail an offer to the snowflake. go func(request *ProxyPoll) { select { case offer := <-snowflake.offerChannel: request.offerChannel <- offer case <-time.After(time.Second * ProxyTimeout): // This snowflake is no longer available to serve clients. ctx.snowflakeLock.Lock() defer ctx.snowflakeLock.Unlock() if snowflake.index != -1 { if request.natType == NATUnrestricted { heap.Remove(ctx.snowflakes, snowflake.index) } else { heap.Remove(ctx.restrictedSnowflakes, snowflake.index) } ctx.metrics.promMetrics.AvailableProxies.With(prometheus.Labels{"nat": request.natType, "type": request.proxyType}).Dec() delete(ctx.idToSnowflake, snowflake.id) close(request.offerChannel) } } }(request) } } // Create and add a Snowflake to the heap. // Required to keep track of proxies between providing them // with an offer and awaiting their second POST with an answer. func (ctx *BrokerContext) AddSnowflake(id string, proxyType string, natType string, clients int) *Snowflake { snowflake := new(Snowflake) snowflake.id = id snowflake.clients = clients snowflake.proxyType = proxyType snowflake.natType = natType snowflake.offerChannel = make(chan *ClientOffer) snowflake.answerChannel = make(chan string) ctx.snowflakeLock.Lock() if natType == NATUnrestricted { heap.Push(ctx.snowflakes, snowflake) } else { heap.Push(ctx.restrictedSnowflakes, snowflake) } ctx.metrics.promMetrics.AvailableProxies.With(prometheus.Labels{"nat": natType, "type": proxyType}).Inc() ctx.snowflakeLock.Unlock() ctx.idToSnowflake[id] = snowflake return snowflake } // Client offer contains an SDP and the NAT type of the client type ClientOffer struct { natType string sdp []byte } func main() { var acmeEmail string var acmeHostnamesCommas string var acmeCertCacheDir string var addr string var geoipDatabase string var geoip6Database string var disableTLS bool var certFilename, keyFilename string var disableGeoip bool var metricsFilename string var unsafeLogging bool flag.StringVar(&acmeEmail, "acme-email", "", "optional contact email for Let's Encrypt notifications") flag.StringVar(&acmeHostnamesCommas, "acme-hostnames", "", "comma-separated hostnames for TLS certificate") flag.StringVar(&certFilename, "cert", "", "TLS certificate file") flag.StringVar(&keyFilename, "key", "", "TLS private key file") flag.StringVar(&acmeCertCacheDir, "acme-cert-cache", "acme-cert-cache", "directory in which certificates should be cached") flag.StringVar(&addr, "addr", ":443", "address to listen on") flag.StringVar(&geoipDatabase, "geoipdb", "/usr/share/tor/geoip", "path to correctly formatted geoip database mapping IPv4 address ranges to country codes") flag.StringVar(&geoip6Database, "geoip6db", "/usr/share/tor/geoip6", "path to correctly formatted geoip database mapping IPv6 address ranges to country codes") flag.BoolVar(&disableTLS, "disable-tls", false, "don't use HTTPS") flag.BoolVar(&disableGeoip, "disable-geoip", false, "don't use geoip for stats collection") flag.StringVar(&metricsFilename, "metrics-log", "", "path to metrics logging output") flag.BoolVar(&unsafeLogging, "unsafe-logging", false, "prevent logs from being scrubbed") flag.Parse() var err error var metricsFile io.Writer var logOutput io.Writer = os.Stderr if unsafeLogging { log.SetOutput(logOutput) } else { // We want to send the log output through our scrubber first log.SetOutput(&safelog.LogScrubber{Output: logOutput}) } log.SetFlags(log.LstdFlags | log.LUTC) if metricsFilename != "" { metricsFile, err = os.OpenFile(metricsFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Fatal(err.Error()) } } else { metricsFile = os.Stdout } metricsLogger := log.New(metricsFile, "", 0) ctx := NewBrokerContext(metricsLogger) if !disableGeoip { err = ctx.metrics.LoadGeoipDatabases(geoipDatabase, geoip6Database) if err != nil { log.Fatal(err.Error()) } } go ctx.Broker() i := &IPC{ctx} http.HandleFunc("/robots.txt", robotsTxtHandler) http.Handle("/proxy", SnowflakeHandler{i, proxyPolls}) http.Handle("/client", SnowflakeHandler{i, clientOffers}) http.Handle("/answer", SnowflakeHandler{i, proxyAnswers}) http.Handle("/debug", SnowflakeHandler{i, debugHandler}) http.Handle("/metrics", MetricsHandler{metricsFilename, metricsHandler}) http.Handle("/prometheus", promhttp.HandlerFor(ctx.metrics.promMetrics.registry, promhttp.HandlerOpts{})) server := http.Server{ Addr: addr, } sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGHUP) // go routine to handle a SIGHUP signal to allow the broker operator to send // a SIGHUP signal when the geoip database files are updated, without requiring // a restart of the broker go func() { for { signal := <-sigChan log.Printf("Received signal: %s. Reloading geoip databases.", signal) if err = ctx.metrics.LoadGeoipDatabases(geoipDatabase, geoip6Database); err != nil { log.Fatalf("reload of Geo IP databases on signal %s returned error: %v", signal, err) } } }() // Handle the various ways of setting up TLS. The legal configurations // are: // --acme-hostnames (with optional --acme-email and/or --acme-cert-cache) // --cert and --key together // --disable-tls // The outputs of this block of code are the disableTLS, // needHTTP01Listener, certManager, and getCertificate variables. if acmeHostnamesCommas != "" { acmeHostnames := strings.Split(acmeHostnamesCommas, ",") log.Printf("ACME hostnames: %q", acmeHostnames) var cache autocert.Cache if err = os.MkdirAll(acmeCertCacheDir, 0700); err != nil { log.Printf("Warning: Couldn't create cache directory %q (reason: %s) so we're *not* using our certificate cache.", acmeCertCacheDir, err) } else { cache = autocert.DirCache(acmeCertCacheDir) } certManager := autocert.Manager{ Cache: cache, Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(acmeHostnames...), Email: acmeEmail, } go func() { log.Printf("Starting HTTP-01 listener") log.Fatal(http.ListenAndServe(":80", certManager.HTTPHandler(nil))) }() server.TLSConfig = &tls.Config{GetCertificate: certManager.GetCertificate} err = server.ListenAndServeTLS("", "") } else if certFilename != "" && keyFilename != "" { if acmeEmail != "" || acmeHostnamesCommas != "" { log.Fatalf("The --cert and --key options are not allowed with --acme-email or --acme-hostnames.") } err = server.ListenAndServeTLS(certFilename, keyFilename) } else if disableTLS { err = server.ListenAndServe() } else { log.Fatal("the --acme-hostnames, --cert and --key, or --disable-tls option is required") } if err != nil { log.Fatal(err) } } snowflake-1.1.0/broker/geoip.go000066400000000000000000000135301411410351300164130ustar00rootroot00000000000000/* This code is for loading database data that maps ip addresses to countries for collecting and presenting statistics on snowflake use that might alert us to censorship events. The functions here are heavily based off of how tor maintains and searches their geoip database The tables used for geoip data must be structured as follows: Recognized line format for IPv4 is: INTIPLOW,INTIPHIGH,CC where INTIPLOW and INTIPHIGH are IPv4 addresses encoded as big-endian 4-byte unsigned integers, and CC is a country code. Note that the IPv4 line format "INTIPLOW","INTIPHIGH","CC","CC3","COUNTRY NAME" is not currently supported. Recognized line format for IPv6 is: IPV6LOW,IPV6HIGH,CC where IPV6LOW and IPV6HIGH are IPv6 addresses and CC is a country code. It also recognizes, and skips over, blank lines and lines that start with '#' (comments). */ package main import ( "bufio" "bytes" "crypto/sha1" "encoding/hex" "fmt" "io" "log" "net" "os" "sort" "strconv" "strings" "sync" ) type GeoIPTable interface { parseEntry(string) (*GeoIPEntry, error) Len() int Append(GeoIPEntry) ElementAt(int) GeoIPEntry Lock() Unlock() } type GeoIPEntry struct { ipLow net.IP ipHigh net.IP country string } type GeoIPv4Table struct { table []GeoIPEntry lock sync.Mutex // synchronization for geoip table accesses and reloads } type GeoIPv6Table struct { table []GeoIPEntry lock sync.Mutex // synchronization for geoip table accesses and reloads } func (table *GeoIPv4Table) Len() int { return len(table.table) } func (table *GeoIPv6Table) Len() int { return len(table.table) } func (table *GeoIPv4Table) Append(entry GeoIPEntry) { (*table).table = append(table.table, entry) } func (table *GeoIPv6Table) Append(entry GeoIPEntry) { (*table).table = append(table.table, entry) } func (table *GeoIPv4Table) ElementAt(i int) GeoIPEntry { return table.table[i] } func (table *GeoIPv6Table) ElementAt(i int) GeoIPEntry { return table.table[i] } func (table *GeoIPv4Table) Lock() { (*table).lock.Lock() } func (table *GeoIPv6Table) Lock() { (*table).lock.Lock() } func (table *GeoIPv4Table) Unlock() { (*table).lock.Unlock() } func (table *GeoIPv6Table) Unlock() { (*table).lock.Unlock() } // Convert a geoip IP address represented as a big-endian unsigned integer to net.IP func geoipStringToIP(ipStr string) (net.IP, error) { ip, err := strconv.ParseUint(ipStr, 10, 32) if err != nil { return net.IPv4(0, 0, 0, 0), fmt.Errorf("error parsing IP %s", ipStr) } var bytes [4]byte bytes[0] = byte(ip & 0xFF) bytes[1] = byte((ip >> 8) & 0xFF) bytes[2] = byte((ip >> 16) & 0xFF) bytes[3] = byte((ip >> 24) & 0xFF) return net.IPv4(bytes[3], bytes[2], bytes[1], bytes[0]), nil } //Parses a line in the provided geoip file that corresponds //to an address range and a two character country code func (table *GeoIPv4Table) parseEntry(candidate string) (*GeoIPEntry, error) { if candidate[0] == '#' { return nil, nil } parsedCandidate := strings.Split(candidate, ",") if len(parsedCandidate) != 3 { return nil, fmt.Errorf("provided geoip file is incorrectly formatted. Could not parse line:\n%s", parsedCandidate) } low, err := geoipStringToIP(parsedCandidate[0]) if err != nil { return nil, err } high, err := geoipStringToIP(parsedCandidate[1]) if err != nil { return nil, err } geoipEntry := &GeoIPEntry{ ipLow: low, ipHigh: high, country: parsedCandidate[2], } return geoipEntry, nil } //Parses a line in the provided geoip file that corresponds //to an address range and a two character country code func (table *GeoIPv6Table) parseEntry(candidate string) (*GeoIPEntry, error) { if candidate[0] == '#' { return nil, nil } parsedCandidate := strings.Split(candidate, ",") if len(parsedCandidate) != 3 { return nil, fmt.Errorf("") } low := net.ParseIP(parsedCandidate[0]) if low == nil { return nil, fmt.Errorf("") } high := net.ParseIP(parsedCandidate[1]) if high == nil { return nil, fmt.Errorf("") } geoipEntry := &GeoIPEntry{ ipLow: low, ipHigh: high, country: parsedCandidate[2], } return geoipEntry, nil } //Loads provided geoip file into our tables //Entries are stored in a table func GeoIPLoadFile(table GeoIPTable, pathname string) error { //open file geoipFile, err := os.Open(pathname) if err != nil { return err } defer geoipFile.Close() hash := sha1.New() table.Lock() defer table.Unlock() hashedFile := io.TeeReader(geoipFile, hash) //read in strings and call parse function scanner := bufio.NewScanner(hashedFile) for scanner.Scan() { entry, err := table.parseEntry(scanner.Text()) if err != nil { return fmt.Errorf("provided geoip file is incorrectly formatted. Line is: %+q", scanner.Text()) } if entry != nil { table.Append(*entry) } } if err := scanner.Err(); err != nil { return err } sha1Hash := hex.EncodeToString(hash.Sum(nil)) log.Println("Using geoip file ", pathname, " with checksum", sha1Hash) log.Println("Loaded ", table.Len(), " entries into table") return nil } //Returns the country location of an IPv4 or IPv6 address, and a boolean value //that indicates whether the IP address was present in the geoip database func GetCountryByAddr(table GeoIPTable, ip net.IP) (string, bool) { table.Lock() defer table.Unlock() //look IP up in database index := sort.Search(table.Len(), func(i int) bool { entry := table.ElementAt(i) return (bytes.Compare(ip.To16(), entry.ipHigh.To16()) <= 0) }) if index == table.Len() { return "", false } // check to see if addr is in the range specified by the returned index // search on IPs in invalid ranges (e.g., 127.0.0.0/8) will return the //country code of the next highest range entry := table.ElementAt(index) if !(bytes.Compare(ip.To16(), entry.ipLow.To16()) >= 0 && bytes.Compare(ip.To16(), entry.ipHigh.To16()) <= 0) { return "", false } return table.ElementAt(index).country, true } snowflake-1.1.0/broker/http.go000066400000000000000000000134601411410351300162710ustar00rootroot00000000000000package main import ( "errors" "io" "io/ioutil" "log" "net/http" "os" "git.torproject.org/pluggable-transports/snowflake.git/common/messages" ) const ( readLimit = 100000 // Maximum number of bytes to be read from an HTTP request ) // Implements the http.Handler interface type SnowflakeHandler struct { *IPC handle func(*IPC, http.ResponseWriter, *http.Request) } func (sh SnowflakeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Session-ID") // Return early if it's CORS preflight. if "OPTIONS" == r.Method { return } sh.handle(sh.IPC, w, r) } // Implements the http.Handler interface type MetricsHandler struct { logFilename string handle func(string, http.ResponseWriter, *http.Request) } func (mh MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Session-ID") // Return early if it's CORS preflight. if "OPTIONS" == r.Method { return } mh.handle(mh.logFilename, w, r) } func robotsTxtHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") if _, err := w.Write([]byte("User-agent: *\nDisallow: /\n")); err != nil { log.Printf("robotsTxtHandler unable to write, with this error: %v", err) } } func metricsHandler(metricsFilename string, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") if metricsFilename == "" { http.NotFound(w, r) return } metricsFile, err := os.OpenFile(metricsFilename, os.O_RDONLY, 0644) if err != nil { log.Println("Error opening metrics file for reading") http.NotFound(w, r) return } if _, err := io.Copy(w, metricsFile); err != nil { log.Printf("copying metricsFile returned error: %v", err) } } func debugHandler(i *IPC, w http.ResponseWriter, r *http.Request) { var response string err := i.Debug(new(interface{}), &response) if err != nil { log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } if _, err := w.Write([]byte(response)); err != nil { log.Printf("writing proxy information returned error: %v ", err) } } /* For snowflake proxies to request a client from the Broker. */ func proxyPolls(i *IPC, w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(http.MaxBytesReader(w, r.Body, readLimit)) if err != nil { log.Println("Invalid data.") w.WriteHeader(http.StatusBadRequest) return } arg := messages.Arg{ Body: body, RemoteAddr: r.RemoteAddr, } var response []byte err = i.ProxyPolls(arg, &response) switch { case err == nil: case errors.Is(err, messages.ErrBadRequest): w.WriteHeader(http.StatusBadRequest) return case errors.Is(err, messages.ErrInternal): fallthrough default: log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } if _, err := w.Write(response); err != nil { log.Printf("proxyPolls unable to write offer with error: %v", err) } } /* Expects a WebRTC SDP offer in the Request to give to an assigned snowflake proxy, which responds with the SDP answer to be sent in the HTTP response back to the client. */ func clientOffers(i *IPC, w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(http.MaxBytesReader(w, r.Body, readLimit)) if err != nil { log.Printf("Error reading client request: %s", err.Error()) w.WriteHeader(http.StatusBadRequest) return } // Handle the legacy version // // We support two client message formats. The legacy format is for backwards // combatability and relies heavily on HTTP headers and status codes to convey // information. isLegacy := false if len(body) > 0 && body[0] == '{' { isLegacy = true req := messages.ClientPollRequest{ Offer: string(body), NAT: r.Header.Get("Snowflake-NAT-Type"), } body, err = req.EncodePollRequest() if err != nil { log.Printf("Error shimming the legacy request: %s", err.Error()) w.WriteHeader(http.StatusInternalServerError) return } } arg := messages.Arg{ Body: body, RemoteAddr: "", } var response []byte err = i.ClientOffers(arg, &response) if err != nil { log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } if isLegacy { resp, err := messages.DecodeClientPollResponse(response) if err != nil { log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } switch resp.Error { case "": response = []byte(resp.Answer) case messages.StrNoProxies: w.WriteHeader(http.StatusServiceUnavailable) return case messages.StrTimedOut: w.WriteHeader(http.StatusGatewayTimeout) return default: panic("unknown error") } } if _, err := w.Write(response); err != nil { log.Printf("clientOffers unable to write answer with error: %v", err) } } /* Expects snowflake proxes which have previously successfully received an offer from proxyHandler to respond with an answer in an HTTP POST, which the broker will pass back to the original client. */ func proxyAnswers(i *IPC, w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(http.MaxBytesReader(w, r.Body, readLimit)) if err != nil { log.Println("Invalid data.") w.WriteHeader(http.StatusBadRequest) return } arg := messages.Arg{ Body: body, RemoteAddr: "", } var response []byte err = i.ProxyAnswers(arg, &response) switch { case err == nil: case errors.Is(err, messages.ErrBadRequest): w.WriteHeader(http.StatusBadRequest) return case errors.Is(err, messages.ErrInternal): fallthrough default: log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } if _, err := w.Write(response); err != nil { log.Printf("proxyAnswers unable to write answer response with error: %v", err) } } snowflake-1.1.0/broker/ipc.go000066400000000000000000000162001411410351300160600ustar00rootroot00000000000000package main import ( "bytes" "container/heap" "fmt" "log" "net" "time" "git.torproject.org/pluggable-transports/snowflake.git/common/messages" "github.com/prometheus/client_golang/prometheus" ) const ( ClientTimeout = 10 ProxyTimeout = 10 NATUnknown = "unknown" NATRestricted = "restricted" NATUnrestricted = "unrestricted" ) type clientVersion int const ( v1 clientVersion = iota ) type IPC struct { ctx *BrokerContext } func (i *IPC) Debug(_ interface{}, response *string) error { var webexts, browsers, standalones, unknowns int var natRestricted, natUnrestricted, natUnknown int i.ctx.snowflakeLock.Lock() s := fmt.Sprintf("current snowflakes available: %d\n", len(i.ctx.idToSnowflake)) for _, snowflake := range i.ctx.idToSnowflake { if snowflake.proxyType == "badge" { browsers++ } else if snowflake.proxyType == "webext" { webexts++ } else if snowflake.proxyType == "standalone" { standalones++ } else { unknowns++ } switch snowflake.natType { case NATRestricted: natRestricted++ case NATUnrestricted: natUnrestricted++ default: natUnknown++ } } i.ctx.snowflakeLock.Unlock() s += fmt.Sprintf("\tstandalone proxies: %d", standalones) s += fmt.Sprintf("\n\tbrowser proxies: %d", browsers) s += fmt.Sprintf("\n\twebext proxies: %d", webexts) s += fmt.Sprintf("\n\tunknown proxies: %d", unknowns) s += fmt.Sprintf("\nNAT Types available:") s += fmt.Sprintf("\n\trestricted: %d", natRestricted) s += fmt.Sprintf("\n\tunrestricted: %d", natUnrestricted) s += fmt.Sprintf("\n\tunknown: %d", natUnknown) *response = s return nil } func (i *IPC) ProxyPolls(arg messages.Arg, response *[]byte) error { sid, proxyType, natType, clients, err := messages.DecodePollRequest(arg.Body) if err != nil { return messages.ErrBadRequest } // Log geoip stats remoteIP, _, err := net.SplitHostPort(arg.RemoteAddr) if err != nil { log.Println("Error processing proxy IP: ", err.Error()) } else { i.ctx.metrics.lock.Lock() i.ctx.metrics.UpdateCountryStats(remoteIP, proxyType, natType) i.ctx.metrics.lock.Unlock() } var b []byte // Wait for a client to avail an offer to the snowflake, or timeout if nil. offer := i.ctx.RequestOffer(sid, proxyType, natType, clients) if offer == nil { i.ctx.metrics.lock.Lock() i.ctx.metrics.proxyIdleCount++ i.ctx.metrics.promMetrics.ProxyPollTotal.With(prometheus.Labels{"nat": natType, "status": "idle"}).Inc() i.ctx.metrics.lock.Unlock() b, err = messages.EncodePollResponse("", false, "") if err != nil { return messages.ErrInternal } *response = b return nil } i.ctx.metrics.promMetrics.ProxyPollTotal.With(prometheus.Labels{"nat": natType, "status": "matched"}).Inc() b, err = messages.EncodePollResponse(string(offer.sdp), true, offer.natType) if err != nil { return messages.ErrInternal } *response = b return nil } func sendClientResponse(resp *messages.ClientPollResponse, response *[]byte) error { data, err := resp.EncodePollResponse() if err != nil { log.Printf("error encoding answer") return messages.ErrInternal } else { *response = []byte(data) return nil } } func (i *IPC) ClientOffers(arg messages.Arg, response *[]byte) error { var version clientVersion startTime := time.Now() body := arg.Body parts := bytes.SplitN(body, []byte("\n"), 2) if len(parts) < 2 { // no version number found err := fmt.Errorf("unsupported message version") return sendClientResponse(&messages.ClientPollResponse{Error: err.Error()}, response) } body = parts[1] if string(parts[0]) == "1.0" { version = v1 } else { err := fmt.Errorf("unsupported message version") return sendClientResponse(&messages.ClientPollResponse{Error: err.Error()}, response) } var offer *ClientOffer switch version { case v1: req, err := messages.DecodeClientPollRequest(body) if err != nil { return sendClientResponse(&messages.ClientPollResponse{Error: err.Error()}, response) } offer = &ClientOffer{ natType: req.NAT, sdp: []byte(req.Offer), } default: panic("unknown version") } // Only hand out known restricted snowflakes to unrestricted clients var snowflakeHeap *SnowflakeHeap if offer.natType == NATUnrestricted { snowflakeHeap = i.ctx.restrictedSnowflakes } else { snowflakeHeap = i.ctx.snowflakes } // Immediately fail if there are no snowflakes available. i.ctx.snowflakeLock.Lock() numSnowflakes := snowflakeHeap.Len() i.ctx.snowflakeLock.Unlock() if numSnowflakes <= 0 { i.ctx.metrics.lock.Lock() i.ctx.metrics.clientDeniedCount++ i.ctx.metrics.promMetrics.ClientPollTotal.With(prometheus.Labels{"nat": offer.natType, "status": "denied"}).Inc() if offer.natType == NATUnrestricted { i.ctx.metrics.clientUnrestrictedDeniedCount++ } else { i.ctx.metrics.clientRestrictedDeniedCount++ } i.ctx.metrics.lock.Unlock() switch version { case v1: resp := &messages.ClientPollResponse{Error: messages.StrNoProxies} return sendClientResponse(resp, response) default: panic("unknown version") } } // Otherwise, find the most available snowflake proxy, and pass the offer to it. // Delete must be deferred in order to correctly process answer request later. i.ctx.snowflakeLock.Lock() snowflake := heap.Pop(snowflakeHeap).(*Snowflake) i.ctx.snowflakeLock.Unlock() snowflake.offerChannel <- offer var err error // Wait for the answer to be returned on the channel or timeout. select { case answer := <-snowflake.answerChannel: i.ctx.metrics.lock.Lock() i.ctx.metrics.clientProxyMatchCount++ i.ctx.metrics.promMetrics.ClientPollTotal.With(prometheus.Labels{"nat": offer.natType, "status": "matched"}).Inc() i.ctx.metrics.lock.Unlock() switch version { case v1: resp := &messages.ClientPollResponse{Answer: answer} err = sendClientResponse(resp, response) default: panic("unknown version") } // Initial tracking of elapsed time. i.ctx.metrics.clientRoundtripEstimate = time.Since(startTime) / time.Millisecond case <-time.After(time.Second * ClientTimeout): log.Println("Client: Timed out.") switch version { case v1: resp := &messages.ClientPollResponse{Error: messages.StrTimedOut} err = sendClientResponse(resp, response) default: panic("unknown version") } } i.ctx.snowflakeLock.Lock() i.ctx.metrics.promMetrics.AvailableProxies.With(prometheus.Labels{"nat": snowflake.natType, "type": snowflake.proxyType}).Dec() delete(i.ctx.idToSnowflake, snowflake.id) i.ctx.snowflakeLock.Unlock() return err } func (i *IPC) ProxyAnswers(arg messages.Arg, response *[]byte) error { answer, id, err := messages.DecodeAnswerRequest(arg.Body) if err != nil || answer == "" { return messages.ErrBadRequest } var success = true i.ctx.snowflakeLock.Lock() snowflake, ok := i.ctx.idToSnowflake[id] i.ctx.snowflakeLock.Unlock() if !ok || snowflake == nil { // The snowflake took too long to respond with an answer, so its client // disappeared / the snowflake is no longer recognized by the Broker. success = false } b, err := messages.EncodeAnswerResponse(success) if err != nil { log.Printf("Error encoding answer: %s", err.Error()) return messages.ErrInternal } *response = b if success { snowflake.answerChannel <- answer } return nil } snowflake-1.1.0/broker/metrics.go000066400000000000000000000201551411410351300167570ustar00rootroot00000000000000/* We export metrics in the format specified in our broker spec: https://gitweb.torproject.org/pluggable-transports/snowflake.git/tree/doc/broker-spec.txt */ package main import ( "fmt" "log" "math" "net" "sort" "sync" "time" "github.com/prometheus/client_golang/prometheus" ) const ( prometheusNamespace = "snowflake" metricsResolution = 60 * 60 * 24 * time.Second //86400 seconds ) type CountryStats struct { standalone map[string]bool badge map[string]bool webext map[string]bool unknown map[string]bool natRestricted map[string]bool natUnrestricted map[string]bool natUnknown map[string]bool counts map[string]int } // Implements Observable type Metrics struct { logger *log.Logger tablev4 *GeoIPv4Table tablev6 *GeoIPv6Table countryStats CountryStats clientRoundtripEstimate time.Duration proxyIdleCount uint clientDeniedCount uint clientRestrictedDeniedCount uint clientUnrestrictedDeniedCount uint clientProxyMatchCount uint // synchronization for access to snowflake metrics lock sync.Mutex promMetrics *PromMetrics } type record struct { cc string count int } type records []record func (r records) Len() int { return len(r) } func (r records) Swap(i, j int) { r[i], r[j] = r[j], r[i] } func (r records) Less(i, j int) bool { if r[i].count == r[j].count { return r[i].cc > r[j].cc } return r[i].count < r[j].count } func (s CountryStats) Display() string { output := "" // Use the records struct to sort our counts map by value. rs := records{} for cc, count := range s.counts { rs = append(rs, record{cc: cc, count: count}) } sort.Sort(sort.Reverse(rs)) for _, r := range rs { output += fmt.Sprintf("%s=%d,", r.cc, r.count) } // cut off trailing "," if len(output) > 0 { return output[:len(output)-1] } return output } func (m *Metrics) UpdateCountryStats(addr string, proxyType string, natType string) { var country string var ok bool if proxyType == "standalone" { if m.countryStats.standalone[addr] { return } } else if proxyType == "badge" { if m.countryStats.badge[addr] { return } } else if proxyType == "webext" { if m.countryStats.webext[addr] { return } } else { if m.countryStats.unknown[addr] { return } } ip := net.ParseIP(addr) if ip.To4() != nil { //This is an IPv4 address if m.tablev4 == nil { return } country, ok = GetCountryByAddr(m.tablev4, ip) } else { if m.tablev6 == nil { return } country, ok = GetCountryByAddr(m.tablev6, ip) } if !ok { country = "??" } //update map of unique ips and counts m.countryStats.counts[country]++ if proxyType == "standalone" { m.countryStats.standalone[addr] = true } else if proxyType == "badge" { m.countryStats.badge[addr] = true } else if proxyType == "webext" { m.countryStats.webext[addr] = true } else { m.countryStats.unknown[addr] = true } m.promMetrics.ProxyTotal.With(prometheus.Labels{ "nat": natType, "type": proxyType, "cc": country, }).Inc() switch natType { case NATRestricted: m.countryStats.natRestricted[addr] = true case NATUnrestricted: m.countryStats.natUnrestricted[addr] = true default: m.countryStats.natUnknown[addr] = true } } func (m *Metrics) LoadGeoipDatabases(geoipDB string, geoip6DB string) error { // Load geoip databases log.Println("Loading geoip databases") tablev4 := new(GeoIPv4Table) err := GeoIPLoadFile(tablev4, geoipDB) if err != nil { m.tablev4 = nil return err } m.tablev4 = tablev4 tablev6 := new(GeoIPv6Table) err = GeoIPLoadFile(tablev6, geoip6DB) if err != nil { m.tablev6 = nil return err } m.tablev6 = tablev6 return nil } func NewMetrics(metricsLogger *log.Logger) (*Metrics, error) { m := new(Metrics) m.countryStats = CountryStats{ counts: make(map[string]int), standalone: make(map[string]bool), badge: make(map[string]bool), webext: make(map[string]bool), unknown: make(map[string]bool), natRestricted: make(map[string]bool), natUnrestricted: make(map[string]bool), natUnknown: make(map[string]bool), } m.logger = metricsLogger m.promMetrics = initPrometheus() // Write to log file every hour with updated metrics go m.logMetrics() return m, nil } // Logs metrics in intervals specified by metricsResolution func (m *Metrics) logMetrics() { heartbeat := time.Tick(metricsResolution) for range heartbeat { m.printMetrics() m.zeroMetrics() } } func (m *Metrics) printMetrics() { m.lock.Lock() m.logger.Println("snowflake-stats-end", time.Now().UTC().Format("2006-01-02 15:04:05"), fmt.Sprintf("(%d s)", int(metricsResolution.Seconds()))) m.logger.Println("snowflake-ips", m.countryStats.Display()) m.logger.Println("snowflake-ips-total", len(m.countryStats.standalone)+ len(m.countryStats.badge)+len(m.countryStats.webext)+len(m.countryStats.unknown)) m.logger.Println("snowflake-ips-standalone", len(m.countryStats.standalone)) m.logger.Println("snowflake-ips-badge", len(m.countryStats.badge)) m.logger.Println("snowflake-ips-webext", len(m.countryStats.webext)) m.logger.Println("snowflake-idle-count", binCount(m.proxyIdleCount)) m.logger.Println("client-denied-count", binCount(m.clientDeniedCount)) m.logger.Println("client-restricted-denied-count", binCount(m.clientRestrictedDeniedCount)) m.logger.Println("client-unrestricted-denied-count", binCount(m.clientUnrestrictedDeniedCount)) m.logger.Println("client-snowflake-match-count", binCount(m.clientProxyMatchCount)) m.logger.Println("snowflake-ips-nat-restricted", len(m.countryStats.natRestricted)) m.logger.Println("snowflake-ips-nat-unrestricted", len(m.countryStats.natUnrestricted)) m.logger.Println("snowflake-ips-nat-unknown", len(m.countryStats.natUnknown)) m.lock.Unlock() } // Restores all metrics to original values func (m *Metrics) zeroMetrics() { m.proxyIdleCount = 0 m.clientDeniedCount = 0 m.clientRestrictedDeniedCount = 0 m.clientUnrestrictedDeniedCount = 0 m.clientProxyMatchCount = 0 m.countryStats.counts = make(map[string]int) m.countryStats.standalone = make(map[string]bool) m.countryStats.badge = make(map[string]bool) m.countryStats.webext = make(map[string]bool) m.countryStats.unknown = make(map[string]bool) m.countryStats.natRestricted = make(map[string]bool) m.countryStats.natUnrestricted = make(map[string]bool) m.countryStats.natUnknown = make(map[string]bool) } // Rounds up a count to the nearest multiple of 8. func binCount(count uint) uint { return uint((math.Ceil(float64(count) / 8)) * 8) } type PromMetrics struct { registry *prometheus.Registry ProxyTotal *prometheus.CounterVec ProxyPollTotal *RoundedCounterVec ClientPollTotal *RoundedCounterVec AvailableProxies *prometheus.GaugeVec } // Initialize metrics for prometheus exporter func initPrometheus() *PromMetrics { promMetrics := &PromMetrics{} promMetrics.registry = prometheus.NewRegistry() promMetrics.ProxyTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: prometheusNamespace, Name: "proxy_total", Help: "The number of unique snowflake IPs", }, []string{"type", "nat", "cc"}, ) promMetrics.AvailableProxies = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Namespace: prometheusNamespace, Name: "available_proxies", Help: "The number of currently available snowflake proxies", }, []string{"type", "nat"}, ) promMetrics.ProxyPollTotal = NewRoundedCounterVec( prometheus.CounterOpts{ Namespace: prometheusNamespace, Name: "rounded_proxy_poll_total", Help: "The number of snowflake proxy polls, rounded up to a multiple of 8", }, []string{"nat", "status"}, ) promMetrics.ClientPollTotal = NewRoundedCounterVec( prometheus.CounterOpts{ Namespace: prometheusNamespace, Name: "rounded_client_poll_total", Help: "The number of snowflake client polls, rounded up to a multiple of 8", }, []string{"nat", "status"}, ) // We need to register our metrics so they can be exported. promMetrics.registry.MustRegister( promMetrics.ClientPollTotal, promMetrics.ProxyPollTotal, promMetrics.ProxyTotal, promMetrics.AvailableProxies, ) return promMetrics } snowflake-1.1.0/broker/prometheus.go000066400000000000000000000040321411410351300175000ustar00rootroot00000000000000/* Implements some additional prometheus metrics that we need for privacy preserving counts of users and proxies */ package main import ( "sync/atomic" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "google.golang.org/protobuf/proto" ) // New Prometheus counter type that produces rounded counts of metrics // for privacy preserving reasons type RoundedCounter interface { prometheus.Metric Inc() } type roundedCounter struct { total uint64 //reflects the true count value uint64 //reflects the rounded count desc *prometheus.Desc labelPairs []*dto.LabelPair } // Implements the RoundedCounter interface func (c *roundedCounter) Inc() { atomic.AddUint64(&c.total, 1) if c.total > c.value { atomic.AddUint64(&c.value, 8) } } // Implements the prometheus.Metric interface func (c *roundedCounter) Desc() *prometheus.Desc { return c.desc } // Implements the prometheus.Metric interface func (c *roundedCounter) Write(m *dto.Metric) error { m.Label = c.labelPairs m.Counter = &dto.Counter{Value: proto.Float64(float64(c.value))} return nil } // New prometheus vector type that will track RoundedCounter metrics // accross multiple labels type RoundedCounterVec struct { *prometheus.MetricVec } func NewRoundedCounterVec(opts prometheus.CounterOpts, labelNames []string) *RoundedCounterVec { desc := prometheus.NewDesc( prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), opts.Help, labelNames, opts.ConstLabels, ) return &RoundedCounterVec{ MetricVec: prometheus.NewMetricVec(desc, func(lvs ...string) prometheus.Metric { if len(lvs) != len(labelNames) { panic("inconsistent cardinality") } return &roundedCounter{desc: desc, labelPairs: prometheus.MakeLabelPairs(desc, lvs)} }), } } // Helper function to return the underlying RoundedCounter metric from MetricVec func (v *RoundedCounterVec) With(labels prometheus.Labels) RoundedCounter { metric, err := v.GetMetricWith(labels) if err != nil { panic(err) } return metric.(RoundedCounter) } snowflake-1.1.0/broker/snowflake-broker_test.go000066400000000000000000000604561411410351300216330ustar00rootroot00000000000000package main import ( "bytes" "container/heap" "io/ioutil" "log" "net" "net/http" "net/http/httptest" "os" "sync" "testing" "time" . "github.com/smartystreets/goconvey/convey" ) func NullLogger() *log.Logger { logger := log.New(os.Stdout, "", 0) logger.SetOutput(ioutil.Discard) return logger } var promOnce sync.Once func TestBroker(t *testing.T) { Convey("Context", t, func() { ctx := NewBrokerContext(NullLogger()) i := &IPC{ctx} Convey("Adds Snowflake", func() { So(ctx.snowflakes.Len(), ShouldEqual, 0) So(len(ctx.idToSnowflake), ShouldEqual, 0) ctx.AddSnowflake("foo", "", NATUnrestricted, 0) So(ctx.snowflakes.Len(), ShouldEqual, 1) So(len(ctx.idToSnowflake), ShouldEqual, 1) }) Convey("Broker goroutine matches clients with proxies", func() { p := new(ProxyPoll) p.id = "test" p.natType = "unrestricted" p.offerChannel = make(chan *ClientOffer) go func(ctx *BrokerContext) { ctx.proxyPolls <- p close(ctx.proxyPolls) }(ctx) ctx.Broker() So(ctx.snowflakes.Len(), ShouldEqual, 1) snowflake := heap.Pop(ctx.snowflakes).(*Snowflake) snowflake.offerChannel <- &ClientOffer{sdp: []byte("test offer")} offer := <-p.offerChannel So(ctx.idToSnowflake["test"], ShouldNotBeNil) So(offer.sdp, ShouldResemble, []byte("test offer")) So(ctx.snowflakes.Len(), ShouldEqual, 0) }) Convey("Request an offer from the Snowflake Heap", func() { done := make(chan *ClientOffer) go func() { offer := ctx.RequestOffer("test", "", NATUnrestricted, 0) done <- offer }() request := <-ctx.proxyPolls request.offerChannel <- &ClientOffer{sdp: []byte("test offer")} offer := <-done So(offer.sdp, ShouldResemble, []byte("test offer")) }) Convey("Responds to client offers...", func() { w := httptest.NewRecorder() data := bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"unknown\"}")) r, err := http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) Convey("with error when no snowflakes are available.", func() { clientOffers(i, w, r) So(w.Code, ShouldEqual, http.StatusOK) So(w.Body.String(), ShouldEqual, `{"error":"no snowflake proxies currently available"}`) }) Convey("with a proxy answer if available.", func() { done := make(chan bool) // Prepare a fake proxy to respond with. snowflake := ctx.AddSnowflake("fake", "", NATUnrestricted, 0) go func() { clientOffers(i, w, r) done <- true }() offer := <-snowflake.offerChannel So(offer.sdp, ShouldResemble, []byte("fake")) snowflake.answerChannel <- "fake answer" <-done So(w.Body.String(), ShouldEqual, `{"answer":"fake answer"}`) So(w.Code, ShouldEqual, http.StatusOK) }) Convey("Times out when no proxy responds.", func() { if testing.Short() { return } done := make(chan bool) snowflake := ctx.AddSnowflake("fake", "", NATUnrestricted, 0) go func() { clientOffers(i, w, r) // Takes a few seconds here... done <- true }() offer := <-snowflake.offerChannel So(offer.sdp, ShouldResemble, []byte("fake")) <-done So(w.Code, ShouldEqual, http.StatusOK) So(w.Body.String(), ShouldEqual, `{"error":"timed out waiting for answer!"}`) }) }) Convey("Responds to legacy client offers...", func() { w := httptest.NewRecorder() data := bytes.NewReader([]byte("{test}")) r, err := http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) r.Header.Set("Snowflake-NAT-TYPE", "restricted") Convey("with 503 when no snowflakes are available.", func() { clientOffers(i, w, r) So(w.Code, ShouldEqual, http.StatusServiceUnavailable) So(w.Body.String(), ShouldEqual, "") }) Convey("with a proxy answer if available.", func() { done := make(chan bool) // Prepare a fake proxy to respond with. snowflake := ctx.AddSnowflake("fake", "", NATUnrestricted, 0) go func() { clientOffers(i, w, r) done <- true }() offer := <-snowflake.offerChannel So(offer.sdp, ShouldResemble, []byte("{test}")) snowflake.answerChannel <- "fake answer" <-done So(w.Body.String(), ShouldEqual, "fake answer") So(w.Code, ShouldEqual, http.StatusOK) }) Convey("Times out when no proxy responds.", func() { if testing.Short() { return } done := make(chan bool) snowflake := ctx.AddSnowflake("fake", "", NATUnrestricted, 0) go func() { clientOffers(i, w, r) // Takes a few seconds here... done <- true }() offer := <-snowflake.offerChannel So(offer.sdp, ShouldResemble, []byte("{test}")) <-done So(w.Code, ShouldEqual, http.StatusGatewayTimeout) }) }) Convey("Responds to proxy polls...", func() { done := make(chan bool) w := httptest.NewRecorder() data := bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0"}`)) r, err := http.NewRequest("POST", "snowflake.broker/proxy", data) So(err, ShouldBeNil) Convey("with a client offer if available.", func() { go func(i *IPC) { proxyPolls(i, w, r) done <- true }(i) // Pass a fake client offer to this proxy p := <-ctx.proxyPolls So(p.id, ShouldEqual, "ymbcCMto7KHNGYlp") p.offerChannel <- &ClientOffer{sdp: []byte("fake offer")} <-done So(w.Code, ShouldEqual, http.StatusOK) So(w.Body.String(), ShouldEqual, `{"Status":"client match","Offer":"fake offer","NAT":""}`) }) Convey("return empty 200 OK when no client offer is available.", func() { go func(i *IPC) { proxyPolls(i, w, r) done <- true }(i) p := <-ctx.proxyPolls So(p.id, ShouldEqual, "ymbcCMto7KHNGYlp") // nil means timeout p.offerChannel <- nil <-done So(w.Body.String(), ShouldEqual, `{"Status":"no match","Offer":"","NAT":""}`) So(w.Code, ShouldEqual, http.StatusOK) }) }) Convey("Responds to proxy answers...", func() { done := make(chan bool) s := ctx.AddSnowflake("test", "", NATUnrestricted, 0) w := httptest.NewRecorder() data := bytes.NewReader([]byte(`{"Version":"1.0","Sid":"test","Answer":"test"}`)) Convey("by passing to the client if valid.", func() { r, err := http.NewRequest("POST", "snowflake.broker/answer", data) So(err, ShouldBeNil) go func(i *IPC) { proxyAnswers(i, w, r) done <- true }(i) answer := <-s.answerChannel <-done So(w.Code, ShouldEqual, http.StatusOK) So(answer, ShouldResemble, "test") }) Convey("with client gone status if the proxy is not recognized", func() { data = bytes.NewReader([]byte(`{"Version":"1.0","Sid":"invalid","Answer":"test"}`)) r, err := http.NewRequest("POST", "snowflake.broker/answer", data) So(err, ShouldBeNil) proxyAnswers(i, w, r) So(w.Code, ShouldEqual, http.StatusOK) b, err := ioutil.ReadAll(w.Body) So(err, ShouldBeNil) So(b, ShouldResemble, []byte(`{"Status":"client gone"}`)) }) Convey("with error if the proxy gives invalid answer", func() { data := bytes.NewReader(nil) r, err := http.NewRequest("POST", "snowflake.broker/answer", data) So(err, ShouldBeNil) proxyAnswers(i, w, r) So(w.Code, ShouldEqual, http.StatusBadRequest) }) Convey("with error if the proxy writes too much data", func() { data := bytes.NewReader(make([]byte, 100001)) r, err := http.NewRequest("POST", "snowflake.broker/answer", data) So(err, ShouldBeNil) proxyAnswers(i, w, r) So(w.Code, ShouldEqual, http.StatusBadRequest) }) }) }) Convey("End-To-End", t, func() { ctx := NewBrokerContext(NullLogger()) i := &IPC{ctx} Convey("Check for client/proxy data race", func() { proxy_done := make(chan bool) client_done := make(chan bool) go ctx.Broker() // Make proxy poll wp := httptest.NewRecorder() datap := bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0"}`)) rp, err := http.NewRequest("POST", "snowflake.broker/proxy", datap) So(err, ShouldBeNil) go func(i *IPC) { proxyPolls(i, wp, rp) proxy_done <- true }(i) // Client offer wc := httptest.NewRecorder() datac := bytes.NewReader([]byte("test")) rc, err := http.NewRequest("POST", "snowflake.broker/client", datac) So(err, ShouldBeNil) go func() { clientOffers(i, wc, rc) client_done <- true }() <-proxy_done So(wp.Code, ShouldEqual, http.StatusOK) // Proxy answers wp = httptest.NewRecorder() datap = bytes.NewReader([]byte(`{"Version":"1.0","Sid":"ymbcCMto7KHNGYlp","Answer":"test"}`)) rp, err = http.NewRequest("POST", "snowflake.broker/answer", datap) So(err, ShouldBeNil) go func(i *IPC) { proxyAnswers(i, wp, rp) proxy_done <- true }(i) <-proxy_done <-client_done }) Convey("Ensure correct snowflake brokering", func() { done := make(chan bool) polled := make(chan bool) // Proxy polls with its ID first... dataP := bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0"}`)) wP := httptest.NewRecorder() rP, err := http.NewRequest("POST", "snowflake.broker/proxy", dataP) So(err, ShouldBeNil) go func() { proxyPolls(i, wP, rP) polled <- true }() // Manually do the Broker goroutine action here for full control. p := <-ctx.proxyPolls So(p.id, ShouldEqual, "ymbcCMto7KHNGYlp") s := ctx.AddSnowflake(p.id, "", NATUnrestricted, 0) go func() { offer := <-s.offerChannel p.offerChannel <- offer }() So(ctx.idToSnowflake["ymbcCMto7KHNGYlp"], ShouldNotBeNil) // Client request blocks until proxy answer arrives. dataC := bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"unknown\"}")) wC := httptest.NewRecorder() rC, err := http.NewRequest("POST", "snowflake.broker/client", dataC) So(err, ShouldBeNil) go func() { clientOffers(i, wC, rC) done <- true }() <-polled So(wP.Code, ShouldEqual, http.StatusOK) So(wP.Body.String(), ShouldResemble, `{"Status":"client match","Offer":"fake","NAT":"unknown"}`) So(ctx.idToSnowflake["ymbcCMto7KHNGYlp"], ShouldNotBeNil) // Follow up with the answer request afterwards wA := httptest.NewRecorder() dataA := bytes.NewReader([]byte(`{"Version":"1.0","Sid":"ymbcCMto7KHNGYlp","Answer":"test"}`)) rA, err := http.NewRequest("POST", "snowflake.broker/answer", dataA) So(err, ShouldBeNil) proxyAnswers(i, wA, rA) So(wA.Code, ShouldEqual, http.StatusOK) <-done So(wC.Code, ShouldEqual, http.StatusOK) So(wC.Body.String(), ShouldEqual, `{"answer":"test"}`) }) }) } func TestSnowflakeHeap(t *testing.T) { Convey("SnowflakeHeap", t, func() { h := new(SnowflakeHeap) heap.Init(h) So(h.Len(), ShouldEqual, 0) s1 := new(Snowflake) s2 := new(Snowflake) s3 := new(Snowflake) s4 := new(Snowflake) s1.clients = 4 s2.clients = 5 s3.clients = 3 s4.clients = 1 heap.Push(h, s1) So(h.Len(), ShouldEqual, 1) heap.Push(h, s2) So(h.Len(), ShouldEqual, 2) heap.Push(h, s3) So(h.Len(), ShouldEqual, 3) heap.Push(h, s4) So(h.Len(), ShouldEqual, 4) heap.Remove(h, 0) So(h.Len(), ShouldEqual, 3) r := heap.Pop(h).(*Snowflake) So(h.Len(), ShouldEqual, 2) So(r.clients, ShouldEqual, 3) So(r.index, ShouldEqual, -1) r = heap.Pop(h).(*Snowflake) So(h.Len(), ShouldEqual, 1) So(r.clients, ShouldEqual, 4) So(r.index, ShouldEqual, -1) r = heap.Pop(h).(*Snowflake) So(h.Len(), ShouldEqual, 0) So(r.clients, ShouldEqual, 5) So(r.index, ShouldEqual, -1) }) } func TestGeoip(t *testing.T) { Convey("Geoip", t, func() { tv4 := new(GeoIPv4Table) err := GeoIPLoadFile(tv4, "test_geoip") So(err, ShouldEqual, nil) tv6 := new(GeoIPv6Table) err = GeoIPLoadFile(tv6, "test_geoip6") So(err, ShouldEqual, nil) Convey("IPv4 Country Mapping Tests", func() { for _, test := range []struct { addr, cc string ok bool }{ { "129.97.208.23", //uwaterloo "CA", true, }, { "127.0.0.1", "", false, }, { "255.255.255.255", "", false, }, { "0.0.0.0", "", false, }, { "223.252.127.255", //test high end of range "JP", true, }, { "223.252.127.255", //test low end of range "JP", true, }, } { country, ok := GetCountryByAddr(tv4, net.ParseIP(test.addr)) So(country, ShouldEqual, test.cc) So(ok, ShouldResemble, test.ok) } }) Convey("IPv6 Country Mapping Tests", func() { for _, test := range []struct { addr, cc string ok bool }{ { "2620:101:f000:0:250:56ff:fe80:168e", //uwaterloo "CA", true, }, { "fd00:0:0:0:0:0:0:1", "", false, }, { "0:0:0:0:0:0:0:0", "", false, }, { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "", false, }, { "2a07:2e47:ffff:ffff:ffff:ffff:ffff:ffff", //test high end of range "FR", true, }, { "2a07:2e40::", //test low end of range "FR", true, }, } { country, ok := GetCountryByAddr(tv6, net.ParseIP(test.addr)) So(country, ShouldEqual, test.cc) So(ok, ShouldResemble, test.ok) } }) // Make sure things behave properly if geoip file fails to load ctx := NewBrokerContext(NullLogger()) if err := ctx.metrics.LoadGeoipDatabases("invalid_filename", "invalid_filename6"); err != nil { log.Printf("loading geo ip databases returned error: %v", err) } ctx.metrics.UpdateCountryStats("127.0.0.1", "", NATUnrestricted) So(ctx.metrics.tablev4, ShouldEqual, nil) }) } func TestMetrics(t *testing.T) { Convey("Test metrics...", t, func() { done := make(chan bool) buf := new(bytes.Buffer) ctx := NewBrokerContext(log.New(buf, "", 0)) i := &IPC{ctx} err := ctx.metrics.LoadGeoipDatabases("test_geoip", "test_geoip6") So(err, ShouldEqual, nil) //Test addition of proxy polls Convey("for proxy polls", func() { w := httptest.NewRecorder() data := bytes.NewReader([]byte("{\"Sid\":\"ymbcCMto7KHNGYlp\",\"Version\":\"1.0\"}")) r, err := http.NewRequest("POST", "snowflake.broker/proxy", data) r.RemoteAddr = "129.97.208.23:8888" //CA geoip So(err, ShouldBeNil) go func(i *IPC) { proxyPolls(i, w, r) done <- true }(i) p := <-ctx.proxyPolls //manually unblock poll p.offerChannel <- nil <-done w = httptest.NewRecorder() data = bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0","Type":"standalone"}`)) r, err = http.NewRequest("POST", "snowflake.broker/proxy", data) r.RemoteAddr = "129.97.208.23:8888" //CA geoip So(err, ShouldBeNil) go func(i *IPC) { proxyPolls(i, w, r) done <- true }(i) p = <-ctx.proxyPolls //manually unblock poll p.offerChannel <- nil <-done w = httptest.NewRecorder() data = bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0","Type":"badge"}`)) r, err = http.NewRequest("POST", "snowflake.broker/proxy", data) r.RemoteAddr = "129.97.208.23:8888" //CA geoip So(err, ShouldBeNil) go func(i *IPC) { proxyPolls(i, w, r) done <- true }(i) p = <-ctx.proxyPolls //manually unblock poll p.offerChannel <- nil <-done w = httptest.NewRecorder() data = bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0","Type":"webext"}`)) r, err = http.NewRequest("POST", "snowflake.broker/proxy", data) r.RemoteAddr = "129.97.208.23:8888" //CA geoip So(err, ShouldBeNil) go func(i *IPC) { proxyPolls(i, w, r) done <- true }(i) p = <-ctx.proxyPolls //manually unblock poll p.offerChannel <- nil <-done ctx.metrics.printMetrics() So(buf.String(), ShouldResemble, "snowflake-stats-end "+time.Now().UTC().Format("2006-01-02 15:04:05")+" (86400 s)\nsnowflake-ips CA=4\nsnowflake-ips-total 4\nsnowflake-ips-standalone 1\nsnowflake-ips-badge 1\nsnowflake-ips-webext 1\nsnowflake-idle-count 8\nclient-denied-count 0\nclient-restricted-denied-count 0\nclient-unrestricted-denied-count 0\nclient-snowflake-match-count 0\nsnowflake-ips-nat-restricted 0\nsnowflake-ips-nat-unrestricted 0\nsnowflake-ips-nat-unknown 1\n") }) //Test addition of client failures Convey("for no proxies available", func() { w := httptest.NewRecorder() data := bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"unknown\"}")) r, err := http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, "client-denied-count 8\nclient-restricted-denied-count 8\nclient-unrestricted-denied-count 0\nclient-snowflake-match-count 0") // Test reset buf.Reset() ctx.metrics.zeroMetrics() ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, "snowflake-ips \nsnowflake-ips-total 0\nsnowflake-ips-standalone 0\nsnowflake-ips-badge 0\nsnowflake-ips-webext 0\nsnowflake-idle-count 0\nclient-denied-count 0\nclient-restricted-denied-count 0\nclient-unrestricted-denied-count 0\nclient-snowflake-match-count 0\nsnowflake-ips-nat-restricted 0\nsnowflake-ips-nat-unrestricted 0\nsnowflake-ips-nat-unknown 0\n") }) //Test addition of client matches Convey("for client-proxy match", func() { w := httptest.NewRecorder() data := bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"unknown\"}")) r, err := http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) // Prepare a fake proxy to respond with. snowflake := ctx.AddSnowflake("fake", "", NATUnrestricted, 0) go func() { clientOffers(i, w, r) done <- true }() offer := <-snowflake.offerChannel So(offer.sdp, ShouldResemble, []byte("fake")) snowflake.answerChannel <- "fake answer" <-done ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, "client-denied-count 0\nclient-restricted-denied-count 0\nclient-unrestricted-denied-count 0\nclient-snowflake-match-count 8") }) //Test rounding boundary Convey("binning boundary", func() { w := httptest.NewRecorder() data := bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"restricted\"}")) r, err := http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data = bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"restricted\"}")) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data = bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"restricted\"}")) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data = bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"restricted\"}")) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data = bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"restricted\"}")) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data = bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"restricted\"}")) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data = bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"restricted\"}")) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data = bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"restricted\"}")) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, "client-denied-count 8\nclient-restricted-denied-count 8\nclient-unrestricted-denied-count 0\n") w = httptest.NewRecorder() data = bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"restricted\"}")) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) buf.Reset() ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, "client-denied-count 16\nclient-restricted-denied-count 16\nclient-unrestricted-denied-count 0\n") }) //Test unique ip Convey("proxy counts by unique ip", func() { w := httptest.NewRecorder() data := bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0"}`)) r, err := http.NewRequest("POST", "snowflake.broker/proxy", data) r.RemoteAddr = "129.97.208.23:8888" //CA geoip So(err, ShouldBeNil) go func(i *IPC) { proxyPolls(i, w, r) done <- true }(i) p := <-ctx.proxyPolls //manually unblock poll p.offerChannel <- nil <-done data = bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0"}`)) r, err = http.NewRequest("POST", "snowflake.broker/proxy", data) if err != nil { log.Printf("unable to get NewRequest with error: %v", err) } r.RemoteAddr = "129.97.208.23:8888" //CA geoip go func(i *IPC) { proxyPolls(i, w, r) done <- true }(i) p = <-ctx.proxyPolls //manually unblock poll p.offerChannel <- nil <-done ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, "snowflake-ips CA=1\nsnowflake-ips-total 1") }) //Test NAT types Convey("proxy counts by NAT type", func() { w := httptest.NewRecorder() data := bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.2","Type":"unknown","NAT":"restricted"}`)) r, err := http.NewRequest("POST", "snowflake.broker/proxy", data) r.RemoteAddr = "129.97.208.23:8888" //CA geoip So(err, ShouldBeNil) go func(i *IPC) { proxyPolls(i, w, r) done <- true }(i) p := <-ctx.proxyPolls //manually unblock poll p.offerChannel <- nil <-done ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, "snowflake-ips-nat-restricted 1\nsnowflake-ips-nat-unrestricted 0\nsnowflake-ips-nat-unknown 0") data = bytes.NewReader([]byte(`{"Sid":"ymbcCMto7KHNGYlp","Version":"1.2","Type":"unknown","NAT":"unrestricted"}`)) r, err = http.NewRequest("POST", "snowflake.broker/proxy", data) if err != nil { log.Printf("unable to get NewRequest with error: %v", err) } r.RemoteAddr = "129.97.208.24:8888" //CA geoip go func(i *IPC) { proxyPolls(i, w, r) done <- true }(i) p = <-ctx.proxyPolls //manually unblock poll p.offerChannel <- nil <-done ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, "snowflake-ips-nat-restricted 1\nsnowflake-ips-nat-unrestricted 1\nsnowflake-ips-nat-unknown 0") }) //Test client failures by NAT type Convey("client failures by NAT type", func() { w := httptest.NewRecorder() data := bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"restricted\"}")) r, err := http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, "client-denied-count 8\nclient-restricted-denied-count 8\nclient-unrestricted-denied-count 0\nclient-snowflake-match-count 0") buf.Reset() ctx.metrics.zeroMetrics() data = bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"unrestricted\"}")) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, "client-denied-count 8\nclient-restricted-denied-count 0\nclient-unrestricted-denied-count 8\nclient-snowflake-match-count 0") buf.Reset() ctx.metrics.zeroMetrics() data = bytes.NewReader( []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"unknown\"}")) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, "client-denied-count 8\nclient-restricted-denied-count 8\nclient-unrestricted-denied-count 0\nclient-snowflake-match-count 0") }) Convey("for country stats order", func() { stats := map[string]int{ "IT": 50, "FR": 200, "TZ": 100, "CN": 250, "RU": 150, "CA": 1, "BE": 1, "PH": 1, } ctx.metrics.countryStats.counts = stats So(ctx.metrics.countryStats.Display(), ShouldEqual, "CN=250,FR=200,RU=150,TZ=100,IT=50,BE=1,CA=1,PH=1") }) }) } snowflake-1.1.0/broker/snowflake-heap.go000066400000000000000000000021051411410351300202100ustar00rootroot00000000000000/* Keeping track of pending available snowflake proxies. */ package main /* The Snowflake struct contains a single interaction over the offer and answer channels. */ type Snowflake struct { id string proxyType string natType string offerChannel chan *ClientOffer answerChannel chan string clients int index int } // Implements heap.Interface, and holds Snowflakes. type SnowflakeHeap []*Snowflake func (sh SnowflakeHeap) Len() int { return len(sh) } func (sh SnowflakeHeap) Less(i, j int) bool { // Snowflakes serving less clients should sort earlier. return sh[i].clients < sh[j].clients } func (sh SnowflakeHeap) Swap(i, j int) { sh[i], sh[j] = sh[j], sh[i] sh[i].index = i sh[j].index = j } func (sh *SnowflakeHeap) Push(s interface{}) { n := len(*sh) snowflake := s.(*Snowflake) snowflake.index = n *sh = append(*sh, snowflake) } // Only valid when Len() > 0. func (sh *SnowflakeHeap) Pop() interface{} { flakes := *sh n := len(flakes) snowflake := flakes[n-1] snowflake.index = -1 *sh = flakes[0 : n-1] return snowflake } snowflake-1.1.0/broker/test_geoip000066400000000000000000000743521411410351300170570ustar00rootroot00000000000000# Last updated based on February 7 2018 Maxmind GeoLite2 Country # wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz # gunzip GeoLite2-Country.mmdb.gz # python mmdb-convert.py GeoLite2-Country.mmdb 16777216,16777471,AU 16777472,16778239,CN 16778240,16779263,AU 16779264,16781311,CN 16781312,16785407,JP 16785408,16793599,CN 16793600,16809983,JP 16809984,16842751,TH 16842752,16843007,CN 16843008,16843263,AU 16843264,16859135,CN 16859136,16875519,JP 16875520,16908287,TH 16908288,16909055,CN 16909056,16909311,US 16909312,16941055,CN 16941056,16973823,TH 16973824,17039359,CN 17039360,17039615,AU 2111307776,2111832063,CN 2111832064,2112487423,TW 2112487424,2112618495,VN 2112618496,2112880639,NZ 2112880640,2113560063,KR 2113560064,2113560319,SG 2113560320,2113683455,KR 2113683456,2113684607,JP 2113684608,2113684671,TW 2113684672,2113685663,JP 2113685664,2113685695,SG 2113685696,2113687999,JP 2113688000,2113688031,AU 2113688032,2113688959,JP 2113688960,2113688991,SG 2113688992,2113691135,JP 2113691136,2113691391,SG 2113691392,2113692415,JP 2113692416,2113692671,HK 2113692672,2113693599,JP 2113693600,2113693615,HK 2113693616,2113693879,JP 2113693880,2113693887,AU 2113693888,2113693951,JP 2113693952,2113694207,HK 2113694208,2113695279,JP 2113695280,2113695287,SG 2113695288,2113716223,JP 2113716224,2113724927,SG 2113724928,2113725183,IN 2113725184,2113728511,SG 2113728512,2113732607,JP 2113732608,2113761279,AU 2113761280,2113765375,VN 2113765376,2113798143,HK 2113798144,2113811455,AU 2113811456,2113812479,GB 2113812480,2113813503,JP 2113813504,2113830911,AU 2113830912,2113863679,CN 2113863680,2113929215,AU 2113929216,2130706431,JP 2147483648,2147483903,NL 2147483904,2147484671,RO 2147484672,2147485695,TR 2147485696,2147487743,DK 2147487744,2147489791,NO 2147489792,2147491839,RU 2147491840,2147494911,DE 2147494912,2147495167,RO 2147495168,2147495423,DE 2147495424,2147496959,RO 2147496960,2147497215,ES 2147497216,2147497471,RO 2147497472,2147497727,PL 2147497728,2147498239,DE 2147498240,2147498495,RO 2147498496,2147500031,DE 2147500032,2147501055,NL 2147501056,2147501311,SK 2147501312,2147501567,NL 2147501568,2147501823,GL 2147501824,2147502079,US 2147502080,2147504127,DK 2147504128,2147508223,RU 2147508224,2147510271,DE 2147510272,2147510783,UA 2147510784,2147511039,RU 2147511040,2147512319,CY 2147512320,2147514879,DE 2147514880,2147516415,IT 2147516416,2147520511,RU 2147520512,2147524607,DE 2147524608,2147526655,RU 2147526656,2147528703,UA 2147528704,2147532799,CZ 2147532800,2147534847,DE 2147534848,2147549183,CY 2147549184,2147557375,US 2147557376,2147557631,TW 2147557632,2147557887,SG 2147557888,2147558143,DE 2147558144,2147558399,TH 2147558400,2147558655,KR 2147558656,2147558911,TW 2147558912,2147559167,SG 2147559168,2147559423,TH 2147559424,2147559679,SG 2147559680,2147559935,US 2147559936,2147560191,DE 2147560192,2147560447,RU 2147560448,2147560703,TH 2147560704,2147560959,TW 2147560960,2147562239,US 2147562240,2147562495,RU 2147562496,2147563263,US 2147563264,2147563519,RU 2147563520,2147564287,US 2147564288,2147564543,AE 2147564544,2147564799,US 2147564800,2147565055,SG 2147565056,2147565311,HK 2147565312,2147565999,TW 2147566000,2147566047,JP 2147566048,2147566079,TW 2147566080,2147569407,US 2147569408,2147569663,TH 2147569664,2147570431,US 2147570432,2147570687,JP 2147570688,2147571455,US 2147571456,2147571711,SG 2147571712,2147573503,US 2147573504,2147573759,SG 2147573760,2147575039,US 2147575040,2147575551,TW 2147575552,2147575807,SG 2147575808,2147576575,US 2147576576,2147576831,TW 2147576832,2147577087,TH 2147577088,2147577599,ID 2147577600,2147579647,US 2147579648,2147579903,ID 2147579904,2147580927,US 2147580928,2147581183,ID 2147581184,2147581439,TH 2147581440,2147592703,US 2147592704,2147592959,HK 2147592960,2147600127,US 2147600128,2147600383,SG 2147600384,2147603711,US 2147603712,2147603967,IN 2147603968,2147942399,US 2147942400,2148007935,DE 2148007936,2148220515,US 2148220516,2148220535,AU 2148220536,2148229151,US 2148229152,2148229183,CA 2148229184,2148459007,US 2148459008,2148459519,TW 2148459520,2148532223,US 2148532224,2148597759,GB 2148597760,2148925439,US 2148925440,2148990975,JP 2148990976,2149253119,US 2149253120,2149384191,JP 2149384192,2150039551,US 2150039552,2150105087,NO 2150105088,2150203391,GB 2150203392,2150236159,AF 2150236160,2150301695,US 2150301696,2150367231,CA 2150367232,2150432767,US 2150432768,2150498303,IT 2150498304,2150957055,US 2150957056,2151022591,JP 2151022592,2151743487,US 2151743488,2151759871,BY 2151759872,2151768063,US 2151768064,2151770111,GB 2151770112,2151772159,BA 2151772160,2151776255,IT 2151776256,2151778303,AT 2151778304,2151780351,RU 2151780352,2151782399,DE 2151782400,2151784447,ES 2151784448,2151792639,IR 2151792640,2151794687,CH 2151794688,2151796735,IT 2151796736,2151800831,DE 2151800832,2151809023,PT 2151809024,2151940095,IT 2151940096,2152464383,RU 2152464384,2152529919,DK 2152529920,2152562687,NO 2152562688,2152595455,DK 2152595456,2152726527,FR 2152726528,2153119743,US 2153119744,2153185279,GB 2153185280,2153250815,SE 2153250816,2153381887,US 2153381888,2153382143,JP 2153382144,2153383679,US 2153383680,2153383935,HK 2153383936,2153384447,US 2153384448,2153385471,GB 2153385472,2153385599,AT 2153385600,2153385663,CZ 2153385664,2153385727,FI 2153385728,2153385791,PL 2153385792,2153385855,PT 2153385856,2153385919,TR 2153385920,2153385983,US 2153385984,2153387007,GB 2153387008,2153387263,CH 2153387264,2153387519,IS 2153387520,2153387775,IE 2153387776,2153388031,CH 2153388032,2153388287,ES 2153388288,2153388543,PL 2153388544,2153391615,US 2153391616,2153391871,HK 2153391872,2153394431,US 2153394432,2153394943,SG 2153394944,2153395455,US 2153395456,2153395711,VN 2153395712,2153396991,US 2153396992,2153397247,IL 2153397248,2153397503,IN 2153397504,2153397759,SA 2153397760,2153398015,QA 2153398016,2153398271,BH 2153398272,2153398783,JP 2153398784,2153399551,US 2153399552,2153399807,KR 2153399808,2153400319,HK 2153400320,2153401087,TW 2153401088,2153401599,MO 2153401600,2153402111,VN 2153402112,2153402367,PH 2153402368,2153403135,KR 2153403136,2153406463,US 2153406464,2153407487,JP 2153407488,2153407743,HK 2153407744,2153407999,AE 2153408000,2153408511,BR 2153408512,2153408767,AU 2153408768,2153409023,PA 2153409024,2153409279,AR 2153409280,2153409535,CR 2153409536,2153409791,CO 2153409792,2153410047,MX 2153410048,2153410303,CA 2153410304,2153410559,TW 2153410560,2153410815,PA 2153410816,2153411071,AR 2153411072,2153411327,CR 2153411328,2153411583,CO 2153411584,2153411839,MX 2153411840,2153412095,SV 2153412096,2153412351,TW 2153412352,2153412607,UY 2153412608,2153413119,AU 2153413120,2153413631,BR 2153413632,2153578495,US 2153578496,2153644031,FR 2153644032,2153906175,US 2153906176,2153971711,GB 2153971712,2154037247,US 2154037248,2154102783,CA 2154102784,2154430463,US 2154430464,2154495999,SG 2154496000,2154561535,US 2154561536,2154627071,CN 2154627072,2155610111,US 2155610112,2155675647,UA 2155675648,2155806719,US 2155806720,2155808767,IT 2155810816,2155812863,FR 2155812864,2155814911,GB 2155814912,2155819007,NL 2155819008,2155819519,DE 2155819520,2155821055,CH 2155821056,2155823103,IT 2155823104,2155825151,DE 2155825152,2155827199,AE 2155827200,2155831295,PL 2155831296,2155833343,RU 2155833344,2155833855,SE 2155833856,2155834623,NL 2155834624,2155834879,LU 2155834880,2155835391,NL 2155835392,2155839487,RO 2155839488,2155843583,FR 2155843584,2155845631,RU 2155845632,2155847679,DE 2155847680,2155849727,ES 2155849728,2155851775,TR 2155853824,2155855871,SE 2155855872,2155872255,SA 2155872256,2156003327,US 2156003328,2156134399,AT 2156134400,2156265471,US 2156265472,2156331007,KR 2156331008,2156593151,US 2156593152,2156658687,IL 2156658688,2156691455,IR 2156691456,2156697599,FR 2156697600,2156699647,GR 2156699648,2156703743,RU 2156703744,2156707839,BG 2156707840,2156709887,RU 2156709888,2156711935,ES 2156711936,2156713983,DE 2156713984,2156716031,NL 2156716032,2156718079,RO 2156718080,2156720127,IS 2156720128,2156724223,BY 2156724224,2156855295,CH 2156855296,2156920831,US 2156920832,2156986367,CA 2156986368,2159017983,US 2159017984,2159083519,DE 2159083520,2159149055,US 2159149056,2159280127,CH 2159280128,2159542271,US 2159542272,2159607807,AU 2159607808,2159673343,IN 2159673344,2159869951,US 2159869952,2159935487,CA 2159935488,2160525311,US 2160525312,2160533503,SG 2160533504,2160541695,NL 2160541696,2160590847,SG 2160590848,2160656383,US 2160656384,2160657407,BR 2160657408,2160658431,HN 2160658432,2160661503,BR 2160661504,2160662527,AR 2160662528,2160664575,BR 2160664576,2160666623,CL 2160666624,2160676863,BR 2160676864,2160677887,AR 2160677888,2160678911,BR 2160678912,2160679935,GF 2160679936,2160684031,BR 2160684032,2160685055,AR 2160685056,2160686079,DO 2160686080,2160687103,CL 2160687104,2160690175,BR 2160690176,2160691199,AR 2160691200,2160693247,BR 2160693248,2160694271,CR 2160694272,2160697343,BR 2160697344,2160698367,EC 2160698368,2160699391,BR 2160699392,2160700415,AR 2160700416,2160713727,BR 2160713728,2160714751,CL 2160714752,2160716799,BR 2160716800,2160717823,AR 2160717824,2160721919,BR 2160721920,2160852991,US 2160852992,2160885759,RU 2160885760,2160893951,AT 2160893952,2160902143,RU 2160902144,2160906239,NL 2160906240,2160908287,FR 2160908288,2160910335,PL 2160910336,2160914431,NL 2160914432,2160918527,SA 2160918528,2161508351,US 2161508352,2161573887,FI 2161573888,2162687999,US 2162688000,2162753535,GB 2162753536,2162819071,CA 2162819072,2162884607,SA 2162884608,2163212287,US 2163212288,2163277823,GB 2163277824,2163408895,US 2163408896,2163474431,GB 2163474432,2163605503,US 2163605504,2163638271,DE 2163638272,2163638527,US 2163638528,2163671039,DE 2163671040,2163867647,US 2163867648,2163933183,AU 2163933184,2164260863,US 2164260864,2164326399,CM 2164326400,2164981759,US 2164981760,2165112831,GB 2165112832,2165178367,DE 2165178368,2165309439,US 2165309440,2165374975,SE 2165374976,2165440511,US 2165440512,2165506047,NG 2165506048,2165571583,US 2165571584,2165637119,FR 2165637120,2165964799,US 2165964800,2166030335,DE 2166030336,2166095871,AT 2166095872,2166161407,CN 2166161408,2166292479,US 2166292480,2166358015,GB 2166358016,2166562559,US 2166562560,2166562815,FI 2166562816,2166571007,US 2166571008,2166575103,GB 2166575104,2166594559,US 2166594560,2166594815,PL 2166594816,2166729471,US 2166729472,2166729727,CA 2166729728,2167209983,US 2167209984,2167242751,DZ 2167242752,2167275519,BF 2167275520,2167930879,US 2167930880,2167996415,NG 2167996416,2168193023,US 2168193024,2168258559,JP 2168258560,2168651775,US 2168651776,2168717311,GB 2168717312,2168782847,US 2168782848,2168913919,DE 2168913920,2169372671,US 2169372672,2169438207,AU 2169438208,2170028031,US 2170028032,2170093567,FR 2170093568,2170159103,US 2170159104,2170224639,VE 2170224640,2170421247,US 2170421248,2170486783,AU 2170486784,2170552319,US 2170552320,2170617855,AU 2170617856,2170683391,CA 2170683392,2170814463,US 2170814464,2170879999,CA 2170880000,2170945535,US 2170945536,2171011071,FR 3652593408,3652593471,ES 3652593472,3652593511,FR 3652593512,3652593519,ES 3652593520,3652593631,FR 3652593632,3652593663,PT 3652593664,3652593943,FR 3652593944,3652593951,ES 3652593952,3652595007,FR 3652595008,3652595071,DE 3652595072,3652595167,FR 3652595168,3652595183,ES 3652595184,3652595871,FR 3652595872,3652595935,PL 3652595936,3652596351,FR 3652596352,3652596415,IT 3652596416,3652596479,FR 3652596480,3652596543,ES 3652596544,3652596799,FR 3652596800,3652596831,CZ 3652596832,3652597183,FR 3652597184,3652597247,DE 3652597248,3652597375,FR 3652597376,3652597383,ES 3652597384,3652597407,FR 3652597408,3652597439,PL 3652597440,3652597887,FR 3652597888,3652597903,GB 3652597904,3652599569,FR 3652599570,3652599570,PT 3652599571,3652599679,FR 3652599680,3652599743,IT 3652599744,3652601855,FR 3652601856,3652603903,PL 3652603904,3652608191,FR 3652608192,3652608223,PT 3652608224,3652608255,FR 3652608256,3652608511,GB 3652608512,3652608639,FR 3652608640,3652608767,GB 3652608768,3652609023,FR 3652609024,3652609279,GB 3652609280,3652609503,FR 3652609504,3652609535,FI 3652609536,3652609727,FR 3652609728,3652609759,PL 3652609760,3652609791,CZ 3652609792,3652609823,FR 3652609824,3652609855,CZ 3652609856,3652609919,FR 3652609920,3652609983,ES 3652609984,3652610047,BE 3652610048,3652611135,FR 3652611136,3652611199,ES 3652611200,3652611231,FR 3652611232,3652611263,PT 3652611264,3652611679,FR 3652611680,3652611711,PT 3652611712,3652611775,NL 3652611776,3652612223,FR 3652612224,3652612287,ES 3652612288,3652612351,FR 3652612352,3652612479,GB 3652612480,3652612543,IE 3652612544,3652612607,NL 3652612608,3652613335,FR 3652613336,3652613343,ES 3652613344,3652613375,FR 3652613376,3652613407,FI 3652613408,3652613615,FR 3652613616,3652613623,ES 3652613624,3652613679,FR 3652613680,3652613695,LT 3652613696,3652614015,FR 3652614016,3652614079,BE 3652614080,3652615871,FR 3652615872,3652615935,DE 3652615936,3652620639,FR 3652620640,3652620671,CZ 3652620672,3652620735,PT 3652620736,3652620799,FR 3652620800,3652620831,PT 3652620832,3652621247,FR 3652621248,3652621311,DE 3652621312,3652621375,FR 3652621376,3652621439,ES 3652621440,3652621503,FR 3652621504,3652621567,IT 3652621568,3652621631,FR 3652621632,3652621663,PT 3652621664,3652621823,FR 3652621824,3652621951,IE 3652621952,3652622271,FR 3652622272,3652622335,GB 3652622336,3652622879,FR 3652622880,3652622911,CZ 3652622912,3652623679,FR 3652623680,3652623807,NL 3652623808,3652624191,FR 3652624192,3652624319,IT 3652624320,3652628479,FR 3652628480,3652628543,IT 3652628544,3652628607,FR 3652628608,3652628639,PL 3652628640,3652628855,FR 3652628856,3652628863,ES 3652628864,3652629743,FR 3652629744,3652629759,ES 3652629760,3652630015,FR 3652630016,3652630031,ES 3652630032,3652630079,FR 3652630080,3652630111,PL 3652630112,3652631295,FR 3652631296,3652631359,BE 3652631360,3652631391,FR 3652631392,3652631407,CH 3652631408,3652631423,FR 3652631424,3652631455,PL 3652631456,3652631551,FR 3652631552,3652631583,CZ 3652631584,3652631807,FR 3652631808,3652631823,GB 3652631824,3652632031,FR 3652632032,3652632063,PT 3652632064,3652632303,FR 3652632304,3652632311,ES 3652632312,3652633599,FR 3652633600,3652634623,DE 3652634624,3652635647,PL 3652635648,3652638655,FR 3652638656,3652638719,ES 3652638720,3652638815,FR 3652638816,3652638847,FI 3652638848,3652638975,GB 3652638976,3652639359,FR 3652639360,3652639423,DE 3652639424,3652639679,FR 3652639680,3652639807,NL 3652639808,3652640575,FR 3652640576,3652640703,GB 3652640704,3652640711,FR 3652640712,3652640719,ES 3652640720,3652640767,FR 3652640768,3652640831,ES 3652640832,3652641727,FR 3652641728,3652641791,GB 3652641792,3652642111,FR 3652642112,3652642175,IE 3652642176,3652642239,FR 3652642240,3652642303,DE 3652642304,3652642367,FR 3652642368,3652642431,GB 3652642432,3652642719,FR 3652642720,3652642751,PT 3652642752,3652642975,FR 3652642976,3652643007,IE 3652643008,3652643375,FR 3652643376,3652643379,ES 3652643380,3652643519,FR 3652643520,3652643583,NL 3652643584,3652643647,ES 3652643648,3652644031,FR 3652644032,3652644063,BE 3652644064,3652644199,FR 3652644200,3652644215,ES 3652644216,3652644223,FR 3652644224,3652644239,NL 3652644240,3652644247,FR 3652644248,3652644255,ES 3652644256,3652644351,FR 3652644352,3652644383,FI 3652644384,3652644415,PL 3652644416,3652644575,FR 3652644576,3652644607,DE 3652644608,3652645119,FR 3652645120,3652645503,GB 3652645504,3652645663,FR 3652645664,3652645695,FI 3652645696,3652645887,FR 3652645888,3652646015,NL 3652646016,3652646079,ES 3652646080,3652646111,FR 3652646112,3652646143,CZ 3652646144,3652646271,NL 3652646272,3652646655,FR 3652646656,3652646719,ES 3652646720,3652646799,FR 3652646800,3652646815,PL 3652646816,3652646847,FR 3652646848,3652646863,FI 3652646864,3652648847,FR 3652648848,3652648863,LT 3652648864,3652648895,FI 3652648896,3652648959,DE 3652648960,3652714495,IE 3652714496,3653238783,DE 3653238784,3653369855,CH 3653369856,3653373951,IT 3653373952,3653378047,NL 3653378048,3653382143,DE 3653382144,3653386239,CH 3653386240,3653390335,DE 3653390336,3653394431,FR 3653394432,3653402623,NL 3653402624,3653406557,GB 3653406558,3653406558,GN 3653406559,3653406617,GB 3653406618,3653406618,GN 3653406619,3653407103,GB 3653407104,3653407111,UG 3653407112,3653408071,GB 3653408072,3653408079,NG 3653408080,3653408231,GB 3653408232,3653408239,KE 3653408240,3653410815,GB 3653410816,3653414911,CZ 3653414912,3653419007,IT 3653419008,3653423103,IL 3653423104,3653427199,GB 3653427200,3653431295,DE 3653431296,3653435391,RU 3653435392,3653439487,DE 3653439488,3653443583,FR 3653443584,3653447679,DE 3653447680,3653451775,LV 3653451776,3653464063,RU 3653464064,3653468159,NL 3653468160,3653472255,GR 3653476352,3653480447,CZ 3653480448,3653484543,DK 3653484544,3653488639,TR 3653488640,3653492735,RU 3653492736,3653500927,NL 3653500928,3653505023,GB 3653505024,3653509119,KZ 3653509120,3653513215,NL 3653513216,3653517311,NO 3653517312,3653525503,AT 3653525504,3653529599,RU 3653529600,3653533695,CZ 3653533696,3653537791,IT 3653537792,3653541887,AT 3653541888,3653545983,UA 3653545984,3653550079,CH 3653550080,3653554175,MK 3653554176,3653558271,CZ 3653558272,3653566463,GB 3653566464,3653570559,RU 3653570560,3653574655,ES 3653574656,3653578751,CZ 3653578752,3653582847,SE 3653582848,3653586943,PL 3653586944,3653591039,DE 3653591040,3653595135,LU 3653595136,3653599231,RU 3653599232,3653601279,CH 3653601280,3653603327,BA 3653603328,3653607423,CZ 3653611520,3653615615,HU 3653615616,3653619711,RU 3653619712,3653623807,CH 3653623808,3653636095,RU 3653636096,3653640191,NL 3653640192,3653648383,GB 3653648384,3653652479,SE 3653652480,3653656575,RU 3653656576,3653660671,GB 3653660672,3653664767,CZ 3653664768,3653668863,DE 3653668864,3653672959,SE 3653672960,3653681151,RU 3653681152,3653685247,ES 3653685248,3653689343,DK 3653689344,3653693439,LV 3653693440,3653697535,DE 3653697536,3653705727,IT 3653705728,3653708331,NO 3653708332,3653708332,FI 3653708333,3653713919,NO 3653713920,3653718015,DE 3653718016,3653722111,AT 3653722112,3653730303,LV 3653730304,3653734399,BA 3653734400,3653738495,KE 3653738496,3653746687,GB 3653746688,3653750783,DE 3653750784,3653754879,RU 3653754880,3653758975,UA 3653758976,3653763071,RU 3653763072,3654025215,IT 3654025216,3654287359,GB 3654287360,3654608404,SE 3654608405,3654608405,NO 3654608406,3654608895,SE 3654608896,3654609919,NO 3654609920,3654610431,SE 3654610432,3654610943,FR 3654610944,3654610951,SE 3654610952,3654610959,DE 3654610960,3654612231,SE 3654612232,3654612239,AT 3654612240,3654612271,SE 3654612272,3654612287,AT 3654612288,3654614047,SE 3654614048,3654614063,GB 3654614064,3654614079,SE 3654614080,3654614271,FI 3654614272,3654811647,SE 3654811648,3654942719,ES 3654942720,3655073791,IR 3655073792,3655335935,IT 3655335936,3657433087,DE 3657433088,3659415455,CN 3659415456,3659415487,SG 3659415488,3659530239,CN 3659530240,3659595775,TW 3659595776,3659628543,ID 3659628544,3659661311,JP 3659661312,3659792383,TW 3659792384,3660054527,KR 3660054528,3660578815,JP 3660578816,3661103103,KR 3661103104,3663986687,CN 3663986688,3663987711,AU 3663987712,3663987967,ID 3663987968,3663989247,JP 3663989248,3663989503,VN 3663989504,3663989759,ID 3663989760,3663990271,AU 3663990272,3663990527,VN 3663990528,3663990783,JP 3663990784,3663991295,HK 3663991296,3663991551,MY 3663991552,3663991807,AU 3663992064,3663992319,NZ 3663992320,3663992575,MY 3663992576,3663993599,NZ 3663993600,3663996159,ID 3663996160,3663996415,AU 3663996416,3663996671,TH 3663996672,3663997183,AU 3663997184,3663997439,ID 3663997440,3663997695,JP 3663997696,3663997951,AU 3663997952,3663998207,MY 3663998208,3663998463,JP 3663998464,3663998975,TH 3663998976,3663999487,IN 3663999488,3663999743,AU 3664000000,3664000767,AU 3664000768,3664001023,ID 3664001024,3664001279,NZ 3664001280,3664001535,LK 3664001536,3664001791,MY 3664002048,3664002303,VN 3664002304,3664002559,LK 3664002560,3664003327,ID 3664003328,3664003583,NZ 3664003584,3664003839,TH 3664003840,3664004095,JP 3664004352,3664004607,MY 3664004864,3664005119,KH 3664005120,3664005887,ID 3664005888,3664006143,MY 3664006144,3664006399,AU 3664006400,3664006655,PF 3664006656,3664006911,AU 3664007168,3664008191,AU 3664008192,3664008447,MN 3664008448,3664008703,PK 3664008960,3664010239,AU 3664010240,3664052223,CN 3664052224,3664084991,NZ 3664084992,3664117759,KR 3664117760,3664248831,HK 3664248832,3664642047,CN 3664642048,3664707583,JP 3664707584,3664773119,MY 3664773120,3666870271,JP 3666870272,3666960455,KR 3666960456,3666960456,US 3666960457,3667918847,KR 3667918848,3668967423,TW 3668967424,3669491711,JP 3669491712,3669557247,TW 3669557248,3669590015,AU 3669590016,3669606399,JP 3669606400,3669614591,CN 3669614592,3669616639,NZ 3669616640,3669618687,AU 3669618688,3669620735,CN 3669620736,3669622783,IN 3669622784,3669688319,SG 3669688320,3669753855,TW 3669753856,3670015999,HK 3670016000,3671064575,CN 3671064576,3671130111,MY 3671130112,3671195647,KR 3671195648,3671326719,TW 3671326720,3671392255,SG 3671392256,3671457791,HK 3671457792,3671588863,AU 3671588864,3672637439,JP 3672637440,3673161727,KR 3673161728,3673686015,CN 3673686016,3673751551,IN 3673751552,3673817087,CN 3673817088,3673882623,HK 3673882624,3673948159,JP 3673948160,3674210303,HK 3674210304,3678404607,JP 3678404608,3678535679,IN 3678535680,3678666751,JP 3678666752,3678928895,TW 3678928896,3678994431,CN 3678994432,3679027199,HK 3679027200,3679059967,JP 3679059968,3679158271,SG 3679158272,3679191039,JP 3679191040,3679453183,HK 3679453184,3679584255,TW 3679584256,3679649791,CN 3679649792,3679682559,ID 3679682560,3679715327,CN 3679715328,3679977471,TW 3679977472,3680108543,NZ 3680108544,3680124927,TW 3680124928,3680125951,IN 3680125952,3680129023,CN 3680129024,3680133119,PH 3680133120,3680137215,IN 3680137216,3680141311,HK 3680141312,3680174079,AU 3680174080,3680206847,TW 3680206848,3680239615,IN 3680239616,3680403455,MY 3680403456,3680436223,JP 3680436224,3680501759,MY 3680501760,3682598911,JP 3682598912,3684575268,CN 3684575269,3684575269,HK 3684575270,3684696063,CN 3684696064,3688366079,JP 3688366080,3689938943,CN 3689938944,3690070015,KR 3690070016,3690463231,CN 3690463232,3690987519,KR 3690987520,3695181823,JP 3695181824,3697278975,KR 3697278976,3697573887,JP 3697573888,3697582079,GB 3697582080,3697586175,SG 3697586176,3697606655,JP 3697606656,3697655807,AU 3697655808,3697672191,CN 3697672192,3697737727,JP 3697737728,3697803263,KR 3697803264,3698327551,JP 3698327552,3698589695,CN 3698589696,3699376127,KR 3699376128,3700424703,TW 3700424704,3700752383,JP 3700752384,3700817919,KR 3700817920,3700981759,JP 3700981760,3701014527,CN 3701014528,3701080063,JP 3701080064,3701211135,CN 3701211136,3701252095,JP 3701252096,3701256191,NC 3701256192,3701258239,SG 3701258240,3701260287,IN 3701260288,3701293055,JP 3701293056,3701301247,AU 3701301248,3701305343,ID 3701305344,3701309439,TW 3701309440,3701374975,JP 3701374976,3701375999,IN 3701376000,3701377023,HK 3701377024,3701380095,IN 3701380096,3701381119,KH 3701381120,3701390335,IN 3701390336,3701391359,AU 3701391360,3701392383,IN 3701392384,3701393407,HK 3701393408,3701394431,MY 3701394432,3701395455,BD 3701395456,3701396479,MY 3701396480,3701397247,NZ 3701397248,3701397503,AU 3701397504,3701398527,JP 3701398528,3701399551,MV 3701399552,3701400575,HK 3701400576,3701401599,TW 3701401600,3701402623,BD 3701402624,3701403647,BT 3701403648,3701404671,CN 3701404672,3701405695,HK 3701405696,3701406719,JP 3701406720,3701407743,HK 3701407744,3701473279,JP 3701473280,3704619007,CN 3704619008,3705667583,JP 3705667584,3705929727,IN 3705929728,3706060799,TW 3706060800,3706126335,KR 3706126336,3706142719,CN 3706142720,3706159103,VN 3706159104,3706191871,CN 3706191872,3706207107,SG 3706207108,3706207108,US 3706207109,3706208255,SG 3706208256,3706224639,CN 3706224640,3706225663,HK 3706225664,3706226687,JP 3706226688,3706231807,HK 3706231808,3706232831,JP 3706232832,3706233343,HK 3706233344,3706234367,JP 3706234368,3706237951,HK 3706237952,3706238975,JP 3706238976,3706244095,HK 3706244096,3706244863,JP 3706244864,3706245887,HK 3706245888,3706246143,JP 3706246144,3706253823,HK 3706253824,3706254335,JP 3706254336,3706256895,HK 3706256896,3706257151,JP 3706257152,3706257407,HK 3706257408,3706322943,AU 3706322944,3706388479,CN 3706388480,3706781695,AU 3706781696,3706847231,HK 3706847232,3706978303,CN 3706978304,3707109375,AU 3707109376,3707174911,HK 3707174912,3707207679,JP 3707207680,3707208703,BD 3707208704,3707209727,NZ 3707209728,3707211775,CN 3707211776,3707215871,NP 3707215872,3707217919,BD 3707217920,3707219967,ID 3707219968,3707222015,AU 3707222016,3707224063,JP 3707224064,3707240447,LK 3707240448,3707568127,CN 3707568128,3707633663,AU 3707633664,3707699199,JP 3707699200,3707764735,SG 3707764736,3708600319,CN 3708600320,3708616703,JP 3708616704,3708813311,CN 3708813312,3715629055,JP 3715629056,3715653631,TW 3715653632,3715655679,BD 3715655680,3715657727,IN 3715657728,3715661823,SG 3715661824,3715670015,AU 3715670016,3715671039,KH 3715671040,3715672063,AU 3715672064,3715674111,JP 3715674112,3715678207,HK 3715678208,3715694591,PK 3715694592,3715710975,VN 3715710976,3715719167,AU 3715719168,3715727359,PH 3715727360,3715729151,AU 3715729152,3715729407,NZ 3715729408,3715735551,AU 3715735552,3715741695,JP 3715741696,3715743743,PH 3715743744,3715760127,JP 3715760128,3715891199,CN 3715891200,3716153343,HK 3716153344,3716170239,SG 3716170240,3716170494,TH 3716170495,3716171519,SG 3716171520,3716171775,JP 3716171776,3716172031,SG 3716172032,3716172287,JP 3716172288,3716173055,SG 3716173056,3716173311,JP 3716173312,3716173567,SG 3716173568,3716173823,JP 3716173824,3716174079,SG 3716174080,3716174083,TH 3716174084,3716174335,JP 3716174336,3716175615,SG 3716175616,3716176895,JP 3716176896,3716178175,SG 3716178176,3716178943,JP 3716178944,3716179967,SG 3716179968,3716181759,JP 3716181760,3716182783,SG 3716182784,3716183295,JP 3716183296,3716183551,SG 3716183552,3716184063,JP 3716184064,3716184319,SG 3716184320,3716184575,JP 3716184576,3716184831,SG 3716184832,3716185087,JP 3716185088,3716186111,SG 3716186112,3716415487,CN 3716415488,3716431871,VN 3716431872,3716440063,KR 3716440064,3716444159,JP 3716444160,3716446207,PK 3716446208,3716464639,JP 3716464640,3716481023,ID 3716481024,3716489215,VN 3716489216,3716493311,MY 3716493312,3716497407,KR 3716497408,3716513791,JP 3716513792,3716530175,KR 3716530176,3716538367,AU 3716538368,3716546559,CN 3716546560,3716677631,IN 3716677632,3716808703,CN 3716808704,3718840319,KR 3718840320,3718905855,TW 3718905856,3719036927,JP 3719036928,3719823359,CN 3719823360,3720347647,JP 3720347648,3720859647,CN 3720859648,3720863743,AU 3720863744,3723493375,CN 3723493376,3725590527,JP 3725590528,3730833407,CN 3730833408,3732602879,KR 3732602880,3732668415,TH 3732668416,3732733951,ID 3732733952,3732799487,CN 3732799488,3732832255,PH 3732832256,3732865023,CN 3732865024,3732930559,PH 3732930560,3733979135,CN 3733979136,3734503423,JP 3734503424,3734765567,NZ 3734765568,3734896639,TW 3734896640,3735027711,JP 3735027712,3735289855,CN 3735289856,3735388159,SG 3735388160,3735404543,LK 3735404544,3735420927,ID 3735420928,3735551999,HK 3735552000,3739222015,CN 3739222016,3739570175,JP 3739570176,3739572223,ID 3739572224,3739574271,AU 3739574272,3739680767,JP 3739680768,3739697151,KR 3739697152,3739746303,JP 3739746304,3740270591,KR 3740270592,3740925951,CN 3740925952,3741024255,TW 3741024256,3741057023,KR 3741057024,3741319167,VN 3741319168,3742367743,CN 3742367744,3742629887,HK 3742629888,3742760959,CN 3742760960,3742892031,TW 3742892032,3742957567,TH 3742957568,3742973951,PH 3742973952,3742982143,SG 3742982144,3742986239,ID 3742986240,3742988287,AU 3742988288,3742990335,VU 3742990336,3743006719,JP 3743006720,3743014911,TH 3743014912,3743016959,AU 3743016960,3743019007,SG 3743019008,3743022079,MY 3743022080,3743023103,BD 3743023104,3743027199,TW 3743027200,3743028223,IN 3743028224,3743029247,AF 3743029248,3743030271,NZ 3743030272,3743035391,IN 3743035392,3743039487,HK 3743039488,3743055871,TW 3743055872,3743088639,KR 3743088640,3743093647,AU 3743093648,3743093648,NZ 3743093649,3743096831,AU 3743096832,3743105023,TW 3743105024,3743106047,AU 3743106048,3743109119,JP 3743109120,3743113215,BD 3743113216,3743115263,AU 3743115264,3743117311,VN 3743117312,3743118335,BD 3743118336,3743119359,JP 3743119360,3743120383,IN 3743120384,3743121407,JP 3743121408,3743125503,MY 3743125504,3743129599,ID 3743129600,3743130623,HK 3743130624,3743130879,SG 3743130880,3743131135,HK 3743131136,3743133695,SG 3743133696,3743134719,AU 3743134720,3743135743,JP 3743135744,3743136767,CN 3743136768,3743137791,MY 3743137792,3743154175,TH 3743154176,3743186943,MY 3743186944,3743219711,KR 3743219712,3743252479,JP 3743252480,3743264767,NC 3743264768,3743268863,JP 3743268864,3743272959,IN 3743272960,3743273983,CN 3743273984,3743275007,BD 3743275008,3743276031,HK 3743276032,3743277055,IN 3743277056,3743281151,PK 3743281152,3743282175,AU 3743282176,3743283199,JP 3743283200,3743284223,HK 3743284224,3743285247,CN 3743285248,3743416319,IN 3743416320,3745513471,KR 3745513472,3749052415,CN 3749052416,3749183487,HK 3749183488,3749838847,CN 3749838848,3749839871,SG 3749839872,3749840895,IN 3749840896,3749841919,CN 3749841920,3749842943,AU 3749842944,3749843967,PH 3749843968,3749844991,ID 3749844992,3749846015,AU 3749846016,3749847039,IN 3749847040,3749855231,HK 3749855232,3749969919,KR 3749969920,3750232063,JP 3750232064,3750756351,TW 3750756352,3752067071,CN 3752067072,3752132607,ID 3752132608,3752133631,BD 3752133632,3752134655,ID 3752134656,3752136703,TW 3752136704,3752137727,NZ 3752137728,3752138751,JP 3752138752,3752140799,IN 3752140800,3752148991,JP 3752148992,3752153087,NZ 3752153088,3752157183,JP 3752157184,3752165375,AU 3752165376,3752198143,KR 3752198144,3752329215,CN 3752329216,3752853503,KR 3752853504,3753902079,IN 3753902080,3754033151,CN 3754033152,3754164223,KR 3754164224,3754229759,IN 3754229760,3754295295,HK 3754295296,3754426367,CN 3754426368,3754491903,TW 3754491904,3754688511,CN 3754688512,3754950655,TH 3754950656,3755474943,CN 3755474944,3755737087,JP 3755737088,3755868159,CN 3755868160,3755933695,KR 3755933696,3755966463,JP 3755966464,3755974655,IN 3755974656,3755976703,JP 3755976704,3755978751,KH 3755978752,3755986943,CN 3755986944,3755988991,JP 3755988992,3755990015,HK 3755990016,3755991039,SG 3755991040,3755999231,JP 3755999232,3757047807,IN 3757047808,3757834239,CN 3757834240,3757850623,AU 3757850624,3757858815,JP 3757858816,3757862911,AU 3757862912,3757867007,JP 3757867008,3757875519,CN 3757875520,3757875583,HK 3757875584,3757899775,CN 3757899776,3757965311,KR 3757965312,3758063615,CN 3758063616,3758079999,HK 3758080000,3758088191,KR 3758088192,3758090239,ID 3758090240,3758091263,AU 3758091264,3758092287,CN 3758092288,3758093311,HK 3758093312,3758094335,IN 3758094336,3758095359,HK 3758095360,3758095871,CN 3758095872,3758096127,SG 3758096128,3758096383,AU snowflake-1.1.0/broker/test_geoip6000066400000000000000000001123301411410351300171320ustar00rootroot00000000000000# Last updated based on February 7 2018 Maxmind GeoLite2 Country # wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz # gunzip GeoLite2-Country.mmdb.gz # python mmdb-convert.py GeoLite2-Country.mmdb 600:8801:9400:5a1:948b:ab15:dde3:61a3,600:8801:9400:5a1:948b:ab15:dde3:61a3,US 2001:200::,2001:200:ffff:ffff:ffff:ffff:ffff:ffff,JP 2001:208::,2001:208:ffff:ffff:ffff:ffff:ffff:ffff,SG 2001:218::,2001:218:ffff:ffff:ffff:ffff:ffff:ffff,JP 2001:220::,2001:220:ffff:ffff:ffff:ffff:ffff:ffff,KR 2001:230::,2001:230:ffff:ffff:ffff:ffff:ffff:ffff,KR 2001:238::,2001:238:ffff:ffff:ffff:ffff:ffff:ffff,TW 2001:240::,2001:240:ffff:ffff:ffff:ffff:ffff:ffff,JP 2620:21:2000::,2620:21:2000:ffff:ffff:ffff:ffff:ffff,US 2620:21:4000::,2620:21:4000:ffff:ffff:ffff:ffff:ffff,US 2620:21:6000::,2620:21:600f:ffff:ffff:ffff:ffff:ffff,US 2620:21:8000::,2620:21:8000:ffff:ffff:ffff:ffff:ffff,US 2620:21:a000::,2620:21:a000:ffff:ffff:ffff:ffff:ffff,US 2620:21:c000::,2620:21:c000:ffff:ffff:ffff:ffff:ffff,CA 2620:21:e000::,2620:21:e000:ffff:ffff:ffff:ffff:ffff,US 2620:22::,2620:22::ffff:ffff:ffff:ffff:ffff,US 2620:22:2000::,2620:22:2000:ffff:ffff:ffff:ffff:ffff,US 2620:22:4000::,2620:22:4000:ffff:ffff:ffff:ffff:ffff,CA 2620:22:6000::,2620:22:6000:ffff:ffff:ffff:ffff:ffff,US 2620:c2:8000::,2620:c2:8000:ffff:ffff:ffff:ffff:ffff,US 2620:c2:c000::,2620:c2:c000:ffff:ffff:ffff:ffff:ffff,US 2620:c3::,2620:c3::ffff:ffff:ffff:ffff:ffff,US 2620:c3:4000::,2620:c3:4000:ffff:ffff:ffff:ffff:ffff,US 2620:c3:8000::,2620:c3:8000:ffff:ffff:ffff:ffff:ffff,US 2620:c3:c000::,2620:c3:c00f:ffff:ffff:ffff:ffff:ffff,US 2620:c4::,2620:c4::ffff:ffff:ffff:ffff:ffff,US 2620:c4:4000::,2620:c4:4000:ffff:ffff:ffff:ffff:ffff,US 2620:c4:8000::,2620:c4:8000:ffff:ffff:ffff:ffff:ffff,US 2620:c4:c000::,2620:c4:c000:ffff:ffff:ffff:ffff:ffff,CA 2620:c5::,2620:c5::ffff:ffff:ffff:ffff:ffff,US 2620:c5:4000::,2620:c5:4000:ffff:ffff:ffff:ffff:ffff,US 2620:c5:c000::,2620:c5:c000:ffff:ffff:ffff:ffff:ffff,US 2620:c6::,2620:c6::ffff:ffff:ffff:ffff:ffff,US 2620:c6:4000::,2620:c6:4000:ffff:ffff:ffff:ffff:ffff,US 2620:c6:8000::,2620:c6:8000:ffff:ffff:ffff:ffff:ffff,US 2620:c6:c000::,2620:c6:c000:ffff:ffff:ffff:ffff:ffff,US 2620:c7::,2620:c7::ffff:ffff:ffff:ffff:ffff,US 2620:c7:4000::,2620:c7:4000:ffff:ffff:ffff:ffff:ffff,US 2620:c7:8000::,2620:c7:8000:ffff:ffff:ffff:ffff:ffff,US 2620:c7:c000::,2620:c7:c000:ffff:ffff:ffff:ffff:ffff,US 2620:c8::,2620:c8::ffff:ffff:ffff:ffff:ffff,US 2620:c8:4000::,2620:c8:4000:ffff:ffff:ffff:ffff:ffff,US 2620:c8:c000::,2620:c8:c00f:ffff:ffff:ffff:ffff:ffff,US 2620:c9::,2620:c9::ffff:ffff:ffff:ffff:ffff,US 2620:c9:4000::,2620:c9:4000:ffff:ffff:ffff:ffff:ffff,US 2620:c9:8000::,2620:c9:8000:ffff:ffff:ffff:ffff:ffff,US 2620:c9:c000::,2620:c9:c000:ffff:ffff:ffff:ffff:ffff,US 2620:ca::,2620:ca::ffff:ffff:ffff:ffff:ffff,US 2620:ca:4000::,2620:ca:4000:ffff:ffff:ffff:ffff:ffff,US 2620:ca:8000::,2620:ca:8000:ffff:ffff:ffff:ffff:ffff,US 2620:ca:c000::,2620:ca:c000:ffff:ffff:ffff:ffff:ffff,US 2620:cb::,2620:cb:f:ffff:ffff:ffff:ffff:ffff,US 2620:cb:4000::,2620:cb:4000:ffff:ffff:ffff:ffff:ffff,US 2620:cb:8000::,2620:cb:8000:ffff:ffff:ffff:ffff:ffff,US 2620:cb:c000::,2620:cb:c000:ffff:ffff:ffff:ffff:ffff,US 2620:cc::,2620:cc::ffff:ffff:ffff:ffff:ffff,US 2620:cc:4000::,2620:cc:4000:ffff:ffff:ffff:ffff:ffff,US 2620:cc:8000::,2620:cc:8000:ffff:ffff:ffff:ffff:ffff,US 2620:cc:c000::,2620:cc:c000:ffff:ffff:ffff:ffff:ffff,US 2620:cd::,2620:cd::ffff:ffff:ffff:ffff:ffff,US 2620:cd:4000::,2620:cd:4000:ffff:ffff:ffff:ffff:ffff,US 2620:cd:8000::,2620:cd:8000:ffff:ffff:ffff:ffff:ffff,US 2620:cd:c000::,2620:cd:c000:ffff:ffff:ffff:ffff:ffff,US 2620:ce::,2620:ce::ffff:ffff:ffff:ffff:ffff,US 2620:ce:4000::,2620:ce:4000:ffff:ffff:ffff:ffff:ffff,US 2620:ce:8000::,2620:ce:8000:ffff:ffff:ffff:ffff:ffff,US 2620:ce:c000::,2620:ce:c000:ffff:ffff:ffff:ffff:ffff,US 2620:cf:4000::,2620:cf:4000:ffff:ffff:ffff:ffff:ffff,US 2620:cf:8000::,2620:cf:8000:ffff:ffff:ffff:ffff:ffff,US 2620:cf:c000::,2620:cf:c00f:ffff:ffff:ffff:ffff:ffff,US 2620:d0::,2620:d0::ffff:ffff:ffff:ffff:ffff,US 2620:d0:4000::,2620:d0:4000:ffff:ffff:ffff:ffff:ffff,US 2620:d0:8000::,2620:d0:8000:ffff:ffff:ffff:ffff:ffff,US 2620:d0:c000::,2620:d0:c000:ffff:ffff:ffff:ffff:ffff,US 2620:d1::,2620:d1::ffff:ffff:ffff:ffff:ffff,US 2620:d1:4000::,2620:d1:4000:ffff:ffff:ffff:ffff:ffff,US 2620:d1:8000::,2620:d1:8000:ffff:ffff:ffff:ffff:ffff,US 2620:d1:c000::,2620:d1:c000:ffff:ffff:ffff:ffff:ffff,US 2620:d2::,2620:d2::ffff:ffff:ffff:ffff:ffff,US 2620:d2:4000::,2620:d2:4000:ffff:ffff:ffff:ffff:ffff,US 2620:d2:8000::,2620:d2:8000:ffff:ffff:ffff:ffff:ffff,US 2620:d2:c000::,2620:d2:c000:ffff:ffff:ffff:ffff:ffff,CA 2620:d3:4000::,2620:d3:4000:ffff:ffff:ffff:ffff:ffff,US 2620:d3:8000::,2620:d3:8000:ffff:ffff:ffff:ffff:ffff,US 2620:d3:c000::,2620:d3:c000:ffff:ffff:ffff:ffff:ffff,US 2620:d4::,2620:d4::ffff:ffff:ffff:ffff:ffff,US 2620:d4:4000::,2620:d4:4000:ffff:ffff:ffff:ffff:ffff,US 2620:d4:8000::,2620:d4:8000:ffff:ffff:ffff:ffff:ffff,US 2620:d5::,2620:d5::ffff:ffff:ffff:ffff:ffff,US 2620:d5:4000::,2620:d5:4000:ffff:ffff:ffff:ffff:ffff,US 2620:d5:8000::,2620:d5:8000:ffff:ffff:ffff:ffff:ffff,US 2620:d5:c000::,2620:d5:c000:ffff:ffff:ffff:ffff:ffff,US 2620:d6::,2620:d6::ffff:ffff:ffff:ffff:ffff,US 2620:d6:4000::,2620:d6:4000:ffff:ffff:ffff:ffff:ffff,US 2620:d6:8000::,2620:d6:8000:ffff:ffff:ffff:ffff:ffff,US 2620:d6:c000::,2620:d6:c000:ffff:ffff:ffff:ffff:ffff,US 2620:d7::,2620:d7::ffff:ffff:ffff:ffff:ffff,US 2620:d7:4000::,2620:d7:4000:ffff:ffff:ffff:ffff:ffff,CA 2620:d7:8000::,2620:d7:8000:ffff:ffff:ffff:ffff:ffff,US 2620:d7:c000::,2620:d7:c000:ffff:ffff:ffff:ffff:ffff,US 2620:d8::,2620:d8::ffff:ffff:ffff:ffff:ffff,US 2620:d8:4000::,2620:d8:4000:ffff:ffff:ffff:ffff:ffff,US 2620:d8:8000::,2620:d8:8000:ffff:ffff:ffff:ffff:ffff,US 2620:d8:c000::,2620:d8:c000:ffff:ffff:ffff:ffff:ffff,US 2620:d9::,2620:d9::ffff:ffff:ffff:ffff:ffff,US 2620:d9:4000::,2620:d9:4000:ffff:ffff:ffff:ffff:ffff,US 2620:d9:8000::,2620:d9:8000:ffff:ffff:ffff:ffff:ffff,US 2620:d9:c000::,2620:d9:c000:ffff:ffff:ffff:ffff:ffff,US 2620:da::,2620:da::ffff:ffff:ffff:ffff:ffff,US 2620:da:4000::,2620:da:4000:ffff:ffff:ffff:ffff:ffff,US 2620:da:c000::,2620:da:c000:ffff:ffff:ffff:ffff:ffff,US 2620:db::,2620:db::ffff:ffff:ffff:ffff:ffff,US 2620:db:4000::,2620:db:4000:ffff:ffff:ffff:ffff:ffff,CA 2620:db:8000::,2620:db:8000:ffff:ffff:ffff:ffff:ffff,US 2620:db:c000::,2620:db:c000:ffff:ffff:ffff:ffff:ffff,US 2620:dc::,2620:dc::ffff:ffff:ffff:ffff:ffff,US 2620:dc:8::,2620:dc:8:ffff:ffff:ffff:ffff:ffff,US 2620:dc:4000::,2620:dc:40ff:ffff:ffff:ffff:ffff:ffff,US 2620:dc:8000::,2620:dc:8000:ffff:ffff:ffff:ffff:ffff,CA 2620:dc:c000::,2620:dc:c000:ffff:ffff:ffff:ffff:ffff,US 2620:dd::,2620:dd::ffff:ffff:ffff:ffff:ffff,CA 2620:dd:4000::,2620:dd:4000:ffff:ffff:ffff:ffff:ffff,US 2620:dd:8000::,2620:dd:8000:ffff:ffff:ffff:ffff:ffff,US 2620:dd:c000::,2620:dd:c000:ffff:ffff:ffff:ffff:ffff,US 2620:de::,2620:de::ffff:ffff:ffff:ffff:ffff,US 2620:de:4000::,2620:de:4000:ffff:ffff:ffff:ffff:ffff,US 2620:de:8000::,2620:de:8000:ffff:ffff:ffff:ffff:ffff,US 2620:de:c000::,2620:de:c000:ffff:ffff:ffff:ffff:ffff,US 2620:df::,2620:df::ffff:ffff:ffff:ffff:ffff,US 2620:df:4000::,2620:df:400f:ffff:ffff:ffff:ffff:ffff,US 2620:df:8000::,2620:df:8000:ffff:ffff:ffff:ffff:ffff,US 2620:df:c000::,2620:df:c000:ffff:ffff:ffff:ffff:ffff,US 2620:e0::,2620:e0::ffff:ffff:ffff:ffff:ffff,US 2620:e0:4000::,2620:e0:4000:ffff:ffff:ffff:ffff:ffff,US 2620:e0:8000::,2620:e0:8000:ffff:ffff:ffff:ffff:ffff,US 2620:e0:c000::,2620:e0:c000:ffff:ffff:ffff:ffff:ffff,US 2620:e1::,2620:e1::ffff:ffff:ffff:ffff:ffff,US 2620:e1:4000::,2620:e1:4000:ffff:ffff:ffff:ffff:ffff,US 2620:e1:8000::,2620:e1:8000:ffff:ffff:ffff:ffff:ffff,US 2620:e1:c000::,2620:e1:c000:ffff:ffff:ffff:ffff:ffff,VG 2620:e2::,2620:e2::ffff:ffff:ffff:ffff:ffff,US 2620:e2:4000::,2620:e2:4000:ffff:ffff:ffff:ffff:ffff,US 2620:e2:8000::,2620:e2:8000:ffff:ffff:ffff:ffff:ffff,US 2620:e2:c000::,2620:e2:c000:ffff:ffff:ffff:ffff:ffff,US 2620:e3::,2620:e3::ffff:ffff:ffff:ffff:ffff,US 2620:e3:4000::,2620:e3:4000:ffff:ffff:ffff:ffff:ffff,US 2620:e3:8000::,2620:e3:8000:ffff:ffff:ffff:ffff:ffff,US 2620:e3:c000::,2620:e3:c000:ffff:ffff:ffff:ffff:ffff,US 2620:e4::,2620:e4::ffff:ffff:ffff:ffff:ffff,US 2620:e4:4000::,2620:e4:4000:ffff:ffff:ffff:ffff:ffff,US 2620:e4:8000::,2620:e4:8000:ffff:ffff:ffff:ffff:ffff,US 2620:e4:c000::,2620:e4:c000:ffff:ffff:ffff:ffff:ffff,US 2620:e5::,2620:e5::ffff:ffff:ffff:ffff:ffff,US 2620:e5:4000::,2620:e5:4000:ffff:ffff:ffff:ffff:ffff,US 2620:e5:8000::,2620:e5:8000:ffff:ffff:ffff:ffff:ffff,US 2620:e5:c000::,2620:e5:c000:ffff:ffff:ffff:ffff:ffff,US 2620:e6::,2620:e6::ffff:ffff:ffff:ffff:ffff,US 2620:e6:4000::,2620:e6:4000:ffff:ffff:ffff:ffff:ffff,US 2620:e6:8000::,2620:e6:8000:ffff:ffff:ffff:ffff:ffff,US 2620:e6:c000::,2620:e6:c000:ffff:ffff:ffff:ffff:ffff,US 2620:e7::,2620:e7::ffff:ffff:ffff:ffff:ffff,US 2620:e7:4000::,2620:e7:4000:ffff:ffff:ffff:ffff:ffff,US 2620:e7:8000::,2620:e7:8000:ffff:ffff:ffff:ffff:ffff,CA 2620:e7:c000::,2620:e7:c000:ffff:ffff:ffff:ffff:ffff,US 2620:e8::,2620:e8::ffff:ffff:ffff:ffff:ffff,US 2620:e8:4000::,2620:e8:4000:ffff:ffff:ffff:ffff:ffff,US 2620:e8:8000::,2620:e8:8000:ffff:ffff:ffff:ffff:ffff,US 2620:e8:c000::,2620:e8:c000:ffff:ffff:ffff:ffff:ffff,US 2620:e9::,2620:e9::ffff:ffff:ffff:ffff:ffff,US 2620:e9:4000::,2620:e9:4000:ffff:ffff:ffff:ffff:ffff,US 2620:e9:8000::,2620:e9:8000:ffff:ffff:ffff:ffff:ffff,US 2620:e9:c000::,2620:e9:c000:ffff:ffff:ffff:ffff:ffff,US 2620:ea::,2620:ea:f:ffff:ffff:ffff:ffff:ffff,US 2620:ea:4000::,2620:ea:4000:ffff:ffff:ffff:ffff:ffff,US 2620:ea:8000::,2620:ea:8000:ffff:ffff:ffff:ffff:ffff,US 2620:eb::,2620:eb::ffff:ffff:ffff:ffff:ffff,US 2620:eb:4000::,2620:eb:4000:ffff:ffff:ffff:ffff:ffff,US 2620:eb:8000::,2620:eb:8000:ffff:ffff:ffff:ffff:ffff,US 2620:eb:c000::,2620:eb:c000:ffff:ffff:ffff:ffff:ffff,US 2620:ec::,2620:ec::ffff:ffff:ffff:ffff:ffff,US 2620:ec:4000::,2620:ec:4000:ffff:ffff:ffff:ffff:ffff,US 2620:ec:8000::,2620:ec:8000:ffff:ffff:ffff:ffff:ffff,US 2620:ec:c000::,2620:ec:c000:ffff:ffff:ffff:ffff:ffff,US 2620:ed::,2620:ed::ffff:ffff:ffff:ffff:ffff,US 2620:ed:4000::,2620:ed:4000:ffff:ffff:ffff:ffff:ffff,CA 2620:ed:8000::,2620:ed:8000:ffff:ffff:ffff:ffff:ffff,US 2620:ed:c000::,2620:ed:c000:ffff:ffff:ffff:ffff:ffff,US 2620:ee::,2620:ee::ffff:ffff:ffff:ffff:ffff,US 2620:ee:4000::,2620:ee:4000:ffff:ffff:ffff:ffff:ffff,US 2620:ee:8000::,2620:ee:8000:ffff:ffff:ffff:ffff:ffff,US 2620:ee:c000::,2620:ee:c00f:ffff:ffff:ffff:ffff:ffff,US 2620:ef:4000::,2620:ef:4000:ffff:ffff:ffff:ffff:ffff,US 2620:ef:8000::,2620:ef:8000:ffff:ffff:ffff:ffff:ffff,US 2620:ef:c000::,2620:ef:c000:ffff:ffff:ffff:ffff:ffff,US 2620:f0::,2620:f0::ffff:ffff:ffff:ffff:ffff,US 2620:f0:4000::,2620:f0:400f:ffff:ffff:ffff:ffff:ffff,US 2620:f0:8000::,2620:f0:8000:ffff:ffff:ffff:ffff:ffff,US 2620:f0:c000::,2620:f0:c002:ffff:ffff:ffff:ffff:ffff,US 2620:f0:c003::,2620:f0:c003:ffff:ffff:ffff:ffff:ffff,NL 2620:f0:c004::,2620:f0:c004:ffff:ffff:ffff:ffff:ffff,US 2620:f0:c005::,2620:f0:c005:ffff:ffff:ffff:ffff:ffff,SG 2620:f0:c006::,2620:f0:c009:ffff:ffff:ffff:ffff:ffff,US 2620:f0:c00a::,2620:f0:c00a:ffff:ffff:ffff:ffff:ffff,CA 2620:f0:c00b::,2620:f0:c00f:ffff:ffff:ffff:ffff:ffff,US 2620:f1:4000::,2620:f1:4000:ffff:ffff:ffff:ffff:ffff,CA 2620:f1:8000::,2620:f1:8000:ffff:ffff:ffff:ffff:ffff,US 2620:f1:c000::,2620:f1:c000:ffff:ffff:ffff:ffff:ffff,US 2620:f2::,2620:f2::ffff:ffff:ffff:ffff:ffff,CA 2620:f2:4000::,2620:f2:4000:ffff:ffff:ffff:ffff:ffff,US 2620:f2:8000::,2620:f2:8000:ffff:ffff:ffff:ffff:ffff,US 2620:f2:c000::,2620:f2:c000:ffff:ffff:ffff:ffff:ffff,US 2620:f3::,2620:f3::ffff:ffff:ffff:ffff:ffff,US 2620:f3:4000::,2620:f3:4000:ffff:ffff:ffff:ffff:ffff,US 2620:f3:8000::,2620:f3:8000:ffff:ffff:ffff:ffff:ffff,US 2620:f3:c000::,2620:f3:c000:ffff:ffff:ffff:ffff:ffff,US 2620:f4::,2620:f4::ffff:ffff:ffff:ffff:ffff,US 2620:f4:4000::,2620:f4:40ff:ffff:ffff:ffff:ffff:ffff,US 2620:f4:8000::,2620:f4:8000:ffff:ffff:ffff:ffff:ffff,CA 2620:f4:c000::,2620:f4:c000:ffff:ffff:ffff:ffff:ffff,US 2620:f5::,2620:f5::ffff:ffff:ffff:ffff:ffff,US 2620:f5:4000::,2620:f5:4000:ffff:ffff:ffff:ffff:ffff,US 2620:f5:8000::,2620:f5:8000:ffff:ffff:ffff:ffff:ffff,US 2620:f5:c000::,2620:f5:c000:ffff:ffff:ffff:ffff:ffff,US 2620:f6::,2620:f6::ffff:ffff:ffff:ffff:ffff,CA 2620:f6:4000::,2620:f6:400f:ffff:ffff:ffff:ffff:ffff,US 2620:f6:8000::,2620:f6:8000:ffff:ffff:ffff:ffff:ffff,US 2620:f6:c000::,2620:f6:c000:ffff:ffff:ffff:ffff:ffff,CA 2620:f7::,2620:f7::ffff:ffff:ffff:ffff:ffff,US 2620:f7:4000::,2620:f7:4000:ffff:ffff:ffff:ffff:ffff,US 2620:f7:8000::,2620:f7:8000:ffff:ffff:ffff:ffff:ffff,US 2620:f7:c000::,2620:f7:c000:ffff:ffff:ffff:ffff:ffff,US 2620:f8::,2620:f8::ffff:ffff:ffff:ffff:ffff,US 2620:f8:4000::,2620:f8:4000:ffff:ffff:ffff:ffff:ffff,US 2620:f8:8000::,2620:f8:8000:ffff:ffff:ffff:ffff:ffff,US 2620:f8:c000::,2620:f8:c000:ffff:ffff:ffff:ffff:ffff,US 2620:f9::,2620:f9:f:ffff:ffff:ffff:ffff:ffff,US 2620:f9:4000::,2620:f9:4000:ffff:ffff:ffff:ffff:ffff,US 2620:f9:8000::,2620:f9:8000:ffff:ffff:ffff:ffff:ffff,US 2620:f9:c000::,2620:f9:c000:ffff:ffff:ffff:ffff:ffff,US 2620:fa::,2620:fa::ffff:ffff:ffff:ffff:ffff,US 2620:fa:4000::,2620:fa:4000:ffff:ffff:ffff:ffff:ffff,US 2620:fa:8000::,2620:fa:8000:ffff:ffff:ffff:ffff:ffff,CA 2620:fa:c000::,2620:fa:c000:ffff:ffff:ffff:ffff:ffff,US 2620:fb::,2620:fb::ffff:ffff:ffff:ffff:ffff,US 2620:fb:4000::,2620:fb:4000:ffff:ffff:ffff:ffff:ffff,US 2620:fb:8000::,2620:fb:8000:ffff:ffff:ffff:ffff:ffff,US 2620:fc::,2620:fc::ffff:ffff:ffff:ffff:ffff,CA 2620:fc:4000::,2620:fc:4000:ffff:ffff:ffff:ffff:ffff,CA 2620:fc:8000::,2620:fc:8000:ffff:ffff:ffff:ffff:ffff,US 2620:fc:c000::,2620:fc:c000:ffff:ffff:ffff:ffff:ffff,US 2620:fd::,2620:fd::ffff:ffff:ffff:ffff:ffff,CA 2620:fd:4000::,2620:fd:4000:ffff:ffff:ffff:ffff:ffff,US 2620:fd:8000::,2620:fd:8000:ffff:ffff:ffff:ffff:ffff,US 2620:fd:c000::,2620:fd:c000:ffff:ffff:ffff:ffff:ffff,CA 2620:fe::,2620:fe::ffff:ffff:ffff:ffff:ffff,US 2620:fe:2040::,2620:fe:2040:ffff:ffff:ffff:ffff:ffff,US 2620:fe:8000::,2620:fe:8000:ffff:ffff:ffff:ffff:ffff,US 2620:fe:c000::,2620:fe:c000:ffff:ffff:ffff:ffff:ffff,US 2620:ff::,2620:ff::ffff:ffff:ffff:ffff:ffff,US 2620:ff:4000::,2620:ff:4000:ffff:ffff:ffff:ffff:ffff,US 2620:ff:8000::,2620:ff:8000:ffff:ffff:ffff:ffff:ffff,US 2620:ff:c000::,2620:ff:c000:ffff:ffff:ffff:ffff:ffff,US 2620:100::,2620:100:f:ffff:ffff:ffff:ffff:ffff,US 2620:100:3000::,2620:100:3007:ffff:ffff:ffff:ffff:ffff,US 2620:100:4000::,2620:100:403f:ffff:ffff:ffff:ffff:ffff,US 2620:100:5000::,2620:100:5007:ffff:ffff:ffff:ffff:ffff,US 2620:100:6000::,2620:100:60ff:ffff:ffff:ffff:ffff:ffff,US 2620:100:7000::,2620:100:700f:ffff:ffff:ffff:ffff:ffff,US 2620:100:8000::,2620:100:8003:ffff:ffff:ffff:ffff:ffff,US 2620:100:9000::,2620:100:900f:ffff:ffff:ffff:ffff:ffff,US 2620:100:a000::,2620:100:a00f:ffff:ffff:ffff:ffff:ffff,US 2620:100:c000::,2620:100:c03f:ffff:ffff:ffff:ffff:ffff,US 2620:100:d000::,2620:100:d00f:ffff:ffff:ffff:ffff:ffff,US 2620:100:e000::,2620:100:e00f:ffff:ffff:ffff:ffff:ffff,US 2620:100:f000::,2620:100:f00f:ffff:ffff:ffff:ffff:ffff,US 2620:101::,2620:101:3:ffff:ffff:ffff:ffff:ffff,US 2620:101:1000::,2620:101:103f:ffff:ffff:ffff:ffff:ffff,US 2620:101:2000::,2620:101:201f:ffff:ffff:ffff:ffff:ffff,US 2620:101:3000::,2620:101:303f:ffff:ffff:ffff:ffff:ffff,US 2620:101:4000::,2620:101:403f:ffff:ffff:ffff:ffff:ffff,US 2620:101:5000::,2620:101:503f:ffff:ffff:ffff:ffff:ffff,US 2620:101:6000::,2620:101:6001:ffff:ffff:ffff:ffff:ffff,US 2620:101:7000::,2620:101:7001:ffff:ffff:ffff:ffff:ffff,US 2620:101:8000::,2620:101:80f1:ffff:ffff:ffff:ffff:ffff,US 2620:101:80f2::,2620:101:80f2:7fff:ffff:ffff:ffff:ffff,CA 2620:101:80f2:8000::,2620:101:80ff:ffff:ffff:ffff:ffff:ffff,US 2620:101:9000::,2620:101:900f:ffff:ffff:ffff:ffff:ffff,US 2620:101:b000::,2620:101:b07f:ffff:ffff:ffff:ffff:ffff,US 2620:101:c000::,2620:101:c0ff:ffff:ffff:ffff:ffff:ffff,CA 2620:101:d000::,2620:101:d007:ffff:ffff:ffff:ffff:ffff,US 2620:101:e000::,2620:101:e00f:ffff:ffff:ffff:ffff:ffff,US 2620:101:f000::,2620:101:f001:ffff:ffff:ffff:ffff:ffff,CA 2620:102::,2620:102:f:ffff:ffff:ffff:ffff:ffff,US 2620:102:2000::,2620:102:200f:ffff:ffff:ffff:ffff:ffff,US 2620:102:3000::,2620:102:300f:ffff:ffff:ffff:ffff:ffff,US 2620:102:4000::,2620:102:403f:ffff:ffff:ffff:ffff:ffff,US 2a07:14c0::,2a07:14c7:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:1500::,2a07:1507:ffff:ffff:ffff:ffff:ffff:ffff,GB 2a07:1540::,2a07:1547:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:1580::,2a07:1587:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:15c0::,2a07:15c7:ffff:ffff:ffff:ffff:ffff:ffff,TR 2a07:1600::,2a07:1607:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:1640::,2a07:1647:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:1680::,2a07:1687:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:16c0::,2a07:16c7:ffff:ffff:ffff:ffff:ffff:ffff,ES 2a07:1700::,2a07:1707:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:1740::,2a07:1747:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:1780::,2a07:1787:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:17c0::,2a07:17c7:ffff:ffff:ffff:ffff:ffff:ffff,UA 2a07:1800::,2a07:1807:ffff:ffff:ffff:ffff:ffff:ffff,AT 2a07:1840::,2a07:1847:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:1880::,2a07:1887:ffff:ffff:ffff:ffff:ffff:ffff,IT 2a07:18c0::,2a07:18c7:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:1900::,2a07:1907:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:1940::,2a07:1947:ffff:ffff:ffff:ffff:ffff:ffff,IT 2a07:1980::,2a07:1987:ffff:ffff:ffff:ffff:ffff:ffff,IL 2a07:19c0::,2a07:19c7:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:1a00::,2a07:1a07:ffff:ffff:ffff:ffff:ffff:ffff,ES 2a07:1a40::,2a07:1a47:ffff:ffff:ffff:ffff:ffff:ffff,PL 2a07:1a80::,2a07:1a80:6fff:ffff:ffff:ffff:ffff:ffff,SE 2a07:1a80:7000::,2a07:1a80:70ff:ffff:ffff:ffff:ffff:ffff,AT 2a07:1a80:7100::,2a07:1a87:ffff:ffff:ffff:ffff:ffff:ffff,SE 2a07:1ac0::,2a07:1ac7:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:1b00::,2a07:1b07:ffff:ffff:ffff:ffff:ffff:ffff,GB 2a07:1b40::,2a07:1b47:ffff:ffff:ffff:ffff:ffff:ffff,GB 2a07:1b80::,2a07:1b87:ffff:ffff:ffff:ffff:ffff:ffff,AT 2a07:1bc0::,2a07:1bc7:ffff:ffff:ffff:ffff:ffff:ffff,PL 2a07:1c00::,2a07:1c07:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:1c40::,2a07:1c44:3ff:ffff:ffff:ffff:ffff:ffff,AT 2a07:1c44:400::,2a07:1c44:4ff:ffff:ffff:ffff:ffff:ffff,DE 2a07:1c44:500::,2a07:1c44:609:ffff:ffff:ffff:ffff:ffff,AT 2a07:1c44:60a::,2a07:1c44:60a:ffff:ffff:ffff:ffff:ffff,DE 2a07:1c44:60b::,2a07:1c44:619:ffff:ffff:ffff:ffff:ffff,AT 2a07:1c44:61a::,2a07:1c44:61a:ffff:ffff:ffff:ffff:ffff,KR 2a07:1c44:61b::,2a07:1c44:67f:ffff:ffff:ffff:ffff:ffff,AT 2a07:1c44:680::,2a07:1c44:6bf:ffff:ffff:ffff:ffff:ffff,KR 2a07:1c44:6c0::,2a07:1c44:6ff:ffff:ffff:ffff:ffff:ffff,DE 2a07:1c44:700::,2a07:1c44:70f:ffff:ffff:ffff:ffff:ffff,US 2a07:1c44:710::,2a07:1c44:1800:ffff:ffff:ffff:ffff:ffff,AT 2a07:1c44:1801::,2a07:1c44:1802:ffff:ffff:ffff:ffff:ffff,US 2a07:1c44:1803::,2a07:1c44:35ff:ffff:ffff:ffff:ffff:ffff,AT 2a07:1c44:3600::,2a07:1c44:36ff:ffff:ffff:ffff:ffff:ffff,GB 2a07:1c44:3700::,2a07:1c44:3fff:ffff:ffff:ffff:ffff:ffff,AT 2a07:1c44:4000::,2a07:1c44:40ff:ffff:ffff:ffff:ffff:ffff,US 2a07:1c44:4100::,2a07:1c44:42ff:ffff:ffff:ffff:ffff:ffff,AT 2a07:1c44:4300::,2a07:1c44:43ff:ffff:ffff:ffff:ffff:ffff,HR 2a07:1c44:4400::,2a07:1c44:4fff:ffff:ffff:ffff:ffff:ffff,AT 2a07:1c44:5000::,2a07:1c44:51ff:ffff:ffff:ffff:ffff:ffff,US 2a07:1c44:5200::,2a07:1c47:ffff:ffff:ffff:ffff:ffff:ffff,AT 2a07:1c80::,2a07:1c87:ffff:ffff:ffff:ffff:ffff:ffff,SE 2a07:1cc0::,2a07:1cc7:ffff:ffff:ffff:ffff:ffff:ffff,PL 2a07:1d00::,2a07:1d07:ffff:ffff:ffff:ffff:ffff:ffff,IR 2a07:1d40::,2a07:1d47:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:1d80::,2a07:1d87:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:1dc0::,2a07:1dc7:ffff:ffff:ffff:ffff:ffff:ffff,PL 2a07:1e00::,2a07:1e07:ffff:ffff:ffff:ffff:ffff:ffff,KZ 2a07:1e40::,2a07:1e47:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:1e80::,2a07:1e87:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:1ec0::,2a07:1ec7:ffff:ffff:ffff:ffff:ffff:ffff,IT 2a07:1f00::,2a07:1f07:ffff:ffff:ffff:ffff:ffff:ffff,CH 2a07:1f40::,2a07:1f47:ffff:ffff:ffff:ffff:ffff:ffff,CZ 2a07:1f80::,2a07:1f87:ffff:ffff:ffff:ffff:ffff:ffff,US 2a07:1fc0::,2a07:1fc7:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:2000::,2a07:2007:ffff:ffff:ffff:ffff:ffff:ffff,IQ 2a07:2040::,2a07:2047:ffff:ffff:ffff:ffff:ffff:ffff,ES 2a07:2080::,2a07:2087:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:20c0::,2a07:20c7:ffff:ffff:ffff:ffff:ffff:ffff,CZ 2a07:2100::,2a07:2107:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:2140::,2a07:2147:ffff:ffff:ffff:ffff:ffff:ffff,CH 2a07:2180::,2a07:2187:ffff:ffff:ffff:ffff:ffff:ffff,SE 2a07:21c0::,2a07:21c7:ffff:ffff:ffff:ffff:ffff:ffff,TR 2a07:2200::,2a07:2207:ffff:ffff:ffff:ffff:ffff:ffff,IR 2a07:2240::,2a07:2247:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:2280::,2a07:2287:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:2300::,2a07:2307:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:2340::,2a07:2347:ffff:ffff:ffff:ffff:ffff:ffff,SE 2a07:2380::,2a07:2387:ffff:ffff:ffff:ffff:ffff:ffff,ES 2a07:23c0::,2a07:23c7:ffff:ffff:ffff:ffff:ffff:ffff,GB 2a07:2400::,2a07:2407:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:2440::,2a07:2447:ffff:ffff:ffff:ffff:ffff:ffff,SE 2a07:2480::,2a07:2487:ffff:ffff:ffff:ffff:ffff:ffff,IR 2a07:24c0::,2a07:24c7:ffff:ffff:ffff:ffff:ffff:ffff,GB 2a07:2500::,2a07:2507:ffff:ffff:ffff:ffff:ffff:ffff,DK 2a07:2540::,2a07:2547:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:2580::,2a07:2587:ffff:ffff:ffff:ffff:ffff:ffff,IT 2a07:25c0::,2a07:25c7:ffff:ffff:ffff:ffff:ffff:ffff,GB 2a07:2600::,2a07:2607:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:2640::,2a07:2647:ffff:ffff:ffff:ffff:ffff:ffff,GB 2a07:2680::,2a07:2687:ffff:ffff:ffff:ffff:ffff:ffff,DK 2a07:26c0::,2a07:26c7:ffff:ffff:ffff:ffff:ffff:ffff,BE 2a07:2700::,2a07:2707:ffff:ffff:ffff:ffff:ffff:ffff,TR 2a07:2740::,2a07:2747:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:2780::,2a07:2787:ffff:ffff:ffff:ffff:ffff:ffff,GB 2a07:27c0::,2a07:27c7:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:2800::,2a07:2807:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:2840::,2a07:2847:ffff:ffff:ffff:ffff:ffff:ffff,IT 2a07:2880::,2a07:2887:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:28c0::,2a07:28c7:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:2900::,2a07:291f:ffff:ffff:ffff:ffff:ffff:ffff,CH 2a07:2a00::,2a07:2a07:ffff:ffff:ffff:ffff:ffff:ffff,ES 2a07:2a40::,2a07:2a47:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:2a80::,2a07:2a87:ffff:ffff:ffff:ffff:ffff:ffff,SE 2a07:2ac0::,2a07:2ac7:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:2b00::,2a07:2b07:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:2b40::,2a07:2b47:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:2b80::,2a07:2b87:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:2bc0::,2a07:2bc7:ffff:ffff:ffff:ffff:ffff:ffff,ES 2a07:2c00::,2a07:2c07:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:2c40::,2a07:2c47:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:2c80::,2a07:2c87:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:2cc0::,2a07:2cc7:ffff:ffff:ffff:ffff:ffff:ffff,IT 2a07:2d00::,2a07:2d07:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:2d40::,2a07:2d47:ffff:ffff:ffff:ffff:ffff:ffff,CH 2a07:2d80::,2a07:2d87:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:2dc0::,2a07:2dc7:ffff:ffff:ffff:ffff:ffff:ffff,FI 2a07:2e00::,2a07:2e07:ffff:ffff:ffff:ffff:ffff:ffff,CH 2a07:2e40::,2a07:2e47:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:2e80::,2a07:2e87:ffff:ffff:ffff:ffff:ffff:ffff,IT 2a07:2ec0::,2a07:2ec7:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:2f00::,2a07:2f07:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:2f40::,2a07:2f47:ffff:ffff:ffff:ffff:ffff:ffff,UA 2a07:2f80::,2a07:2f87:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:2fc0::,2a07:2fc7:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:3000::,2a07:3007:ffff:ffff:ffff:ffff:ffff:ffff,ES 2a07:3040::,2a07:3047:ffff:ffff:ffff:ffff:ffff:ffff,PL 2a07:3080::,2a07:3087:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:30c0::,2a07:30c7:ffff:ffff:ffff:ffff:ffff:ffff,CZ 2a07:3100::,2a07:3107:ffff:ffff:ffff:ffff:ffff:ffff,RO 2a07:3140::,2a07:3147:ffff:ffff:ffff:ffff:ffff:ffff,BE 2a07:3180::,2a07:3187:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:31c0::,2a07:31c7:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:3200::,2a07:3207:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:3240::,2a07:3247:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:3280::,2a07:3287:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:32c0::,2a07:32c7:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:3300::,2a07:3307:ffff:ffff:ffff:ffff:ffff:ffff,TR 2a07:3340::,2a07:3347:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:3380::,2a07:3387:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:33c0::,2a07:33c7:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:3400::,2a07:3407:ffff:ffff:ffff:ffff:ffff:ffff,UA 2a07:3440::,2a07:3447:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:3480::,2a07:3487:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:3500::,2a07:3507:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:3540::,2a07:3547:ffff:ffff:ffff:ffff:ffff:ffff,IT 2a07:3580::,2a07:3587:ffff:ffff:ffff:ffff:ffff:ffff,ES 2a07:35c0::,2a07:35c7:ffff:ffff:ffff:ffff:ffff:ffff,UA 2a07:3600::,2a07:3607:ffff:ffff:ffff:ffff:ffff:ffff,GB 2a07:3640::,2a07:3647:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:3680::,2a07:3687:ffff:ffff:ffff:ffff:ffff:ffff,LB 2a07:36c0::,2a07:36c7:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:3700::,2a07:3707:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:3740::,2a07:3747:ffff:ffff:ffff:ffff:ffff:ffff,AT 2a07:3780::,2a07:3787:ffff:ffff:ffff:ffff:ffff:ffff,IS 2a07:37c0::,2a07:37c7:ffff:ffff:ffff:ffff:ffff:ffff,BE 2a07:3800::,2a07:3807:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:3840::,2a07:3847:ffff:ffff:ffff:ffff:ffff:ffff,HR 2a07:3880::,2a07:3887:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:38c0::,2a07:38c7:ffff:ffff:ffff:ffff:ffff:ffff,GB 2a07:3900::,2a07:3907:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:3940::,2a07:3947:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:3980::,2a07:3987:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:39c0::,2a07:39c7:ffff:ffff:ffff:ffff:ffff:ffff,DK 2a07:3a00::,2a07:3a07:ffff:ffff:ffff:ffff:ffff:ffff,ES 2a07:3a80::,2a07:3a87:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:3ac0::,2a07:3ac7:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:3b00::,2a07:3b07:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:3b40::,2a07:3b47:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:3b80::,2a07:3b87:ffff:ffff:ffff:ffff:ffff:ffff,GI 2a07:3bc0::,2a07:3bc7:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:3c00::,2a07:3c07:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:3c40::,2a07:3c47:ffff:ffff:ffff:ffff:ffff:ffff,DE 2a07:3c80::,2a07:3c87:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:3d00::,2a07:3d07:ffff:ffff:ffff:ffff:ffff:ffff,IT 2a07:3d80::,2a07:3d87:ffff:ffff:ffff:ffff:ffff:ffff,CZ 2a07:3dc0::,2a07:3dc7:ffff:ffff:ffff:ffff:ffff:ffff,DK 2a07:3e00::,2a07:3e07:ffff:ffff:ffff:ffff:ffff:ffff,CH 2a07:3e40::,2a07:3e47:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:3e80::,2a07:3e87:ffff:ffff:ffff:ffff:ffff:ffff,NL 2a07:3ec0::,2a07:3ec7:ffff:ffff:ffff:ffff:ffff:ffff,GB 2a07:3f00::,2a07:3f07:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:3f40::,2a07:3f47:ffff:ffff:ffff:ffff:ffff:ffff,IE 2a07:3f80::,2a07:3f87:ffff:ffff:ffff:ffff:ffff:ffff,SK 2a07:3fc0::,2a07:3fc7:ffff:ffff:ffff:ffff:ffff:ffff,SE 2a07:4000::,2a07:4007:ffff:ffff:ffff:ffff:ffff:ffff,NO 2a07:4040::,2a07:4047:ffff:ffff:ffff:ffff:ffff:ffff,SE 2a07:4080::,2a07:4087:ffff:ffff:ffff:ffff:ffff:ffff,AT 2a07:40c0::,2a07:40c7:ffff:ffff:ffff:ffff:ffff:ffff,IL 2a07:4100::,2a07:4107:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:4140::,2a07:4147:ffff:ffff:ffff:ffff:ffff:ffff,MD 2a07:4180::,2a07:4187:ffff:ffff:ffff:ffff:ffff:ffff,GB 2a07:41c0::,2a07:41c7:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:4200::,2a07:4207:ffff:ffff:ffff:ffff:ffff:ffff,FR 2a07:4240::,2a07:4247:ffff:ffff:ffff:ffff:ffff:ffff,RU 2a07:4280::,2a07:4287:ffff:ffff:ffff:ffff:ffff:ffff,GB 2a07:42c0::,2a07:42c7:ffff:ffff:ffff:ffff:ffff:ffff,DK 2a07:4340::,2a07:4347:ffff:ffff:ffff:ffff:ffff:ffff,AE 2a0c:af80::,2a0c:af87:ffff:ffff:ffff:ffff:ffff:ffff,GB 2c0f:f950::,2c0f:f950:ffff:ffff:ffff:ffff:ffff:ffff,SS 2c0f:f958::,2c0f:f958:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:f960::,2c0f:f960:ffff:ffff:ffff:ffff:ffff:ffff,TZ 2c0f:f968::,2c0f:f968:ffff:ffff:ffff:ffff:ffff:ffff,MZ 2c0f:f970::,2c0f:f970:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:f978::,2c0f:f978:ffff:ffff:ffff:ffff:ffff:ffff,CD 2c0f:f980::,2c0f:f980:ffff:ffff:ffff:ffff:ffff:ffff,NA 2c0f:f988::,2c0f:f988:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:f990::,2c0f:f990:ffff:ffff:ffff:ffff:ffff:ffff,GN 2c0f:f998::,2c0f:f998:ffff:ffff:ffff:ffff:ffff:ffff,MR 2c0f:f9a0::,2c0f:f9a0:ffff:ffff:ffff:ffff:ffff:ffff,MW 2c0f:f9a8::,2c0f:f9a8:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:f9b0::,2c0f:f9b0:ffff:ffff:ffff:ffff:ffff:ffff,GA 2c0f:f9b8::,2c0f:f9b8:1:ffff:ffff:ffff:ffff:ffff,MU 2c0f:f9b8:2::,2c0f:f9b8:2:ffff:ffff:ffff:ffff:ffff,US 2c0f:f9b8:3::,2c0f:f9b8:ffff:ffff:ffff:ffff:ffff:ffff,MU 2c0f:f9c0::,2c0f:f9c0:ffff:ffff:ffff:ffff:ffff:ffff,BW 2c0f:f9c8::,2c0f:f9c8:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:f9d0::,2c0f:f9d0:ffff:ffff:ffff:ffff:ffff:ffff,TZ 2c0f:f9d8::,2c0f:f9d8:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:f9e0::,2c0f:f9e0:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:f9e8::,2c0f:f9e8:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:f9f0::,2c0f:f9f0:ffff:ffff:ffff:ffff:ffff:ffff,MG 2c0f:f9f8::,2c0f:f9f8:ffff:ffff:ffff:ffff:ffff:ffff,BJ 2c0f:fa00::,2c0f:fa00:ffff:ffff:ffff:ffff:ffff:ffff,GH 2c0f:fa08::,2c0f:fa08:ffff:ffff:ffff:ffff:ffff:ffff,CD 2c0f:fa10::,2c0f:fa10:fffc:ffff:ffff:ffff:ffff:ffff,MU 2c0f:fa10:fffd::,2c0f:fa10:fffd:7fff:ffff:ffff:ffff:ffff,ZM 2c0f:fa10:fffd:8000::,2c0f:fa10:ffff:ffff:ffff:ffff:ffff:ffff,MU 2c0f:fa18::,2c0f:fa18:ffff:ffff:ffff:ffff:ffff:ffff,MA 2c0f:fa20::,2c0f:fa20:ffff:ffff:ffff:ffff:ffff:ffff,SS 2c0f:fa28::,2c0f:fa28:ffff:ffff:ffff:ffff:ffff:ffff,MG 2c0f:fa38::,2c0f:fa38:ffff:ffff:ffff:ffff:ffff:ffff,AO 2c0f:fa40::,2c0f:fa40:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fa48::,2c0f:fa48:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fa58::,2c0f:fa58:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fa60::,2c0f:fa60:ffff:ffff:ffff:ffff:ffff:ffff,AO 2c0f:fa68::,2c0f:fa68:ffff:ffff:ffff:ffff:ffff:ffff,GH 2c0f:fa70::,2c0f:fa70:ffff:ffff:ffff:ffff:ffff:ffff,AO 2c0f:fa78::,2c0f:fa78:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fa80::,2c0f:fa80:ffff:ffff:ffff:ffff:ffff:ffff,AO 2c0f:fa88::,2c0f:fa88:ffff:ffff:ffff:ffff:ffff:ffff,ST 2c0f:fa90::,2c0f:fa90:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fa98::,2c0f:fa98:ffff:ffff:ffff:ffff:ffff:ffff,ZW 2c0f:faa0::,2c0f:faa7:ffff:ffff:ffff:ffff:ffff:ffff,SD 2c0f:fab0::,2c0f:fabf:ffff:ffff:ffff:ffff:ffff:ffff,TN 2c0f:fac0::,2c0f:fac0:ffff:ffff:ffff:ffff:ffff:ffff,MW 2c0f:fac8::,2c0f:fac8:ffff:ffff:ffff:ffff:ffff:ffff,BW 2c0f:fad8::,2c0f:fad8:ffff:ffff:ffff:ffff:ffff:ffff,CM 2c0f:fae0::,2c0f:fae0:ffff:ffff:ffff:ffff:ffff:ffff,CM 2c0f:fae8::,2c0f:fae8:ffff:ffff:ffff:ffff:ffff:ffff,KE 2c0f:faf0::,2c0f:faf0:ffff:ffff:ffff:ffff:ffff:ffff,AO 2c0f:faf8::,2c0f:faf8:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fb00::,2c0f:fb00:ffff:ffff:ffff:ffff:ffff:ffff,UG 2c0f:fb08::,2c0f:fb08:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fb10::,2c0f:fb10:ffff:ffff:ffff:ffff:ffff:ffff,LY 2c0f:fb18::,2c0f:fb18:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fb20::,2c0f:fb20:ffff:ffff:ffff:ffff:ffff:ffff,MA 2c0f:fb30::,2c0f:fb30:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fb38::,2c0f:fb38:ffff:ffff:ffff:ffff:ffff:ffff,SO 2c0f:fb40::,2c0f:fb40:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fb48::,2c0f:fb48:ffff:ffff:ffff:ffff:ffff:ffff,MZ 2c0f:fb50::,2c0f:fb50:ffff:ffff:ffff:ffff:ffff:ffff,KE 2c0f:fb58::,2c0f:fb58:ffff:ffff:ffff:ffff:ffff:ffff,AO 2c0f:fb60::,2c0f:fb60:ffff:ffff:ffff:ffff:ffff:ffff,KE 2c0f:fb68::,2c0f:fb68:ffff:ffff:ffff:ffff:ffff:ffff,LS 2c0f:fb70::,2c0f:fb70:ffff:ffff:ffff:ffff:ffff:ffff,AO 2c0f:fb78::,2c0f:fb78:ffff:ffff:ffff:ffff:ffff:ffff,TZ 2c0f:fb80::,2c0f:fb80:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fb88::,2c0f:fb88:ffff:ffff:ffff:ffff:ffff:ffff,TZ 2c0f:fb90::,2c0f:fb90:ffff:ffff:ffff:ffff:ffff:ffff,MZ 2c0f:fb98::,2c0f:fb98:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:fba0::,2c0f:fba0:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fba8::,2c0f:fba8:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:fbb0::,2c0f:fbb0:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fbb8::,2c0f:fbb8:ffff:ffff:ffff:ffff:ffff:ffff,TZ 2c0f:fbc0::,2c0f:fbc0:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fbc8::,2c0f:fbc8:ffff:ffff:ffff:ffff:ffff:ffff,UG 2c0f:fbd0::,2c0f:fbd0:ffff:ffff:ffff:ffff:ffff:ffff,GN 2c0f:fbd8::,2c0f:fbd8:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fbe0::,2c0f:fc1f:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fc40::,2c0f:fc40:ffff:ffff:ffff:ffff:ffff:ffff,EG 2c0f:fc48::,2c0f:fc48:ffff:ffff:ffff:ffff:ffff:ffff,MW 2c0f:fc58::,2c0f:fc58:ffff:ffff:ffff:ffff:ffff:ffff,MW 2c0f:fc60::,2c0f:fc61:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:fc68::,2c0f:fc68:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fc70::,2c0f:fc70:ffff:ffff:ffff:ffff:ffff:ffff,KE 2c0f:fc80::,2c0f:fc80:ffff:ffff:ffff:ffff:ffff:ffff,KE 2c0f:fc88::,2c0f:fc89:ffff:ffff:ffff:ffff:ffff:ffff,EG 2c0f:fc90::,2c0f:fc90:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:fc98::,2c0f:fc98:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:fca0::,2c0f:fca0:ffff:ffff:ffff:ffff:ffff:ffff,GH 2c0f:fca8::,2c0f:fca8:ffff:ffff:ffff:ffff:ffff:ffff,GH 2c0f:fcb0::,2c0f:fcb0:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fcb8::,2c0f:fcb8:ffff:ffff:ffff:ffff:ffff:ffff,GM 2c0f:fcc8::,2c0f:fcc8:ffff:ffff:ffff:ffff:ffff:ffff,ZM 2c0f:fcd0::,2c0f:fcd0:ffff:ffff:ffff:ffff:ffff:ffff,ZM 2c0f:fcd8::,2c0f:fcd8:ffff:ffff:ffff:ffff:ffff:ffff,SO 2c0f:fce0::,2c0f:fce0:ffff:ffff:ffff:ffff:ffff:ffff,KE 2c0f:fce8::,2c0f:fce8:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fcf0::,2c0f:fcf0:ffff:ffff:ffff:ffff:ffff:ffff,TZ 2c0f:fcf8::,2c0f:fcf8:ffff:ffff:ffff:ffff:ffff:ffff,GH 2c0f:fd00::,2c0f:fd00:ffff:ffff:ffff:ffff:ffff:ffff,LS 2c0f:fd08::,2c0f:fd08:ffff:ffff:ffff:ffff:ffff:ffff,GM 2c0f:fd10::,2c0f:fd10:ffff:ffff:ffff:ffff:ffff:ffff,TZ 2c0f:fd18::,2c0f:fd18:ffff:ffff:ffff:ffff:ffff:ffff,SC 2c0f:fd20::,2c0f:fd20:ffff:ffff:ffff:ffff:ffff:ffff,TZ 2c0f:fd28::,2c0f:fd28:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:fd30::,2c0f:fd30:ffff:ffff:ffff:ffff:ffff:ffff,TZ 2c0f:fd38::,2c0f:fd38:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:fd40::,2c0f:fd40:ffff:ffff:ffff:ffff:ffff:ffff,ZM 2c0f:fd48::,2c0f:fd48:ffff:ffff:ffff:ffff:ffff:ffff,ZW 2c0f:fd50::,2c0f:fd50:ffff:ffff:ffff:ffff:ffff:ffff,MW 2c0f:fd58::,2c0f:fd58:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fd60::,2c0f:fd60:ffff:ffff:ffff:ffff:ffff:ffff,UG 2c0f:fd68::,2c0f:fd68:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fd78::,2c0f:fd78:ffff:ffff:ffff:ffff:ffff:ffff,BI 2c0f:fd80::,2c0f:fd80:ffff:ffff:ffff:ffff:ffff:ffff,BF 2c0f:fd88::,2c0f:fd88:ffff:ffff:ffff:ffff:ffff:ffff,GH 2c0f:fd90::,2c0f:fd90:ffff:ffff:ffff:ffff:ffff:ffff,ZM 2c0f:fd98::,2c0f:fd98:ffff:ffff:ffff:ffff:ffff:ffff,ZM 2c0f:fda0::,2c0f:fda0:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fda8::,2c0f:fda8:ffff:ffff:ffff:ffff:ffff:ffff,TZ 2c0f:fdb0::,2c0f:fdb0:ffff:ffff:ffff:ffff:ffff:ffff,TZ 2c0f:fdb8::,2c0f:fdb8:ffff:ffff:ffff:ffff:ffff:ffff,UG 2c0f:fdc0::,2c0f:fdc0:ffff:ffff:ffff:ffff:ffff:ffff,TZ 2c0f:fdc8::,2c0f:fdc8:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fdd0::,2c0f:fdd0:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fdd8::,2c0f:fdd8:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fde8::,2c0f:fde8:ffff:ffff:ffff:ffff:ffff:ffff,MW 2c0f:fdf0::,2c0f:fdf0:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fdf8::,2c0f:fdf8:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fe08::,2c0f:fe08:ffff:ffff:ffff:ffff:ffff:ffff,KE 2c0f:fe10::,2c0f:fe10:ffff:ffff:ffff:ffff:ffff:ffff,UG 2c0f:fe18::,2c0f:fe18:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fe20::,2c0f:fe20:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fe28::,2c0f:fe28:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fe30::,2c0f:fe30:ffff:ffff:ffff:ffff:ffff:ffff,MU 2c0f:fe38::,2c0f:fe38:ffff:ffff:ffff:ffff:ffff:ffff,KE 2c0f:fe40::,2c0f:fe40:8001:f:ffff:ffff:ffff:ffff,MU 2c0f:fe40:8001:10::,2c0f:fe40:8001:10:ffff:ffff:ffff:ffff,KE 2c0f:fe40:8001:11::,2c0f:fe40:80fe:ffff:ffff:ffff:ffff:ffff,MU 2c0f:fe40:80ff::,2c0f:fe40:80ff:7fff:ffff:ffff:ffff:ffff,KE 2c0f:fe40:80ff:8000::,2c0f:fe40:ffff:ffff:ffff:ffff:ffff:ffff,MU 2c0f:fe50::,2c0f:fe50:ffff:ffff:ffff:ffff:ffff:ffff,DZ 2c0f:fe58::,2c0f:fe58:ffff:ffff:ffff:ffff:ffff:ffff,LS 2c0f:fe60::,2c0f:fe60:ffff:ffff:ffff:ffff:ffff:ffff,RW 2c0f:fe68::,2c0f:fe68:ffff:ffff:ffff:ffff:ffff:ffff,MU 2c0f:fe70::,2c0f:fe70:ffff:ffff:ffff:ffff:ffff:ffff,UG 2c0f:fe78::,2c0f:fe78:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fe88::,2c0f:fe88:ffff:ffff:ffff:ffff:ffff:ffff,KE 2c0f:fe90::,2c0f:fe90:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fe98::,2c0f:fe98:ffff:ffff:ffff:ffff:ffff:ffff,TZ 2c0f:fea0::,2c0f:fea0:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:fea8::,2c0f:fea8:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:feb0::,2c0f:feb0:16:ffff:ffff:ffff:ffff:ffff,MU 2c0f:feb0:17::,2c0f:feb0:17:7fff:ffff:ffff:ffff:ffff,KE 2c0f:feb0:17:8000::,2c0f:feb0:1e:ffff:ffff:ffff:ffff:ffff,MU 2c0f:feb0:1f::,2c0f:feb0:1f:7fff:ffff:ffff:ffff:ffff,ZA 2c0f:feb0:1f:8000::,2c0f:feb0:1f:ffff:ffff:ffff:ffff:ffff,MU 2c0f:feb0:20::,2c0f:feb0:20:7fff:ffff:ffff:ffff:ffff,ZA 2c0f:feb0:20:8000::,2c0f:feb0:2f:7fff:ffff:ffff:ffff:ffff,MU 2c0f:feb0:2f:8000::,2c0f:feb0:2f:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:feb0:30::,2c0f:feb1:ffff:ffff:ffff:ffff:ffff:ffff,MU 2c0f:feb8::,2c0f:feb8:ffff:ffff:ffff:ffff:ffff:ffff,ZM 2c0f:fec0::,2c0f:fec0:ffff:ffff:ffff:ffff:ffff:ffff,UG 2c0f:fec8::,2c0f:fec8:ffff:ffff:ffff:ffff:ffff:ffff,SD 2c0f:fed8::,2c0f:fed8:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:fee0::,2c0f:fee0:ffff:ffff:ffff:ffff:ffff:ffff,EG 2c0f:fef0::,2c0f:fef0:ffff:ffff:ffff:ffff:ffff:ffff,SC 2c0f:fef8::,2c0f:fef8:ffff:ffff:ffff:ffff:ffff:ffff,KE 2c0f:ff00::,2c0f:ff00:ffff:ffff:ffff:ffff:ffff:ffff,BW 2c0f:ff08::,2c0f:ff08:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:ff10::,2c0f:ff10:ffff:ffff:ffff:ffff:ffff:ffff,CD 2c0f:ff18::,2c0f:ff18:ffff:ffff:ffff:ffff:ffff:ffff,KE 2c0f:ff20::,2c0f:ff20:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:ff28::,2c0f:ff28:ffff:ffff:ffff:ffff:ffff:ffff,SD 2c0f:ff30::,2c0f:ff30:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:ff40::,2c0f:ff80:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:ff88::,2c0f:ff88:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:ff90::,2c0f:ff90:ffff:ffff:ffff:ffff:ffff:ffff,KE 2c0f:ff98::,2c0f:ff98:ffff:ffff:ffff:ffff:ffff:ffff,UG 2c0f:ffa0::,2c0f:ffa0:ffff:ffff:ffff:ffff:ffff:ffff,UG 2c0f:ffa8::,2c0f:ffa8:ffff:ffff:ffff:ffff:ffff:ffff,LS 2c0f:ffb0::,2c0f:ffb0:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:ffb8::,2c0f:ffb8:ffff:ffff:ffff:ffff:ffff:ffff,SD 2c0f:ffc0::,2c0f:ffc0:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:ffc8::,2c0f:ffc8:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:ffd0::,2c0f:ffd0:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:ffd8::,2c0f:ffd8:ffff:ffff:ffff:ffff:ffff:ffff,ZA 2c0f:ffe8::,2c0f:ffe8:ffff:ffff:ffff:ffff:ffff:ffff,NG 2c0f:fff0::,2c0f:fff0:ffff:ffff:ffff:ffff:ffff:ffff,NG snowflake-1.1.0/client/000077500000000000000000000000001411410351300147515ustar00rootroot00000000000000snowflake-1.1.0/client/README.md000066400000000000000000000007701411410351300162340ustar00rootroot00000000000000This is the Tor client component of Snowflake. It is based on goptlib. ### Flags The client uses these following `torrc` options by default: ``` ClientTransportPlugin snowflake exec ./client \ -url https://snowflake-broker.azureedge.net/ \ -front ajax.aspnetcdn.com \ -ice stun:stun.l.google.com:19302 ``` `-url` should be the URL of a Broker instance. `-front` is an optional front domain for the Broker request. `-ice` is a comma-separated list of ICE servers. These can be STUN or TURN servers. snowflake-1.1.0/client/lib/000077500000000000000000000000001411410351300155175ustar00rootroot00000000000000snowflake-1.1.0/client/lib/interfaces.go000066400000000000000000000014601411410351300201720ustar00rootroot00000000000000package lib import ( "net" ) // Interface for catching Snowflakes. (aka the remote dialer) type Tongue interface { Catch() (*WebRTCPeer, error) // Get the maximum number of snowflakes GetMax() int } // Interface for collecting some number of Snowflakes, for passing along // ultimately to the SOCKS handler. type SnowflakeCollector interface { // Add a Snowflake to the collection. // Implementation should decide how to connect and maintain the webRTCConn. Collect() (*WebRTCPeer, error) // Remove and return the most available Snowflake from the collection. Pop() *WebRTCPeer // Signal when the collector has stopped collecting. Melted() <-chan struct{} } // Interface to adapt to goptlib's SocksConn struct. type SocksConnector interface { Grant(*net.TCPAddr) error Reject() error net.Conn } snowflake-1.1.0/client/lib/lib_test.go000066400000000000000000000174221411410351300176610ustar00rootroot00000000000000package lib import ( "bytes" "fmt" "io/ioutil" "net" "net/http" "testing" "time" "git.torproject.org/pluggable-transports/snowflake.git/common/util" . "github.com/smartystreets/goconvey/convey" ) type MockTransport struct { statusOverride int body []byte } // Just returns a response with fake SDP answer. func (m *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) { s := ioutil.NopCloser(bytes.NewReader(m.body)) r := &http.Response{ StatusCode: m.statusOverride, Body: s, } return r, nil } type FakeDialer struct { max int } func (w FakeDialer) Catch() (*WebRTCPeer, error) { fmt.Println("Caught a dummy snowflake.") return &WebRTCPeer{closed: make(chan struct{})}, nil } func (w FakeDialer) GetMax() int { return w.max } type FakeSocksConn struct { net.Conn rejected bool } func (f FakeSocksConn) Reject() error { f.rejected = true return nil } func (f FakeSocksConn) Grant(addr *net.TCPAddr) error { return nil } type FakePeers struct{ toRelease *WebRTCPeer } func (f FakePeers) Collect() (*WebRTCPeer, error) { return &WebRTCPeer{}, nil } func (f FakePeers) Pop() *WebRTCPeer { return nil } func (f FakePeers) Melted() <-chan struct{} { return nil } func TestSnowflakeClient(t *testing.T) { Convey("Peers", t, func() { Convey("Can construct", func() { d := &FakeDialer{max: 1} p, _ := NewPeers(d) So(p.Tongue.GetMax(), ShouldEqual, 1) So(p.snowflakeChan, ShouldNotBeNil) So(cap(p.snowflakeChan), ShouldEqual, 1) }) Convey("Collecting a Snowflake requires a Tongue.", func() { p, err := NewPeers(nil) So(err, ShouldNotBeNil) // Set the dialer so that collection is possible. d := &FakeDialer{max: 1} p, err = NewPeers(d) _, err = p.Collect() So(err, ShouldBeNil) So(p.Count(), ShouldEqual, 1) // S _, err = p.Collect() }) Convey("Collection continues until capacity.", func() { c := 5 p, _ := NewPeers(FakeDialer{max: c}) // Fill up to capacity. for i := 0; i < c; i++ { fmt.Println("Adding snowflake ", i) _, err := p.Collect() So(err, ShouldBeNil) So(p.Count(), ShouldEqual, i+1) } // But adding another gives an error. So(p.Count(), ShouldEqual, c) _, err := p.Collect() So(err, ShouldNotBeNil) So(p.Count(), ShouldEqual, c) // But popping allows it to continue. s := p.Pop() s.Close() So(s, ShouldNotBeNil) So(p.Count(), ShouldEqual, c-1) _, err = p.Collect() So(err, ShouldBeNil) So(p.Count(), ShouldEqual, c) }) Convey("Count correctly purges peers marked for deletion.", func() { p, _ := NewPeers(FakeDialer{max: 5}) p.Collect() p.Collect() p.Collect() p.Collect() So(p.Count(), ShouldEqual, 4) s := p.Pop() s.Close() So(p.Count(), ShouldEqual, 3) s = p.Pop() s.Close() So(p.Count(), ShouldEqual, 2) }) Convey("End Closes all peers.", func() { cnt := 5 p, _ := NewPeers(FakeDialer{max: cnt}) for i := 0; i < cnt; i++ { p.activePeers.PushBack(&WebRTCPeer{closed: make(chan struct{})}) } So(p.Count(), ShouldEqual, cnt) p.End() <-p.Melted() So(p.Count(), ShouldEqual, 0) }) Convey("Pop skips over closed peers.", func() { p, _ := NewPeers(FakeDialer{max: 4}) wc1, _ := p.Collect() wc2, _ := p.Collect() wc3, _ := p.Collect() So(wc1, ShouldNotBeNil) So(wc2, ShouldNotBeNil) So(wc3, ShouldNotBeNil) wc1.Close() r := p.Pop() So(p.Count(), ShouldEqual, 2) So(r, ShouldEqual, wc2) wc4, _ := p.Collect() wc2.Close() wc3.Close() r = p.Pop() So(r, ShouldEqual, wc4) }) Convey("Terminate Connect() loop", func() { p, _ := NewPeers(FakeDialer{max: 4}) go func() { for { p.Collect() select { case <-p.Melted(): return default: } } }() <-time.After(10 * time.Second) p.End() <-p.Melted() So(p.Count(), ShouldEqual, 0) }) }) Convey("Dialers", t, func() { Convey("Can construct WebRTCDialer.", func() { broker := &BrokerChannel{Host: "test"} d := NewWebRTCDialer(broker, nil, 1) So(d, ShouldNotBeNil) So(d.BrokerChannel, ShouldNotBeNil) So(d.BrokerChannel.Host, ShouldEqual, "test") }) SkipConvey("WebRTCDialer can Catch a snowflake.", func() { broker := &BrokerChannel{Host: "test"} d := NewWebRTCDialer(broker, nil, 1) conn, err := d.Catch() So(conn, ShouldBeNil) So(err, ShouldNotBeNil) }) }) Convey("Rendezvous", t, func() { transport := &MockTransport{ http.StatusOK, []byte(`{"answer": "{\"type\":\"answer\",\"sdp\":\"fake\"}" }`), } fakeOffer, err := util.DeserializeSessionDescription(`{"type":"offer","sdp":"test"}`) if err != nil { panic(err) } Convey("Construct BrokerChannel with no front domain", func() { b, err := NewBrokerChannel("test.broker", "", transport, false) So(b.url, ShouldNotBeNil) So(err, ShouldBeNil) So(b.url.Path, ShouldResemble, "test.broker") So(b.transport, ShouldNotBeNil) }) Convey("Construct BrokerChannel *with* front domain", func() { b, err := NewBrokerChannel("test.broker", "front", transport, false) So(b.url, ShouldNotBeNil) So(err, ShouldBeNil) So(b.url.Path, ShouldResemble, "test.broker") So(b.url.Host, ShouldResemble, "front") So(b.transport, ShouldNotBeNil) }) Convey("BrokerChannel.Negotiate responds with answer", func() { b, err := NewBrokerChannel("test.broker", "", transport, false) So(err, ShouldBeNil) answer, err := b.Negotiate(fakeOffer) So(err, ShouldBeNil) So(answer, ShouldNotBeNil) So(answer.SDP, ShouldResemble, "fake") }) Convey("BrokerChannel.Negotiate fails", func() { b, err := NewBrokerChannel("test.broker", "", &MockTransport{http.StatusOK, []byte(`{"error": "no snowflake proxies currently available"}`)}, false) So(err, ShouldBeNil) answer, err := b.Negotiate(fakeOffer) So(err, ShouldNotBeNil) So(answer, ShouldBeNil) }) Convey("BrokerChannel.Negotiate fails with unexpected error", func() { b, err := NewBrokerChannel("test.broker", "", &MockTransport{http.StatusInternalServerError, []byte("\n")}, false) So(err, ShouldBeNil) answer, err := b.Negotiate(fakeOffer) So(err, ShouldNotBeNil) So(answer, ShouldBeNil) So(err.Error(), ShouldResemble, BrokerErrorUnexpected) }) Convey("BrokerChannel.Negotiate fails with large read", func() { b, err := NewBrokerChannel("test.broker", "", &MockTransport{http.StatusOK, make([]byte, 100001, 100001)}, false) So(err, ShouldBeNil) answer, err := b.Negotiate(fakeOffer) So(err, ShouldNotBeNil) So(answer, ShouldBeNil) So(err.Error(), ShouldResemble, "unexpected EOF") }) }) } func TestWebRTCPeer(t *testing.T) { Convey("WebRTCPeer", t, func(c C) { p := &WebRTCPeer{closed: make(chan struct{})} Convey("checks for staleness", func() { go p.checkForStaleness(time.Second) <-time.After(2 * time.Second) So(p.Closed(), ShouldEqual, true) }) }) } func TestICEServerParser(t *testing.T) { Convey("Test parsing of ICE servers", t, func() { for _, test := range []struct { input []string urls [][]string length int }{ { []string{"stun:stun.l.google.com:19302"}, [][]string{[]string{"stun:stun.l.google.com:19302"}}, 1, }, { []string{"stun:stun.l.google.com:19302", "stun.ekiga.net"}, [][]string{[]string{"stun:stun.l.google.com:19302"}, []string{"stun.ekiga.net"}}, 2, }, { []string{"stun:stun.l.google.com:19302", "stun.ekiga.net"}, [][]string{[]string{"stun:stun.l.google.com:19302"}, []string{"stun.ekiga.net"}}, 2, }, } { servers := parseIceServers(test.input) if test.urls == nil { So(servers, ShouldBeNil) } else { So(servers, ShouldNotBeNil) } So(len(servers), ShouldEqual, test.length) for _, server := range servers { So(test.urls, ShouldContain, server.URLs) } } }) } snowflake-1.1.0/client/lib/peers.go000066400000000000000000000066541411410351300171770ustar00rootroot00000000000000package lib import ( "container/list" "errors" "fmt" "log" "sync" ) // Container which keeps track of multiple WebRTC remote peers. // Implements |SnowflakeCollector|. // // Maintaining a set of pre-connected Peers with fresh but inactive datachannels // allows allows rapid recovery when the current WebRTC Peer disconnects. // // Note: For now, only one remote can be active at any given moment. // This is a property of Tor circuits & its current multiplexing constraints, // but could be updated if that changes. // (Also, this constraint does not necessarily apply to the more generic PT // version of Snowflake) type Peers struct { Tongue BytesLogger BytesLogger snowflakeChan chan *WebRTCPeer activePeers *list.List melt chan struct{} collectLock sync.Mutex } // Construct a fresh container of remote peers. func NewPeers(tongue Tongue) (*Peers, error) { p := &Peers{} // Use buffered go channel to pass snowflakes onwards to the SOCKS handler. if tongue == nil { return nil, errors.New("missing Tongue to catch Snowflakes with") } p.snowflakeChan = make(chan *WebRTCPeer, tongue.GetMax()) p.activePeers = list.New() p.melt = make(chan struct{}) p.Tongue = tongue return p, nil } // As part of |SnowflakeCollector| interface. func (p *Peers) Collect() (*WebRTCPeer, error) { // Engage the Snowflake Catching interface, which must be available. p.collectLock.Lock() defer p.collectLock.Unlock() select { case <-p.melt: return nil, fmt.Errorf("Snowflakes have melted") default: } if nil == p.Tongue { return nil, errors.New("missing Tongue to catch Snowflakes with") } cnt := p.Count() capacity := p.Tongue.GetMax() s := fmt.Sprintf("Currently at [%d/%d]", cnt, capacity) if cnt >= capacity { return nil, fmt.Errorf("At capacity [%d/%d]", cnt, capacity) } log.Println("WebRTC: Collecting a new Snowflake.", s) // BUG: some broker conflict here. connection, err := p.Tongue.Catch() if nil != err { return nil, err } // Track new valid Snowflake in internal collection and pass along. p.activePeers.PushBack(connection) p.snowflakeChan <- connection return connection, nil } // Pop blocks until an available, valid snowflake appears. Returns nil after End // has been called. func (p *Peers) Pop() *WebRTCPeer { for { snowflake, ok := <-p.snowflakeChan if !ok { return nil } if snowflake.Closed() { continue } // Set to use the same rate-limited traffic logger to keep consistency. snowflake.BytesLogger = p.BytesLogger return snowflake } } // As part of |SnowflakeCollector| interface. func (p *Peers) Melted() <-chan struct{} { return p.melt } // Returns total available Snowflakes (including the active one) // The count only reduces when connections themselves close, rather than when // they are popped. func (p *Peers) Count() int { p.purgeClosedPeers() return p.activePeers.Len() } func (p *Peers) purgeClosedPeers() { for e := p.activePeers.Front(); e != nil; { next := e.Next() conn := e.Value.(*WebRTCPeer) // Purge those marked for deletion. if conn.Closed() { p.activePeers.Remove(e) } e = next } } // Close all Peers contained here. func (p *Peers) End() { close(p.melt) p.collectLock.Lock() defer p.collectLock.Unlock() close(p.snowflakeChan) cnt := p.Count() for e := p.activePeers.Front(); e != nil; { next := e.Next() conn := e.Value.(*WebRTCPeer) conn.Close() p.activePeers.Remove(e) e = next } log.Printf("WebRTC: melted all %d snowflakes.", cnt) } snowflake-1.1.0/client/lib/rendezvous.go000066400000000000000000000131151411410351300202530ustar00rootroot00000000000000// WebRTC rendezvous requires the exchange of SessionDescriptions between // peers in order to establish a PeerConnection. // // This file contains the one method currently available to Snowflake: // // - Domain-fronted HTTP signaling. The Broker automatically exchange offers // and answers between this client and some remote WebRTC proxy. package lib import ( "bytes" "errors" "io" "io/ioutil" "log" "net/http" "net/url" "sync" "time" "git.torproject.org/pluggable-transports/snowflake.git/common/messages" "git.torproject.org/pluggable-transports/snowflake.git/common/nat" "git.torproject.org/pluggable-transports/snowflake.git/common/util" "github.com/pion/webrtc/v3" ) const ( BrokerErrorUnexpected string = "Unexpected error, no answer." readLimit = 100000 //Maximum number of bytes to be read from an HTTP response ) // Signalling Channel to the Broker. 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. keepLocalAddresses bool NATType string lock sync.Mutex } // We make a copy of DefaultTransport because we want the default Dial // and TLSHandshakeTimeout settings. But we want to disable the default // ProxyFromEnvironment setting. func CreateBrokerTransport() http.RoundTripper { transport := http.DefaultTransport.(*http.Transport) transport.Proxy = nil transport.ResponseHeaderTimeout = 15 * time.Second return transport } // 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, keepLocalAddresses bool) (*BrokerChannel, error) { targetURL, err := url.Parse(broker) if err != nil { return nil, err } log.Println("Rendezvous using Broker at:", broker) bc := new(BrokerChannel) bc.url = targetURL if front != "" { // Optional front domain. log.Println("Domain fronting using:", front) bc.Host = bc.url.Host bc.url.Host = front } bc.transport = transport bc.keepLocalAddresses = keepLocalAddresses bc.NATType = nat.NATUnknown return bc, nil } func limitedRead(r io.Reader, limit int64) ([]byte, error) { p, err := ioutil.ReadAll(&io.LimitedReader{R: r, N: limit + 1}) if err != nil { return p, err } else if int64(len(p)) == limit+1 { return p[0:limit], io.ErrUnexpectedEOF } return p, err } // Roundtrip HTTP POST using WebRTC SessionDescriptions. // // Send an SDP offer to the broker, which assigns a proxy and responds // with an SDP answer from a designated remote WebRTC peer. func (bc *BrokerChannel) Negotiate(offer *webrtc.SessionDescription) ( *webrtc.SessionDescription, error) { log.Println("Negotiating via BrokerChannel...\nTarget URL: ", bc.Host, "\nFront URL: ", bc.url.Host) // 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 if !bc.keepLocalAddresses { offer = &webrtc.SessionDescription{ Type: offer.Type, SDP: util.StripLocalAddresses(offer.SDP), } } offerSDP, err := util.SerializeSessionDescription(offer) if err != nil { return nil, err } // Encode client poll request bc.lock.Lock() req := &messages.ClientPollRequest{ Offer: offerSDP, NAT: bc.NATType, } body, err := req.EncodePollRequest() bc.lock.Unlock() if err != nil { return nil, err } data := bytes.NewReader([]byte(body)) // Suffix with broker's client registration handler. clientURL := bc.url.ResolveReference(&url.URL{Path: "client"}) request, err := http.NewRequest("POST", clientURL.String(), data) if nil != err { return nil, err } if "" != bc.Host { // Set true host if necessary. request.Host = bc.Host } resp, err := bc.transport.RoundTrip(request) if nil != err { return nil, err } defer resp.Body.Close() log.Printf("BrokerChannel Response:\n%s\n\n", resp.Status) switch resp.StatusCode { case http.StatusOK: body, err := limitedRead(resp.Body, readLimit) if nil != err { return nil, err } log.Printf("Received answer: %s", string(body)) resp, err := messages.DecodeClientPollResponse(body) if err != nil { return nil, err } if resp.Error != "" { return nil, errors.New(resp.Error) } return util.DeserializeSessionDescription(resp.Answer) default: return nil, errors.New(BrokerErrorUnexpected) } } func (bc *BrokerChannel) SetNATType(NATType string) { bc.lock.Lock() bc.NATType = NATType bc.lock.Unlock() log.Printf("NAT Type: %s", NATType) } // Implements the |Tongue| interface to catch snowflakes, using BrokerChannel. type WebRTCDialer struct { *BrokerChannel webrtcConfig *webrtc.Configuration max int } func NewWebRTCDialer(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int) *WebRTCDialer { config := webrtc.Configuration{ ICEServers: iceServers, } return &WebRTCDialer{ BrokerChannel: broker, webrtcConfig: &config, max: max, } } // Initialize a WebRTC Connection by signaling through the broker. func (w WebRTCDialer) Catch() (*WebRTCPeer, error) { // TODO: [#25591] Fetch ICE server information from Broker. // TODO: [#25596] Consider TURN servers here too. return NewWebRTCPeer(w.webrtcConfig, w.BrokerChannel) } // Returns the maximum number of snowflakes to collect func (w WebRTCDialer) GetMax() int { return w.max } snowflake-1.1.0/client/lib/snowflake.go000066400000000000000000000201061411410351300200360ustar00rootroot00000000000000package lib import ( "context" "errors" "log" "math/rand" "net" "strings" "time" "git.torproject.org/pluggable-transports/snowflake.git/common/nat" "git.torproject.org/pluggable-transports/snowflake.git/common/turbotunnel" "github.com/pion/webrtc/v3" "github.com/xtaci/kcp-go/v5" "github.com/xtaci/smux" ) const ( ReconnectTimeout = 10 * time.Second SnowflakeTimeout = 20 * time.Second // How long to wait for the OnOpen callback on a DataChannel. DataChannelTimeout = 10 * time.Second ) type dummyAddr struct{} func (addr dummyAddr) Network() string { return "dummy" } func (addr dummyAddr) String() string { return "dummy" } // Transport is a structure with methods that conform to the Go PT v2.1 API // https://github.com/Pluggable-Transports/Pluggable-Transports-spec/blob/master/releases/PTSpecV2.1/Pluggable%20Transport%20Specification%20v2.1%20-%20Go%20Transport%20API.pdf type Transport struct { dialer *WebRTCDialer } // Create a new Snowflake transport client that can spawn multiple Snowflake connections. // brokerURL and frontDomain are the urls for the broker host and domain fronting host // iceAddresses are the STUN/TURN urls needed for WebRTC negotiation // keepLocalAddresses is a flag to enable sending local network addresses (for testing purposes) // max is the maximum number of snowflakes the client should gather for each SOCKS connection func NewSnowflakeClient(brokerURL, frontDomain string, iceAddresses []string, keepLocalAddresses bool, max int) (*Transport, error) { log.Println("\n\n\n --- Starting Snowflake Client ---") iceServers := parseIceServers(iceAddresses) // chooses a random subset of servers from inputs rand.Seed(time.Now().UnixNano()) rand.Shuffle(len(iceServers), func(i, j int) { iceServers[i], iceServers[j] = iceServers[j], iceServers[i] }) if len(iceServers) > 2 { iceServers = iceServers[:(len(iceServers)+1)/2] } log.Printf("Using ICE servers:") for _, server := range iceServers { log.Printf("url: %v", strings.Join(server.URLs, " ")) } // Use potentially domain-fronting broker to rendezvous. broker, err := NewBrokerChannel( brokerURL, frontDomain, CreateBrokerTransport(), keepLocalAddresses) if err != nil { return nil, err } go updateNATType(iceServers, broker) transport := &Transport{dialer: NewWebRTCDialer(broker, iceServers, max)} return transport, nil } // Create a new Snowflake connection. Starts the collection of snowflakes and returns a // smux Stream. func (t *Transport) Dial() (net.Conn, error) { // Cleanup functions to run before returning, in case of an error. var cleanup []func() defer func() { // Run cleanup in reverse order, as defer does. for i := len(cleanup) - 1; i >= 0; i-- { cleanup[i]() } }() // Prepare to collect remote WebRTC peers. snowflakes, err := NewPeers(t.dialer) if err != nil { return nil, err } cleanup = append(cleanup, func() { snowflakes.End() }) // Use a real logger to periodically output how much traffic is happening. snowflakes.BytesLogger = NewBytesSyncLogger() log.Printf("---- SnowflakeConn: begin collecting snowflakes ---") go connectLoop(snowflakes) // Create a new smux session log.Printf("---- SnowflakeConn: starting a new session ---") pconn, sess, err := newSession(snowflakes) if err != nil { return nil, err } cleanup = append(cleanup, func() { pconn.Close() sess.Close() }) // On the smux session we overlay a stream. stream, err := sess.OpenStream() if err != nil { return nil, err } // Begin exchanging data. log.Printf("---- SnowflakeConn: begin stream %v ---", stream.ID()) cleanup = append(cleanup, func() { stream.Close() }) // All good, clear the cleanup list. cleanup = nil return &SnowflakeConn{Stream: stream, sess: sess, pconn: pconn, snowflakes: snowflakes}, nil } type SnowflakeConn struct { *smux.Stream sess *smux.Session pconn net.PacketConn snowflakes *Peers } func (conn *SnowflakeConn) Close() error { log.Printf("---- SnowflakeConn: closed stream %v ---", conn.ID()) conn.Stream.Close() log.Printf("---- SnowflakeConn: end collecting snowflakes ---") conn.snowflakes.End() conn.pconn.Close() log.Printf("---- SnowflakeConn: discarding finished session ---") conn.sess.Close() return nil //TODO: return errors if any of the above do } // loop through all provided STUN servers until we exhaust the list or find // one that is compatable with RFC 5780 func updateNATType(servers []webrtc.ICEServer, broker *BrokerChannel) { var restrictedNAT bool var err error for _, server := range servers { addr := strings.TrimPrefix(server.URLs[0], "stun:") restrictedNAT, err = nat.CheckIfRestrictedNAT(addr) if err == nil { if restrictedNAT { broker.SetNATType(nat.NATRestricted) } else { broker.SetNATType(nat.NATUnrestricted) } break } } if err != nil { broker.SetNATType(nat.NATUnknown) } } // Returns a slice of webrtc.ICEServer given a slice of addresses func parseIceServers(addresses []string) []webrtc.ICEServer { var servers []webrtc.ICEServer if len(addresses) == 0 { return nil } for _, url := range addresses { url = strings.TrimSpace(url) servers = append(servers, webrtc.ICEServer{ URLs: []string{url}, }) } return servers } // newSession returns a new smux.Session and the net.PacketConn it is running // over. The net.PacketConn successively connects through Snowflake proxies // pulled from snowflakes. func newSession(snowflakes SnowflakeCollector) (net.PacketConn, *smux.Session, error) { clientID := turbotunnel.NewClientID() // We build a persistent KCP session on a sequence of ephemeral WebRTC // connections. This dialContext tells RedialPacketConn how to get a new // WebRTC connection when the previous one dies. Inside each WebRTC // connection, we use EncapsulationPacketConn to encode packets into a // stream. dialContext := func(ctx context.Context) (net.PacketConn, error) { log.Printf("redialing on same connection") // Obtain an available WebRTC remote. May block. conn := snowflakes.Pop() if conn == nil { return nil, errors.New("handler: Received invalid Snowflake") } log.Println("---- Handler: snowflake assigned ----") // Send the magic Turbo Tunnel token. _, err := conn.Write(turbotunnel.Token[:]) if err != nil { return nil, err } // Send ClientID prefix. _, err = conn.Write(clientID[:]) if err != nil { return nil, err } return NewEncapsulationPacketConn(dummyAddr{}, dummyAddr{}, conn), nil } pconn := turbotunnel.NewRedialPacketConn(dummyAddr{}, dummyAddr{}, dialContext) // conn is built on the underlying RedialPacketConn—when one WebRTC // connection dies, another one will be found to take its place. The // sequence of packets across multiple WebRTC connections drives the KCP // engine. conn, err := kcp.NewConn2(dummyAddr{}, nil, 0, 0, pconn) if err != nil { pconn.Close() return nil, nil, err } // Permit coalescing the payloads of consecutive sends. conn.SetStreamMode(true) // Set the maximum send and receive window sizes to a high number // Removes KCP bottlenecks: https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/issues/40026 conn.SetWindowSize(65535, 65535) // Disable the dynamic congestion window (limit only by the // maximum of local and remote static windows). conn.SetNoDelay( 0, // default nodelay 0, // default interval 0, // default resend 1, // nc=1 => congestion window off ) // On the KCP connection we overlay an smux session and stream. smuxConfig := smux.DefaultConfig() smuxConfig.Version = 2 smuxConfig.KeepAliveTimeout = 10 * time.Minute sess, err := smux.Client(conn, smuxConfig) if err != nil { conn.Close() pconn.Close() return nil, nil, err } return pconn, sess, err } // Maintain |SnowflakeCapacity| number of available WebRTC connections, to // transfer to the Tor SOCKS handler when needed. func connectLoop(snowflakes SnowflakeCollector) { for { timer := time.After(ReconnectTimeout) _, err := snowflakes.Collect() if err != nil { log.Printf("WebRTC: %v Retrying...", err) } select { case <-timer: continue case <-snowflakes.Melted(): log.Println("ConnectLoop: stopped.") return } } } snowflake-1.1.0/client/lib/turbotunnel.go000066400000000000000000000035341411410351300204340ustar00rootroot00000000000000package lib import ( "bufio" "errors" "io" "net" "time" "git.torproject.org/pluggable-transports/snowflake.git/common/encapsulation" ) var errNotImplemented = errors.New("not implemented") // EncapsulationPacketConn implements the net.PacketConn interface over an // io.ReadWriteCloser stream, using the encapsulation package to represent // packets in a stream. type EncapsulationPacketConn struct { io.ReadWriteCloser localAddr net.Addr remoteAddr net.Addr bw *bufio.Writer } // NewEncapsulationPacketConn makes func NewEncapsulationPacketConn( localAddr, remoteAddr net.Addr, conn io.ReadWriteCloser, ) *EncapsulationPacketConn { return &EncapsulationPacketConn{ ReadWriteCloser: conn, localAddr: localAddr, remoteAddr: remoteAddr, bw: bufio.NewWriter(conn), } } // ReadFrom reads an encapsulated packet from the stream. func (c *EncapsulationPacketConn) ReadFrom(p []byte) (int, net.Addr, error) { data, err := encapsulation.ReadData(c.ReadWriteCloser) if err != nil { return 0, c.remoteAddr, err } return copy(p, data), c.remoteAddr, nil } // WriteTo writes an encapsulated packet to the stream. func (c *EncapsulationPacketConn) WriteTo(p []byte, addr net.Addr) (int, error) { // addr is ignored. _, err := encapsulation.WriteData(c.bw, p) if err == nil { err = c.bw.Flush() } if err != nil { return 0, err } return len(p), nil } // LocalAddr returns the localAddr value that was passed to // NewEncapsulationPacketConn. func (c *EncapsulationPacketConn) LocalAddr() net.Addr { return c.localAddr } func (c *EncapsulationPacketConn) SetDeadline(t time.Time) error { return errNotImplemented } func (c *EncapsulationPacketConn) SetReadDeadline(t time.Time) error { return errNotImplemented } func (c *EncapsulationPacketConn) SetWriteDeadline(t time.Time) error { return errNotImplemented } snowflake-1.1.0/client/lib/util.go000066400000000000000000000026731411410351300170330ustar00rootroot00000000000000package lib import ( "log" "time" ) const ( LogTimeInterval = 5 * time.Second ) type BytesLogger interface { AddOutbound(int) AddInbound(int) } // Default BytesLogger does nothing. type BytesNullLogger struct{} func (b BytesNullLogger) AddOutbound(amount int) {} func (b BytesNullLogger) AddInbound(amount int) {} // BytesSyncLogger uses channels to safely log from multiple sources with output // occuring at reasonable intervals. type BytesSyncLogger struct { outboundChan chan int inboundChan chan int } // NewBytesSyncLogger returns a new BytesSyncLogger and starts it loggin. func NewBytesSyncLogger() *BytesSyncLogger { b := &BytesSyncLogger{ outboundChan: make(chan int, 5), inboundChan: make(chan int, 5), } go b.log() return b } func (b *BytesSyncLogger) log() { var outbound, inbound, outEvents, inEvents int ticker := time.NewTicker(LogTimeInterval) for { select { case <-ticker.C: if outEvents > 0 || inEvents > 0 { log.Printf("Traffic Bytes (in|out): %d | %d -- (%d OnMessages, %d Sends)", inbound, outbound, inEvents, outEvents) } outbound = 0 outEvents = 0 inbound = 0 inEvents = 0 case amount := <-b.outboundChan: outbound += amount outEvents++ case amount := <-b.inboundChan: inbound += amount inEvents++ } } } func (b *BytesSyncLogger) AddOutbound(amount int) { b.outboundChan <- amount } func (b *BytesSyncLogger) AddInbound(amount int) { b.inboundChan <- amount } snowflake-1.1.0/client/lib/webrtc.go000066400000000000000000000136141411410351300173410ustar00rootroot00000000000000package lib import ( "crypto/rand" "encoding/hex" "errors" "io" "log" "sync" "time" "github.com/pion/webrtc/v3" ) // Remote WebRTC peer. // // Handles preparation of go-webrtc PeerConnection. Only ever has // one DataChannel. type WebRTCPeer struct { id string pc *webrtc.PeerConnection transport *webrtc.DataChannel recvPipe *io.PipeReader writePipe *io.PipeWriter mu sync.Mutex // protects the following: lastReceive time.Time open chan struct{} // Channel to notify when datachannel opens closed chan struct{} once sync.Once // Synchronization for PeerConnection destruction BytesLogger BytesLogger } // Construct a WebRTC PeerConnection. func NewWebRTCPeer(config *webrtc.Configuration, broker *BrokerChannel) (*WebRTCPeer, error) { connection := new(WebRTCPeer) { var buf [8]byte if _, err := rand.Read(buf[:]); err != nil { panic(err) } connection.id = "snowflake-" + hex.EncodeToString(buf[:]) } connection.closed = make(chan struct{}) // Override with something that's not NullLogger to have real logging. connection.BytesLogger = &BytesNullLogger{} // Pipes remain the same even when DataChannel gets switched. connection.recvPipe, connection.writePipe = io.Pipe() err := connection.connect(config, broker) if err != nil { connection.Close() return nil, err } return connection, nil } // Read bytes from local SOCKS. // As part of |io.ReadWriter| func (c *WebRTCPeer) Read(b []byte) (int, error) { return c.recvPipe.Read(b) } // Writes bytes out to remote WebRTC. // As part of |io.ReadWriter| func (c *WebRTCPeer) Write(b []byte) (int, error) { err := c.transport.Send(b) if err != nil { return 0, err } c.BytesLogger.AddOutbound(len(b)) return len(b), nil } //Returns a boolean indicated whether the peer is closed func (c *WebRTCPeer) Closed() bool { select { case <-c.closed: return true default: } return false } func (c *WebRTCPeer) Close() error { c.once.Do(func() { close(c.closed) c.cleanup() log.Printf("WebRTC: Closing") }) return nil } // Prevent long-lived broken remotes. // Should also update the DataChannel in underlying go-webrtc's to make Closes // more immediate / responsive. func (c *WebRTCPeer) checkForStaleness(timeout time.Duration) { c.mu.Lock() c.lastReceive = time.Now() c.mu.Unlock() for { c.mu.Lock() lastReceive := c.lastReceive c.mu.Unlock() if time.Since(lastReceive) > timeout { log.Printf("WebRTC: No messages received for %v -- closing stale connection.", timeout) c.Close() return } select { case <-c.closed: return case <-time.After(time.Second): } } } func (c *WebRTCPeer) connect(config *webrtc.Configuration, broker *BrokerChannel) error { log.Println(c.id, " connecting...") // TODO: When go-webrtc is more stable, it's possible that a new // PeerConnection won't need to be re-prepared each time. c.preparePeerConnection(config) answer, err := broker.Negotiate(c.pc.LocalDescription()) if err != nil { return err } log.Printf("Received Answer.\n") err = c.pc.SetRemoteDescription(*answer) if nil != err { log.Println("WebRTC: Unable to SetRemoteDescription:", err) return err } // Wait for the datachannel to open or time out select { case <-c.open: case <-time.After(DataChannelTimeout): c.transport.Close() return errors.New("timeout waiting for DataChannel.OnOpen") } go c.checkForStaleness(SnowflakeTimeout) return nil } // preparePeerConnection creates a new WebRTC PeerConnection and returns it // after ICE candidate gathering is complete.. func (c *WebRTCPeer) preparePeerConnection(config *webrtc.Configuration) error { var err error c.pc, err = webrtc.NewPeerConnection(*config) if err != nil { log.Printf("NewPeerConnection ERROR: %s", err) return err } ordered := true dataChannelOptions := &webrtc.DataChannelInit{ Ordered: &ordered, } // We must create the data channel before creating an offer // https://github.com/pion/webrtc/wiki/Release-WebRTC@v3.0.0 dc, err := c.pc.CreateDataChannel(c.id, dataChannelOptions) if err != nil { log.Printf("CreateDataChannel ERROR: %s", err) return err } dc.OnOpen(func() { log.Println("WebRTC: DataChannel.OnOpen") close(c.open) }) dc.OnClose(func() { log.Println("WebRTC: DataChannel.OnClose") c.Close() }) dc.OnMessage(func(msg webrtc.DataChannelMessage) { if len(msg.Data) <= 0 { log.Println("0 length message---") } n, err := c.writePipe.Write(msg.Data) c.BytesLogger.AddInbound(n) if err != nil { // TODO: Maybe shouldn't actually close. log.Println("Error writing to SOCKS pipe") if inerr := c.writePipe.CloseWithError(err); inerr != nil { log.Printf("c.writePipe.CloseWithError returned error: %v", inerr) } } c.mu.Lock() c.lastReceive = time.Now() c.mu.Unlock() }) c.transport = dc c.open = make(chan struct{}) log.Println("WebRTC: DataChannel created.") // Allow candidates to accumulate until ICEGatheringStateComplete. done := webrtc.GatheringCompletePromise(c.pc) offer, err := c.pc.CreateOffer(nil) // TODO: Potentially timeout and retry if ICE isn't working. if err != nil { log.Println("Failed to prepare offer", err) c.pc.Close() return err } log.Println("WebRTC: Created offer") err = c.pc.SetLocalDescription(offer) if err != nil { log.Println("Failed to prepare offer", err) c.pc.Close() return err } log.Println("WebRTC: Set local description") <-done // Wait for ICE candidate gathering to complete. log.Println("WebRTC: PeerConnection created.") return nil } // Close all channels and transports func (c *WebRTCPeer) cleanup() { // Close this side of the SOCKS pipe. if c.writePipe != nil { // c.writePipe can be nil in tests. c.writePipe.Close() } if nil != c.transport { log.Printf("WebRTC: closing DataChannel") c.transport.Close() } if nil != c.pc { log.Printf("WebRTC: closing PeerConnection") err := c.pc.Close() if nil != err { log.Printf("Error closing peerconnection...") } } } snowflake-1.1.0/client/snowflake.go000066400000000000000000000132511411410351300172730ustar00rootroot00000000000000// Client transport plugin for the Snowflake pluggable transport. package main import ( "flag" "io" "io/ioutil" "log" "net" "os" "os/signal" "path/filepath" "strings" "sync" "syscall" pt "git.torproject.org/pluggable-transports/goptlib.git" sf "git.torproject.org/pluggable-transports/snowflake.git/client/lib" "git.torproject.org/pluggable-transports/snowflake.git/common/safelog" ) const ( DefaultSnowflakeCapacity = 1 ) // Exchanges bytes between two ReadWriters. // (In this case, between a SOCKS connection and a snowflake transport conn) func copyLoop(socks, sfconn io.ReadWriter) { done := make(chan struct{}, 2) go func() { if _, err := io.Copy(socks, sfconn); err != nil { log.Printf("copying Snowflake to SOCKS resulted in error: %v", err) } done <- struct{}{} }() go func() { if _, err := io.Copy(sfconn, socks); err != nil { log.Printf("copying SOCKS to Snowflake resulted in error: %v", err) } done <- struct{}{} }() <-done log.Println("copy loop ended") } // Accept local SOCKS connections and connect to a Snowflake connection func socksAcceptLoop(ln *pt.SocksListener, transport *sf.Transport, shutdown chan struct{}, wg *sync.WaitGroup) { defer ln.Close() for { conn, err := ln.AcceptSocks() if err != nil { if err, ok := err.(net.Error); ok && err.Temporary() { continue } log.Printf("SOCKS accept error: %s", err) break } log.Printf("SOCKS accepted: %v", conn.Req) wg.Add(1) go func() { defer wg.Done() defer conn.Close() err := conn.Grant(&net.TCPAddr{IP: net.IPv4zero, Port: 0}) if err != nil { log.Printf("conn.Grant error: %s", err) return } handler := make(chan struct{}) go func() { defer close(handler) sconn, err := transport.Dial() if err != nil { log.Printf("dial error: %s", err) return } defer sconn.Close() // copy between the created Snowflake conn and the SOCKS conn copyLoop(conn, sconn) }() select { case <-shutdown: log.Println("Received shutdown signal") case <-handler: log.Println("Handler ended") } return }() } } 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") 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") keepLocalAddresses := flag.Bool("keep-local-addresses", false, "keep local LAN address ICE candidates") unsafeLogging := flag.Bool("unsafe-logging", false, "prevent logs from being scrubbed") max := flag.Int("max", DefaultSnowflakeCapacity, "capacity for number of multiplexed WebRTC peers") // Deprecated oldLogToStateDir := flag.Bool("logToStateDir", false, "use -log-to-state-dir instead") oldKeepLocalAddresses := flag.Bool("keepLocalAddresses", false, "use -keep-local-addresses instead") flag.Parse() log.SetFlags(log.LstdFlags | log.LUTC) // Don't write to stderr; versions of tor earlier than about 0.3.5.6 do // not read from the pipe, and eventually we will deadlock because the // buffer is full. // https://bugs.torproject.org/26360 // https://bugs.torproject.org/25600#comment:14 var logOutput = ioutil.Discard if *logFilename != "" { if *logToStateDir || *oldLogToStateDir { stateDir, err := pt.MakeStateDir() if err != nil { log.Fatal(err) } *logFilename = filepath.Join(stateDir, *logFilename) } logFile, err := os.OpenFile(*logFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) if err != nil { log.Fatal(err) } defer logFile.Close() logOutput = logFile } if *unsafeLogging { log.SetOutput(logOutput) } else { // We want to send the log output through our scrubber first log.SetOutput(&safelog.LogScrubber{Output: logOutput}) } iceAddresses := strings.Split(strings.TrimSpace(*iceServersCommas), ",") transport, err := sf.NewSnowflakeClient(*brokerURL, *frontDomain, iceAddresses, *keepLocalAddresses || *oldKeepLocalAddresses, *max) if err != nil { log.Fatal("Failed to start snowflake transport: ", err) } // Begin goptlib client process. ptInfo, err := pt.ClientSetup(nil) if err != nil { log.Fatal(err) } if ptInfo.ProxyURL != nil { pt.ProxyError("proxy is not supported") os.Exit(1) } listeners := make([]net.Listener, 0) shutdown := make(chan struct{}) var wg sync.WaitGroup for _, methodName := range ptInfo.MethodNames { switch methodName { case "snowflake": // TODO: Be able to recover when SOCKS dies. ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") if err != nil { pt.CmethodError(methodName, err.Error()) break } log.Printf("Started SOCKS listener at %v.", ln.Addr()) go socksAcceptLoop(ln, transport, shutdown, &wg) pt.Cmethod(methodName, ln.Version(), ln.Addr()) listeners = append(listeners, ln) default: pt.CmethodError(methodName, "no such method") } } pt.CmethodsDone() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM) if os.Getenv("TOR_PT_EXIT_ON_STDIN_CLOSE") == "1" { // This environment variable means we should treat EOF on stdin // just like SIGTERM: https://bugs.torproject.org/15435. go func() { if _, err := io.Copy(ioutil.Discard, os.Stdin); err != nil { log.Printf("calling io.Copy(ioutil.Discard, os.Stdin) returned error: %v", err) } log.Printf("synthesizing SIGTERM because of stdin close") sigChan <- syscall.SIGTERM }() } // Wait for a signal. <-sigChan log.Println("stopping snowflake") // Signal received, shut down. for _, ln := range listeners { ln.Close() } close(shutdown) wg.Wait() log.Println("snowflake is done.") } snowflake-1.1.0/client/torrc000066400000000000000000000010551411410351300160260ustar00rootroot00000000000000UseBridges 1 DataDirectory datadir ClientTransportPlugin snowflake exec ./client \ -url https://snowflake-broker.torproject.net.global.prod.fastly.net/ \ -front cdn.sstatic.net \ -ice stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,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.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 Bridge snowflake 192.0.2.3:1 SocksPort auto snowflake-1.1.0/client/torrc-localhost000066400000000000000000000002461411410351300200150ustar00rootroot00000000000000UseBridges 1 DataDirectory datadir ClientTransportPlugin snowflake exec ./client \ -url http://localhost:8080/ \ -keep-local-addresses Bridge snowflake 192.0.2.3:1 snowflake-1.1.0/common/000077500000000000000000000000001411410351300147635ustar00rootroot00000000000000snowflake-1.1.0/common/encapsulation/000077500000000000000000000000001411410351300176305ustar00rootroot00000000000000snowflake-1.1.0/common/encapsulation/encapsulation.go000066400000000000000000000142661411410351300230350ustar00rootroot00000000000000// Package encapsulation implements a way of encoding variable-size chunks of // data and padding into a byte stream. // // Each chunk of data or padding starts with a variable-size length prefix. One // bit ("d") in the first byte of the prefix indicates whether the chunk // represents data or padding (1=data, 0=padding). Another bit ("c" for // "continuation") is the indicates whether there are more bytes in the length // prefix. The remaining 6 bits ("x") encode part of the length value. // dcxxxxxx // If the continuation bit is set, then the next byte is also part of the length // prefix. It lacks the "d" bit, has its own "c" bit, and 7 value-carrying bits // ("y"). // cyyyyyyy // The length is decoded by concatenating value-carrying bits, from left to // right, of all value-carrying bits, up to and including the first byte whose // "c" bit is 0. Although in principle this encoding would allow for length // prefixes of any size, length prefixes are arbitrarily limited to 3 bytes and // any attempt to read or write a longer one is an error. These are therefore // the only valid formats: // 00xxxxxx xxxxxx₂ bytes of padding // 10xxxxxx xxxxxx₂ bytes of data // 01xxxxxx 0yyyyyyy xxxxxxyyyyyyy₂ bytes of padding // 11xxxxxx 0yyyyyyy xxxxxxyyyyyyy₂ bytes of data // 01xxxxxx 1yyyyyyy 0zzzzzzz xxxxxxyyyyyyyzzzzzzz₂ bytes of padding // 11xxxxxx 1yyyyyyy 0zzzzzzz xxxxxxyyyyyyyzzzzzzz₂ bytes of data // The maximum encodable length is 11111111111111111111₂ = 0xfffff = 1048575. // There is no requirement to use a length prefix of minimum size; i.e. 00000100 // and 01000000 00000100 are both valid encodings of the value 4. // // After the length prefix follow that many bytes of padding or data. There are // no restrictions on the value of bytes comprising padding. // // The idea for this encapsulation is sketched here: // https://github.com/net4people/bbs/issues/9#issuecomment-524095186 package encapsulation import ( "errors" "io" "io/ioutil" ) // ErrTooLong is the error returned when an encoded length prefix is longer than // 3 bytes, or when ReadData receives an input whose length is too large to // encode in a 3-byte length prefix. var ErrTooLong = errors.New("length prefix is too long") // ReadData returns a new slice with the contents of the next available data // chunk, skipping over any padding chunks that may come first. The returned // error value is nil if and only if a data chunk was present and was read in // its entirety. The returned error is io.EOF only if r ended before the first // byte of a length prefix. If r ended in the middle of a length prefix or // data/padding, the returned error is io.ErrUnexpectedEOF. func ReadData(r io.Reader) ([]byte, error) { for { var b [1]byte _, err := r.Read(b[:]) if err != nil { // This is the only place we may return a real io.EOF. return nil, err } isData := (b[0] & 0x80) != 0 moreLength := (b[0] & 0x40) != 0 n := int(b[0] & 0x3f) for i := 0; moreLength; i++ { if i >= 2 { return nil, ErrTooLong } _, err := r.Read(b[:]) if err == io.EOF { err = io.ErrUnexpectedEOF } if err != nil { return nil, err } moreLength = (b[0] & 0x80) != 0 n = (n << 7) | int(b[0]&0x7f) } if isData { p := make([]byte, n) _, err := io.ReadFull(r, p) if err == io.EOF { err = io.ErrUnexpectedEOF } if err != nil { return nil, err } return p, err } else { _, err := io.CopyN(ioutil.Discard, r, int64(n)) if err == io.EOF { err = io.ErrUnexpectedEOF } if err != nil { return nil, err } } } } // dataPrefixForLength returns a length prefix for the given length, with the // "d" bit set to 1. func dataPrefixForLength(n int) ([]byte, error) { switch { case (n>>0)&0x3f == (n >> 0): return []byte{0x80 | byte((n>>0)&0x3f)}, nil case (n>>7)&0x3f == (n >> 7): return []byte{0xc0 | byte((n>>7)&0x3f), byte((n >> 0) & 0x7f)}, nil case (n>>14)&0x3f == (n >> 14): return []byte{0xc0 | byte((n>>14)&0x3f), 0x80 | byte((n>>7)&0x7f), byte((n >> 0) & 0x7f)}, nil default: return nil, ErrTooLong } } // WriteData encodes a data chunk into w. It returns the total number of bytes // written; i.e., including the length prefix. The error is ErrTooLong if the // length of data cannot fit into a length prefix. func WriteData(w io.Writer, data []byte) (int, error) { prefix, err := dataPrefixForLength(len(data)) if err != nil { return 0, err } total := 0 n, err := w.Write(prefix) total += n if err != nil { return total, err } n, err = w.Write(data) total += n return total, err } var paddingBuffer = make([]byte, 1024) // WritePadding encodes padding chunks, whose total size (including their own // length prefixes) is n. Returns the total number of bytes written to w, which // will be exactly n unless there was an error. The error cannot be ErrTooLong // because this function will write multiple padding chunks if necessary to // reach the requested size. Panics if n is negative. func WritePadding(w io.Writer, n int) (int, error) { if n < 0 { panic("negative length") } total := 0 for n > 0 { p := len(paddingBuffer) if p > n { p = n } n -= p var prefix []byte switch { case ((p-1)>>0)&0x3f == ((p - 1) >> 0): p = p - 1 prefix = []byte{byte((p >> 0) & 0x3f)} case ((p-2)>>7)&0x3f == ((p - 2) >> 7): p = p - 2 prefix = []byte{0x40 | byte((p>>7)&0x3f), byte((p >> 0) & 0x7f)} case ((p-3)>>14)&0x3f == ((p - 3) >> 14): p = p - 3 prefix = []byte{0x40 | byte((p>>14)&0x3f), 0x80 | byte((p>>7)&0x3f), byte((p >> 0) & 0x7f)} } nn, err := w.Write(prefix) total += nn if err != nil { return total, err } nn, err = w.Write(paddingBuffer[:p]) total += nn if err != nil { return total, err } } return total, nil } // MaxDataForSize returns the length of the longest slice that can pe passed to // WriteData, whose total encoded size (including length prefix) is no larger // than n. Call this to find out if a chunk of data will fit into a length // budget. Panics if n == 0. func MaxDataForSize(n int) int { if n == 0 { panic("zero length") } prefix, err := dataPrefixForLength(n) if err == ErrTooLong { return (1 << (6 + 7 + 7)) - 1 - 3 } else if err != nil { panic(err) } return n - len(prefix) } snowflake-1.1.0/common/encapsulation/encapsulation_test.go000066400000000000000000000235011411410351300240640ustar00rootroot00000000000000package encapsulation import ( "bytes" "io" "math/rand" "testing" ) // Return a byte slice with non-trivial contents. func pseudorandomBuffer(n int) []byte { source := rand.NewSource(0) p := make([]byte, n) for i := 0; i < len(p); i++ { p[i] = byte(source.Int63() & 0xff) } return p } func mustWriteData(w io.Writer, p []byte) int { n, err := WriteData(w, p) if err != nil { panic(err) } return n } func mustWritePadding(w io.Writer, n int) int { n, err := WritePadding(w, n) if err != nil { panic(err) } return n } // Test that ReadData(WriteData()) recovers the original data. func TestRoundtrip(t *testing.T) { // Test above and below interesting thresholds. for _, i := range []int{ 0x00, 0x01, 0x3e, 0x3f, 0x40, 0x41, 0xfe, 0xff, 0x100, 0x101, 0x1ffe, 0x1fff, 0x2000, 0x2001, 0xfffe, 0xffff, 0x10000, 0x10001, 0xffffe, 0xfffff, } { original := pseudorandomBuffer(i) var enc bytes.Buffer n, err := WriteData(&enc, original) if err != nil { t.Fatalf("size %d, WriteData returned error %v", i, err) } if enc.Len() != n { t.Fatalf("size %d, returned length was %d, written length was %d", i, n, enc.Len()) } inverse, err := ReadData(&enc) if err != nil { t.Fatalf("size %d, ReadData returned error %v", i, err) } if !bytes.Equal(inverse, original) { t.Fatalf("size %d, got <%x>, expected <%x>", i, inverse, original) } } } // Test that WritePadding writes exactly as much as requested. func TestPaddingLength(t *testing.T) { // Test above and below interesting thresholds. WritePadding also gets // values above 0xfffff, the maximum value of a single length prefix. for _, i := range []int{ 0x00, 0x01, 0x3f, 0x40, 0x41, 0x42, 0xff, 0x100, 0x101, 0x102, 0x2000, 0x2001, 0x2002, 0x2003, 0x10000, 0x10001, 0x10002, 0x10003, 0x100001, 0x100002, 0x100003, 0x100004, } { var enc bytes.Buffer n, err := WritePadding(&enc, i) if err != nil { t.Fatalf("size %d, WritePadding returned error %v", i, err) } if n != i { t.Fatalf("requested %d bytes, returned %d", i, n) } if enc.Len() != n { t.Fatalf("requested %d bytes, wrote %d bytes", i, enc.Len()) } } } // Test that ReadData skips over padding. func TestSkipPadding(t *testing.T) { var data = [][]byte{{}, {}, []byte("hello"), {}, []byte("world")} var enc bytes.Buffer mustWritePadding(&enc, 10) mustWritePadding(&enc, 100) mustWriteData(&enc, data[0]) mustWriteData(&enc, data[1]) mustWritePadding(&enc, 10) mustWriteData(&enc, data[2]) mustWriteData(&enc, data[3]) mustWritePadding(&enc, 10) mustWriteData(&enc, data[4]) mustWritePadding(&enc, 10) mustWritePadding(&enc, 10) for i, expected := range data { actual, err := ReadData(&enc) if err != nil { t.Fatalf("slice %d, got error %v, expected %v", i, err, nil) } if !bytes.Equal(actual, expected) { t.Fatalf("slice %d, got <%x>, expected <%x>", i, actual, expected) } } p, err := ReadData(&enc) if p != nil || err != io.EOF { t.Fatalf("got (<%x>, %v), expected (%v, %v)", p, err, nil, io.EOF) } } // Test that EOF before a length prefix returns io.EOF. func TestEOF(t *testing.T) { p, err := ReadData(bytes.NewReader(nil)) if p != nil || err != io.EOF { t.Fatalf("got (<%x>, %v), expected (%v, %v)", p, err, nil, io.EOF) } } // Test that an EOF while reading a length prefix, or while reading the // subsequent data/padding, returns io.ErrUnexpectedEOF. func TestUnexpectedEOF(t *testing.T) { for _, test := range [][]byte{ {0x40}, // expecting a second length byte {0xc0}, // expecting a second length byte {0x41, 0x80}, // expecting a third length byte {0xc1, 0x80}, // expecting a third length byte {0x02}, // expecting 2 bytes of padding {0x82}, // expecting 2 bytes of data {0x02, 'X'}, // expecting 1 byte of padding {0x82, 'X'}, // expecting 1 byte of data {0x41, 0x00}, // expecting 128 bytes of padding {0xc1, 0x00}, // expecting 128 bytes of data {0x41, 0x00, 'X'}, // expecting 127 bytes of padding {0xc1, 0x00, 'X'}, // expecting 127 bytes of data {0x41, 0x80, 0x00}, // expecting 32768 bytes of padding {0xc1, 0x80, 0x00}, // expecting 32768 bytes of data {0x41, 0x80, 0x00, 'X'}, // expecting 32767 bytes of padding {0xc1, 0x80, 0x00, 'X'}, // expecting 32767 bytes of data } { p, err := ReadData(bytes.NewReader(test)) if p != nil || err != io.ErrUnexpectedEOF { t.Fatalf("<%x> got (<%x>, %v), expected (%v, %v)", test, p, err, nil, io.ErrUnexpectedEOF) } } } // Test that length encodings that are longer than they could be are still // interpreted. func TestNonMinimalLengthEncoding(t *testing.T) { for _, test := range []struct { enc []byte expected []byte }{ {[]byte{0x81, 'X'}, []byte("X")}, {[]byte{0xc0, 0x01, 'X'}, []byte("X")}, {[]byte{0xc0, 0x80, 0x01, 'X'}, []byte("X")}, } { p, err := ReadData(bytes.NewReader(test.enc)) if err != nil { t.Fatalf("<%x> got error %v, expected %v", test.enc, err, nil) } if !bytes.Equal(p, test.expected) { t.Fatalf("<%x> got <%x>, expected <%x>", test.enc, p, test.expected) } } } // Test that ReadData only reads up to 3 bytes of length prefix. func TestReadLimits(t *testing.T) { // Test the maximum length that's possible with 3 bytes of length // prefix. maxLength := (0x3f << 14) | (0x7f << 7) | 0x7f data := bytes.Repeat([]byte{'X'}, maxLength) prefix := []byte{0xff, 0xff, 0x7f} // encodes 0xfffff p, err := ReadData(bytes.NewReader(append(prefix, data...))) if err != nil { t.Fatalf("got error %v, expected %v", err, nil) } if !bytes.Equal(p, data) { t.Fatalf("got %d bytes unequal to %d bytes", len(p), len(data)) } // Test a 4-byte prefix. prefix = []byte{0xc0, 0xc0, 0x80, 0x80} // encodes 0x100000 data = bytes.Repeat([]byte{'X'}, maxLength+1) p, err = ReadData(bytes.NewReader(append(prefix, data...))) if p != nil || err != ErrTooLong { t.Fatalf("got (<%x>, %v), expected (%v, %v)", p, err, nil, ErrTooLong) } // Test that 4 bytes don't work, even when they encode an integer that // would fix in 3 bytes. prefix = []byte{0xc0, 0x80, 0x80, 0x80} // encodes 0x0 data = []byte{} p, err = ReadData(bytes.NewReader(append(prefix, data...))) if p != nil || err != ErrTooLong { t.Fatalf("got (<%x>, %v), expected (%v, %v)", p, err, nil, ErrTooLong) } // Do the same tests with padding lengths. data = []byte("hello") prefix = []byte{0x7f, 0xff, 0x7f} // encodes 0xfffff padding := bytes.Repeat([]byte{'X'}, maxLength) enc := bytes.NewBuffer(append(prefix, padding...)) mustWriteData(enc, data) p, err = ReadData(enc) if err != nil { t.Fatalf("got error %v, expected %v", err, nil) } if !bytes.Equal(p, data) { t.Fatalf("got <%x>, expected <%x>", p, data) } prefix = []byte{0x40, 0xc0, 0x80, 0x80} // encodes 0x100000 padding = bytes.Repeat([]byte{'X'}, maxLength+1) enc = bytes.NewBuffer(append(prefix, padding...)) mustWriteData(enc, data) p, err = ReadData(enc) if p != nil || err != ErrTooLong { t.Fatalf("got (<%x>, %v), expected (%v, %v)", p, err, nil, ErrTooLong) } prefix = []byte{0x40, 0x80, 0x80, 0x80} // encodes 0x0 padding = []byte{} enc = bytes.NewBuffer(append(prefix, padding...)) mustWriteData(enc, data) p, err = ReadData(enc) if p != nil || err != ErrTooLong { t.Fatalf("got (<%x>, %v), expected (%v, %v)", p, err, nil, ErrTooLong) } } // Test that WriteData and WritePadding only accept lengths that can be encoded // in up to 3 bytes of length prefix. func TestWriteLimits(t *testing.T) { maxLength := (0x3f << 14) | (0x7f << 7) | 0x7f var enc bytes.Buffer n, err := WriteData(&enc, bytes.Repeat([]byte{'X'}, maxLength)) if n != maxLength+3 || err != nil { t.Fatalf("got (%d, %v), expected (%d, %v)", n, err, maxLength, nil) } enc.Reset() n, err = WriteData(&enc, bytes.Repeat([]byte{'X'}, maxLength+1)) if n != 0 || err != ErrTooLong { t.Fatalf("got (%d, %v), expected (%d, %v)", n, err, 0, ErrTooLong) } // Padding gets an extra 3 bytes because the prefix is counted as part // of the length. enc.Reset() n, err = WritePadding(&enc, maxLength+3) if n != maxLength+3 || err != nil { t.Fatalf("got (%d, %v), expected (%d, %v)", n, err, maxLength+3, nil) } // Writing a too-long padding is okay because WritePadding will break it // into smaller chunks. enc.Reset() n, err = WritePadding(&enc, maxLength+4) if n != maxLength+4 || err != nil { t.Fatalf("got (%d, %v), expected (%d, %v)", n, err, maxLength+4, nil) } } // Test that WritePadding panics when given a negative length. func TestNegativeLength(t *testing.T) { for _, n := range []int{-1, ^0} { var enc bytes.Buffer panicked, nn, err := testNegativeLengthSub(t, &enc, n) if !panicked { t.Fatalf("WritePadding(%d) returned (%d, %v) instead of panicking", n, nn, err) } } } // Calls WritePadding(w, n) and augments the return value with a flag indicating // whether the call panicked. func testNegativeLengthSub(t *testing.T, w io.Writer, n int) (panicked bool, nn int, err error) { defer func() { if r := recover(); r != nil { panicked = true } }() t.Helper() nn, err = WritePadding(w, n) return false, n, err } // Test that MaxDataForSize panics when given a 0 length. func TestMaxDataForSizeZero(t *testing.T) { defer func() { if r := recover(); r == nil { t.Fatal("didn't panic") } }() MaxDataForSize(0) } // Test thresholds of available sizes for MaxDataForSize. func TestMaxDataForSize(t *testing.T) { for _, test := range []struct { size int expected int }{ {0x01, 0x00}, {0x02, 0x01}, {0x3f, 0x3e}, {0x40, 0x3e}, {0x41, 0x3f}, {0x1fff, 0x1ffd}, {0x2000, 0x1ffd}, {0x2001, 0x1ffe}, {0xfffff, 0xffffc}, {0x100000, 0xffffc}, {0x100001, 0xffffc}, {0x7fffffff, 0xffffc}, } { max := MaxDataForSize(test.size) if max != test.expected { t.Fatalf("size %d, got %d, expected %d", test.size, max, test.expected) } } } snowflake-1.1.0/common/messages/000077500000000000000000000000001411410351300165725ustar00rootroot00000000000000snowflake-1.1.0/common/messages/client.go000066400000000000000000000047301411410351300204030ustar00rootroot00000000000000//Package for communication with the snowflake broker //import "git.torproject.org/pluggable-transports/snowflake.git/common/messages" package messages import ( "encoding/json" "fmt" ) const ClientVersion = "1.0" /* Client--Broker protocol v1.x specification: All messages contain the version number followed by a new line and then the message body := \n := . := | There are two different types of body messages, each encoded in JSON format == ClientPollRequest == := { offer: [nat: (unknown|restricted|unrestricted)] } The NAT field is optional, and if it is missing a value of "unknown" will be assumed. == ClientPollResponse == := { [answer: ] [error: ] } If the broker succeeded in matching the client with a proxy, the answer field MUST contain a valid SDP answer, and the error field MUST be empty. If the answer field is empty, the error field MUST contain a string explaining with a reason for the error. */ type ClientPollRequest struct { Offer string `json:"offer"` NAT string `json:"nat"` } // Encodes a poll message from a snowflake client func (req *ClientPollRequest) EncodePollRequest() ([]byte, error) { body, err := json.Marshal(req) if err != nil { return nil, err } return append([]byte(ClientVersion+"\n"), body...), nil } // Decodes a poll message from a snowflake client func DecodeClientPollRequest(data []byte) (*ClientPollRequest, error) { var message ClientPollRequest err := json.Unmarshal(data, &message) if err != nil { return nil, err } if message.Offer == "" { return nil, fmt.Errorf("no supplied offer") } if message.NAT == "" { message.NAT = "unknown" } return &message, nil } type ClientPollResponse struct { Answer string `json:"answer,omitempty"` Error string `json:"error,omitempty"` } // Encodes a poll response for a snowflake client func (resp *ClientPollResponse) EncodePollResponse() ([]byte, error) { return json.Marshal(resp) } // Decodes a poll response for a snowflake client // If the Error field is empty, the Answer should be non-empty func DecodeClientPollResponse(data []byte) (*ClientPollResponse, error) { var message ClientPollResponse err := json.Unmarshal(data, &message) if err != nil { return nil, err } if message.Error == "" && message.Answer == "" { return nil, fmt.Errorf("received empty broker response") } return &message, nil } snowflake-1.1.0/common/messages/ipc.go000066400000000000000000000004601411410351300176740ustar00rootroot00000000000000package messages import ( "errors" ) type Arg struct { Body []byte RemoteAddr string } var ( ErrBadRequest = errors.New("bad request") ErrInternal = errors.New("internal error") StrTimedOut = "timed out waiting for answer!" StrNoProxies = "no snowflake proxies currently available" ) snowflake-1.1.0/common/messages/messages_test.go000066400000000000000000000206411411410351300217720ustar00rootroot00000000000000package messages import ( "bytes" "encoding/json" "fmt" "testing" . "github.com/smartystreets/goconvey/convey" ) func TestDecodeProxyPollRequest(t *testing.T) { Convey("Context", t, func() { for _, test := range []struct { sid string proxyType string natType string clients int data string err error }{ { //Version 1.0 proxy message "ymbcCMto7KHNGYlp", "", "unknown", 0, `{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0"}`, nil, }, { //Version 1.1 proxy message "ymbcCMto7KHNGYlp", "standalone", "unknown", 0, `{"Sid":"ymbcCMto7KHNGYlp","Version":"1.1","Type":"standalone"}`, nil, }, { //Version 1.2 proxy message "ymbcCMto7KHNGYlp", "standalone", "restricted", 0, `{"Sid":"ymbcCMto7KHNGYlp","Version":"1.2","Type":"standalone", "NAT":"restricted"}`, nil, }, { //Version 1.2 proxy message with clients "ymbcCMto7KHNGYlp", "standalone", "restricted", 24, `{"Sid":"ymbcCMto7KHNGYlp","Version":"1.2","Type":"standalone", "NAT":"restricted","Clients":24}`, nil, }, { //Version 0.X proxy message: "", "", "", 0, "", &json.SyntaxError{}, }, { "", "", "", 0, `{"Sid":"ymbcCMto7KHNGYlp"}`, fmt.Errorf(""), }, { "", "", "", 0, "{}", fmt.Errorf(""), }, { "", "", "", 0, `{"Version":"1.0"}`, fmt.Errorf(""), }, { "", "", "", 0, `{"Version":"2.0"}`, fmt.Errorf(""), }, } { sid, proxyType, natType, clients, err := DecodePollRequest([]byte(test.data)) So(sid, ShouldResemble, test.sid) So(proxyType, ShouldResemble, test.proxyType) So(natType, ShouldResemble, test.natType) So(clients, ShouldEqual, test.clients) So(err, ShouldHaveSameTypeAs, test.err) } }) } func TestEncodeProxyPollRequests(t *testing.T) { Convey("Context", t, func() { b, err := EncodePollRequest("ymbcCMto7KHNGYlp", "standalone", "unknown", 16) So(err, ShouldEqual, nil) sid, proxyType, natType, clients, err := DecodePollRequest(b) So(sid, ShouldEqual, "ymbcCMto7KHNGYlp") So(proxyType, ShouldEqual, "standalone") So(natType, ShouldEqual, "unknown") So(clients, ShouldEqual, 16) So(err, ShouldEqual, nil) }) } func TestDecodeProxyPollResponse(t *testing.T) { Convey("Context", t, func() { for _, test := range []struct { offer string data string err error }{ { "fake offer", `{"Status":"client match","Offer":"fake offer","NAT":"unknown"}`, nil, }, { "", `{"Status":"no match"}`, nil, }, { "", `{"Status":"client match"}`, fmt.Errorf("no supplied offer"), }, { "", `{"Test":"test"}`, fmt.Errorf(""), }, } { offer, _, err := DecodePollResponse([]byte(test.data)) So(err, ShouldHaveSameTypeAs, test.err) So(offer, ShouldResemble, test.offer) } }) } func TestEncodeProxyPollResponse(t *testing.T) { Convey("Context", t, func() { b, err := EncodePollResponse("fake offer", true, "restricted") So(err, ShouldEqual, nil) offer, natType, err := DecodePollResponse(b) So(offer, ShouldEqual, "fake offer") So(natType, ShouldEqual, "restricted") So(err, ShouldEqual, nil) b, err = EncodePollResponse("", false, "unknown") So(err, ShouldEqual, nil) offer, natType, err = DecodePollResponse(b) So(offer, ShouldEqual, "") So(natType, ShouldEqual, "unknown") So(err, ShouldEqual, nil) }) } func TestDecodeProxyAnswerRequest(t *testing.T) { Convey("Context", t, func() { for _, test := range []struct { answer string sid string data string err error }{ { "test", "test", `{"Version":"1.0","Sid":"test","Answer":"test"}`, nil, }, { "", "", `{"type":"offer","sdp":"v=0\r\no=- 4358805017720277108 2 IN IP4 [scrubbed]\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 [scrubbed]\r\na=candidate:3769337065 1 udp 2122260223 [scrubbed] 56688 typ host generation 0 network-id 1 network-cost 50\r\na=candidate:2921887769 1 tcp 1518280447 [scrubbed] 35441 typ host tcptype passive generation 0 network-id 1 network-cost 50\r\na=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"}`, fmt.Errorf(""), }, { "", "", `{"Version":"1.0","Answer":"test"}`, fmt.Errorf(""), }, { "", "", `{"Version":"1.0","Sid":"test"}`, fmt.Errorf(""), }, } { answer, sid, err := DecodeAnswerRequest([]byte(test.data)) So(answer, ShouldResemble, test.answer) So(sid, ShouldResemble, test.sid) So(err, ShouldHaveSameTypeAs, test.err) } }) } func TestEncodeProxyAnswerRequest(t *testing.T) { Convey("Context", t, func() { b, err := EncodeAnswerRequest("test answer", "test sid") So(err, ShouldEqual, nil) answer, sid, err := DecodeAnswerRequest(b) So(answer, ShouldEqual, "test answer") So(sid, ShouldEqual, "test sid") So(err, ShouldEqual, nil) }) } func TestDecodeProxyAnswerResponse(t *testing.T) { Convey("Context", t, func() { for _, test := range []struct { success bool data string err error }{ { true, `{"Status":"success"}`, nil, }, { false, `{"Status":"client gone"}`, nil, }, { false, `{"Test":"test"}`, fmt.Errorf(""), }, } { success, err := DecodeAnswerResponse([]byte(test.data)) So(success, ShouldResemble, test.success) So(err, ShouldHaveSameTypeAs, test.err) } }) } func TestEncodeProxyAnswerResponse(t *testing.T) { Convey("Context", t, func() { b, err := EncodeAnswerResponse(true) So(err, ShouldEqual, nil) success, err := DecodeAnswerResponse(b) So(success, ShouldEqual, true) So(err, ShouldEqual, nil) b, err = EncodeAnswerResponse(false) So(err, ShouldEqual, nil) success, err = DecodeAnswerResponse(b) So(success, ShouldEqual, false) So(err, ShouldEqual, nil) }) } func TestDecodeClientPollRequest(t *testing.T) { Convey("Context", t, func() { for _, test := range []struct { natType string offer string data string err error }{ { //version 1.0 client message "unknown", "fake", `{"nat":"unknown","offer":"fake"}`, nil, }, { //version 1.0 client message "unknown", "fake", `{"offer":"fake"}`, nil, }, { //unknown version "", "", `{"version":"2.0"}`, fmt.Errorf(""), }, { //no offer "", "", `{"nat":"unknown"}`, fmt.Errorf(""), }, } { req, err := DecodeClientPollRequest([]byte(test.data)) if test.err == nil { So(req.NAT, ShouldResemble, test.natType) So(req.Offer, ShouldResemble, test.offer) } So(err, ShouldHaveSameTypeAs, test.err) } }) } func TestEncodeClientPollRequests(t *testing.T) { Convey("Context", t, func() { req1 := &ClientPollRequest{ NAT: "unknown", Offer: "fake", } b, err := req1.EncodePollRequest() So(err, ShouldEqual, nil) fmt.Println(string(b)) parts := bytes.SplitN(b, []byte("\n"), 2) So(string(parts[0]), ShouldEqual, "1.0") b = parts[1] req2, err := DecodeClientPollRequest(b) So(err, ShouldEqual, nil) So(req2, ShouldResemble, req1) }) } func TestDecodeClientPollResponse(t *testing.T) { Convey("Context", t, func() { for _, test := range []struct { answer string msg string data string }{ { "fake answer", "", `{"answer":"fake answer"}`, }, { "", "no snowflakes", `{"error":"no snowflakes"}`, }, } { resp, err := DecodeClientPollResponse([]byte(test.data)) So(err, ShouldBeNil) So(resp.Answer, ShouldResemble, test.answer) So(resp.Error, ShouldResemble, test.msg) } }) } func TestEncodeClientPollResponse(t *testing.T) { Convey("Context", t, func() { resp1 := &ClientPollResponse{ Answer: "fake answer", } b, err := resp1.EncodePollResponse() So(err, ShouldEqual, nil) resp2, err := DecodeClientPollResponse(b) So(err, ShouldEqual, nil) So(resp1, ShouldResemble, resp2) resp1 = &ClientPollResponse{ Error: "failed", } b, err = resp1.EncodePollResponse() So(err, ShouldEqual, nil) resp2, err = DecodeClientPollResponse(b) So(err, ShouldEqual, nil) So(resp1, ShouldResemble, resp2) }) } snowflake-1.1.0/common/messages/proxy.go000066400000000000000000000112001411410351300202740ustar00rootroot00000000000000//Package for communication with the snowflake broker //import "git.torproject.org/pluggable-transports/snowflake.git/common/messages" package messages import ( "encoding/json" "fmt" "strings" ) const version = "1.2" /* Version 1.2 specification: == ProxyPollRequest == { Sid: [generated session id of proxy], Version: 1.2, Type: ["badge"|"webext"|"standalone"], NAT: ["unknown"|"restricted"|"unrestricted"], Clients: [number of current clients, rounded down to multiples of 8] } == ProxyPollResponse == 1) If a client is matched: HTTP 200 OK { Status: "client match", { type: offer, sdp: [WebRTC SDP] }, NAT: ["unknown"|"restricted"|"unrestricted"] } 2) If a client is not matched: HTTP 200 OK { Status: "no match" } 3) If the request is malformed: HTTP 400 BadRequest == ProxyAnswerRequest == { Sid: [generated session id of proxy], Version: 1.2, Answer: { type: answer, sdp: [WebRTC SDP] } } == ProxyAnswerResponse == 1) If the client retrieved the answer: HTTP 200 OK { Status: "success" } 2) If the client left: HTTP 200 OK { Status: "client gone" } 3) If the request is malformed: HTTP 400 BadRequest */ type ProxyPollRequest struct { Sid string Version string Type string NAT string Clients int } func EncodePollRequest(sid string, proxyType string, natType string, clients int) ([]byte, error) { return json.Marshal(ProxyPollRequest{ Sid: sid, Version: version, Type: proxyType, NAT: natType, Clients: clients, }) } // Decodes a poll message from a snowflake proxy and returns the // sid, proxy type, nat type and clients of the proxy on success // and an error if it failed func DecodePollRequest(data []byte) (sid string, proxyType string, natType string, clients int, err error) { var message ProxyPollRequest err = json.Unmarshal(data, &message) if err != nil { return } majorVersion := strings.Split(message.Version, ".")[0] if majorVersion != "1" { err = fmt.Errorf("using unknown version") return } // Version 1.x requires an Sid if message.Sid == "" { err = fmt.Errorf("no supplied session id") return } natType = message.NAT if natType == "" { natType = "unknown" } return message.Sid, message.Type, natType, message.Clients, nil } type ProxyPollResponse struct { Status string Offer string NAT string } func EncodePollResponse(offer string, success bool, natType string) ([]byte, error) { if success { return json.Marshal(ProxyPollResponse{ Status: "client match", Offer: offer, NAT: natType, }) } return json.Marshal(ProxyPollResponse{ Status: "no match", }) } // Decodes a poll response from the broker and returns an offer and the client's NAT type // If there is a client match, the returned offer string will be non-empty func DecodePollResponse(data []byte) (string, string, error) { var message ProxyPollResponse err := json.Unmarshal(data, &message) if err != nil { return "", "", err } if message.Status == "" { return "", "", fmt.Errorf("received invalid data") } if message.Status == "client match" { if message.Offer == "" { return "", "", fmt.Errorf("no supplied offer") } } else { message.Offer = "" } natType := message.NAT if natType == "" { natType = "unknown" } return message.Offer, natType, nil } type ProxyAnswerRequest struct { Version string Sid string Answer string } func EncodeAnswerRequest(answer string, sid string) ([]byte, error) { return json.Marshal(ProxyAnswerRequest{ Version: version, Sid: sid, Answer: answer, }) } // Returns the sdp answer and proxy sid func DecodeAnswerRequest(data []byte) (string, string, error) { var message ProxyAnswerRequest err := json.Unmarshal(data, &message) if err != nil { return "", "", err } majorVersion := strings.Split(message.Version, ".")[0] if majorVersion != "1" { return "", "", fmt.Errorf("using unknown version") } if message.Sid == "" || message.Answer == "" { return "", "", fmt.Errorf("no supplied sid or answer") } return message.Answer, message.Sid, nil } type ProxyAnswerResponse struct { Status string } func EncodeAnswerResponse(success bool) ([]byte, error) { if success { return json.Marshal(ProxyAnswerResponse{ Status: "success", }) } return json.Marshal(ProxyAnswerResponse{ Status: "client gone", }) } func DecodeAnswerResponse(data []byte) (bool, error) { var message ProxyAnswerResponse var success bool err := json.Unmarshal(data, &message) if err != nil { return success, err } if message.Status == "" { return success, fmt.Errorf("received invalid data") } if message.Status == "success" { success = true } return success, nil } snowflake-1.1.0/common/nat/000077500000000000000000000000001411410351300155455ustar00rootroot00000000000000snowflake-1.1.0/common/nat/nat.go000066400000000000000000000160751411410351300166670ustar00rootroot00000000000000/* The majority of this code is taken from a utility I wrote for pion/stun https://github.com/pion/stun/blob/master/cmd/stun-nat-behaviour/main.go Copyright 2018 Pion LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package nat import ( "errors" "fmt" "log" "net" "time" "github.com/pion/stun" ) var ErrTimedOut = errors.New("timed out waiting for response") const ( NATUnknown = "unknown" NATRestricted = "restricted" NATUnrestricted = "unrestricted" ) // This function checks the NAT mapping and filtering // behaviour and returns true if the NAT is restrictive // (address-dependent mapping and/or port-dependent filtering) // and false if the NAT is unrestrictive (meaning it // will work with most other NATs), func CheckIfRestrictedNAT(server string) (bool, error) { return isRestrictedMapping(server) } // Performs two tests from RFC 5780 to determine whether the mapping type // of the client's NAT is address-independent or address-dependent // Returns true if the mapping is address-dependent and false otherwise func isRestrictedMapping(addrStr string) (bool, error) { var xorAddr1 stun.XORMappedAddress var xorAddr2 stun.XORMappedAddress mapTestConn, err := connect(addrStr) if err != nil { log.Printf("Error creating STUN connection: %s", err.Error()) return false, err } defer mapTestConn.Close() // Test I: Regular binding request message := stun.MustBuild(stun.TransactionID, stun.BindingRequest) resp, err := mapTestConn.RoundTrip(message, mapTestConn.PrimaryAddr) if err == ErrTimedOut { log.Printf("Error: no response from server") return false, err } if err != nil { log.Printf("Error receiving response from server: %s", err.Error()) return false, err } // Decoding XOR-MAPPED-ADDRESS attribute from message. if err = xorAddr1.GetFrom(resp); err != nil { log.Printf("Error retrieving XOR-MAPPED-ADDRESS resonse: %s", err.Error()) return false, err } // Decoding OTHER-ADDRESS attribute from message. var otherAddr stun.OtherAddress if err = otherAddr.GetFrom(resp); err != nil { log.Println("NAT discovery feature not supported by this server") return false, err } if err = mapTestConn.AddOtherAddr(otherAddr.String()); err != nil { log.Printf("Failed to resolve address %s\t", otherAddr.String()) return false, err } // Test II: Send binding request to other address resp, err = mapTestConn.RoundTrip(message, mapTestConn.OtherAddr) if err == ErrTimedOut { log.Printf("Error: no response from server") return false, err } if err != nil { log.Printf("Error retrieving server response: %s", err.Error()) return false, err } // Decoding XOR-MAPPED-ADDRESS attribute from message. if err = xorAddr2.GetFrom(resp); err != nil { log.Printf("Error retrieving XOR-MAPPED-ADDRESS resonse: %s", err.Error()) return false, err } return xorAddr1.String() != xorAddr2.String(), nil } // Performs two tests from RFC 5780 to determine whether the filtering type // of the client's NAT is port-dependent. // Returns true if the filtering is port-dependent and false otherwise // Note: This function is no longer used because a client's NAT type is // determined only by their mapping type, but the functionality might // be useful in the future and remains here. func isRestrictedFiltering(addrStr string) (bool, error) { var xorAddr stun.XORMappedAddress mapTestConn, err := connect(addrStr) if err != nil { log.Printf("Error creating STUN connection: %s", err.Error()) return false, err } defer mapTestConn.Close() // Test I: Regular binding request message := stun.MustBuild(stun.TransactionID, stun.BindingRequest) resp, err := mapTestConn.RoundTrip(message, mapTestConn.PrimaryAddr) if err == ErrTimedOut { log.Printf("Error: no response from server") return false, err } if err != nil { log.Printf("Error: %s", err.Error()) return false, err } // Decoding XOR-MAPPED-ADDRESS attribute from message. if err = xorAddr.GetFrom(resp); err != nil { log.Printf("Error retrieving XOR-MAPPED-ADDRESS from resonse: %s", err.Error()) return false, err } // Test III: Request port change message.Add(stun.AttrChangeRequest, []byte{0x00, 0x00, 0x00, 0x02}) _, err = mapTestConn.RoundTrip(message, mapTestConn.PrimaryAddr) if err != ErrTimedOut && err != nil { // something else went wrong log.Printf("Error reading response from server: %s", err.Error()) return false, err } return err == ErrTimedOut, nil } // Given an address string, returns a StunServerConn func connect(addrStr string) (*StunServerConn, error) { // Creating a "connection" to STUN server. addr, err := net.ResolveUDPAddr("udp4", addrStr) if err != nil { log.Printf("Error resolving address: %s\n", err.Error()) return nil, err } c, err := net.ListenUDP("udp4", nil) if err != nil { return nil, err } mChan := listen(c) return &StunServerConn{ conn: c, PrimaryAddr: addr, messageChan: mChan, }, nil } type StunServerConn struct { conn net.PacketConn PrimaryAddr *net.UDPAddr OtherAddr *net.UDPAddr messageChan chan *stun.Message } func (c *StunServerConn) Close() { c.conn.Close() } func (c *StunServerConn) RoundTrip(msg *stun.Message, addr net.Addr) (*stun.Message, error) { _, err := c.conn.WriteTo(msg.Raw, addr) if err != nil { return nil, err } // Wait for response or timeout select { case m, ok := <-c.messageChan: if !ok { return nil, fmt.Errorf("error reading from messageChan") } return m, nil case <-time.After(10 * time.Second): return nil, ErrTimedOut } } func (c *StunServerConn) AddOtherAddr(addrStr string) error { addr2, err := net.ResolveUDPAddr("udp4", addrStr) if err != nil { return err } c.OtherAddr = addr2 return nil } // taken from https://github.com/pion/stun/blob/master/cmd/stun-traversal/main.go func listen(conn *net.UDPConn) chan *stun.Message { messages := make(chan *stun.Message) go func() { for { buf := make([]byte, 1024) n, _, err := conn.ReadFromUDP(buf) if err != nil { close(messages) return } buf = buf[:n] m := new(stun.Message) m.Raw = buf err = m.Decode() if err != nil { close(messages) return } messages <- m } }() return messages } snowflake-1.1.0/common/safelog/000077500000000000000000000000001411410351300164035ustar00rootroot00000000000000snowflake-1.1.0/common/safelog/log.go000066400000000000000000000040071411410351300175140ustar00rootroot00000000000000//Package for a safer logging wrapper around the standard logging package //import "git.torproject.org/pluggable-transports/snowflake.git/common/safelog" package safelog import ( "bytes" "io" "regexp" "sync" ) const ipv4Address = `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}` const ipv6Address = `([0-9a-fA-F]{0,4}:){5,7}([0-9a-fA-F]{0,4})?` const ipv6Compressed = `([0-9a-fA-F]{0,4}:){0,5}([0-9a-fA-F]{0,4})?(::)([0-9a-fA-F]{0,4}:){0,5}([0-9a-fA-F]{0,4})?` const ipv6Full = `(` + ipv6Address + `(` + ipv4Address + `))` + `|(` + ipv6Compressed + `(` + ipv4Address + `))` + `|(` + ipv6Address + `)` + `|(` + ipv6Compressed + `)` const optionalPort = `(:\d{1,5})?` const addressPattern = `((` + ipv4Address + `)|(\[(` + ipv6Full + `)\])|(` + ipv6Full + `))` + optionalPort const fullAddrPattern = `(^|\s|[^\w:])` + addressPattern + `(\s|(:\s)|[^\w:]|$)` var scrubberPatterns = []*regexp.Regexp{ regexp.MustCompile(fullAddrPattern), } var addressRegexp = regexp.MustCompile(addressPattern) // An io.Writer that can be used as the output for a logger that first // sanitizes logs and then writes to the provided io.Writer type LogScrubber struct { Output io.Writer buffer []byte lock sync.Mutex } func (ls *LogScrubber) Lock() { (*ls).lock.Lock() } func (ls *LogScrubber) Unlock() { (*ls).lock.Unlock() } func scrub(b []byte) []byte { scrubbedBytes := b for _, pattern := range scrubberPatterns { // this is a workaround since go does not yet support look ahead or look // behind for regular expressions. scrubbedBytes = pattern.ReplaceAllFunc(scrubbedBytes, func(b []byte) []byte { return addressRegexp.ReplaceAll(b, []byte("[scrubbed]")) }) } return scrubbedBytes } func (ls *LogScrubber) Write(b []byte) (n int, err error) { ls.Lock() defer ls.Unlock() n = len(b) ls.buffer = append(ls.buffer, b...) for { i := bytes.LastIndexByte(ls.buffer, '\n') if i == -1 { return } fullLines := ls.buffer[:i+1] _, err = ls.Output.Write(scrub(fullLines)) if err != nil { return } ls.buffer = ls.buffer[i+1:] } } snowflake-1.1.0/common/safelog/log_test.go000066400000000000000000000104461411410351300205570ustar00rootroot00000000000000package safelog import ( "bytes" "log" "testing" ) //Check to make sure that addresses split across calls to write are still scrubbed func TestLogScrubberSplit(t *testing.T) { input := []byte("test\nhttp2: panic serving [2620:101:f000:780:9097:75b1:519f:dbb8]:58344: interface conversion: *http2.responseWriter is not http.Hijacker: missing method Hijack\n") expected := "test\nhttp2: panic serving [scrubbed]: interface conversion: *http2.responseWriter is not http.Hijacker: missing method Hijack\n" var buff bytes.Buffer scrubber := &LogScrubber{Output: &buff} n, err := scrubber.Write(input[:12]) //test\nhttp2: if n != 12 { t.Errorf("wrong number of bytes %d", n) } if err != nil { t.Errorf("%q", err) } if buff.String() != "test\n" { t.Errorf("Got %q, expected %q", buff.String(), "test\n") } n, err = scrubber.Write(input[12:30]) //panic serving [2620:101:f if n != 18 { t.Errorf("wrong number of bytes %d", n) } if err != nil { t.Errorf("%q", err) } if buff.String() != "test\n" { t.Errorf("Got %q, expected %q", buff.String(), "test\n") } n, err = scrubber.Write(input[30:]) //000:780:9097:75b1:519f:dbb8]:58344: interface conversion: *http2.responseWriter is not http.Hijacker: missing method Hijack\n if n != (len(input) - 30) { t.Errorf("wrong number of bytes %d", n) } if err != nil { t.Errorf("%q", err) } if buff.String() != expected { t.Errorf("Got %q, expected %q", buff.String(), expected) } } //Test the log scrubber on known problematic log messages func TestLogScrubberMessages(t *testing.T) { for _, test := range []struct { input, expected string }{ { "http: TLS handshake error from 129.97.208.23:38310: ", "http: TLS handshake error from [scrubbed]: \n", }, { "http2: panic serving [2620:101:f000:780:9097:75b1:519f:dbb8]:58344: interface conversion: *http2.responseWriter is not http.Hijacker: missing method Hijack", "http2: panic serving [scrubbed]: interface conversion: *http2.responseWriter is not http.Hijacker: missing method Hijack\n", }, { //Make sure it doesn't scrub fingerprint "a=fingerprint:sha-256 33:B6:FA:F6:94:CA:74:61:45:4A:D2:1F:2C:2F:75:8A:D9:EB:23:34:B2:30:E9:1B:2A:A6:A9:E0:44:72:CC:74", "a=fingerprint:sha-256 33:B6:FA:F6:94:CA:74:61:45:4A:D2:1F:2C:2F:75:8A:D9:EB:23:34:B2:30:E9:1B:2A:A6:A9:E0:44:72:CC:74\n", }, { //try with enclosing parens "(1:2:3:4:c:d:e:f) {1:2:3:4:c:d:e:f}", "([scrubbed]) {[scrubbed]}\n", }, { //Make sure it doesn't scrub timestamps "2019/05/08 15:37:31 starting", "2019/05/08 15:37:31 starting\n", }, } { var buff bytes.Buffer log.SetFlags(0) //remove all extra log output for test comparisons log.SetOutput(&LogScrubber{Output: &buff}) log.Print(test.input) if buff.String() != test.expected { t.Errorf("%q: got %q, expected %q", test.input, buff.String(), test.expected) } } } func TestLogScrubberGoodFormats(t *testing.T) { for _, addr := range []string{ // IPv4 "1.2.3.4", "255.255.255.255", // IPv4 with port "1.2.3.4:55", "255.255.255.255:65535", // IPv6 "1:2:3:4:c:d:e:f", "1111:2222:3333:4444:CCCC:DDDD:EEEE:FFFF", // IPv6 with brackets "[1:2:3:4:c:d:e:f]", "[1111:2222:3333:4444:CCCC:DDDD:EEEE:FFFF]", // IPv6 with brackets and port "[1:2:3:4:c:d:e:f]:55", "[1111:2222:3333:4444:CCCC:DDDD:EEEE:FFFF]:65535", // compressed IPv6 "::f", "::d:e:f", "1:2:3::", "1:2:3::d:e:f", "1:2:3:d:e:f::", "::1:2:3:d:e:f", "1111:2222:3333::DDDD:EEEE:FFFF", // compressed IPv6 with brackets "[::d:e:f]", "[1:2:3::]", "[1:2:3::d:e:f]", "[1111:2222:3333::DDDD:EEEE:FFFF]", "[1:2:3:4:5:6::8]", "[1::7:8]", // compressed IPv6 with brackets and port "[1::]:58344", "[::d:e:f]:55", "[1:2:3::]:55", "[1:2:3::d:e:f]:55", "[1111:2222:3333::DDDD:EEEE:FFFF]:65535", // IPv4-compatible and IPv4-mapped "::255.255.255.255", "::ffff:255.255.255.255", "[::255.255.255.255]", "[::ffff:255.255.255.255]", "[::255.255.255.255]:65535", "[::ffff:255.255.255.255]:65535", "[::ffff:0:255.255.255.255]", "[2001:db8:3:4::192.0.2.33]", } { var buff bytes.Buffer log.SetFlags(0) //remove all extra log output for test comparisons log.SetOutput(&LogScrubber{Output: &buff}) log.Print(addr) if buff.String() != "[scrubbed]\n" { t.Errorf("%q: Got %q, expected %q", addr, buff.String(), "[scrubbed]\n") } } } snowflake-1.1.0/common/turbotunnel/000077500000000000000000000000001411410351300173445ustar00rootroot00000000000000snowflake-1.1.0/common/turbotunnel/clientid.go000066400000000000000000000016451411410351300214740ustar00rootroot00000000000000package turbotunnel import ( "crypto/rand" "encoding/hex" ) // ClientID is an abstract identifier that binds together all the communications // belonging to a single client session, even though those communications may // arrive from multiple IP addresses or over multiple lower-level connections. // It plays the same role that an (IP address, port number) tuple plays in a // net.UDPConn: it's the return address pertaining to a long-lived abstract // client session. The client attaches its ClientID to each of its // communications, enabling the server to disambiguate requests among its many // clients. ClientID implements the net.Addr interface. type ClientID [8]byte func NewClientID() ClientID { var id ClientID _, err := rand.Read(id[:]) if err != nil { panic(err) } return id } func (id ClientID) Network() string { return "clientid" } func (id ClientID) String() string { return hex.EncodeToString(id[:]) } snowflake-1.1.0/common/turbotunnel/clientmap.go000066400000000000000000000102721411410351300216510ustar00rootroot00000000000000package turbotunnel import ( "container/heap" "net" "sync" "time" ) // clientRecord is a record of a recently seen client, with the time it was last // seen and a send queue. type clientRecord struct { Addr net.Addr LastSeen time.Time SendQueue chan []byte } // ClientMap manages a mapping of live clients (keyed by address, which will be // a ClientID) to their respective send queues. ClientMap's functions are safe // to call from multiple goroutines. type ClientMap struct { // We use an inner structure to avoid exposing public heap.Interface // functions to users of clientMap. inner clientMapInner // Synchronizes access to inner. lock sync.Mutex } // NewClientMap creates a ClientMap that expires clients after a timeout. // // The timeout does not have to be kept in sync with QUIC's internal idle // timeout. If a client is removed from the client map while the QUIC session is // still live, the worst that can happen is a loss of whatever packets were in // the send queue at the time. If QUIC later decides to send more packets to the // same client, we'll instantiate a new send queue, and if the client ever // connects again with the proper client ID, we'll deliver them. func NewClientMap(timeout time.Duration) *ClientMap { m := &ClientMap{ inner: clientMapInner{ byAge: make([]*clientRecord, 0), byAddr: make(map[net.Addr]int), }, } go func() { for { time.Sleep(timeout / 2) now := time.Now() m.lock.Lock() m.inner.removeExpired(now, timeout) m.lock.Unlock() } }() return m } // SendQueue returns the send queue corresponding to addr, creating it if // necessary. func (m *ClientMap) SendQueue(addr net.Addr) chan []byte { m.lock.Lock() defer m.lock.Unlock() return m.inner.SendQueue(addr, time.Now()) } // clientMapInner is the inner type of ClientMap, implementing heap.Interface. // byAge is the backing store, a heap ordered by LastSeen time, to facilitate // expiring old client records. byAddr is a map from addresses (i.e., ClientIDs) // to heap indices, to allow looking up by address. Unlike ClientMap, // clientMapInner requires external synchonization. type clientMapInner struct { byAge []*clientRecord byAddr map[net.Addr]int } // removeExpired removes all client records whose LastSeen timestamp is more // than timeout in the past. func (inner *clientMapInner) removeExpired(now time.Time, timeout time.Duration) { for len(inner.byAge) > 0 && now.Sub(inner.byAge[0].LastSeen) >= timeout { heap.Pop(inner) } } // SendQueue finds the existing client record corresponding to addr, or creates // a new one if none exists yet. It updates the client record's LastSeen time // and returns its SendQueue. func (inner *clientMapInner) SendQueue(addr net.Addr, now time.Time) chan []byte { var record *clientRecord i, ok := inner.byAddr[addr] if ok { // Found one, update its LastSeen. record = inner.byAge[i] record.LastSeen = now heap.Fix(inner, i) } else { // Not found, create a new one. record = &clientRecord{ Addr: addr, LastSeen: now, SendQueue: make(chan []byte, queueSize), } heap.Push(inner, record) } return record.SendQueue } // heap.Interface for clientMapInner. func (inner *clientMapInner) Len() int { if len(inner.byAge) != len(inner.byAddr) { panic("inconsistent clientMap") } return len(inner.byAge) } func (inner *clientMapInner) Less(i, j int) bool { return inner.byAge[i].LastSeen.Before(inner.byAge[j].LastSeen) } func (inner *clientMapInner) Swap(i, j int) { inner.byAge[i], inner.byAge[j] = inner.byAge[j], inner.byAge[i] inner.byAddr[inner.byAge[i].Addr] = i inner.byAddr[inner.byAge[j].Addr] = j } func (inner *clientMapInner) Push(x interface{}) { record := x.(*clientRecord) if _, ok := inner.byAddr[record.Addr]; ok { panic("duplicate address in clientMap") } // Insert into byAddr map. inner.byAddr[record.Addr] = len(inner.byAge) // Insert into byAge slice. inner.byAge = append(inner.byAge, record) } func (inner *clientMapInner) Pop() interface{} { n := len(inner.byAddr) // Remove from byAge slice. record := inner.byAge[n-1] inner.byAge[n-1] = nil inner.byAge = inner.byAge[:n-1] // Remove from byAddr map. delete(inner.byAddr, record.Addr) close(record.SendQueue) return record } snowflake-1.1.0/common/turbotunnel/consts.go000066400000000000000000000010731411410351300212050ustar00rootroot00000000000000// Package turbotunnel provides support for overlaying a virtual net.PacketConn // on some other network carrier. // // https://github.com/net4people/bbs/issues/9 package turbotunnel import "errors" // This magic prefix is how a client opts into turbo tunnel mode. It is just a // randomly generated byte string. var Token = [8]byte{0x12, 0x93, 0x60, 0x5d, 0x27, 0x81, 0x75, 0xf5} // The size of receive and send queues. const queueSize = 32 var errClosedPacketConn = errors.New("operation on closed connection") var errNotImplemented = errors.New("not implemented") snowflake-1.1.0/common/turbotunnel/queuepacketconn.go000066400000000000000000000107341411410351300230720ustar00rootroot00000000000000package turbotunnel import ( "net" "sync" "sync/atomic" "time" ) // taggedPacket is a combination of a []byte and a net.Addr, encapsulating the // return type of PacketConn.ReadFrom. type taggedPacket struct { P []byte Addr net.Addr } // QueuePacketConn implements net.PacketConn by storing queues of packets. There // is one incoming queue (where packets are additionally tagged by the source // address of the client that sent them). There are many outgoing queues, one // for each client address that has been recently seen. The QueueIncoming method // inserts a packet into the incoming queue, to eventually be returned by // ReadFrom. WriteTo inserts a packet into an address-specific outgoing queue, // which can later by accessed through the OutgoingQueue method. type QueuePacketConn struct { clients *ClientMap localAddr net.Addr recvQueue chan taggedPacket closeOnce sync.Once closed chan struct{} // What error to return when the QueuePacketConn is closed. err atomic.Value } // NewQueuePacketConn makes a new QueuePacketConn, set to track recent clients // for at least a duration of timeout. func NewQueuePacketConn(localAddr net.Addr, timeout time.Duration) *QueuePacketConn { return &QueuePacketConn{ clients: NewClientMap(timeout), localAddr: localAddr, recvQueue: make(chan taggedPacket, queueSize), closed: make(chan struct{}), } } // QueueIncoming queues and incoming packet and its source address, to be // returned in a future call to ReadFrom. func (c *QueuePacketConn) QueueIncoming(p []byte, addr net.Addr) { select { case <-c.closed: // If we're closed, silently drop it. return default: } // Copy the slice so that the caller may reuse it. buf := make([]byte, len(p)) copy(buf, p) select { case c.recvQueue <- taggedPacket{buf, addr}: default: // Drop the incoming packet if the receive queue is full. } } // OutgoingQueue returns the queue of outgoing packets corresponding to addr, // creating it if necessary. The contents of the queue will be packets that are // written to the address in question using WriteTo. func (c *QueuePacketConn) OutgoingQueue(addr net.Addr) <-chan []byte { return c.clients.SendQueue(addr) } // ReadFrom returns a packet and address previously stored by QueueIncoming. func (c *QueuePacketConn) ReadFrom(p []byte) (int, net.Addr, error) { select { case <-c.closed: return 0, nil, &net.OpError{Op: "read", Net: c.LocalAddr().Network(), Addr: c.LocalAddr(), Err: c.err.Load().(error)} default: } select { case <-c.closed: return 0, nil, &net.OpError{Op: "read", Net: c.LocalAddr().Network(), Addr: c.LocalAddr(), Err: c.err.Load().(error)} case packet := <-c.recvQueue: return copy(p, packet.P), packet.Addr, nil } } // WriteTo queues an outgoing packet for the given address. The queue can later // be retrieved using the OutgoingQueue method. func (c *QueuePacketConn) WriteTo(p []byte, addr net.Addr) (int, error) { select { case <-c.closed: return 0, &net.OpError{Op: "write", Net: c.LocalAddr().Network(), Addr: c.LocalAddr(), Err: c.err.Load().(error)} default: } // Copy the slice so that the caller may reuse it. buf := make([]byte, len(p)) copy(buf, p) select { case c.clients.SendQueue(addr) <- buf: return len(buf), nil default: // Drop the outgoing packet if the send queue is full. return len(buf), nil } } // closeWithError unblocks pending operations and makes future operations fail // with the given error. If err is nil, it becomes errClosedPacketConn. func (c *QueuePacketConn) closeWithError(err error) error { var newlyClosed bool c.closeOnce.Do(func() { newlyClosed = true // Store the error to be returned by future PacketConn // operations. if err == nil { err = errClosedPacketConn } c.err.Store(err) close(c.closed) }) if !newlyClosed { return &net.OpError{Op: "close", Net: c.LocalAddr().Network(), Addr: c.LocalAddr(), Err: c.err.Load().(error)} } return nil } // Close unblocks pending operations and makes future operations fail with a // "closed connection" error. func (c *QueuePacketConn) Close() error { return c.closeWithError(nil) } // LocalAddr returns the localAddr value that was passed to NewQueuePacketConn. func (c *QueuePacketConn) LocalAddr() net.Addr { return c.localAddr } func (c *QueuePacketConn) SetDeadline(t time.Time) error { return errNotImplemented } func (c *QueuePacketConn) SetReadDeadline(t time.Time) error { return errNotImplemented } func (c *QueuePacketConn) SetWriteDeadline(t time.Time) error { return errNotImplemented } snowflake-1.1.0/common/turbotunnel/redialpacketconn.go000066400000000000000000000132661411410351300232110ustar00rootroot00000000000000package turbotunnel import ( "context" "errors" "net" "sync" "sync/atomic" "time" ) // RedialPacketConn implements a long-lived net.PacketConn atop a sequence of // other, transient net.PacketConns. RedialPacketConn creates a new // net.PacketConn by calling a provided dialContext function. Whenever the // net.PacketConn experiences a ReadFrom or WriteTo error, RedialPacketConn // calls the dialContext function again and starts sending and receiving packets // on the new net.PacketConn. RedialPacketConn's own ReadFrom and WriteTo // methods return an error only when the dialContext function returns an error. // // RedialPacketConn uses static local and remote addresses that are independent // of those of any dialed net.PacketConn. type RedialPacketConn struct { localAddr net.Addr remoteAddr net.Addr dialContext func(context.Context) (net.PacketConn, error) recvQueue chan []byte sendQueue chan []byte closed chan struct{} closeOnce sync.Once // The first dial error, which causes the clientPacketConn to be // closed and is returned from future read/write operations. Compare to // the rerr and werr in io.Pipe. err atomic.Value } // NewQueuePacketConn makes a new RedialPacketConn, with the given static local // and remote addresses, and dialContext function. func NewRedialPacketConn( localAddr, remoteAddr net.Addr, dialContext func(context.Context) (net.PacketConn, error), ) *RedialPacketConn { c := &RedialPacketConn{ localAddr: localAddr, remoteAddr: remoteAddr, dialContext: dialContext, recvQueue: make(chan []byte, queueSize), sendQueue: make(chan []byte, queueSize), closed: make(chan struct{}), err: atomic.Value{}, } go c.dialLoop() return c } // dialLoop repeatedly calls c.dialContext and passes the resulting // net.PacketConn to c.exchange. It returns only when c is closed or dialContext // returns an error. func (c *RedialPacketConn) dialLoop() { ctx, cancel := context.WithCancel(context.Background()) for { select { case <-c.closed: cancel() return default: } conn, err := c.dialContext(ctx) if err != nil { c.closeWithError(err) cancel() return } c.exchange(conn) conn.Close() } } // exchange calls ReadFrom on the given net.PacketConn and places the resulting // packets in the receive queue, and takes packets from the send queue and calls // WriteTo on them, making the current net.PacketConn active. func (c *RedialPacketConn) exchange(conn net.PacketConn) { readErrCh := make(chan error) writeErrCh := make(chan error) go func() { defer close(readErrCh) for { select { case <-c.closed: return case <-writeErrCh: return default: } var buf [1500]byte n, _, err := conn.ReadFrom(buf[:]) if err != nil { readErrCh <- err return } p := make([]byte, n) copy(p, buf[:]) select { case c.recvQueue <- p: default: // OK to drop packets. } } }() go func() { defer close(writeErrCh) for { select { case <-c.closed: return case <-readErrCh: return case p := <-c.sendQueue: _, err := conn.WriteTo(p, c.remoteAddr) if err != nil { writeErrCh <- err return } } } }() select { case <-readErrCh: case <-writeErrCh: } } // ReadFrom reads a packet from the currently active net.PacketConn. The // packet's original remote address is replaced with the RedialPacketConn's own // remote address. func (c *RedialPacketConn) ReadFrom(p []byte) (int, net.Addr, error) { select { case <-c.closed: return 0, nil, &net.OpError{Op: "read", Net: c.LocalAddr().Network(), Source: c.LocalAddr(), Addr: c.remoteAddr, Err: c.err.Load().(error)} default: } select { case <-c.closed: return 0, nil, &net.OpError{Op: "read", Net: c.LocalAddr().Network(), Source: c.LocalAddr(), Addr: c.remoteAddr, Err: c.err.Load().(error)} case buf := <-c.recvQueue: return copy(p, buf), c.remoteAddr, nil } } // WriteTo writes a packet to the currently active net.PacketConn. The addr // argument is ignored and instead replaced with the RedialPacketConn's own // remote address. func (c *RedialPacketConn) WriteTo(p []byte, addr net.Addr) (int, error) { // addr is ignored. select { case <-c.closed: return 0, &net.OpError{Op: "write", Net: c.LocalAddr().Network(), Source: c.LocalAddr(), Addr: c.remoteAddr, Err: c.err.Load().(error)} default: } buf := make([]byte, len(p)) copy(buf, p) select { case c.sendQueue <- buf: return len(buf), nil default: // Drop the outgoing packet if the send queue is full. return len(buf), nil } } // closeWithError unblocks pending operations and makes future operations fail // with the given error. If err is nil, it becomes errClosedPacketConn. func (c *RedialPacketConn) closeWithError(err error) error { var once bool c.closeOnce.Do(func() { // Store the error to be returned by future read/write // operations. if err == nil { err = errors.New("operation on closed connection") } c.err.Store(err) close(c.closed) once = true }) if !once { return &net.OpError{Op: "close", Net: c.LocalAddr().Network(), Addr: c.LocalAddr(), Err: c.err.Load().(error)} } return nil } // Close unblocks pending operations and makes future operations fail with a // "closed connection" error. func (c *RedialPacketConn) Close() error { return c.closeWithError(nil) } // LocalAddr returns the localAddr value that was passed to NewRedialPacketConn. func (c *RedialPacketConn) LocalAddr() net.Addr { return c.localAddr } func (c *RedialPacketConn) SetDeadline(t time.Time) error { return errNotImplemented } func (c *RedialPacketConn) SetReadDeadline(t time.Time) error { return errNotImplemented } func (c *RedialPacketConn) SetWriteDeadline(t time.Time) error { return errNotImplemented } snowflake-1.1.0/common/util/000077500000000000000000000000001411410351300157405ustar00rootroot00000000000000snowflake-1.1.0/common/util/util.go000066400000000000000000000050451411410351300172500ustar00rootroot00000000000000package util import ( "encoding/json" "errors" "net" "github.com/pion/ice/v2" "github.com/pion/sdp/v3" "github.com/pion/webrtc/v3" ) func SerializeSessionDescription(desc *webrtc.SessionDescription) (string, error) { bytes, err := json.Marshal(*desc) if err != nil { return "", err } return string(bytes), nil } func DeserializeSessionDescription(msg string) (*webrtc.SessionDescription, error) { var parsed map[string]interface{} err := json.Unmarshal([]byte(msg), &parsed) if err != nil { return nil, err } if _, ok := parsed["type"]; !ok { return nil, errors.New("cannot deserialize SessionDescription without type field") } if _, ok := parsed["sdp"]; !ok { return nil, errors.New("cannot deserialize SessionDescription without sdp field") } var stype webrtc.SDPType switch parsed["type"].(string) { default: return nil, errors.New("Unknown SDP type") case "offer": stype = webrtc.SDPTypeOffer case "pranswer": stype = webrtc.SDPTypePranswer case "answer": stype = webrtc.SDPTypeAnswer case "rollback": stype = webrtc.SDPTypeRollback } return &webrtc.SessionDescription{ Type: stype, SDP: parsed["sdp"].(string), }, nil } // 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) || // Carrier-Grade NAT as per https://tools.ietf.org/htm/rfc6598 (ip4[0] == 100 && ip4[1]&0xc0 == 64) || // Dynamic Configuration as per https://tools.ietf.org/htm/rfc3927 (ip4[0] == 169 && ip4[1] == 254) } // 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 { var desc sdp.SessionDescription err := desc.Unmarshal([]byte(str)) if err != nil { return str } for _, m := range desc.MediaDescriptions { attrs := make([]sdp.Attribute, 0) for _, a := range m.Attributes { if a.IsICECandidate() { c, err := ice.UnmarshalCandidate(a.Value) if err == nil && c.Type() == ice.CandidateTypeHost { ip := net.ParseIP(c.Address()) if ip != nil && (IsLocal(ip) || ip.IsUnspecified() || ip.IsLoopback()) { /* no append in this case */ continue } } } attrs = append(attrs, a) } m.Attributes = attrs } bts, err := desc.Marshal() if err != nil { return str } return string(bts) } snowflake-1.1.0/common/util/util_test.go000066400000000000000000000037341411410351300203120ustar00rootroot00000000000000package util import ( "testing" . "github.com/smartystreets/goconvey/convey" ) func TestUtil(t *testing.T) { Convey("Strip", t, func() { const offerStart = "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 100.127.50.5 56688 typ host generation 0 network-id 1 network-cost 50\r\n" + // IsLocal IPv4 "a=candidate:3769337065 1 udp 2122260223 169.254.250.88 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) }) } snowflake-1.1.0/common/websocketconn/000077500000000000000000000000001411410351300176275ustar00rootroot00000000000000snowflake-1.1.0/common/websocketconn/websocketconn.go000066400000000000000000000057021411410351300230260ustar00rootroot00000000000000package websocketconn import ( "io" "time" "github.com/gorilla/websocket" ) // An abstraction that makes an underlying WebSocket connection look like a // net.Conn. type Conn struct { *websocket.Conn Reader io.Reader Writer io.Writer } func (conn *Conn) Read(b []byte) (n int, err error) { return conn.Reader.Read(b) } func (conn *Conn) Write(b []byte) (n int, err error) { return conn.Writer.Write(b) } func (conn *Conn) Close() error { conn.Reader.(*io.PipeReader).Close() conn.Writer.(*io.PipeWriter).Close() // Ignore any error in trying to write a Close frame. _ = conn.Conn.WriteControl(websocket.CloseMessage, []byte{}, time.Now().Add(time.Second)) return conn.Conn.Close() } func (conn *Conn) SetDeadline(t time.Time) error { errRead := conn.Conn.SetReadDeadline(t) errWrite := conn.Conn.SetWriteDeadline(t) err := errRead if err == nil { err = errWrite } return err } func readLoop(w io.Writer, ws *websocket.Conn) error { for { messageType, r, err := ws.NextReader() if err != nil { return err } if messageType != websocket.BinaryMessage && messageType != websocket.TextMessage { continue } _, err = io.Copy(w, r) if err != nil { return err } } } func writeLoop(ws *websocket.Conn, r io.Reader) error { for { var buf [2048]byte n, err := r.Read(buf[:]) if err != nil { return err } data := buf[:n] w, err := ws.NextWriter(websocket.BinaryMessage) if err != nil { return err } n, err = w.Write(data) if err != nil { return err } err = w.Close() if err != nil { return err } } } // websocket.Conn methods start returning websocket.CloseError after the // connection has been closed. We want to instead interpret that as io.EOF, just // as you would find with a normal net.Conn. This only converts // websocket.CloseErrors with known codes; other codes like CloseProtocolError // and CloseAbnormalClosure will still be reported as anomalous. func closeErrorToEOF(err error) error { if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { err = io.EOF } return err } // Create a new Conn. func New(ws *websocket.Conn) *Conn { // Set up synchronous pipes to serialize reads and writes to the // underlying websocket.Conn. // // https://godoc.org/github.com/gorilla/websocket#hdr-Concurrency // "Connections support one concurrent reader and one concurrent writer. // Applications are responsible for ensuring that no more than one // goroutine calls the write methods (NextWriter, etc.) concurrently and // that no more than one goroutine calls the read methods (NextReader, // etc.) concurrently. The Close and WriteControl methods can be called // concurrently with all other methods." pr1, pw1 := io.Pipe() go func() { pw1.CloseWithError(closeErrorToEOF(readLoop(pw1, ws))) }() pr2, pw2 := io.Pipe() go func() { pr2.CloseWithError(closeErrorToEOF(writeLoop(ws, pr2))) }() return &Conn{ Conn: ws, Reader: pr1, Writer: pw2, } } snowflake-1.1.0/common/websocketconn/websocketconn_test.go000066400000000000000000000131361411410351300240650ustar00rootroot00000000000000package websocketconn import ( "bytes" "fmt" "io" "io/ioutil" "net" "net/http" "net/url" "sync" "testing" "time" "github.com/gorilla/websocket" ) // Returns a (server, client) pair of websocketconn.Conns. func connPair() (*Conn, *Conn, error) { // Will be assigned inside server.Handler. var serverConn *Conn // Start up a web server to receive the request. ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return nil, nil, err } defer ln.Close() errCh := make(chan error) server := http.Server{ Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { upgrader := websocket.Upgrader{ CheckOrigin: func(*http.Request) bool { return true }, } ws, err := upgrader.Upgrade(rw, req, nil) if err != nil { errCh <- err return } serverConn = New(ws) close(errCh) }), } defer server.Close() go func() { err := server.Serve(ln) if err != nil && err != http.ErrServerClosed { errCh <- err } }() // Make a request to the web server. urlStr := (&url.URL{Scheme: "ws", Host: ln.Addr().String()}).String() ws, _, err := (&websocket.Dialer{}).Dial(urlStr, nil) if err != nil { return nil, nil, err } clientConn := New(ws) // The server is finished when errCh is written to or closed. err = <-errCh if err != nil { return nil, nil, err } return serverConn, clientConn, nil } // Test that you can write in chunks and read the result concatenated. func TestWrite(t *testing.T) { tests := [][][]byte{ {}, {[]byte("foo")}, {[]byte("foo"), []byte("bar")}, {{}, []byte("foo"), {}, {}, []byte("bar")}, } for _, test := range tests { s, c, err := connPair() if err != nil { t.Fatal(err) } // This is a little awkward because we need to read to and write // from both ends of the Conn, and we need to do it in separate // goroutines because otherwise a Write may block waiting for // someone to Read it. Here we set up a loop in a separate // goroutine, reading from the Conn s and writing to the dataCh // and errCh channels, whose ultimate effect in the select loop // below is like // data, err := ioutil.ReadAll(s) dataCh := make(chan []byte) errCh := make(chan error) go func() { for { var buf [1024]byte n, err := s.Read(buf[:]) if err != nil { errCh <- err return } p := make([]byte, n) copy(p, buf[:]) dataCh <- p } }() // Write the data to the client side of the Conn, one chunk at a // time. for i, chunk := range test { n, err := c.Write(chunk) if err != nil || n != len(chunk) { t.Fatalf("%+q Write chunk %d: got (%d, %v), expected (%d, %v)", test, i, n, err, len(chunk), nil) } } // We cannot immediately c.Close here, because that closes the // connection right away, without waiting for buffered data to // be sent. // Pull data and err from the server goroutine above. var data []byte err = nil loop: for { select { case p := <-dataCh: data = append(data, p...) case err = <-errCh: break loop case <-time.After(100 * time.Millisecond): break loop } } s.Close() c.Close() // Now data and err contain the result of reading everything // from s. expected := bytes.Join(test, []byte{}) if err != nil || !bytes.Equal(data, expected) { t.Fatalf("%+q ReadAll: got (%+q, %v), expected (%+q, %v)", test, data, err, expected, nil) } } } // Test that multiple goroutines may call Read on a Conn simultaneously. Run // this with // go test -race func TestConcurrentRead(t *testing.T) { s, c, err := connPair() if err != nil { t.Fatal(err) } defer s.Close() // Set up multiple threads reading from the same conn. errCh := make(chan error, 2) var wg sync.WaitGroup wg.Add(2) for i := 0; i < 2; i++ { go func() { defer wg.Done() _, err := io.Copy(ioutil.Discard, s) if err != nil { errCh <- err } }() } // Write a bunch of data to the other end. for i := 0; i < 2000; i++ { _, err := fmt.Fprintf(c, "%d", i) if err != nil { c.Close() t.Fatalf("Write: %v", err) } } c.Close() wg.Wait() close(errCh) err = <-errCh if err != nil { t.Fatalf("Read: %v", err) } } // Test that multiple goroutines may call Write on a Conn simultaneously. Run // this with // go test -race func TestConcurrentWrite(t *testing.T) { s, c, err := connPair() if err != nil { t.Fatal(err) } // Set up multiple threads writing to the same conn. errCh := make(chan error, 3) var wg sync.WaitGroup wg.Add(2) for i := 0; i < 2; i++ { go func() { defer wg.Done() for j := 0; j < 1000; j++ { _, err := fmt.Fprintf(s, "%d", j) if err != nil { errCh <- err break } } }() } go func() { wg.Wait() err := s.Close() if err != nil { errCh <- err } close(errCh) }() // Read from the other end. _, err = io.Copy(ioutil.Discard, c) c.Close() if err != nil { t.Fatalf("Read: %v", err) } err = <-errCh if err != nil { t.Fatalf("Write: %v", err) } } // Test that Read and Write methods return errors after Close. func TestClose(t *testing.T) { s, c, err := connPair() if err != nil { t.Fatal(err) } defer c.Close() err = s.Close() if err != nil { t.Fatal(err) } var buf [10]byte n, err := s.Read(buf[:]) if n != 0 || err == nil { t.Fatalf("Read after Close returned (%v, %v), expected (%v, non-nil)", n, err, 0) } _, err = s.Write([]byte{1, 2, 3}) // Here we break the abstraction a little and look for a specific error, // io.ErrClosedPipe. This is because we know the Conn uses an io.Pipe // internally. if err != io.ErrClosedPipe { t.Fatalf("Write after Close returned %v, expected %v", err, io.ErrClosedPipe) } } snowflake-1.1.0/doc/000077500000000000000000000000001411410351300142405ustar00rootroot00000000000000snowflake-1.1.0/doc/broker-spec.txt000066400000000000000000000127561411410351300172300ustar00rootroot00000000000000 Snowflake broker protocol 0. Scope and Preliminaries The Snowflake broker is used to hand out Snowflake proxies to clients using the Snowflake pluggable transport. There are some similarities to the function of the broker and how BridgeDB hands out Tor bridges. This document specifies how the Snowflake broker interacts with other parts of the Tor ecosystem, starting with the metrics CollecTor module and to be expanded upon later. 1. Metrics Reporting (version 1.1) Metrics data from the Snowflake broker can be retrieved by sending an HTTP GET request to https://[Snowflake broker URL]/metrics and consists of the following items: "snowflake-stats-end" YYYY-MM-DD HH:MM:SS (NSEC s) NL [At start, exactly once.] YYYY-MM-DD HH:MM:SS defines the end of the included measurement interval of length NSEC seconds (86400 seconds by default). "snowflake-ips" [CC=NUM,CC=NUM,...,CC=NUM] NL [At most once.] List of mappings from two-letter country codes to the number of unique IP addresses of Snowflake proxies that have polled. Each country code only appears once. "snowflake-ips-total" NUM NL [At most once.] A count of the total number of unique IP addresses of Snowflake proxies that have polled. "snowflake-ips-standalone" NUM NL [At most once.] A count of the total number of unique IP addresses of snowflake proxies of type "standalone" that have polled. "snowflake-ips-badge" NUM NL [At most once.] A count of the total number of unique IP addresses of snowflake proxies of type "badge" that have polled. "snowflake-ips-webext" NUM NL [At most once.] A count of the total number of unique IP addresses of snowflake proxies of type "webext" that have polled. "snowflake-idle-count" NUM NL [At most once.] A count of the number of times a proxy has polled but received no client offer, rounded up to the nearest multiple of 8. "client-denied-count" NUM NL [At most once.] A count of the number of times a client has requested a proxy from the broker but no proxies were available, rounded up to the nearest multiple of 8. "client-restricted-denied-count" NUM NL [At most once.] A count of the number of times a client with a restricted or unknown NAT type has requested a proxy from the broker but no proxies were available, rounded up to the nearest multiple of 8. "client-unrestricted-denied-count" NUM NL [At most once.] A count of the number of times a client with an unrestricted NAT type has requested a proxy from the broker but no proxies were available, rounded up to the nearest multiple of 8. "client-snowflake-match-count" NUM NL [At most once.] A count of the number of times a client successfully received a proxy from the broker, rounded up to the nearest multiple of 8. "snowflake-ips-nat-restricted" NUM NL [At most once.] A count of the total number of unique IP addresses of snowflake proxies that have a restricted NAT type. "snowflake-ips-nat-unrestricted" NUM NL [At most once.] A count of the total number of unique IP addresses of snowflake proxies that have an unrestricted NAT type. "snowflake-ips-nat-unknown" NUM NL [At most once.] A count of the total number of unique IP addresses of snowflake proxies that have an unknown NAT type. 2. Broker messaging specification and endpoints The broker facilitates the connection of snowflake clients and snowflake proxies through the exchange of WebRTC SDP information with its endpoints. 2.1. Client interactions with the broker Clients interact with the broker by making a POST request to `/client` with the offer SDP in the request body: ``` POST /client HTTP [offer SDP] ``` If the broker is behind a domain-fronted connection, this request is accompanied with the necessary HOST information. If the client is matched up with a proxy, they receive a 200 OK response with the proxy's answer SDP in the request body: ``` HTTP 200 OK [answer SDP] ``` If no proxies were available, they receive a 503 status code: ``` HTTP 503 Service Unavailable ``` 2.2 Proxy interactions with the broker Proxies poll the broker with a proxy poll request to `/proxy`: ``` POST /proxy HTTP { Sid: [generated session id of proxy], Version: 1.1, Type: ["badge"|"webext"|"standalone"|"mobile"], NAT: ["unknown"|"restricted"|"unrestricted"], Clients: [number of current clients, rounded down to multiples of 8] } ``` If the request is well-formed, they receive a 200 OK response. If a client is matched: ``` HTTP 200 OK { Status: "client match", { type: offer, sdp: [WebRTC SDP] } } ``` If a client is not matched: ``` HTTP 200 OK { Status: "no match" } ``` If the request is malformed: ``` HTTP 400 BadRequest ``` If they are matched with a client, they provide their SDP answer with a POST request to `/answer`: ``` POST /answer HTTP { Sid: [generated session id of proxy], Version: 1.1, Answer: { type: answer, sdp: [WebRTC SDP] } } ``` If the request is well-formed, they receive a 200 OK response. If the client retrieved the answer: ``` HTTP 200 OK { Status: "success" } ``` If the client left: ``` HTTP 200 OK { Status: "client gone" } 3) If the request is malformed: HTTP 400 BadRequest ``` snowflake-1.1.0/doc/snowflake-client.1000066400000000000000000000017611411410351300175740ustar00rootroot00000000000000.TH SNOWFLAKE-CLIENT "1" "June 2021" "snowflake-client" "User Commands" .SH NAME snowflake-client \- WebRTC pluggable transport client for Tor .SH DESCRIPTION Snowflake helps users circumvent censorship by making a WebRTC connection to volunteer proxies. These proxies relay Tor traffic to a Snowflake bridge and then through the Tor network. .SS "Usage of snowflake-client:" .HP \fB\-front\fR string .IP front domain .HP \fB\-ice\fR string .IP comma\-separated list of ICE servers .HP \fB\-keep\-local\-addresses\fR .IP keep local LAN address ICE candidates .HP \fB\-log\fR string .IP name of log file .HP \fB\-log\-to\-state\-dir\fR .IP resolve the log file relative to tor's pt state dir .HP \fB\-logToStateDir\fR .IP use \fB\-log\-to\-state\-dir\fR instead .HP \fB\-max\fR int .IP capacity for number of multiplexed WebRTC peers (default 1) .HP \fB\-unsafe\-logging\fR .IP prevent logs from being scrubbed .HP \fB\-url\fR string .IP URL of signaling broker .SH "SEE ALSO" https://snowflake.torproject.org snowflake-1.1.0/doc/snowflake-proxy.1000066400000000000000000000016561411410351300175020ustar00rootroot00000000000000.TH SNOWFLAKE-PROXY "1" "June 2021" "swnoflake-proxy" "User Commands" .SH NAME snowflake-proxy \- WebRTC pluggable transport proxy for Tor .SH DESCRIPTION Snowflake helps users circumvent censorship by making a WebRTC connection to volunteer proxies. These proxies relay Tor traffic to a Snowflake bridge and then through the Tor network. .SS "Usage of snowflake-proxy:" .HP \fB\-broker\fR string .IP broker URL (default "https://snowflake\-broker.bamsoftware.com/") .HP \fB\-capacity\fR uint .IP maximum concurrent clients (default 10) .HP \fB\-keep\-local\-addresses\fR .IP keep local LAN address ICE candidates .HP \fB\-log\fR string .IP log filename .HP \fB\-relay\fR string .IP websocket relay URL (default "wss://snowflake.bamsoftware.com/") .HP \fB\-stun\fR string .IP stun URL (default "stun:stun.stunprotocol.org:3478") .HP \fB\-unsafe\-logging\fR .IP prevent logs from being scrubbed .SH "SEE ALSO" https://snowflake.torproject.org snowflake-1.1.0/go.mod000066400000000000000000000014431411410351300146030ustar00rootroot00000000000000module git.torproject.org/pluggable-transports/snowflake.git go 1.13 require ( git.torproject.org/pluggable-transports/goptlib.git v1.1.0 github.com/google/uuid v1.2.0 // indirect github.com/gorilla/websocket v1.4.1 github.com/pion/ice/v2 v2.0.15 github.com/pion/sdp/v3 v3.0.4 github.com/pion/stun v0.3.5 github.com/pion/transport v0.12.3 // indirect github.com/pion/webrtc/v3 v3.0.15 github.com/prometheus/client_golang v1.10.0 github.com/prometheus/client_model v0.2.0 github.com/smartystreets/goconvey v1.6.4 github.com/xtaci/kcp-go/v5 v5.6.1 github.com/xtaci/smux v1.5.15 golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e // indirect google.golang.org/protobuf v1.23.0 ) snowflake-1.1.0/go.sum000066400000000000000000001530251411410351300146340ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= git.torproject.org/pluggable-transports/goptlib.git v1.1.0 h1:LMQAA8pAho+QtYrrVNimJQiINNEwcwuuD99vezD/PAo= git.torproject.org/pluggable-transports/goptlib.git v1.1.0/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I= github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/klauspost/reedsolomon v1.9.9 h1:qCL7LZlv17xMixl55nq2/Oa1Y86nfO8EqDfv2GHND54= github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 h1:ULR/QWMgcgRiZLUjSSJMU+fW+RDMstRdmnDWj9Q+AsA= github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0= github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg= github.com/pion/dtls/v2 v2.0.4 h1:WuUcqi6oYMu/noNTz92QrF1DaFj4eXbhQ6dzaaAwOiI= github.com/pion/dtls/v2 v2.0.4/go.mod h1:qAkFscX0ZHoI1E07RfYPoRw3manThveu+mlTDdOxoGI= github.com/pion/dtls/v2 v2.0.8 h1:reGe8rNIMfO/UAeFLqO61tl64t154Qfkr4U3Gzu1tsg= github.com/pion/dtls/v2 v2.0.8/go.mod h1:QuDII+8FVvk9Dp5t5vYIMTo7hh7uBkra+8QIm7QGm10= github.com/pion/ice/v2 v2.0.15 h1:KZrwa2ciL9od8+TUVJiYTNsCW9J5lktBjGwW1MacEnQ= github.com/pion/ice/v2 v2.0.15/go.mod h1:ZIiVGevpgAxF/cXiIVmuIUtCb3Xs4gCzCbXB6+nFkSI= github.com/pion/interceptor v0.0.10 h1:dXFyFWRJFwmzQqyn0U8dUAbOJu+JJnMVAqxmvTu30B4= github.com/pion/interceptor v0.0.10/go.mod h1:qzeuWuD/ZXvPqOnxNcnhWfkCZ2e1kwwslicyyPnhoK4= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY= github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo= github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0= github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U= github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0= github.com/pion/sctp v1.7.11 h1:UCnj7MsobLKLuP/Hh+JMiI/6W5Bs/VF45lWKgHFjSIE= github.com/pion/sctp v1.7.11/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0= github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8= github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk= github.com/pion/srtp/v2 v2.0.2 h1:664iGzVmaY7KYS5M0gleY0DscRo9ReDfTxQrq4UgGoU= github.com/pion/srtp/v2 v2.0.2/go.mod h1:VEyLv4CuxrwGY8cxM+Ng3bmVy8ckz/1t6A0q/msKOw0= github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= github.com/pion/transport v0.8.10 h1:lTiobMEw2PG6BH/mgIVqTV2mBp/mPT+IJLaN8ZxgdHk= github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8= github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE= github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A= github.com/pion/transport v0.12.1/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= github.com/pion/transport v0.12.2 h1:WYEjhloRHt1R86LhUKjC5y+P52Y11/QqEUalvtzVoys= github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= github.com/pion/transport v0.12.3 h1:vdBfvfU/0Wq8kd2yhUMSDB/x+O4Z9MYVl2fJ5BT4JZw= github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A= github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA= github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw= github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI= github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths= github.com/pion/webrtc/v3 v3.0.15 h1:g8MMJohjQoj0+pTrU329tWM6dvCieNTgnjtqv1kmEdY= github.com/pion/webrtc/v3 v3.0.15/go.mod h1:uUt2nRSsCnK/nfzTAfOmaeLan26ZJ0aP9iwjc/gcC2Y= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg= github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y= github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/templexxx/cpu v0.0.1 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY= github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= github.com/templexxx/cpu v0.0.7 h1:pUEZn8JBy/w5yzdYWgx+0m0xL9uk6j4K91C5kOViAzo= github.com/templexxx/cpu v0.0.7/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg= github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo= github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xtaci/kcp-go/v5 v5.6.1 h1:Pwn0aoeNSPF9dTS7IgiPXn0HEtaIlVb6y5UKWPsx8bI= github.com/xtaci/kcp-go/v5 v5.6.1/go.mod h1:W3kVPyNYwZ06p79dNwFWQOVFrdcBpDBsdyvK8moQrYo= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= github.com/xtaci/smux v1.5.15 h1:6hMiXswcleXj5oNfcJc+DXS8Vj36XX2LaX98udog6Kc= github.com/xtaci/smux v1.5.15/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 h1:gzMM0EjIYiRmJI3+jBdFuoynZlpxa2JQZsolKu09BXo= golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 h1:3uJsdck53FDIpWwLeAXlia9p4C8j0BO2xZrqzKpL0D8= golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e h1:XNp2Flc/1eWQGk5BLzqTAN7fQIwIbfyVTuVxXxZh73M= golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123 h1:4JSJPND/+4555t1HfXYF4UEqDqiSKCgeV0+hbA8hMs4= golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= snowflake-1.1.0/probetest/000077500000000000000000000000001411410351300155025ustar00rootroot00000000000000snowflake-1.1.0/probetest/Dockerfile000066400000000000000000000000511411410351300174700ustar00rootroot00000000000000FROM golang:1.13 COPY probetest /go/bin snowflake-1.1.0/probetest/README.md000066400000000000000000000030161411410351300167610ustar00rootroot00000000000000This is code for a remote probe test component of Snowflake. ### Overview This is a probe test server to allow proxies to test their compatability with Snowflake. Right now the only type of test implemented is a compatability check for clients with symmetric NATs. ### Running your own The server uses TLS by default. There is a `--disable-tls` option for testing purposes, but you should use TLS in production. To build the probe server, run ```go build``` To deploy the probe server, first set the necessary env variables with ``` export HOSTNAMES=${YOUR HOSTNAMES} export EMAIL=${YOUR EMAIL} ``` then run ```docker-compose up``` Setting up a symmetric NAT configuration requires a few extra steps. After upping the docker container, run ```docker inspect snowflake-probetest``` to find the subnet used by the probetest container. Then run ```sudo iptables -L -t nat``` to find the POSTROUTING rules for the subnet. It should look something like this: ``` Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.19.0.0/16 anywhere ``` to modify this rule, execute the command ```sudo iptables -t nat -R POSTROUTING $RULE_NUM -s 172.19.0.0/16 -j MASQUERADE --random``` where RULE_NUM is the numbered rule corresponding to your docker container's subnet masquerade rule. Afterwards, you should see the rule changed to be: ``` Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.19.0.0/16 anywhere random ``` snowflake-1.1.0/probetest/docker-compose.yml000066400000000000000000000006161411410351300211420ustar00rootroot00000000000000 version: "3.8" services: snowflake-probetest: build: . container_name: snowflake-probetest ports: - "8443:8443" volumes: - /home/snowflake-broker/acme-cert-cache:/go/bin/acme-cert-cache entrypoint: [ "probetest" , "-addr", ":8443" , "-acme-hostnames", $HOSTNAMES, "-acme-email", $EMAIL, "-acme-cert-cache", "/go/bin/acme-cert-cache"] snowflake-1.1.0/probetest/probetest.go000066400000000000000000000156331411410351300200500ustar00rootroot00000000000000/* Probe test server to check the reachability of Snowflake proxies from clients with symmetric NATs. The probe server receives an offer from a proxy, returns an answer, and then attempts to establish a datachannel connection to that proxy. The proxy will self-determine whether the connection opened successfully. */ package main import ( "crypto/tls" "flag" "fmt" "io" "io/ioutil" "log" "net/http" "os" "strings" "time" "git.torproject.org/pluggable-transports/snowflake.git/common/messages" "git.torproject.org/pluggable-transports/snowflake.git/common/safelog" "git.torproject.org/pluggable-transports/snowflake.git/common/util" "github.com/pion/webrtc/v3" "golang.org/x/crypto/acme/autocert" ) const ( readLimit = 100000 //Maximum number of bytes to be read from an HTTP request dataChannelTimeout = 20 * time.Second //time after which we assume proxy data channel will not open stunUrl = "stun:stun.l.google.com:19302" //default STUN URL ) // Create a PeerConnection from an SDP offer. Blocks until the gathering of ICE // candidates is complete and the answer is available in LocalDescription. func makePeerConnectionFromOffer(sdp *webrtc.SessionDescription, dataChan chan struct{}) (*webrtc.PeerConnection, error) { config := webrtc.Configuration{ ICEServers: []webrtc.ICEServer{ { URLs: []string{stunUrl}, }, }, } pc, err := webrtc.NewPeerConnection(config) if err != nil { return nil, fmt.Errorf("accept: NewPeerConnection: %s", err) } pc.OnDataChannel(func(dc *webrtc.DataChannel) { dc.OnOpen(func() { close(dataChan) }) dc.OnClose(func() { dc.Close() }) }) // As of v3.0.0, pion-webrtc uses trickle ICE by default. // We have to wait for candidate gathering to complete // before we send the offer done := webrtc.GatheringCompletePromise(pc) err = pc.SetRemoteDescription(*sdp) if err != nil { if inerr := pc.Close(); inerr != nil { log.Printf("unable to call pc.Close after pc.SetRemoteDescription with error: %v", inerr) } return nil, fmt.Errorf("accept: SetRemoteDescription: %s", err) } answer, err := pc.CreateAnswer(nil) if err != nil { if inerr := pc.Close(); inerr != nil { log.Printf("ICE gathering has generated an error when calling pc.Close: %v", inerr) } return nil, err } err = pc.SetLocalDescription(answer) if err != nil { if err = pc.Close(); err != nil { log.Printf("pc.Close after setting local description returned : %v", err) } return nil, err } // Wait for ICE candidate gathering to complete <-done return pc, nil } func probeHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") resp, err := ioutil.ReadAll(http.MaxBytesReader(w, r.Body, readLimit)) if nil != err { log.Println("Invalid data.") w.WriteHeader(http.StatusBadRequest) return } offer, _, err := messages.DecodePollResponse(resp) if err != nil { log.Printf("Error reading offer: %s", err.Error()) w.WriteHeader(http.StatusBadRequest) return } if offer == "" { log.Printf("Error processing session description: %s", err.Error()) w.WriteHeader(http.StatusBadRequest) return } sdp, err := util.DeserializeSessionDescription(offer) if err != nil { log.Printf("Error processing session description: %s", err.Error()) w.WriteHeader(http.StatusBadRequest) return } dataChan := make(chan struct{}) pc, err := makePeerConnectionFromOffer(sdp, dataChan) if err != nil { log.Printf("Error making WebRTC connection: %s", err) w.WriteHeader(http.StatusInternalServerError) return } sdp = &webrtc.SessionDescription{ Type: pc.LocalDescription().Type, SDP: util.StripLocalAddresses(pc.LocalDescription().SDP), } answer, err := util.SerializeSessionDescription(sdp) if err != nil { log.Printf("Error making WebRTC connection: %s", err) w.WriteHeader(http.StatusInternalServerError) return } body, err := messages.EncodeAnswerRequest(answer, "stub-sid") if err != nil { log.Printf("Error making WebRTC connection: %s", err) w.WriteHeader(http.StatusInternalServerError) return } w.Write(body) // Set a timeout on peerconnection. If the connection state has not // advanced to PeerConnectionStateConnected in this time, // destroy the peer connection and return the token. go func() { timer := time.NewTimer(dataChannelTimeout) defer timer.Stop() select { case <-dataChan: case <-timer.C: } if err := pc.Close(); err != nil { log.Printf("Error calling pc.Close: %v", err) } }() return } func main() { var acmeEmail string var acmeHostnamesCommas string var acmeCertCacheDir string var addr string var disableTLS bool var certFilename, keyFilename string var unsafeLogging bool flag.StringVar(&acmeEmail, "acme-email", "", "optional contact email for Let's Encrypt notifications") flag.StringVar(&acmeHostnamesCommas, "acme-hostnames", "", "comma-separated hostnames for TLS certificate") flag.StringVar(&acmeCertCacheDir, "acme-cert-cache", "acme-cert-cache", "directory in which certificates should be cached") flag.StringVar(&certFilename, "cert", "", "TLS certificate file") flag.StringVar(&keyFilename, "key", "", "TLS private key file") flag.StringVar(&addr, "addr", ":8443", "address to listen on") flag.BoolVar(&disableTLS, "disable-tls", false, "don't use HTTPS") flag.BoolVar(&unsafeLogging, "unsafe-logging", false, "prevent logs from being scrubbed") flag.Parse() var logOutput io.Writer = os.Stderr if unsafeLogging { log.SetOutput(logOutput) } else { // Scrub log output just in case an address ends up there log.SetOutput(&safelog.LogScrubber{Output: logOutput}) } log.SetFlags(log.LstdFlags | log.LUTC) http.HandleFunc("/probe", probeHandler) server := http.Server{ Addr: addr, } var err error if acmeHostnamesCommas != "" { acmeHostnames := strings.Split(acmeHostnamesCommas, ",") log.Printf("ACME hostnames: %q", acmeHostnames) var cache autocert.Cache if err = os.MkdirAll(acmeCertCacheDir, 0700); err != nil { log.Printf("Warning: Couldn't create cache directory %q (reason: %s) so we're *not* using our certificate cache.", acmeCertCacheDir, err) } else { cache = autocert.DirCache(acmeCertCacheDir) } certManager := autocert.Manager{ Cache: cache, Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(acmeHostnames...), Email: acmeEmail, } // start certificate manager handler go func() { log.Printf("Starting HTTP-01 listener") log.Fatal(http.ListenAndServe(":80", certManager.HTTPHandler(nil))) }() server.TLSConfig = &tls.Config{GetCertificate: certManager.GetCertificate} err = server.ListenAndServeTLS("", "") } else if certFilename != "" && keyFilename != "" { err = server.ListenAndServeTLS(certFilename, keyFilename) } else if disableTLS { err = server.ListenAndServe() } else { log.Fatal("the --cert and --key, --acme-hostnames, or --disable-tls option is required") } if err != nil { log.Println(err) } } snowflake-1.1.0/proxy/000077500000000000000000000000001411410351300146545ustar00rootroot00000000000000snowflake-1.1.0/proxy/README.md000066400000000000000000000001311411410351300161260ustar00rootroot00000000000000This is a standalone (not browser-based) version of the Snowflake proxy. Usage: ./proxy snowflake-1.1.0/proxy/proxy-go_test.go000066400000000000000000000310201411410351300200220ustar00rootroot00000000000000package main import ( "bytes" "fmt" "io" "io/ioutil" "net" "net/http" "strconv" "strings" "testing" "git.torproject.org/pluggable-transports/snowflake.git/common/messages" "git.torproject.org/pluggable-transports/snowflake.git/common/util" "github.com/pion/webrtc/v3" . "github.com/smartystreets/goconvey/convey" ) // Set up a mock broker to communicate with type MockTransport struct { statusOverride int body []byte } // Just returns a response with fake SDP answer. func (m *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) { s := ioutil.NopCloser(bytes.NewReader(m.body)) r := &http.Response{ StatusCode: m.statusOverride, Body: s, } return r, nil } // Set up a mock faulty transport type FaultyTransport struct { statusOverride int body []byte } // Just returns a response with fake SDP answer. func (f *FaultyTransport) RoundTrip(req *http.Request) (*http.Response, error) { return nil, fmt.Errorf("TransportFailed") } func TestRemoteIPFromSDP(t *testing.T) { tests := []struct { sdp string expected net.IP }{ // https://tools.ietf.org/html/rfc4566#section-5 {`v=0 o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5 s=SDP Seminar i=A Seminar on the session description protocol u=http://www.example.com/seminars/sdp.pdf e=j.doe@example.com (Jane Doe) c=IN IP4 224.2.17.12/127 t=2873397496 2873404696 a=recvonly m=audio 49170 RTP/AVP 0 m=video 51372 RTP/AVP 99 a=rtpmap:99 h263-1998/90000 `, net.ParseIP("224.2.17.12")}, // local addresses only {`v=0 o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5 s=SDP Seminar i=A Seminar on the session description protocol u=http://www.example.com/seminars/sdp.pdf e=j.doe@example.com (Jane Doe) c=IN IP4 10.47.16.5/127 t=2873397496 2873404696 a=recvonly m=audio 49170 RTP/AVP 0 m=video 51372 RTP/AVP 99 a=rtpmap:99 h263-1998/90000 `, nil}, // Remote IP in candidate attribute only {`v=0 o=- 4358805017720277108 2 IN IP4 0.0.0.0 s=- t=0 0 a=group:BUNDLE data a=msid-semantic: WMS m=application 56688 DTLS/SCTP 5000 c=IN IP4 0.0.0.0 a=candidate:3769337065 1 udp 2122260223 1.2.3.4 56688 typ host generation 0 network-id 1 network-cost 50 a=ice-ufrag:aMAZ a=ice-pwd:jcHb08Jjgrazp2dzjdrvPPvV a=ice-options:trickle a=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 a=setup:actpass a=mid:data a=sctpmap:5000 webrtc-datachannel 1024 `, net.ParseIP("1.2.3.4")}, // Unspecified address {`v=0 o=jdoe 2890844526 2890842807 IN IP4 0.0.0.0 s=SDP Seminar i=A Seminar on the session description protocol u=http://www.example.com/seminars/sdp.pdf e=j.doe@example.com (Jane Doe) t=2873397496 2873404696 a=recvonly m=audio 49170 RTP/AVP 0 m=video 51372 RTP/AVP 99 a=rtpmap:99 h263-1998/90000 `, nil}, // Missing c= line {`v=0 o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5 s=SDP Seminar i=A Seminar on the session description protocol u=http://www.example.com/seminars/sdp.pdf e=j.doe@example.com (Jane Doe) t=2873397496 2873404696 a=recvonly m=audio 49170 RTP/AVP 0 m=video 51372 RTP/AVP 99 a=rtpmap:99 h263-1998/90000 `, nil}, // Single line, IP address only {`v=0 o=- 4358805017720277108 2 IN IP4 0.0.0.0 s=- t=0 0 a=group:BUNDLE data a=msid-semantic: WMS m=application 56688 DTLS/SCTP 5000 c=IN IP4 224.2.1.1 `, net.ParseIP("224.2.1.1")}, // Same, with TTL {`v=0 o=- 4358805017720277108 2 IN IP4 0.0.0.0 s=- t=0 0 a=group:BUNDLE data a=msid-semantic: WMS m=application 56688 DTLS/SCTP 5000 c=IN IP4 224.2.1.1/127 `, net.ParseIP("224.2.1.1")}, // Same, with TTL and multicast addresses {`v=0 o=- 4358805017720277108 2 IN IP4 0.0.0.0 s=- t=0 0 a=group:BUNDLE data a=msid-semantic: WMS m=application 56688 DTLS/SCTP 5000 c=IN IP4 224.2.1.1/127/3 `, net.ParseIP("224.2.1.1")}, // IPv6, address only {`v=0 o=- 4358805017720277108 2 IN IP4 0.0.0.0 s=- t=0 0 a=group:BUNDLE data a=msid-semantic: WMS m=application 56688 DTLS/SCTP 5000 c=IN IP6 FF15::101 `, net.ParseIP("ff15::101")}, // Same, with multicast addresses {`v=0 o=- 4358805017720277108 2 IN IP4 0.0.0.0 s=- t=0 0 a=group:BUNDLE data a=msid-semantic: WMS m=application 56688 DTLS/SCTP 5000 c=IN IP6 FF15::101/3 `, net.ParseIP("ff15::101")}, // Multiple c= lines {`v=0 o=- 4358805017720277108 2 IN IP4 0.0.0.0 s=- t=0 0 a=group:BUNDLE data a=msid-semantic: WMS m=application 56688 DTLS/SCTP 5000 c=IN IP4 1.2.3.4 c=IN IP4 5.6.7.8 `, net.ParseIP("1.2.3.4")}, // Modified from SDP sent by snowflake-client. {`v=0 o=- 7860378660295630295 2 IN IP4 127.0.0.1 s=- t=0 0 a=group:BUNDLE data a=msid-semantic: WMS m=application 54653 DTLS/SCTP 5000 c=IN IP4 1.2.3.4 a=candidate:3581707038 1 udp 2122260223 192.168.0.1 54653 typ host generation 0 network-id 1 network-cost 50 a=candidate:2617212910 1 tcp 1518280447 192.168.0.1 59673 typ host tcptype passive generation 0 network-id 1 network-cost 50 a=candidate:2082671819 1 udp 1686052607 1.2.3.4 54653 typ srflx raddr 192.168.0.1 rport 54653 generation 0 network-id 1 network-cost 50 a=ice-ufrag:IBdf a=ice-pwd:G3lTrrC9gmhQx481AowtkhYz a=fingerprint:sha-256 53:F8:84:D9:3C:1F:A0:44:AA:D6:3C:65:80:D3:CB:6F:23:90:17:41:06:F9:9C:10:D8:48:4A:A8:B6:FA:14:A1 a=setup:actpass a=mid:data a=sctpmap:5000 webrtc-datachannel 1024 `, net.ParseIP("1.2.3.4")}, // Improper character within IPv4 {`v=0 o=- 4358805017720277108 2 IN IP4 0.0.0.0 s=- t=0 0 a=group:BUNDLE data a=msid-semantic: WMS m=application 56688 DTLS/SCTP 5000 c=IN IP4 224.2z.1.1 `, nil}, // Improper character within IPv6 {`v=0 o=- 4358805017720277108 2 IN IP4 0.0.0.0 s=- t=0 0 a=group:BUNDLE data a=msid-semantic: WMS m=application 56688 DTLS/SCTP 5000 c=IN IP6 ff15:g::101 `, nil}, // Bogus "IP7" addrtype {`v=0 o=- 4358805017720277108 2 IN IP4 0.0.0.0 s=- t=0 0 a=group:BUNDLE data a=msid-semantic: WMS m=application 56688 DTLS/SCTP 5000 c=IN IP7 1.2.3.4 `, nil}, } for _, test := range tests { // https://tools.ietf.org/html/rfc4566#section-5: "The sequence // CRLF (0x0d0a) is used to end a record, although parsers // SHOULD be tolerant and also accept records terminated with a // single newline character." We represent the test cases with // LF line endings for convenience, and test them both that way // and with CRLF line endings. lfSDP := test.sdp crlfSDP := strings.Replace(lfSDP, "\n", "\r\n", -1) ip := remoteIPFromSDP(lfSDP) if !ip.Equal(test.expected) { t.Errorf("expected %q, got %q from %q", test.expected, ip, lfSDP) } ip = remoteIPFromSDP(crlfSDP) if !ip.Equal(test.expected) { t.Errorf("expected %q, got %q from %q", test.expected, ip, crlfSDP) } } } func TestSessionDescriptions(t *testing.T) { Convey("Session description deserialization", t, func() { for _, test := range []struct { msg string ret *webrtc.SessionDescription }{ { "test", nil, }, { `{"type":"answer"}`, nil, }, { `{"sdp":"test"}`, nil, }, { `{"type":"test", "sdp":"test"}`, nil, }, { `{"type":"answer", "sdp":"test"}`, &webrtc.SessionDescription{ Type: webrtc.SDPTypeAnswer, SDP: "test", }, }, { `{"type":"pranswer", "sdp":"test"}`, &webrtc.SessionDescription{ Type: webrtc.SDPTypePranswer, SDP: "test", }, }, { `{"type":"rollback", "sdp":"test"}`, &webrtc.SessionDescription{ Type: webrtc.SDPTypeRollback, SDP: "test", }, }, { `{"type":"offer", "sdp":"test"}`, &webrtc.SessionDescription{ Type: webrtc.SDPTypeOffer, SDP: "test", }, }, } { desc, _ := util.DeserializeSessionDescription(test.msg) So(desc, ShouldResemble, test.ret) } }) Convey("Session description serialization", t, func() { for _, test := range []struct { desc *webrtc.SessionDescription ret string }{ { &webrtc.SessionDescription{ Type: webrtc.SDPTypeOffer, SDP: "test", }, `{"type":"offer","sdp":"test"}`, }, } { msg, err := util.SerializeSessionDescription(test.desc) So(msg, ShouldResemble, test.ret) So(err, ShouldBeNil) } }) } func TestBrokerInteractions(t *testing.T) { const sampleSDP = `"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\na=candidate:3769337065 1 udp 2122260223 8.8.8.8 56688 typ host generation 0 network-id 1 network-cost 50\r\na=candidate:2921887769 1 tcp 1518280447 8.8.8.8 35441 typ host tcptype passive generation 0 network-id 1 network-cost 50\r\na=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"` const sampleOffer = `{"type":"offer","sdp":` + sampleSDP + `}` const sampleAnswer = `{"type":"answer","sdp":` + sampleSDP + `}` Convey("Proxy connections to broker", t, func() { broker, err := newSignalingServer("localhost", false) So(err, ShouldEqual, nil) tokens = newTokens(0) //Mock peerConnection config = webrtc.Configuration{ ICEServers: []webrtc.ICEServer{ { URLs: []string{"stun:stun.l.google.com:19302"}, }, }, } pc, _ := webrtc.NewPeerConnection(config) offer, _ := util.DeserializeSessionDescription(sampleOffer) pc.SetRemoteDescription(*offer) answer, _ := pc.CreateAnswer(nil) pc.SetLocalDescription(answer) Convey("polls broker correctly", func() { var err error b, err := messages.EncodePollResponse(sampleOffer, true, "unknown") So(err, ShouldEqual, nil) broker.transport = &MockTransport{ http.StatusOK, b, } sdp := broker.pollOffer(sampleOffer) expectedSDP, _ := strconv.Unquote(sampleSDP) So(sdp.SDP, ShouldResemble, expectedSDP) }) Convey("handles poll error", func() { var err error b := []byte("test") So(err, ShouldEqual, nil) broker.transport = &MockTransport{ http.StatusOK, b, } sdp := broker.pollOffer(sampleOffer) So(sdp, ShouldBeNil) }) Convey("sends answer to broker", func() { var err error b, err := messages.EncodeAnswerResponse(true) So(err, ShouldEqual, nil) broker.transport = &MockTransport{ http.StatusOK, b, } err = broker.sendAnswer(sampleAnswer, pc) So(err, ShouldEqual, nil) b, err = messages.EncodeAnswerResponse(false) So(err, ShouldEqual, nil) broker.transport = &MockTransport{ http.StatusOK, b, } err = broker.sendAnswer(sampleAnswer, pc) So(err, ShouldNotBeNil) }) Convey("handles answer error", func() { //Error if faulty transport broker.transport = &FaultyTransport{} err := broker.sendAnswer(sampleAnswer, pc) So(err, ShouldNotBeNil) //Error if status code is not ok broker.transport = &MockTransport{ http.StatusGone, []byte(""), } err = broker.sendAnswer("test", pc) So(err, ShouldNotEqual, nil) So(err.Error(), ShouldResemble, "error sending answer to broker: remote returned status code 410") //Error if we can't parse broker message broker.transport = &MockTransport{ http.StatusOK, []byte("test"), } err = broker.sendAnswer("test", pc) So(err, ShouldNotBeNil) //Error if broker message surpasses read limit broker.transport = &MockTransport{ http.StatusOK, make([]byte, 100001), } err = broker.sendAnswer("test", pc) So(err, ShouldNotBeNil) }) }) } func TestUtilityFuncs(t *testing.T) { Convey("LimitedRead", t, func() { c, s := net.Pipe() Convey("Successful read", func() { go func() { bytes := make([]byte, 50) c.Write(bytes) c.Close() }() bytes, err := limitedRead(s, 60) So(len(bytes), ShouldEqual, 50) So(err, ShouldBeNil) }) Convey("Large read", func() { go func() { bytes := make([]byte, 50) c.Write(bytes) c.Close() }() bytes, err := limitedRead(s, 49) So(len(bytes), ShouldEqual, 49) So(err, ShouldEqual, io.ErrUnexpectedEOF) }) Convey("Failed read", func() { s.Close() bytes, err := limitedRead(s, 49) So(len(bytes), ShouldEqual, 0) So(err, ShouldEqual, io.ErrClosedPipe) }) }) Convey("SessionID Generation", t, func() { sid1 := genSessionID() sid2 := genSessionID() So(sid1, ShouldNotEqual, sid2) }) Convey("CopyLoop", t, func() { c1, s1 := net.Pipe() c2, s2 := net.Pipe() go CopyLoop(s1, s2) go func() { bytes := []byte("Hello!") c1.Write(bytes) }() bytes := make([]byte, 6) n, err := c2.Read(bytes) So(n, ShouldEqual, 6) So(err, ShouldEqual, nil) So(bytes, ShouldResemble, []byte("Hello!")) s1.Close() //Check that copy loop has closed other connection _, err = s2.Write(bytes) So(err, ShouldNotBeNil) }) } snowflake-1.1.0/proxy/snowflake.go000066400000000000000000000360121411410351300171760ustar00rootroot00000000000000package main import ( "bytes" "crypto/rand" "encoding/base64" "flag" "fmt" "io" "io/ioutil" "log" "net" "net/http" "net/url" "os" "strings" "sync" "time" "git.torproject.org/pluggable-transports/snowflake.git/common/messages" "git.torproject.org/pluggable-transports/snowflake.git/common/safelog" "git.torproject.org/pluggable-transports/snowflake.git/common/util" "git.torproject.org/pluggable-transports/snowflake.git/common/websocketconn" "github.com/gorilla/websocket" "github.com/pion/webrtc/v3" ) const defaultBrokerURL = "https://snowflake-broker.bamsoftware.com/" const defaultProbeURL = "https://snowflake-broker.torproject.net:8443/probe" const defaultRelayURL = "wss://snowflake.bamsoftware.com/" const defaultSTUNURL = "stun:stun.stunprotocol.org:3478" const pollInterval = 5 * time.Second const ( NATUnknown = "unknown" NATRestricted = "restricted" NATUnrestricted = "unrestricted" ) //amount of time after sending an SDP answer before the proxy assumes the //client is not going to connect const dataChannelTimeout = 20 * time.Second const readLimit = 100000 //Maximum number of bytes to be read from an HTTP request var broker *SignalingServer var relayURL string var currentNATType = NATUnknown const ( sessionIDLength = 16 ) var ( tokens *tokens_t config webrtc.Configuration client http.Client ) // Checks whether an IP address is a remote address for the client func isRemoteAddress(ip net.IP) bool { return !(util.IsLocal(ip) || ip.IsUnspecified() || ip.IsLoopback()) } func genSessionID() string { buf := make([]byte, sessionIDLength) _, err := rand.Read(buf) if err != nil { panic(err.Error()) } return strings.TrimRight(base64.StdEncoding.EncodeToString(buf), "=") } func limitedRead(r io.Reader, limit int64) ([]byte, error) { p, err := ioutil.ReadAll(&io.LimitedReader{R: r, N: limit + 1}) if err != nil { return p, err } else if int64(len(p)) == limit+1 { return p[0:limit], io.ErrUnexpectedEOF } return p, err } type SignalingServer struct { url *url.URL transport http.RoundTripper keepLocalAddresses bool } func newSignalingServer(rawURL string, keepLocalAddresses bool) (*SignalingServer, error) { var err error s := new(SignalingServer) s.keepLocalAddresses = keepLocalAddresses s.url, err = url.Parse(rawURL) if err != nil { return nil, fmt.Errorf("invalid broker url: %s", err) } s.transport = http.DefaultTransport.(*http.Transport) s.transport.(*http.Transport).ResponseHeaderTimeout = 30 * time.Second return s, nil } func (s *SignalingServer) Post(path string, payload io.Reader) ([]byte, error) { req, err := http.NewRequest("POST", path, payload) if err != nil { return nil, err } resp, err := s.transport.RoundTrip(req) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("remote returned status code %d", resp.StatusCode) } defer resp.Body.Close() return limitedRead(resp.Body, readLimit) } func (s *SignalingServer) pollOffer(sid string) *webrtc.SessionDescription { brokerPath := s.url.ResolveReference(&url.URL{Path: "proxy"}) timeOfNextPoll := time.Now() for { // Sleep until we're scheduled to poll again. now := time.Now() time.Sleep(timeOfNextPoll.Sub(now)) // Compute the next time to poll -- if it's in the past, that // means that the POST took longer than pollInterval, so we're // allowed to do another one immediately. timeOfNextPoll = timeOfNextPoll.Add(pollInterval) if timeOfNextPoll.Before(now) { timeOfNextPoll = now } numClients := int((tokens.count() / 8) * 8) // Round down to 8 body, err := messages.EncodePollRequest(sid, "standalone", currentNATType, numClients) if err != nil { log.Printf("Error encoding poll message: %s", err.Error()) return nil } resp, err := s.Post(brokerPath.String(), bytes.NewBuffer(body)) if err != nil { log.Printf("error polling broker: %s", err.Error()) } offer, _, err := messages.DecodePollResponse(resp) if err != nil { log.Printf("Error reading broker response: %s", err.Error()) log.Printf("body: %s", resp) return nil } if offer != "" { offer, err := util.DeserializeSessionDescription(offer) if err != nil { log.Printf("Error processing session description: %s", err.Error()) return nil } return offer } } } func (s *SignalingServer) sendAnswer(sid string, pc *webrtc.PeerConnection) error { brokerPath := s.url.ResolveReference(&url.URL{Path: "answer"}) ld := pc.LocalDescription() if !s.keepLocalAddresses { ld = &webrtc.SessionDescription{ Type: ld.Type, SDP: util.StripLocalAddresses(ld.SDP), } } answer, err := util.SerializeSessionDescription(ld) if err != nil { return err } body, err := messages.EncodeAnswerRequest(answer, sid) if err != nil { return err } resp, err := s.Post(brokerPath.String(), bytes.NewBuffer(body)) if err != nil { return fmt.Errorf("error sending answer to broker: %s", err.Error()) } success, err := messages.DecodeAnswerResponse(resp) if err != nil { return err } if !success { return fmt.Errorf("broker returned client timeout") } return nil } func CopyLoop(c1 io.ReadWriteCloser, c2 io.ReadWriteCloser) { var wg sync.WaitGroup copyer := func(dst io.ReadWriteCloser, src io.ReadWriteCloser) { defer wg.Done() // Ignore io.ErrClosedPipe because it is likely caused by the // termination of copyer in the other direction. if _, err := io.Copy(dst, src); err != nil && err != io.ErrClosedPipe { log.Printf("io.Copy inside CopyLoop generated an error: %v", err) } dst.Close() src.Close() } wg.Add(2) go copyer(c1, c2) go copyer(c2, c1) wg.Wait() } // We pass conn.RemoteAddr() as an additional parameter, rather than calling // conn.RemoteAddr() inside this function, as a workaround for a hang that // otherwise occurs inside of conn.pc.RemoteDescription() (called by // RemoteAddr). https://bugs.torproject.org/18628#comment:8 func datachannelHandler(conn *webRTCConn, remoteAddr net.Addr) { defer conn.Close() defer tokens.ret() u, err := url.Parse(relayURL) if err != nil { log.Fatalf("invalid relay url: %s", err) } if remoteAddr != nil { // Encode client IP address in relay URL q := u.Query() clientIP := remoteAddr.String() q.Set("client_ip", clientIP) u.RawQuery = q.Encode() } else { log.Printf("no remote address given in websocket") } ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil) if err != nil { log.Printf("error dialing relay: %s", err) return } wsConn := websocketconn.New(ws) log.Printf("connected to relay") defer wsConn.Close() CopyLoop(conn, wsConn) log.Printf("datachannelHandler ends") } // Create a PeerConnection from an SDP offer. Blocks until the gathering of ICE // candidates is complete and the answer is available in LocalDescription. // Installs an OnDataChannel callback that creates a webRTCConn and passes it to // datachannelHandler. func makePeerConnectionFromOffer(sdp *webrtc.SessionDescription, config webrtc.Configuration, dataChan chan struct{}, handler func(conn *webRTCConn, remoteAddr net.Addr)) (*webrtc.PeerConnection, error) { pc, err := webrtc.NewPeerConnection(config) if err != nil { return nil, fmt.Errorf("accept: NewPeerConnection: %s", err) } pc.OnDataChannel(func(dc *webrtc.DataChannel) { log.Println("OnDataChannel") close(dataChan) pr, pw := io.Pipe() conn := &webRTCConn{pc: pc, dc: dc, pr: pr} conn.bytesLogger = NewBytesSyncLogger() dc.OnOpen(func() { log.Println("OnOpen channel") }) dc.OnClose(func() { conn.lock.Lock() defer conn.lock.Unlock() log.Println("OnClose channel") log.Println(conn.bytesLogger.ThroughputSummary()) conn.dc = nil dc.Close() pw.Close() }) dc.OnMessage(func(msg webrtc.DataChannelMessage) { var n int n, err = pw.Write(msg.Data) if err != nil { if inerr := pw.CloseWithError(err); inerr != nil { log.Printf("close with error generated an error: %v", inerr) } } conn.bytesLogger.AddOutbound(n) if n != len(msg.Data) { panic("short write") } }) go handler(conn, conn.RemoteAddr()) }) // As of v3.0.0, pion-webrtc uses trickle ICE by default. // We have to wait for candidate gathering to complete // before we send the offer done := webrtc.GatheringCompletePromise(pc) err = pc.SetRemoteDescription(*sdp) if err != nil { if inerr := pc.Close(); inerr != nil { log.Printf("unable to call pc.Close after pc.SetRemoteDescription with error: %v", inerr) } return nil, fmt.Errorf("accept: SetRemoteDescription: %s", err) } log.Println("sdp offer successfully received.") log.Println("Generating answer...") answer, err := pc.CreateAnswer(nil) // blocks on ICE gathering. we need to add a timeout if needed // not putting this in a separate go routine, because we need // SetLocalDescription(answer) to be called before sendAnswer if err != nil { if inerr := pc.Close(); inerr != nil { log.Printf("ICE gathering has generated an error when calling pc.Close: %v", inerr) } return nil, err } err = pc.SetLocalDescription(answer) if err != nil { if err = pc.Close(); err != nil { log.Printf("pc.Close after setting local description returned : %v", err) } return nil, err } // Wait for ICE candidate gathering to complete <-done return pc, nil } // Create a new PeerConnection. Blocks until the gathering of ICE // candidates is complete and the answer is available in LocalDescription. func makeNewPeerConnection(config webrtc.Configuration, dataChan chan struct{}) (*webrtc.PeerConnection, error) { pc, err := webrtc.NewPeerConnection(config) if err != nil { return nil, fmt.Errorf("accept: NewPeerConnection: %s", err) } // Must create a data channel before creating an offer // https://github.com/pion/webrtc/wiki/Release-WebRTC@v3.0.0 dc, err := pc.CreateDataChannel("test", &webrtc.DataChannelInit{}) if err != nil { log.Printf("CreateDataChannel ERROR: %s", err) return nil, err } dc.OnOpen(func() { log.Println("WebRTC: DataChannel.OnOpen") close(dataChan) }) dc.OnClose(func() { log.Println("WebRTC: DataChannel.OnClose") dc.Close() }) offer, err := pc.CreateOffer(nil) // TODO: Potentially timeout and retry if ICE isn't working. if err != nil { log.Println("Failed to prepare offer", err) pc.Close() return nil, err } log.Println("WebRTC: Created offer") // As of v3.0.0, pion-webrtc uses trickle ICE by default. // We have to wait for candidate gathering to complete // before we send the offer done := webrtc.GatheringCompletePromise(pc) err = pc.SetLocalDescription(offer) if err != nil { log.Println("Failed to prepare offer", err) pc.Close() return nil, err } log.Println("WebRTC: Set local description") // Wait for ICE candidate gathering to complete <-done return pc, nil } func runSession(sid string) { offer := broker.pollOffer(sid) if offer == nil { log.Printf("bad offer from broker") tokens.ret() return } dataChan := make(chan struct{}) pc, err := makePeerConnectionFromOffer(offer, config, dataChan, datachannelHandler) if err != nil { log.Printf("error making WebRTC connection: %s", err) tokens.ret() return } err = broker.sendAnswer(sid, pc) if err != nil { log.Printf("error sending answer to client through broker: %s", err) if inerr := pc.Close(); inerr != nil { log.Printf("error calling pc.Close: %v", inerr) } tokens.ret() return } // Set a timeout on peerconnection. If the connection state has not // advanced to PeerConnectionStateConnected in this time, // destroy the peer connection and return the token. select { case <-dataChan: log.Println("Connection successful.") case <-time.After(dataChannelTimeout): log.Println("Timed out waiting for client to open data channel.") if err := pc.Close(); err != nil { log.Printf("error calling pc.Close: %v", err) } tokens.ret() } } func main() { var capacity uint var stunURL string var logFilename string var rawBrokerURL string var unsafeLogging bool var keepLocalAddresses bool flag.UintVar(&capacity, "capacity", 0, "maximum concurrent clients") flag.StringVar(&rawBrokerURL, "broker", defaultBrokerURL, "broker URL") flag.StringVar(&relayURL, "relay", defaultRelayURL, "websocket relay URL") flag.StringVar(&stunURL, "stun", defaultSTUNURL, "stun URL") flag.StringVar(&logFilename, "log", "", "log filename") flag.BoolVar(&unsafeLogging, "unsafe-logging", false, "prevent logs from being scrubbed") flag.BoolVar(&keepLocalAddresses, "keep-local-addresses", false, "keep local LAN address ICE candidates") flag.Parse() var logOutput io.Writer = os.Stderr log.SetFlags(log.LstdFlags | log.LUTC) if logFilename != "" { f, err := os.OpenFile(logFilename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) if err != nil { log.Fatal(err) } defer f.Close() logOutput = io.MultiWriter(os.Stderr, f) } if unsafeLogging { log.SetOutput(logOutput) } else { // We want to send the log output through our scrubber first log.SetOutput(&safelog.LogScrubber{Output: logOutput}) } log.Println("starting") var err error broker, err = newSignalingServer(rawBrokerURL, keepLocalAddresses) if err != nil { log.Fatal(err) } _, err = url.Parse(stunURL) if err != nil { log.Fatalf("invalid stun url: %s", err) } _, err = url.Parse(relayURL) if err != nil { log.Fatalf("invalid relay url: %s", err) } config = webrtc.Configuration{ ICEServers: []webrtc.ICEServer{ { URLs: []string{stunURL}, }, }, } tokens = newTokens(capacity) // use probetest to determine NAT compatability checkNATType(config, defaultProbeURL) log.Printf("NAT type: %s", currentNATType) for { tokens.get() sessionID := genSessionID() runSession(sessionID) } } func checkNATType(config webrtc.Configuration, probeURL string) { probe, err := newSignalingServer(probeURL, false) if err != nil { log.Printf("Error parsing url: %s", err.Error()) } // create offer dataChan := make(chan struct{}) pc, err := makeNewPeerConnection(config, dataChan) if err != nil { log.Printf("error making WebRTC connection: %s", err) return } offer := pc.LocalDescription() sdp, err := util.SerializeSessionDescription(offer) log.Printf("Offer: %s", sdp) if err != nil { log.Printf("Error encoding probe message: %s", err.Error()) return } // send offer body, err := messages.EncodePollResponse(sdp, true, "") if err != nil { log.Printf("Error encoding probe message: %s", err.Error()) return } resp, err := probe.Post(probe.url.String(), bytes.NewBuffer(body)) if err != nil { log.Printf("error polling probe: %s", err.Error()) return } sdp, _, err = messages.DecodeAnswerRequest(resp) if err != nil { log.Printf("Error reading probe response: %s", err.Error()) return } answer, err := util.DeserializeSessionDescription(sdp) if err != nil { log.Printf("Error setting answer: %s", err.Error()) return } err = pc.SetRemoteDescription(*answer) if err != nil { log.Printf("Error setting answer: %s", err.Error()) return } select { case <-dataChan: currentNATType = NATUnrestricted case <-time.After(dataChannelTimeout): currentNATType = NATRestricted } if err := pc.Close(); err != nil { log.Printf("error calling pc.Close: %v", err) } } snowflake-1.1.0/proxy/tokens.go000066400000000000000000000011351411410351300165060ustar00rootroot00000000000000package main import ( "sync/atomic" ) type tokens_t struct { ch chan struct{} capacity uint clients int64 } func newTokens(capacity uint) *tokens_t { var ch chan struct{} if capacity != 0 { ch = make(chan struct{}, capacity) } return &tokens_t{ ch: ch, capacity: capacity, clients: 0, } } func (t *tokens_t) get() { atomic.AddInt64(&t.clients, 1) if t.capacity != 0 { t.ch <- struct{}{} } } func (t *tokens_t) ret() { atomic.AddInt64(&t.clients, -1) if t.capacity != 0 { <-t.ch } } func (t tokens_t) count() int64 { return atomic.LoadInt64(&t.clients) } snowflake-1.1.0/proxy/tokens_test.go000066400000000000000000000010641411410351300175460ustar00rootroot00000000000000package main import ( "testing" . "github.com/smartystreets/goconvey/convey" ) func TestTokens(t *testing.T) { Convey("Tokens", t, func() { tokens := newTokens(2) So(tokens.count(), ShouldEqual, 0) tokens.get() So(tokens.count(), ShouldEqual, 1) tokens.ret() So(tokens.count(), ShouldEqual, 0) }) Convey("Tokens capacity 0", t, func() { tokens := newTokens(0) So(tokens.count(), ShouldEqual, 0) for i := 0; i < 20; i++ { tokens.get() } So(tokens.count(), ShouldEqual, 20) tokens.ret() So(tokens.count(), ShouldEqual, 19) }) } snowflake-1.1.0/proxy/util.go000066400000000000000000000036601411410351300161650ustar00rootroot00000000000000package main import ( "fmt" "time" ) type BytesLogger interface { AddOutbound(int) AddInbound(int) ThroughputSummary() string } // Default BytesLogger does nothing. type BytesNullLogger struct{} func (b BytesNullLogger) AddOutbound(amount int) {} func (b BytesNullLogger) AddInbound(amount int) {} func (b BytesNullLogger) ThroughputSummary() string { return "" } // BytesSyncLogger uses channels to safely log from multiple sources with output // occuring at reasonable intervals. type BytesSyncLogger struct { outboundChan, inboundChan chan int outbound, inbound, outEvents, inEvents int start time.Time } // NewBytesSyncLogger returns a new BytesSyncLogger and starts it loggin. func NewBytesSyncLogger() *BytesSyncLogger { b := &BytesSyncLogger{ outboundChan: make(chan int, 5), inboundChan: make(chan int, 5), } go b.log() b.start = time.Now() return b } func (b *BytesSyncLogger) log() { for { select { case amount := <-b.outboundChan: b.outbound += amount b.outEvents++ case amount := <-b.inboundChan: b.inbound += amount b.inEvents++ } } } func (b *BytesSyncLogger) AddOutbound(amount int) { b.outboundChan <- amount } func (b *BytesSyncLogger) AddInbound(amount int) { b.inboundChan <- amount } func (b *BytesSyncLogger) ThroughputSummary() string { var inUnit, outUnit string units := []string{"B", "KB", "MB", "GB"} inbound := b.inbound outbound := b.outbound for i, u := range units { inUnit = u if (inbound < 1000) || (i == len(units)-1) { break } inbound = inbound / 1000 } for i, u := range units { outUnit = u if (outbound < 1000) || (i == len(units)-1) { break } outbound = outbound / 1000 } t := time.Now() return fmt.Sprintf("Traffic throughput (up|down): %d %s|%d %s -- (%d OnMessages, %d Sends, over %d seconds)", inbound, inUnit, outbound, outUnit, b.outEvents, b.inEvents, int(t.Sub(b.start).Seconds())) } snowflake-1.1.0/proxy/webrtcconn.go000066400000000000000000000051401411410351300173470ustar00rootroot00000000000000package main import ( "fmt" "io" "log" "net" "regexp" "sync" "time" "github.com/pion/ice/v2" "github.com/pion/sdp/v3" "github.com/pion/webrtc/v3" ) var remoteIPPatterns = []*regexp.Regexp{ /* IPv4 */ regexp.MustCompile(`(?m)^c=IN IP4 ([\d.]+)(?:(?:\/\d+)?\/\d+)?(:? |\r?\n)`), /* IPv6 */ regexp.MustCompile(`(?m)^c=IN IP6 ([0-9A-Fa-f:.]+)(?:\/\d+)?(:? |\r?\n)`), } type webRTCConn struct { dc *webrtc.DataChannel pc *webrtc.PeerConnection pr *io.PipeReader lock sync.Mutex // Synchronization for DataChannel destruction once sync.Once // Synchronization for PeerConnection destruction bytesLogger BytesLogger } func (c *webRTCConn) Read(b []byte) (int, error) { return c.pr.Read(b) } func (c *webRTCConn) Write(b []byte) (int, error) { c.bytesLogger.AddInbound(len(b)) c.lock.Lock() defer c.lock.Unlock() if c.dc != nil { c.dc.Send(b) } return len(b), nil } func (c *webRTCConn) Close() (err error) { c.once.Do(func() { err = c.pc.Close() }) return } func (c *webRTCConn) LocalAddr() net.Addr { return nil } func (c *webRTCConn) RemoteAddr() net.Addr { //Parse Remote SDP offer and extract client IP clientIP := remoteIPFromSDP(c.pc.RemoteDescription().SDP) if clientIP == nil { return nil } return &net.IPAddr{IP: clientIP, Zone: ""} } func (c *webRTCConn) SetDeadline(t time.Time) error { // nolint: golint return fmt.Errorf("SetDeadline not implemented") } func (c *webRTCConn) SetReadDeadline(t time.Time) error { // nolint: golint return fmt.Errorf("SetReadDeadline not implemented") } func (c *webRTCConn) SetWriteDeadline(t time.Time) error { // nolint: golint return fmt.Errorf("SetWriteDeadline not implemented") } func remoteIPFromSDP(str string) net.IP { // Look for remote IP in "a=candidate" attribute fields // https://tools.ietf.org/html/rfc5245#section-15.1 var desc sdp.SessionDescription err := desc.Unmarshal([]byte(str)) if err != nil { log.Println("Error parsing SDP: ", err.Error()) return nil } for _, m := range desc.MediaDescriptions { for _, a := range m.Attributes { if a.IsICECandidate() { c, err := ice.UnmarshalCandidate(a.Value) if err == nil { ip := net.ParseIP(c.Address()) if ip != nil && isRemoteAddress(ip) { return ip } } } } } // Finally look for remote IP in "c=" Connection Data field // https://tools.ietf.org/html/rfc4566#section-5.7 for _, pattern := range remoteIPPatterns { m := pattern.FindStringSubmatch(str) if m != nil { // Ignore parsing errors, ParseIP returns nil. ip := net.ParseIP(m[1]) if ip != nil && isRemoteAddress(ip) { return ip } } } return nil } snowflake-1.1.0/server/000077500000000000000000000000001411410351300150015ustar00rootroot00000000000000snowflake-1.1.0/server/README.md000066400000000000000000000042561411410351300162670ustar00rootroot00000000000000This is the server transport plugin for Snowflake. The actual transport protocol it uses is [WebSocket](https://tools.ietf.org/html/rfc6455). In Snowflake, the client connects to the proxy using WebRTC, and the proxy connects to the server (this program) using WebSocket. # Setup The server needs to be able to listen on port 80 in order to generate its TLS certificates. On Linux, use the `setcap` program to enable the server to listen on port 80 without running as root: ``` setcap 'cap_net_bind_service=+ep' /usr/local/bin/snowflake-server ``` Here is a short example of configuring your torrc file to run the Snowflake server under Tor: ``` SocksPort 0 ORPort 9001 ExtORPort auto BridgeRelay 1 ServerTransportListenAddr snowflake 0.0.0.0:443 ServerTransportPlugin snowflake exec ./server --acme-hostnames snowflake.example --acme-email admin@snowflake.example --log /var/log/tor/snowflake-server.log ``` The domain names given to the `--acme-hostnames` option should resolve to the IP address of the server. You can give more than one, separated by commas. # TLS The server uses TLS WebSockets by default: wss:// not ws://. There is a `--disable-tls` option for testing purposes, but you should use TLS in production. The server automatically fetches certificates from [Let's Encrypt](https://en.wikipedia.org/wiki/Let's_Encrypt) as needed. Use the `--acme-hostnames` option to tell the server what hostnames it may request certificates for. You can optionally provide a contact email address, using the `--acme-email` option, so that Let's Encrypt can inform you of any problems. The server will cache TLS certificate data in the directory `pt_state/snowflake-certificate-cache` inside the tor state directory. In order to fetch certificates automatically, the server needs to listen on port 80, in addition to whatever ports it is listening on for WebSocket connections. This is a requirement of the ACME protocol used by Let's Encrypt. The program will exit if it can't bind to port 80. On Linux, you can use the `setcap` program, part of libcap2, to enable the server to bind to low-numbered ports without having to run as root: ``` setcap 'cap_net_bind_service=+ep' /usr/local/bin/snowflake-server ``` snowflake-1.1.0/server/lib/000077500000000000000000000000001411410351300155475ustar00rootroot00000000000000snowflake-1.1.0/server/lib/http.go000066400000000000000000000160531411410351300170620ustar00rootroot00000000000000package lib import ( "bufio" "bytes" "fmt" "io" "log" "net" "net/http" "sync" "time" "git.torproject.org/pluggable-transports/snowflake.git/common/encapsulation" "git.torproject.org/pluggable-transports/snowflake.git/common/turbotunnel" "git.torproject.org/pluggable-transports/snowflake.git/common/websocketconn" "github.com/gorilla/websocket" ) const requestTimeout = 10 * time.Second // How long to remember outgoing packets for a client, when we don't currently // have an active WebSocket connection corresponding to that client. Because a // client session may span multiple WebSocket connections, we keep packets we // aren't able to send immediately in memory, for a little while but not // indefinitely. const clientMapTimeout = 1 * time.Minute // How big to make the map of ClientIDs to IP addresses. The map is used in // turbotunnelMode to store a reasonable IP address for a client session that // may outlive any single WebSocket connection. const clientIDAddrMapCapacity = 1024 // How long to wait for ListenAndServe or ListenAndServeTLS to return an error // before deciding that it's not going to return. const listenAndServeErrorTimeout = 100 * time.Millisecond var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } // clientIDAddrMap stores short-term mappings from ClientIDs to IP addresses. // When we call pt.DialOr, tor wants us to provide a USERADDR string that // represents the remote IP address of the client (for metrics purposes, etc.). // This data structure bridges the gap between ServeHTTP, which knows about IP // addresses, and handleStream, which is what calls pt.DialOr. The common piece // of information linking both ends of the chain is the ClientID, which is // attached to the WebSocket connection and every session. var clientIDAddrMap = newClientIDMap(clientIDAddrMapCapacity) // overrideReadConn is a net.Conn with an overridden Read method. Compare to // recordingConn at // https://dave.cheney.net/2015/05/22/struct-composition-with-go. type overrideReadConn struct { net.Conn io.Reader } func (conn *overrideReadConn) Read(p []byte) (int, error) { return conn.Reader.Read(p) } type HTTPHandler struct { // pconn is the adapter layer between stream-oriented WebSocket // connections and the packet-oriented KCP layer. pconn *turbotunnel.QueuePacketConn ln *SnowflakeListener } func (handler *HTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Println(err) return } conn := websocketconn.New(ws) defer conn.Close() // Pass the address of client as the remote address of incoming connection clientIPParam := r.URL.Query().Get("client_ip") addr := clientAddr(clientIPParam) var token [len(turbotunnel.Token)]byte _, err = io.ReadFull(conn, token[:]) if err != nil { // Don't bother logging EOF: that happens with an unused // connection, which clients make frequently as they maintain a // pool of proxies. if err != io.EOF { log.Printf("reading token: %v", err) } return } switch { case bytes.Equal(token[:], turbotunnel.Token[:]): err = turbotunnelMode(conn, addr, handler.pconn) default: // We didn't find a matching token, which means that we are // dealing with a client that doesn't know about such things. // "Unread" the token by constructing a new Reader and pass it // to the old one-session-per-WebSocket mode. conn2 := &overrideReadConn{Conn: conn, Reader: io.MultiReader(bytes.NewReader(token[:]), conn)} err = oneshotMode(conn2, addr, handler.ln) } if err != nil { log.Println(err) return } } // oneshotMode handles clients that did not send turbotunnel.Token at the start // of their stream. These clients use the WebSocket as a raw pipe, and expect // their session to begin and end when this single WebSocket does. func oneshotMode(conn net.Conn, addr net.Addr, ln *SnowflakeListener) error { return ln.QueueConn(&SnowflakeClientConn{Conn: conn, address: addr}) } // turbotunnelMode handles clients that sent turbotunnel.Token at the start of // their stream. These clients expect to send and receive encapsulated packets, // with a long-lived session identified by ClientID. func turbotunnelMode(conn net.Conn, addr net.Addr, pconn *turbotunnel.QueuePacketConn) error { // Read the ClientID prefix. Every packet encapsulated in this WebSocket // connection pertains to the same ClientID. var clientID turbotunnel.ClientID _, err := io.ReadFull(conn, clientID[:]) if err != nil { return fmt.Errorf("reading ClientID: %v", err) } // Store a a short-term mapping from the ClientID to the client IP // address attached to this WebSocket connection. tor will want us to // provide a client IP address when we call pt.DialOr. But a KCP session // does not necessarily correspond to any single IP address--it's // composed of packets that are carried in possibly multiple WebSocket // streams. We apply the heuristic that the IP address of the most // recent WebSocket connection that has had to do with a session, at the // time the session is established, is the IP address that should be // credited for the entire KCP session. clientIDAddrMap.Set(clientID, addr) var wg sync.WaitGroup wg.Add(2) done := make(chan struct{}) // The remainder of the WebSocket stream consists of encapsulated // packets. We read them one by one and feed them into the // QueuePacketConn on which kcp.ServeConn was set up, which eventually // leads to KCP-level sessions in the acceptSessions function. go func() { defer wg.Done() defer close(done) // Signal the write loop to finish for { p, err := encapsulation.ReadData(conn) if err != nil { return } pconn.QueueIncoming(p, clientID) } }() // At the same time, grab packets addressed to this ClientID and // encapsulate them into the downstream. go func() { defer wg.Done() defer conn.Close() // Signal the read loop to finish // Buffer encapsulation.WriteData operations to keep length // prefixes in the same send as the data that follows. bw := bufio.NewWriter(conn) for { select { case <-done: return case p, ok := <-pconn.OutgoingQueue(clientID): if !ok { return } _, err := encapsulation.WriteData(bw, p) if err == nil { err = bw.Flush() } if err != nil { return } } } }() wg.Wait() return nil } type ClientMapAddr string func (addr ClientMapAddr) Network() string { return "snowflake" } func (addr ClientMapAddr) String() string { return string(addr) } // Return a client address func clientAddr(clientIPParam string) net.Addr { if clientIPParam == "" { return ClientMapAddr("") } // Check if client addr is a valid IP clientIP := net.ParseIP(clientIPParam) if clientIP == nil { return ClientMapAddr("") } // Check if client addr is 0.0.0.0 or [::]. Some proxies erroneously // report an address of 0.0.0.0: https://bugs.torproject.org/33157. if clientIP.IsUnspecified() { return ClientMapAddr("") } // Add a stub port number. USERADDR requires a port number. return ClientMapAddr((&net.TCPAddr{IP: clientIP, Port: 1, Zone: ""}).String()) } snowflake-1.1.0/server/lib/server_test.go000066400000000000000000000022711411410351300204450ustar00rootroot00000000000000package lib import ( "net" "strconv" "testing" . "github.com/smartystreets/goconvey/convey" ) func TestClientAddr(t *testing.T) { Convey("Testing clientAddr", t, func() { // good tests for _, test := range []struct { input string expected net.IP }{ {"1.2.3.4", net.ParseIP("1.2.3.4")}, {"1:2::3:4", net.ParseIP("1:2::3:4")}, } { useraddr := clientAddr(test.input).String() host, port, err := net.SplitHostPort(useraddr) if err != nil { t.Errorf("clientAddr(%q) → SplitHostPort error %v", test.input, err) continue } if !test.expected.Equal(net.ParseIP(host)) { t.Errorf("clientAddr(%q) → host %q, not %v", test.input, host, test.expected) } portNo, err := strconv.Atoi(port) if err != nil { t.Errorf("clientAddr(%q) → port %q", test.input, port) continue } if portNo == 0 { t.Errorf("clientAddr(%q) → port %d", test.input, portNo) } } // bad tests for _, input := range []string{ "", "abc", "1.2.3.4.5", "[12::34]", "0.0.0.0", "[::]", } { useraddr := clientAddr(input).String() if useraddr != "" { t.Errorf("clientAddr(%q) → %q, not %q", input, useraddr, "") } } }) } snowflake-1.1.0/server/lib/snowflake.go000066400000000000000000000162301411410351300200710ustar00rootroot00000000000000package lib import ( "crypto/tls" "fmt" "io" "log" "net" "net/http" "sync" "time" "git.torproject.org/pluggable-transports/snowflake.git/common/turbotunnel" "github.com/xtaci/kcp-go/v5" "github.com/xtaci/smux" "golang.org/x/net/http2" ) // Transport is a structure with methods that conform to the Go PT v2.1 API // https://github.com/Pluggable-Transports/Pluggable-Transports-spec/blob/master/releases/PTSpecV2.1/Pluggable%20Transport%20Specification%20v2.1%20-%20Go%20Transport%20API.pdf type Transport struct { getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error) } func NewSnowflakeServer(getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error)) *Transport { return &Transport{getCertificate: getCertificate} } func (t *Transport) Listen(addr net.Addr) (*SnowflakeListener, error) { listener := &SnowflakeListener{addr: addr, queue: make(chan net.Conn, 65534)} handler := HTTPHandler{ // pconn is shared among all connections to this server. It // overlays packet-based client sessions on top of ephemeral // WebSocket connections. pconn: turbotunnel.NewQueuePacketConn(addr, clientMapTimeout), } server := &http.Server{ Addr: addr.String(), Handler: &handler, ReadTimeout: requestTimeout, } // We need to override server.TLSConfig.GetCertificate--but first // server.TLSConfig needs to be non-nil. If we just create our own new // &tls.Config, it will lack the default settings that the net/http // package sets up for things like HTTP/2. Therefore we first call // http2.ConfigureServer for its side effect of initializing // server.TLSConfig properly. An alternative would be to make a dummy // net.Listener, call Serve on it, and let it return. // https://github.com/golang/go/issues/16588#issuecomment-237386446 err := http2.ConfigureServer(server, nil) if err != nil { return nil, err } server.TLSConfig.GetCertificate = t.getCertificate // Another unfortunate effect of the inseparable net/http ListenAndServe // is that we can't check for Listen errors like "permission denied" and // "address already in use" without potentially entering the infinite // loop of Serve. The hack we apply here is to wait a short time, // listenAndServeErrorTimeout, to see if an error is returned (because // it's better if the error message goes to the tor log through // SMETHOD-ERROR than if it only goes to the snowflake log). errChan := make(chan error) go func() { if t.getCertificate == nil { // TLS is disabled log.Printf("listening with plain HTTP on %s", addr) err := server.ListenAndServe() if err != nil { log.Printf("error in ListenAndServe: %s", err) } errChan <- err } else { log.Printf("listening with HTTPS on %s", addr) err := server.ListenAndServeTLS("", "") if err != nil { log.Printf("error in ListenAndServeTLS: %s", err) } errChan <- err } }() select { case err = <-errChan: break case <-time.After(listenAndServeErrorTimeout): break } listener.server = server // Start a KCP engine, set up to read and write its packets over the // WebSocket connections that arrive at the web server. // handler.ServeHTTP is responsible for encapsulation/decapsulation of // packets on behalf of KCP. KCP takes those packets and turns them into // sessions which appear in the acceptSessions function. ln, err := kcp.ServeConn(nil, 0, 0, handler.pconn) if err != nil { server.Close() return nil, err } go func() { defer ln.Close() err := listener.acceptSessions(ln) if err != nil { log.Printf("acceptSessions: %v", err) } }() listener.ln = ln return listener, nil } type SnowflakeListener struct { addr net.Addr queue chan net.Conn server *http.Server ln *kcp.Listener closed chan struct{} closeOnce sync.Once } // Allows the caller to accept incoming Snowflake connections // We accept connections from a queue to accommodate both incoming // smux Streams and legacy non-turbotunnel connections func (l *SnowflakeListener) Accept() (net.Conn, error) { select { case <-l.closed: //channel has been closed, no longer accepting connections return nil, io.ErrClosedPipe case conn := <-l.queue: return conn, nil } } func (l *SnowflakeListener) Addr() net.Addr { return l.addr } func (l *SnowflakeListener) Close() error { // Close our HTTP server and our KCP listener l.closeOnce.Do(func() { close(l.closed) l.server.Close() l.ln.Close() }) return nil } // acceptStreams layers an smux.Session on the KCP connection and awaits streams // on it. Passes each stream to our SnowflakeListener accept queue. func (l *SnowflakeListener) acceptStreams(conn *kcp.UDPSession) error { // Look up the IP address associated with this KCP session, via the // ClientID that is returned by the session's RemoteAddr method. addr, ok := clientIDAddrMap.Get(conn.RemoteAddr().(turbotunnel.ClientID)) if !ok { // This means that the map is tending to run over capacity, not // just that there was not client_ip on the incoming connection. // We store "" in the map in the absence of client_ip. This log // message means you should increase clientIDAddrMapCapacity. log.Printf("no address in clientID-to-IP map (capacity %d)", clientIDAddrMapCapacity) } smuxConfig := smux.DefaultConfig() smuxConfig.Version = 2 smuxConfig.KeepAliveTimeout = 10 * time.Minute sess, err := smux.Server(conn, smuxConfig) if err != nil { return err } for { stream, err := sess.AcceptStream() if err != nil { if err, ok := err.(net.Error); ok && err.Temporary() { continue } return err } l.QueueConn(&SnowflakeClientConn{Conn: stream, address: addr}) } } // acceptSessions listens for incoming KCP connections and passes them to // acceptStreams. It is handler.ServeHTTP that provides the network interface // that drives this function. func (l *SnowflakeListener) acceptSessions(ln *kcp.Listener) error { for { conn, err := ln.AcceptKCP() if err != nil { if err, ok := err.(net.Error); ok && err.Temporary() { continue } return err } // Permit coalescing the payloads of consecutive sends. conn.SetStreamMode(true) // Set the maximum send and receive window sizes to a high number // Removes KCP bottlenecks: https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/issues/40026 conn.SetWindowSize(65535, 65535) // Disable the dynamic congestion window (limit only by the // maximum of local and remote static windows). conn.SetNoDelay( 0, // default nodelay 0, // default interval 0, // default resend 1, // nc=1 => congestion window off ) go func() { defer conn.Close() err := l.acceptStreams(conn) if err != nil && err != io.ErrClosedPipe { log.Printf("acceptStreams: %v", err) } }() } } func (l *SnowflakeListener) QueueConn(conn net.Conn) error { select { case <-l.closed: return fmt.Errorf("accepted connection on closed listener") case l.queue <- conn: return nil } } // A wrapper for the underlying oneshot or turbotunnel conn // because we need to reference our mapping to determine the client // address type SnowflakeClientConn struct { net.Conn address net.Addr } func (conn *SnowflakeClientConn) RemoteAddr() net.Addr { return conn.address } snowflake-1.1.0/server/lib/turbotunnel.go000066400000000000000000000057371411410351300204730ustar00rootroot00000000000000package lib import ( "net" "sync" "git.torproject.org/pluggable-transports/snowflake.git/common/turbotunnel" ) // clientIDMap is a fixed-capacity mapping from ClientIDs to a net.Addr. // Adding a new entry using the Set method causes the oldest existing entry to // be forgotten. // // This data type is meant to be used to remember the IP address associated with // a ClientID, during the short period of time between when a WebSocket // connection with that ClientID began, and when a KCP session is established. // // The design requirements of this type are that it needs to remember a mapping // for only a short time, and old entries should expire so as not to consume // unbounded memory. It is not a critical error if an entry is forgotten before // it is needed; better to forget entries than to use too much memory. type clientIDMap struct { lock sync.Mutex // entries is a circular buffer of (ClientID, addr) pairs. entries []struct { clientID turbotunnel.ClientID addr net.Addr } // oldest is the index of the oldest member of the entries buffer, the // one that will be overwritten at the next call to Set. oldest int // current points to the index of the most recent entry corresponding to // each ClientID. current map[turbotunnel.ClientID]int } // newClientIDMap makes a new clientIDMap with the given capacity. func newClientIDMap(capacity int) *clientIDMap { return &clientIDMap{ entries: make([]struct { clientID turbotunnel.ClientID addr net.Addr }, capacity), oldest: 0, current: make(map[turbotunnel.ClientID]int), } } // Set adds a mapping from clientID to addr, replacing any previous mapping for // clientID. It may also cause the clientIDMap to forget at most one other // mapping, the oldest one. func (m *clientIDMap) Set(clientID turbotunnel.ClientID, addr net.Addr) { m.lock.Lock() defer m.lock.Unlock() if len(m.entries) == 0 { // The invariant m.oldest < len(m.entries) does not hold in this // special case. return } // m.oldest is the index of the entry we're about to overwrite. If it's // the current entry for any ClientID, we need to delete that clientID // from the current map (that ClientID is now forgotten). if i, ok := m.current[m.entries[m.oldest].clientID]; ok && i == m.oldest { delete(m.current, m.entries[m.oldest].clientID) } // Overwrite the oldest entry. m.entries[m.oldest].clientID = clientID m.entries[m.oldest].addr = addr // Add the overwritten entry to the quick-lookup map. m.current[clientID] = m.oldest // What was the oldest entry is now the newest. m.oldest = (m.oldest + 1) % len(m.entries) } // Get returns a previously stored mapping. The second return value indicates // whether clientID was actually present in the map. If it is false, then the // returned address will be nil. func (m *clientIDMap) Get(clientID turbotunnel.ClientID) (net.Addr, bool) { m.lock.Lock() defer m.lock.Unlock() if i, ok := m.current[clientID]; ok { return m.entries[i].addr, true } else { return nil, false } } snowflake-1.1.0/server/lib/turbotunnel_test.go000066400000000000000000000074711411410351300215270ustar00rootroot00000000000000package lib import ( "encoding/binary" "net" "testing" "git.torproject.org/pluggable-transports/snowflake.git/common/turbotunnel" ) func TestClientIDMap(t *testing.T) { // Convert a uint64 into a ClientID. id := func(n uint64) turbotunnel.ClientID { var clientID turbotunnel.ClientID binary.PutUvarint(clientID[:], n) return clientID } // Does m.Get(key) and checks that the output matches what is expected. expectGet := func(m *clientIDMap, clientID turbotunnel.ClientID, expectedAddr string, expectedOK bool) { t.Helper() addr, ok := m.Get(clientID) if (ok && addr.String() != expectedAddr) || ok != expectedOK { t.Errorf("expected (%+q, %v), got (%+q, %v)", expectedAddr, expectedOK, addr, ok) } } // Checks that the len of m.current is as expected. expectSize := func(m *clientIDMap, expectedLen int) { t.Helper() if len(m.current) != expectedLen { t.Errorf("expected map len %d, got %d %+v", expectedLen, len(m.current), m.current) } } // Convert a string to a net.Addr ip := func(addr string) net.Addr { ret, err := net.ResolveIPAddr("ip", addr) if err != nil { t.Errorf("received error: %s", err.Error()) } return ret } // Zero-capacity map can't remember anything. { m := newClientIDMap(0) expectSize(m, 0) expectGet(m, id(0), "", false) expectGet(m, id(1234), "", false) m.Set(id(0), ip("1.1.1.1")) expectSize(m, 0) expectGet(m, id(0), "", false) expectGet(m, id(1234), "", false) m.Set(id(1234), ip("1.1.1.1")) expectSize(m, 0) expectGet(m, id(0), "", false) expectGet(m, id(1234), "", false) } { m := newClientIDMap(1) expectSize(m, 0) expectGet(m, id(0), "", false) expectGet(m, id(1), "", false) m.Set(id(0), ip("1.1.1.1")) expectSize(m, 1) expectGet(m, id(0), "1.1.1.1", true) expectGet(m, id(1), "", false) m.Set(id(1), ip("1.1.1.2")) // forgets the (0, "1.1.1.1") entry expectSize(m, 1) expectGet(m, id(0), "", false) expectGet(m, id(1), "1.1.1.2", true) m.Set(id(1), ip("1.1.1.3")) // forgets the (1, "1.1.1.2") entry expectSize(m, 1) expectGet(m, id(0), "", false) expectGet(m, id(1), "1.1.1.3", true) } { m := newClientIDMap(5) m.Set(id(0), ip("1.1.1.1")) m.Set(id(1), ip("1.1.1.2")) m.Set(id(2), ip("1.1.1.3")) m.Set(id(0), ip("1.1.1.4")) // shadows the (0, "1.1.1.1") entry m.Set(id(3), ip("1.1.1.5")) expectSize(m, 4) expectGet(m, id(0), "1.1.1.4", true) expectGet(m, id(1), "1.1.1.2", true) expectGet(m, id(2), "1.1.1.3", true) expectGet(m, id(3), "1.1.1.5", true) expectGet(m, id(4), "", false) m.Set(id(4), ip("1.1.1.6")) // forgets the (0, "1.1.1.1") entry but should preserve (0, "1.1.1.4") expectSize(m, 5) expectGet(m, id(0), "1.1.1.4", true) expectGet(m, id(1), "1.1.1.2", true) expectGet(m, id(2), "1.1.1.3", true) expectGet(m, id(3), "1.1.1.5", true) expectGet(m, id(4), "1.1.1.6", true) m.Set(id(5), ip("1.1.1.7")) // forgets the (1, "1.1.1.2") entry m.Set(id(0), ip("1.1.1.8")) // forgets the (2, "1.1.1.3") entry and shadows (0, "1.1.1.4") expectSize(m, 4) expectGet(m, id(0), "1.1.1.8", true) expectGet(m, id(1), "", false) expectGet(m, id(2), "", false) expectGet(m, id(3), "1.1.1.5", true) expectGet(m, id(4), "1.1.1.6", true) expectGet(m, id(5), "1.1.1.7", true) m.Set(id(0), ip("1.1.1.9")) // forgets the (0, "1.1.1.4") entry and shadows (0, "1.1.1.8") m.Set(id(0), ip("1.1.1.10")) // forgets the (3, "1.1.1.5") entry and shadows (0, "1.1.1.9") m.Set(id(0), ip("1.1.1.11")) // forgets the (4, "1.1.1.6") entry and shadows (0, "1.1.1.10") m.Set(id(0), ip("1.1.1.12")) // forgets the (5, "1.1.1.7") entry and shadows (0, "1.1.1.11") expectSize(m, 1) expectGet(m, id(0), "1.1.1.12", true) expectGet(m, id(1), "", false) expectGet(m, id(2), "", false) expectGet(m, id(3), "", false) expectGet(m, id(4), "", false) expectGet(m, id(5), "", false) } } snowflake-1.1.0/server/server.go000066400000000000000000000171171411410351300166450ustar00rootroot00000000000000// Snowflake-specific websocket server plugin. It reports the transport name as // "snowflake". package main import ( "flag" "fmt" "io" "io/ioutil" "log" "net" "net/http" "os" "os/signal" "path/filepath" "strings" "sync" "syscall" "git.torproject.org/pluggable-transports/snowflake.git/common/safelog" "golang.org/x/crypto/acme/autocert" pt "git.torproject.org/pluggable-transports/goptlib.git" sf "git.torproject.org/pluggable-transports/snowflake.git/server/lib" ) const ptMethodName = "snowflake" var ptInfo pt.ServerInfo func usage() { fmt.Fprintf(os.Stderr, `Usage: %s [OPTIONS] WebSocket server pluggable transport for Snowflake. Works only as a managed proxy. Uses TLS with ACME (Let's Encrypt) by default. Set the certificate hostnames with the --acme-hostnames option. Use ServerTransportListenAddr in torrc to choose the listening port. When using TLS, this program will open an additional HTTP listener on port 80 to work with ACME. `, os.Args[0]) flag.PrintDefaults() } //proxy copies data bidirectionally from one connection to another. func proxy(local *net.TCPConn, conn net.Conn) { var wg sync.WaitGroup wg.Add(2) go func() { if _, err := io.Copy(conn, local); err != nil && err != io.ErrClosedPipe { log.Printf("error copying ORPort to WebSocket %v", err) } local.CloseRead() conn.Close() wg.Done() }() go func() { if _, err := io.Copy(local, conn); err != nil && err != io.ErrClosedPipe { log.Printf("error copying WebSocket to ORPort %v", err) } local.CloseWrite() conn.Close() wg.Done() }() wg.Wait() } //handleConn bidirectionally connects a client snowflake connection with an ORPort. func handleConn(conn net.Conn) error { addr := conn.RemoteAddr().String() statsChannel <- addr != "" or, err := pt.DialOr(&ptInfo, addr, ptMethodName) if err != nil { return fmt.Errorf("failed to connect to ORPort: %s", err) } defer or.Close() proxy(or, conn) return nil } //acceptLoop accepts incoming client snowflake connection and passes them to a handler function. func acceptLoop(ln net.Listener) { for { conn, err := ln.Accept() if err != nil { if err, ok := err.(net.Error); ok && err.Temporary() { continue } log.Printf("Snowflake accept error: %s", err) break } go func() { defer conn.Close() err := handleConn(conn) if err != nil { log.Printf("handleConn: %v", err) } }() } } func getCertificateCacheDir() (string, error) { stateDir, err := pt.MakeStateDir() if err != nil { return "", err } return filepath.Join(stateDir, "snowflake-certificate-cache"), nil } func main() { var acmeEmail string var acmeHostnamesCommas string var disableTLS bool var logFilename string var unsafeLogging bool flag.Usage = usage flag.StringVar(&acmeEmail, "acme-email", "", "optional contact email for Let's Encrypt notifications") flag.StringVar(&acmeHostnamesCommas, "acme-hostnames", "", "comma-separated hostnames for TLS certificate") flag.BoolVar(&disableTLS, "disable-tls", false, "don't use HTTPS") flag.StringVar(&logFilename, "log", "", "log file to write to") flag.BoolVar(&unsafeLogging, "unsafe-logging", false, "prevent logs from being scrubbed") flag.Parse() log.SetFlags(log.LstdFlags | log.LUTC) var logOutput io.Writer = os.Stderr if logFilename != "" { f, err := os.OpenFile(logFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) if err != nil { log.Fatalf("can't open log file: %s", err) } defer f.Close() logOutput = f } if unsafeLogging { log.SetOutput(logOutput) } else { // We want to send the log output through our scrubber first log.SetOutput(&safelog.LogScrubber{Output: logOutput}) } if !disableTLS && acmeHostnamesCommas == "" { log.Fatal("the --acme-hostnames option is required") } acmeHostnames := strings.Split(acmeHostnamesCommas, ",") log.Printf("starting") var err error ptInfo, err = pt.ServerSetup(nil) if err != nil { log.Fatalf("error in setup: %s", err) } go statsThread() var certManager *autocert.Manager if !disableTLS { log.Printf("ACME hostnames: %q", acmeHostnames) var cache autocert.Cache var cacheDir string cacheDir, err = getCertificateCacheDir() if err == nil { log.Printf("caching ACME certificates in directory %q", cacheDir) cache = autocert.DirCache(cacheDir) } else { log.Printf("disabling ACME certificate cache: %s", err) } certManager = &autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(acmeHostnames...), Email: acmeEmail, Cache: cache, } } // The ACME HTTP-01 responder only works when it is running on port 80. // We actually open the port in the loop below, so that any errors can // be reported in the SMETHOD-ERROR of some bindaddr. // https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#http-challenge needHTTP01Listener := !disableTLS listeners := make([]net.Listener, 0) for _, bindaddr := range ptInfo.Bindaddrs { if bindaddr.MethodName != ptMethodName { pt.SmethodError(bindaddr.MethodName, "no such method") continue } if needHTTP01Listener { addr := *bindaddr.Addr addr.Port = 80 log.Printf("Starting HTTP-01 ACME listener") var lnHTTP01 *net.TCPListener lnHTTP01, err = net.ListenTCP("tcp", &addr) if err != nil { log.Printf("error opening HTTP-01 ACME listener: %s", err) pt.SmethodError(bindaddr.MethodName, "HTTP-01 ACME listener: "+err.Error()) continue } server := &http.Server{ Addr: addr.String(), Handler: certManager.HTTPHandler(nil), } go func() { log.Fatal(server.Serve(lnHTTP01)) }() listeners = append(listeners, lnHTTP01) needHTTP01Listener = false } // We're not capable of listening on port 0 (i.e., an ephemeral port // unknown in advance). The reason is that while the net/http package // exposes ListenAndServe and ListenAndServeTLS, those functions never // return, so there's no opportunity to find out what the port number // is, in between the Listen and Serve steps. // https://groups.google.com/d/msg/Golang-nuts/3F1VRCCENp8/3hcayZiwYM8J if bindaddr.Addr.Port == 0 { err := fmt.Errorf( "cannot listen on port %d; configure a port using ServerTransportListenAddr", bindaddr.Addr.Port) log.Printf("error opening listener: %s", err) pt.SmethodError(bindaddr.MethodName, err.Error()) continue } var transport *sf.Transport args := pt.Args{} if disableTLS { args.Add("tls", "no") transport = sf.NewSnowflakeServer(nil) } else { args.Add("tls", "yes") for _, hostname := range acmeHostnames { args.Add("hostname", hostname) } transport = sf.NewSnowflakeServer(certManager.GetCertificate) } ln, err := transport.Listen(bindaddr.Addr) if err != nil { log.Printf("error opening listener: %s", err) pt.SmethodError(bindaddr.MethodName, err.Error()) continue } defer ln.Close() go acceptLoop(ln) pt.SmethodArgs(bindaddr.MethodName, bindaddr.Addr, args) listeners = append(listeners, ln) } pt.SmethodsDone() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM) if os.Getenv("TOR_PT_EXIT_ON_STDIN_CLOSE") == "1" { // This environment variable means we should treat EOF on stdin // just like SIGTERM: https://bugs.torproject.org/15435. go func() { if _, err := io.Copy(ioutil.Discard, os.Stdin); err != nil { log.Printf("error copying os.Stdin to ioutil.Discard: %v", err) } log.Printf("synthesizing SIGTERM because of stdin close") sigChan <- syscall.SIGTERM }() } // Wait for a signal. sig := <-sigChan // Signal received, shut down. log.Printf("caught signal %q, exiting", sig) for _, ln := range listeners { ln.Close() } } snowflake-1.1.0/server/stats.go000066400000000000000000000015501411410351300164670ustar00rootroot00000000000000package main // This code handled periodic statistics logging. // // The only thing it keeps track of is how many connections had the client_ip // parameter. Write true to statsChannel to record a connection with client_ip; // write false for without. import ( "log" "time" ) const ( statsInterval = 24 * time.Hour ) var ( statsChannel = make(chan bool) ) func statsThread() { var numClientIP, numConnections uint64 prevTime := time.Now() deadline := time.After(statsInterval) for { select { case v := <-statsChannel: if v { numClientIP++ } numConnections++ case <-deadline: now := time.Now() log.Printf("in the past %.f s, %d/%d connections had client_ip", (now.Sub(prevTime)).Seconds(), numClientIP, numConnections) numClientIP = 0 numConnections = 0 prevTime = now deadline = time.After(statsInterval) } } } snowflake-1.1.0/server/torrc000066400000000000000000000004041411410351300160530ustar00rootroot00000000000000SocksPort 0 ORPort 9001 ExtORPort auto BridgeRelay 1 ServerTransportListenAddr snowflake 0.0.0.0:443 ServerTransportPlugin snowflake exec ./server --acme-hostnames snowflake.example --acme-email admin@snowflake.example --log /var/log/tor/snowflake-server.log