Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:ojkastl_buildservice:Branch_network
snowflake
snowflake-2.9.2.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File snowflake-2.9.2.obscpio of Package snowflake
07070100000000000081A400000000000000000000000165F88C50000000E6000000000000000000000000000000000000001B00000000snowflake-2.9.2/.gitignore*.swp *.swo *.swn *.swm .DS_Store datadir/ broker/broker client/client server/server proxy/proxy probetest/probetest snowflake.log ignore/ # from running the vagrant setup /.vagrant/ /sdk-tools-linux-*.zip* /android-ndk-* /tools/07070100000001000081A400000000000000000000000165F88C5000002C07000000000000000000000000000000000000001F00000000snowflake-2.9.2/.gitlab-ci.ymlstages: - test - deploy - container-build variables: DEBIAN_FRONTEND: noninteractive DEBIAN_OLD_STABLE: buster DEBIAN_STABLE: bullseye REPRODUCIBLE_FLAGS: -trimpath -ldflags=-buildid= # set up apt for automated use .apt-template: &apt-template - export LC_ALL=C.UTF-8 - export DEBIAN_FRONTEND=noninteractive - echo Etc/UTC > /etc/timezone - echo 'quiet "1";' 'APT::Install-Recommends "0";' 'APT::Install-Suggests "0";' 'APT::Acquire::Retries "20";' 'APT::Get::Assume-Yes "true";' 'Dpkg::Use-Pty "0";' > /etc/apt/apt.conf.d/99gitlab - apt-get update - apt-get dist-upgrade # 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: GOPATH: /usr/share/gocode before_script: - apt-get 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-goptlib-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 before_script: - apt-get update - apt-get -qy install --no-install-recommends ca-certificates git lbzip2 .go-test: &go-test - gofmt -d . - test -z "$(go fmt ./...)" - go vet ./... - go test -v -race ./... - cd $CI_PROJECT_DIR/client/ - go get - go build $REPRODUCIBLE_FLAGS .test-template: &test-template artifacts: name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_JOB_ID}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}" paths: - client/*.aar - client/*.jar - client/client expire_in: 1 week when: on_success after_script: - echo "Download debug artifacts from https://gitlab.com/${CI_PROJECT_PATH}/-/jobs" # this file changes every time but should not be cached - rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock - rm -rf $GRADLE_USER_HOME/caches/*/plugin-resolution/ # -- jobs ------------------------------------------------------------ android: image: golang:1.21-$DEBIAN_STABLE variables: ANDROID_HOME: /usr/lib/android-sdk LANG: C.UTF-8 cache: paths: - .gradle/wrapper - .gradle/caches <<: *test-template before_script: - *apt-template - apt-get install android-sdk-platform-23 android-sdk-platform-tools build-essential curl default-jdk-headless git gnupg unzip wget ca-certificates lbzip2 - ndk=android-ndk-r21e-linux-x86_64.zip - wget --continue --no-verbose https://dl.google.com/android/repository/$ndk - echo "ad7ce5467e18d40050dc51b8e7affc3e635c85bd8c59be62de32352328ed467e $ndk" > $ndk.sha256 - sha256sum -c $ndk.sha256 - unzip -q $ndk - rm ${ndk}* - mv android-ndk-* $ANDROID_HOME/ndk-bundle/ - chmod -R a+rX $ANDROID_HOME script: - *go-test - export GRADLE_USER_HOME=$CI_PROJECT_DIR/.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/gobind - go install golang.org/x/mobile/cmd/gomobile - gomobile init - cd $CI_PROJECT_DIR/client # gomobile builds a shared library not a CLI executable - sed -i 's,^package main$,package snowflakeclient,' *.go - go get golang.org/x/mobile/bind - gomobile bind -v -target=android $REPRODUCIBLE_FLAGS . go-1.21: image: golang:1.21-$DEBIAN_STABLE <<: *golang-docker-debian-template <<: *test-template script: - *go-test debian-testing: image: debian:testing <<: *debian-native-template <<: *test-template script: - *go-test shadow-integration: image: golang:1.21-$DEBIAN_STABLE variables: SHADOW_VERSION: "193924aae0dab30ffda0abe29467f552949849fa" TGEN_VERSION: "v1.1.2" cache: key: sf-integration-$SHADOW_VERSION-$TGEN_VERSION paths: - /opt/ artifacts: paths: - shadow.data.tar.gz when: on_failure tags: - amd64 - tpa script: - apt-get update - apt-get install -y git tor - mkdir -p ~/.local/bin - mkdir -p ~/.local/src - export PATH=$PATH:$CI_PROJECT_DIR/opt/bin/ # Install shadow and tgen - pushd ~/.local/src - | if [ ! -f opt/shadow/bin/shadow ] then echo "The required version of shadow was not cached, building from source" git clone --shallow-since=2021-08-01 https://github.com/shadow/shadow.git pushd shadow/ git checkout $SHADOW_VERSION CONTAINER=debian:stable-slim ci/container_scripts/install_deps.sh CC=gcc CONTAINER=debian:stable-slim ci/container_scripts/install_extra_deps.sh export PATH="$HOME/.cargo/bin:${PATH}" ./setup build --jobs $(nproc) --prefix $CI_PROJECT_DIR/opt/ ./setup install popd fi - | if [ ! -f opt/shadow/bin/tgen ] then echo "The required version of tgen was not cached, building from source" git clone --branch $TGEN_VERSION --depth 1 https://github.com/shadow/tgen.git pushd tgen/ apt-get install -y cmake libglib2.0-dev libigraph-dev mkdir build && cd build cmake .. -DCMAKE_INSTALL_PREFIX=$CI_PROJECT_DIR/opt/ make make install popd fi install $CI_PROJECT_DIR/opt/bin/tgen ~/.local/bin/tgen - popd # Apply snowflake patch(es) - | git clone --depth 1 https://github.com/cohosh/shadow-snowflake-minimal git am -3 shadow-snowflake-minimal/*.patch # Install snowflake binaries to .local folder - | for app in "proxy" "client" "server" "broker" "probetest"; do pushd $app go build install $app ~/.local/bin/snowflake-$app popd done # Install stun server - GOBIN=~/.local/bin go install github.com/gortc/stund@latest # Run a minimal snowflake shadow experiment - pushd shadow-snowflake-minimal/ - shadow --log-level=debug --model-unblocked-syscall-latency=true snowflake-minimal.yaml > shadow.log - tar -czvf $CI_PROJECT_DIR/shadow.data.tar.gz shadow.data/ # Check to make sure streams succeeded - | if [ $(grep -c "stream-success" shadow.data/hosts/snowflakeclient/tgen.*.stdout) = 10 ] then echo "All streams in shadow completed successfully" else echo "Shadow simulation failed" exit 1 fi generate_tarball: stage: deploy image: golang:1.21-$DEBIAN_STABLE rules: - if: $CI_COMMIT_TAG script: - go mod vendor - tar czf ${CI_PROJECT_NAME}-${CI_COMMIT_TAG}.tar.gz --transform "s,^,${CI_PROJECT_NAME}-${CI_COMMIT_TAG}/," * after_script: - echo TAR_JOB_ID=$CI_JOB_ID >> generate_tarball.env artifacts: paths: - ${CI_PROJECT_NAME}-${CI_COMMIT_TAG}.tar.gz reports: dotenv: generate_tarball.env release-job: stage: deploy image: registry.gitlab.com/gitlab-org/release-cli:latest rules: - if: $CI_COMMIT_TAG needs: - job: generate_tarball artifacts: true script: - echo "running release_job" release: name: 'Release $CI_COMMIT_TAG' description: 'Created using the release-cli' tag_name: '$CI_COMMIT_TAG' ref: '$CI_COMMIT_TAG' assets: links: - name: '${CI_PROJECT_NAME}-${CI_COMMIT_TAG}.tar.gz' url: '${CI_PROJECT_URL}/-/jobs/${TAR_JOB_ID}/artifacts/file/${CI_PROJECT_NAME}-${CI_COMMIT_TAG}.tar.gz' # Build the container only if the commit is to main, or it is a tag. # If the commit is to main, then the docker image tag should be set to `latest`. # If it is a tag, then the docker image tag should be set to the tag name. build-container: variables: TAG: $CI_COMMIT_TAG # Will not be set on a non-tag build, will be set later stage: container-build parallel: matrix: - ARCH: amd64 - ARCH: arm64 - ARCH: s390x tags: - $ARCH image: name: gcr.io/kaniko-project/executor:debug entrypoint: [""] script: - if [ $CI_COMMIT_REF_NAME == "main" ]; then export TAG='latest'; fi - >- echo "Building Docker image with tag: $TAG" /kaniko/executor --context "${CI_PROJECT_DIR}" --dockerfile "${CI_PROJECT_DIR}/Dockerfile" --destination "${CI_REGISTRY_IMAGE}:${TAG}_${ARCH}" rules: - if: $CI_COMMIT_REF_NAME == "main" - if: $CI_COMMIT_TAG merge-manifests: variables: TAG: $CI_COMMIT_TAG stage: container-build needs: - job: build-container artifacts: false image: name: mplatform/manifest-tool:alpine entrypoint: [""] script: - if [ $CI_COMMIT_REF_NAME == "main" ]; then export TAG='latest'; fi - >- manifest-tool --username="${CI_REGISTRY_USER}" --password="${CI_REGISTRY_PASSWORD}" push from-args --platforms linux/amd64,linux/arm64,linux/s390x --template "${CI_REGISTRY_IMAGE}:${TAG}_ARCH" --target "${CI_REGISTRY_IMAGE}:${TAG}" rules: - if: $CI_COMMIT_REF_NAME == "main" when: always - if: $CI_COMMIT_TAG when: always # If this is a tag, then we want to additionally tag the image as `stable` tag-container-release: stage: container-build image: quay.io/podman/stable allow_failure: false variables: IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG RELEASE_TAG: $CI_REGISTRY_IMAGE:stable script: - echo "Tagging docker image with stable tag" - echo -n "$CI_JOB_TOKEN" | podman login -u gitlab-ci-token --password-stdin $CI_REGISTRY - podman pull $IMAGE_TAG || true - podman tag $IMAGE_TAG $RELEASE_TAG - podman push $RELEASE_TAG rules: - if: $CI_COMMIT_TAG when: always clean-image-tags: stage: container-build needs: - job: merge-manifests artifacts: false image: containers.torproject.org/tpo/tpa/base-images:bookworm before_script: - *apt-template - apt-get install -y jq curl script: - "REGISTRY_ID=$(curl --silent --request GET --header \"JOB-TOKEN: ${CI_JOB_TOKEN}\" \"https://gitlab.torproject.org/api/v4/projects/${CI_PROJECT_ID}/registry/repositories\" | jq '.[].id')" - "curl --request DELETE --data \"name_regex_delete=(latest|${CI_COMMIT_TAG})_.*\" --header \"JOB-TOKEN: ${CI_JOB_TOKEN}\" \"https://gitlab.torproject.org/api/v4/projects/${CI_PROJECT_ID}/registry/repositories/${REGISTRY_ID}/tags\"" rules: - if: $CI_COMMIT_REF_NAME == "main" when: always - if: $CI_COMMIT_TAG when: always 07070100000002000081A400000000000000000000000165F88C5000000000000000000000000000000000000000000000001C00000000snowflake-2.9.2/.gitmodules07070100000003000081A400000000000000000000000165F88C50000000D0000000000000000000000000000000000000001C00000000snowflake-2.9.2/.travis.ymllanguage: go dist: xenial go_import_path: git.torproject.org/pluggable-transports/snowflake.git/v2 go: - 1.13.x script: - test -z "$(go fmt ./...)" - go vet ./... - go test -v -race ./... 07070100000004000081A400000000000000000000000165F88C50000000A5000000000000000000000000000000000000002000000000snowflake-2.9.2/CONTRIBUTING.md - 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. 07070100000005000081A400000000000000000000000165F88C5000002530000000000000000000000000000000000000001A00000000snowflake-2.9.2/ChangeLogChanges in version v2.9.2 - 2024-03-18 - Issue 40288: Add integration testing with Shadow - Issue 40345: Automatically build and push containers to our registry - Issue 40339: Fix client ID reuse bug in SQS rendezvous - Issue 40341: Modify SQS rendezvous arguments to use b64 encoded parameters - Issue 40330: Add new metrics at the broker for per-country rendezvous stats - Issue 40345: Update docker container tags - Bump versions of dependencies Changes in version v2.9.1 - 2024-02-27 - Issue 40335: Fix release job - Change deprecated io/ioutil package to io package - Bump versions of dependencies Changes in version v2.9.0 - 2024-02-05 - Issue 40285: Add vcs revision to version string - Issue 40294: Update recommended torrc options in client README - Issue 40306: Scrub space-separated IP addresses - Add proxy commandline option for probe server URL - Use SetNet setting in probest to ignore net.Interfaces error - Add probetest commandline option for STUN URL - Issue 26151: Implement SQS rendezvous in client and broker - Add broker metrics to track rendezvous method - Cosmetic code quality fixes - Bump versions of dependencies Changes in version v2.8.1 - 2023-12-21 - Issue 40276: Reduce allocations in encapsulation.ReadData - Issue 40310: Remove excessive logging for closed proxy connections - Issue 40278: Add network fix for old version of android to proxy - Bump versions of dependencies Changes in version v2.8.0 - 2023-11-20 - Issue 40069: Add outbound proxy support - Issue 40301: Fix for a bug in domain fronting configurations - Issue 40302: Remove throughput summary from proxy logger - Issue 40302: Change proxy stats logging to only log stats for traffic that occurred in the summary interval - Update renovate bot configuration to use Go 1.21 - Bump versions of dependencies Changes in version v2.7.0 - 2023-10-16 7142fa3 fix(proxy): Correctly close connection pipe when dealing with error 6393af6 Remove proxy churn measurements from broker. a615e8b fix(proxy): remove _potential_ deadlock d434549 Maintain backward compatability with old clients 9fdfb3d Randomly select front domain from comma-separated list 5cdf52c Update dependencies 1559963 chore(deps): update module github.com/xtaci/kcp-go/v5 to v5.6.3 60e66be Remove Golang 1.20 from CI Testing 1d069ca Update CI targets to test android from golang 1.21 3a050c6 Use ShouldBeNil to check for nil values e45e8e5 chore(deps): update module github.com/smartystreets/goconvey to v1.8.1 f47ca18 chore(deps): update module gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib to v1.5.0 106da49 chore(deps): update module github.com/pion/webrtc/v3 to v3.2.20 2844ac6 Update CI targets to include only Go 1.20 and 1.21 f4e1ab9 chore(deps): update module golang.org/x/net to v0.15.0 caaff70 Update module golang.org/x/sys to v0.12.0 Changes in version v2.6.1 - 2023-09-11 - a3bfc28 Update module golang.org/x/crypto to v0.12.0 - e37e15a Update golang Docker tag to v1.21 - b632c7d Workaround for shadow in lieu of AF_NETLINK support - 0cb2975 Update module golang.org/x/net to v0.13.0 [SECURITY] - f73fe6e Keep the 'v' from the tag on the released .tar.gz - 8104732 Change DefaultRelayURL back to wss://snowflake.torproject.net/. - d932cb2 feat: add option to expose the stats by using metrics - af73ab7 Add renovate config - aaeab3f Update dependencies - 58c3121 Close temporary UDPSession in TestQueuePacketConnWriteToKCP. - 80980a3 Fix a comment left over from turbotunnel-quic. - 08d1c6d Bump minimum required version of go Changes in version v2.6.0 - 2023-06-19 - Issue 40243: Implement datachannel flow control at proxy - Issue 40087: Append Let's Encrypt ISRG Root X1 to cert pool - Issue 40198: Use IP_BIND_ADDRESS_NO_PORT when dialing the ORPort on linux - Move from gitweb to gitlab - Add warning log at broker when proxy does not connect with client - Fix unit tests after SDP validation - Soften non-critical log from error to warning - Issue 40231: Validate SDP offers and answers - Add scanner error check to ClusterCounter.Count - Fix server benchmark tests - Issue 40260: Use a sync.Pool to reuse QueuePacketConn buffers - Issue 40043: Restore ListenAndServe error in server - Update pion webrtc library versions - Issue 40108: Add outbound address config option to proxy - Issue 40260: Fix a data race in the Snowflake server - Issue 40216: Add utls-imitate, utls-nosni documentation to the README - Fix up/down traffic stats in standalone proxy - Issue 40226: Filter out ICE servers that are not STUN - Issue 40226: Update README to reflect the type of ICE servers we support - Issue 40226: Parse ICE servers using the pion/ice library function - Bring client torrc up to date with Tor Browser Changes in version v2.5.1 - 2023-01-18 - Issue 40249: Fix issue with Skip Hello Verify patch Changes in version v2.5.0 - 2023-01-18 - Issue 40249: Apply Skip Hello Verify Migration Changes in version v2.4.3 - 2023-01-16 - Fix version number in version.go Changes in version v2.4.2 - 2023-01-13 - Issue 40208: Enhance help info for capacity flag - Issue 40232: Update README and fix help output - Issue 40173: Increase clientIDAddrMapCapacity - Issue 40177: Manually unlock mutex in ClientMap.SendQueue - Issue 40177: Have SnowflakeClientConn implement io.WriterTo - Issue 40179: Reduce turbotunnel queueSize from 2048 to 512 - Issue 40187/40199: Take ownership of buffer in QueuePacketConn QueueIncoming/WriteTo - Add more tests for URL encoded IPs (safelog) - Fix server flag name - Issue 40200: Use multiple parallel KCP state machines in the server - Add a num-turbotunnel server transport option - Issue: 40241: Switch default proxy STUN server to stun.l.google.com Changes in version v2.4.1 - 2022-12-01 - Issue 40224: Bug fix in utls roundtripper Changes in version v2.4.0 - 2022-11-29 - Fix proxy command line help output - Issue 40123: Reduce multicast DNS candidates - Add ICE ephemeral ports range setting - Reformat using Go 1.19 - Update CI tests to include latest and minimum Go versions - Issue 40184: Use fixed unit for bandwidth logging - Update gorilla/websocket to v1.5.0 - Issue 40175: Server performance improvements - Issue 40183: Change snowflake proxy log verbosity - Issue 40117: Display proxy NAT type in logs - Issue 40198: Add a `orport-srcaddr` server transport option - Add gofmt output to CI test - Issue 40185: Change bandwidth type from int to int64 to prevent overflow - Add version output support to snowflake - Issue 40229: Change regexes for ipv6 addresses to catch url-encoded addresses - Issue 40220: Close stale connections in standalone proxy Changes in version v2.3.0 - 2022-06-23 - Issue 40146: Avoid performing two NAT probe tests at startup - Issue 40134: Log messages from client NAT check failures are confusing - Issue 34075: Implement metrics to measure snowflake churn - Issue 28651: Prepare all pieces of the snowflake pipeline for a second snowflake bridge - Issue 40129: Distributed Snowflake Server Support Changes in version v2.2.0 - 2022-05-25 - Issue 40099: Initialize SnowflakeListener.closed - Add connection failure events for proxy timeouts - Issue 40103: Fix proxy logging verb tense - Fix up and downstream metrics output for proxy - Issue 40054: uTLS for broker negotiation - Forward bridge fingerprint from client to broker (WIP, Issue 28651) - Issue 40104: Make it easier to configure proxy type - Remove version from ClientPollRequest - Issue 40124: Move tor-specific code out of library - Issue 40115: Scrub pt event logs - Issue 40127: Bump webrtc and dtls library versions - Bump version of webrtc and dtls to fix dtls CVEs - Issue 40141: Ensure library calls of events can be scrubbed Changes in version v2.1.0 - 2022-02-08 - Issue 40098: Remove support for legacy one shot mode - Issue 40079: Make connection summary at proxy privacy preserving - Issue 40076: Add snowflake event API for notifications of connection events - Issue 40084: Increase capacity of client address map at the server - Issue 40060: Further clean up snowflake server logs - Issue 40089: Validate proxy and client supplied strings at broker - Issue 40014: Update version of DTLS library to include fingerprinting fixes - Issue 40075: Support recurring NAT type check in standalone proxy Changes in version v2.0.0 - 2021-11-04 - Turn the standalone snowflake proxy code into a library - Clean up and reworked the snowflake client and server library code - Unify broker/bridge domains to *.torproject.net - Updates to the snowflake library documentation - New package functions to define and set a rendezvous method with the broker - Factor out the broker geoip code into its own external library - Bug fix to check error calls in preparePeerConnection - Bug fixes in snowflake tests - Issue 40059: add the ability to pass in snowflake arguments through SOCKS - Increase buffer sizes for sending and receiving snowflake data - Issue 25985: rendezvous with the broker using AMP cache - Issue 40055: wait for the full poll interval between proxy polls Changes 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. 07070100000006000081A400000000000000000000000165F88C5000000393000000000000000000000000000000000000001B00000000snowflake-2.9.2/DockerfileFROM docker.io/library/golang:1.22 AS build # Set some labels # io.containers.autoupdate label will instruct podman to reach out to the corres # corresponding registry to check if the image has been updated. If an image # must be updated, Podman pulls it down and restarts the systemd unit executing # the container. See podman-auto-update(1) for more details, or # https://docs.podman.io/en/latest/markdown/podman-auto-update.1.html LABEL io.containers.autoupdate=registry LABEL org.opencontainers.image.authors="anti-censorship-team@lists.torproject.org" ADD . /app WORKDIR /app/proxy RUN go get RUN CGO_ENABLED=0 go build -o proxy -ldflags '-extldflags "-static" -w -s' . FROM scratch COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo COPY --from=build /app/proxy/proxy /bin/proxy ENTRYPOINT [ "/bin/proxy" ] 07070100000007000081A400000000000000000000000165F88C500000071E000000000000000000000000000000000000001800000000snowflake-2.9.2/LICENSE 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. ================================================================================ 07070100000008000081A400000000000000000000000165F88C50000013D3000000000000000000000000000000000000001A00000000snowflake-2.9.2/README.md# 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. <!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> **Table of Contents** - [Structure of this Repository](#structure-of-this-repository) - [Usage](#usage) - [Using Snowflake with Tor](#using-snowflake-with-tor) - [Running a Snowflake Proxy](#running-a-snowflake-proxy) - [Using the Snowflake Library with Other Applications](#using-the-snowflake-library-with-other-applications) - [Test Environment](#test-environment) - [FAQ](#faq) - [More info and links](#more-info-and-links) <!-- END doctoc generated TOC please keep comment here to allow auto update --> ### Structure of this Repository - `broker/` contains code for the Snowflake broker - `doc/` contains Snowflake documentation and manpages - `client/` contains the Tor pluggable transport client and client library code - `common/` contains generic libraries used by multiple pieces of Snowflake - `proxy/` contains code for the Go standalone Snowflake proxy - `probetest/` contains code for a NAT probetesting service - `server/` contains the Tor pluggable transport server and server library code ### Usage Snowflake is currently deployed as a pluggable transport for Tor. #### Using Snowflake with Tor To use the Snowflake client with Tor, you will need to add the appropriate `Bridge` and `ClientTransportPlugin` lines to your [torrc](https://2019.www.torproject.org/docs/tor-manual.html.en) file. See the [client README](client) for more information on building and running the Snowflake client. #### Running a Snowflake Proxy You can contribute to Snowflake by running a Snowflake proxy. We have the option to run a proxy in your browser or as a standalone Go program. See our [community documentation](https://community.torproject.org/relay/setup/snowflake/) for more details. #### Using the Snowflake Library with Other Applications Snowflake can be used as a Go API, and adheres to the [v2.1 pluggable transports specification](). For more information on using the Snowflake Go library, see the [Snowflake library documentation](doc/using-the-snowflake-library.md). ### 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... ### More info and links We have more documentation in the [Snowflake wiki](https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/wikis/home) and at https://snowflake.torproject.org/. ##### -- Android AAR Reproducible Build Setup -- Using `gomobile` it is possible to build snowflake as shared libraries for all the architectures supported by Android. This is in the _.gitlab-ci.yml_, which runs in GitLab CI. It is also possible to run this setup in a Virtual Machine using [vagrant](https://www.vagrantup.com/). Just run `vagrant up` and it will create and provision the VM. `vagrant ssh` to get into the VM to use it as a development environment. ##### uTLS Settings Snowflake communicate with broker that serves as signaling server with TLS based domain fronting connection, which may be identified by its usage of Go language TLS stack. uTLS is a software library designed to initiate the TLS Client Hello fingerprint of browsers or other popular software's TLS stack to evade censorship based on TLS client hello fingerprint with `-utls-imitate` . You can use `-version` to see a list of supported values. Depending on client and server configuration, it may not always work as expected as not all extensions are correctly implemented. You can also remove SNI (Server Name Indication) from client hello to evade censorship with `-utls-nosni`, not all servers supports this. 07070100000009000081A400000000000000000000000165F88C5000000963000000000000000000000000000000000000001C00000000snowflake-2.9.2/Vagrantfilerequire 'pathname' require 'tempfile' require 'yaml' srvpath = Pathname.new(File.dirname(__FILE__)).realpath configfile = YAML.load_file(File.join(srvpath, "/.gitlab-ci.yml")) remote_url = 'https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake' # set up essential environment variables env = configfile['variables'] env = env.merge(configfile['android']['variables']) env['CI_PROJECT_DIR'] = '/builds/tpo/anti-censorship/pluggable-transports/snowflake' env_file = Tempfile.new('env') File.chmod(0644, env_file.path) env.each do |k,v| env_file.write("export #{k}='#{v}'\n") end env_file.rewind sourcepath = '/etc/profile.d/env.sh' header = "#!/bin/bash -ex\nsource #{sourcepath}\ncd $CI_PROJECT_DIR\n" before_script_file = Tempfile.new('before_script') File.chmod(0755, before_script_file.path) before_script_file.write(header) configfile['android']['before_script'].flatten.each do |line| before_script_file.write(line) before_script_file.write("\n") end before_script_file.rewind script_file = Tempfile.new('script') File.chmod(0755, script_file.path) script_file.write(header) configfile['android']['script'].flatten.each do |line| script_file.write(line) script_file.write("\n") end script_file.rewind Vagrant.configure("2") do |config| config.vm.box = "debian/bullseye64" config.vm.synced_folder '.', '/vagrant', disabled: true config.vm.provision "file", source: env_file.path, destination: 'env.sh' config.vm.provision :shell, inline: <<-SHELL set -ex mv ~vagrant/env.sh #{sourcepath} source #{sourcepath} test -d /go || mkdir /go mkdir -p $(dirname $CI_PROJECT_DIR) chown -R vagrant.vagrant $(dirname $CI_PROJECT_DIR) apt-get update apt-get -qy install --no-install-recommends git git clone #{remote_url} $CI_PROJECT_DIR chmod -R a+rX,u+w /go $CI_PROJECT_DIR chown -R vagrant.vagrant /go $CI_PROJECT_DIR SHELL config.vm.provision "file", source: before_script_file.path, destination: 'before_script.sh' config.vm.provision "file", source: script_file.path, destination: 'script.sh' config.vm.provision :shell, inline: '/home/vagrant/before_script.sh' config.vm.provision :shell, privileged: false, inline: '/home/vagrant/script.sh' # remove this or comment it out to use VirtualBox instead of libvirt config.vm.provider :libvirt do |libvirt| libvirt.memory = 1536 end end 0707010000000A000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001700000000snowflake-2.9.2/broker0707010000000B000081A400000000000000000000000165F88C5000000882000000000000000000000000000000000000002100000000snowflake-2.9.2/broker/README.md<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> **Table of Contents** - [Overview](#overview) - [Running your own](#running-your-own) <!-- END doctoc generated TOC please keep comment here to allow auto update --> This 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. 0707010000000C000081A400000000000000000000000165F88C5000000ACB000000000000000000000000000000000000001E00000000snowflake-2.9.2/broker/amp.gopackage main import ( "log" "net/http" "strings" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/amp" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/util" ) // ampClientOffers is the AMP-speaking endpoint for client poll messages, // intended for access via an AMP cache. In contrast to the other clientOffers, // the client's encoded poll message is stored in the URL path rather than the // HTTP request body (because an AMP cache does not support POST), and the // encoded client poll response is sent back as AMP-armored HTML. func ampClientOffers(i *IPC, w http.ResponseWriter, r *http.Request) { // The encoded client poll message immediately follows the /amp/client/ // path prefix, so this function unfortunately needs to be aware of and // remote its own routing prefix. path := strings.TrimPrefix(r.URL.Path, "/amp/client/") if path == r.URL.Path { // The path didn't start with the expected prefix. This probably // indicates an internal bug. log.Println("ampClientOffers: unexpected prefix in path") w.WriteHeader(http.StatusInternalServerError) return } var encPollReq []byte var response []byte var err error encPollReq, err = amp.DecodePath(path) if err == nil { arg := messages.Arg{ Body: encPollReq, RemoteAddr: util.GetClientIp(r), RendezvousMethod: messages.RendezvousAmpCache, } err = i.ClientOffers(arg, &response) } else { response, err = (&messages.ClientPollResponse{ Error: "cannot decode URL path", }).EncodePollResponse() } if err != nil { // We couldn't even construct a JSON object containing an error // message :( Nothing to do but signal an error at the HTTP // layer. The AMP cache will translate this 500 status into a // 404 status. // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#redirect-%26-error-handling log.Printf("ampClientOffers: %v", err) w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html") // Attempt to hint to an AMP cache not to waste resources caching this // document. "The Google AMP Cache considers any document fresh for at // least 15 seconds." // https://developers.google.com/amp/cache/overview#google-amp-cache-updates w.Header().Set("Cache-Control", "max-age=15") w.WriteHeader(http.StatusOK) enc, err := amp.NewArmorEncoder(w) if err != nil { log.Printf("amp.NewArmorEncoder: %v", err) return } defer enc.Close() if _, err := enc.Write(response); err != nil { log.Printf("ampClientOffers: unable to write answer: %v", err) } } 0707010000000D000081A400000000000000000000000165F88C5000000B8B000000000000000000000000000000000000002600000000snowflake-2.9.2/broker/bridge-list.go/* (*BridgeListHolderFileBased).LoadBridgeInfo loads a Snowflake Server bridge info description file, its format is as follows: This file should be in newline-delimited JSON format(https://jsonlines.org/). For each line, the format of json data should be in the format of: {"displayName":"default", "webSocketAddress":"wss://snowflake.torproject.net/", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80A72"} displayName:string is the name of this bridge. This value is not currently used programmatically. webSocketAddress:string is the WebSocket URL of this bridge. This will be the address proxy used to connect to this snowflake server. fingerprint:string is the identifier of the bridge. This will be used by a client to identify the bridge it wishes to connect to. The existence of ANY other fields is NOT permitted. The file will be considered invalid if there is at least one invalid json record. In this case, an error will be returned, and none of the records will be loaded. */ package main import ( "bufio" "bytes" "encoding/json" "errors" "io" "sync" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint" ) var ErrBridgeNotFound = errors.New("bridge not found") func NewBridgeListHolder() BridgeListHolderFileBased { return &bridgeListHolder{} } type bridgeListHolder struct { bridgeInfo map[bridgefingerprint.Fingerprint]BridgeInfo accessBridgeInfo sync.RWMutex } type BridgeListHolder interface { GetBridgeInfo(bridgefingerprint.Fingerprint) (BridgeInfo, error) } type BridgeListHolderFileBased interface { BridgeListHolder LoadBridgeInfo(reader io.Reader) error } type BridgeInfo struct { DisplayName string `json:"displayName"` WebSocketAddress string `json:"webSocketAddress"` Fingerprint string `json:"fingerprint"` } func (h *bridgeListHolder) GetBridgeInfo(fingerprint bridgefingerprint.Fingerprint) (BridgeInfo, error) { h.accessBridgeInfo.RLock() defer h.accessBridgeInfo.RUnlock() if bridgeInfo, ok := h.bridgeInfo[fingerprint]; ok { return bridgeInfo, nil } return BridgeInfo{}, ErrBridgeNotFound } func (h *bridgeListHolder) LoadBridgeInfo(reader io.Reader) error { bridgeInfoMap := map[bridgefingerprint.Fingerprint]BridgeInfo{} inputScanner := bufio.NewScanner(reader) for inputScanner.Scan() { inputLine := inputScanner.Bytes() bridgeInfo := BridgeInfo{} decoder := json.NewDecoder(bytes.NewReader(inputLine)) decoder.DisallowUnknownFields() if err := decoder.Decode(&bridgeInfo); err != nil { return err } var bridgeFingerprint bridgefingerprint.Fingerprint var err error if bridgeFingerprint, err = bridgefingerprint.FingerprintFromHexString(bridgeInfo.Fingerprint); err != nil { return err } bridgeInfoMap[bridgeFingerprint] = bridgeInfo } h.accessBridgeInfo.Lock() defer h.accessBridgeInfo.Unlock() h.bridgeInfo = bridgeInfoMap return nil } 0707010000000E000081A400000000000000000000000165F88C5000000DCE000000000000000000000000000000000000002B00000000snowflake-2.9.2/broker/bridge-list_test.gopackage main import ( "bytes" "encoding/hex" . "github.com/smartystreets/goconvey/convey" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint" "testing" ) const DefaultBridges = `{"displayName":"default", "webSocketAddress":"wss://snowflake.torproject.org", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80A72"} ` const ImaginaryBridges = `{"displayName":"default", "webSocketAddress":"wss://snowflake.torproject.org", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80A72"} {"displayName":"imaginary-1", "webSocketAddress":"wss://imaginary-1-snowflake.torproject.org", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80B00"} {"displayName":"imaginary-2", "webSocketAddress":"wss://imaginary-2-snowflake.torproject.org", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80B01"} {"displayName":"imaginary-3", "webSocketAddress":"wss://imaginary-3-snowflake.torproject.org", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80B02"} {"displayName":"imaginary-4", "webSocketAddress":"wss://imaginary-4-snowflake.torproject.org", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80B03"} {"displayName":"imaginary-5", "webSocketAddress":"wss://imaginary-5-snowflake.torproject.org", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80B04"} {"displayName":"imaginary-6", "webSocketAddress":"wss://imaginary-6-snowflake.torproject.org", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80B05"} {"displayName":"imaginary-7", "webSocketAddress":"wss://imaginary-7-snowflake.torproject.org", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80B06"} {"displayName":"imaginary-8", "webSocketAddress":"wss://imaginary-8-snowflake.torproject.org", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80B07"} {"displayName":"imaginary-9", "webSocketAddress":"wss://imaginary-9-snowflake.torproject.org", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80B08"} {"displayName":"imaginary-10", "webSocketAddress":"wss://imaginary-10-snowflake.torproject.org", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80B09"} ` func TestBridgeLoad(t *testing.T) { Convey("load default list", t, func() { bridgeList := NewBridgeListHolder() So(bridgeList.LoadBridgeInfo(bytes.NewReader([]byte(DefaultBridges))), ShouldBeNil) { bridgeFingerprint := [20]byte{} { n, err := hex.Decode(bridgeFingerprint[:], []byte("2B280B23E1107BB62ABFC40DDCC8824814F80A72")) So(n, ShouldEqual, 20) So(err, ShouldBeNil) } Fingerprint, err := bridgefingerprint.FingerprintFromBytes(bridgeFingerprint[:]) So(err, ShouldBeNil) bridgeInfo, err := bridgeList.GetBridgeInfo(Fingerprint) So(err, ShouldBeNil) So(bridgeInfo.DisplayName, ShouldEqual, "default") So(bridgeInfo.WebSocketAddress, ShouldEqual, "wss://snowflake.torproject.org") } }) Convey("load imaginary list", t, func() { bridgeList := NewBridgeListHolder() So(bridgeList.LoadBridgeInfo(bytes.NewReader([]byte(ImaginaryBridges))), ShouldBeNil) { bridgeFingerprint := [20]byte{} { n, err := hex.Decode(bridgeFingerprint[:], []byte("2B280B23E1107BB62ABFC40DDCC8824814F80B07")) So(n, ShouldEqual, 20) So(err, ShouldBeNil) } Fingerprint, err := bridgefingerprint.FingerprintFromBytes(bridgeFingerprint[:]) So(err, ShouldBeNil) bridgeInfo, err := bridgeList.GetBridgeInfo(Fingerprint) So(err, ShouldBeNil) So(bridgeInfo.DisplayName, ShouldEqual, "imaginary-8") So(bridgeInfo.WebSocketAddress, ShouldEqual, "wss://imaginary-8-snowflake.torproject.org") } }) } 0707010000000F000081A400000000000000000000000165F88C500000326F000000000000000000000000000000000000002100000000snowflake-2.9.2/broker/broker.go/* 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 ( "bytes" "container/heap" "context" "crypto/tls" "flag" "io" "log" "net/http" "os" "os/signal" "strings" "sync" "syscall" "time" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/namematcher" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/safelog" "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 bridgeList BridgeListHolderFileBased allowedRelayPattern string presumedPatternForLegacyClient string } func (ctx *BrokerContext) GetBridgeInfo(fingerprint bridgefingerprint.Fingerprint) (BridgeInfo, error) { return ctx.bridgeList.GetBridgeInfo(fingerprint) } 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") } bridgeListHolder := NewBridgeListHolder() const DefaultBridges = `{"displayName":"default", "webSocketAddress":"wss://snowflake.torproject.net/", "fingerprint":"2B280B23E1107BB62ABFC40DDCC8824814F80A72"} ` bridgeListHolder.LoadBridgeInfo(bytes.NewReader([]byte(DefaultBridges))) return &BrokerContext{ snowflakes: snowflakes, restrictedSnowflakes: rSnowflakes, idToSnowflake: make(map[string]*Snowflake), proxyPolls: make(chan *ProxyPoll), metrics: metrics, bridgeList: bridgeListHolder, } } // 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.idToSnowflake[id] = snowflake ctx.snowflakeLock.Unlock() return snowflake } func (ctx *BrokerContext) InstallBridgeListProfile(reader io.Reader, relayPattern, presumedPatternForLegacyClient string) error { if err := ctx.bridgeList.LoadBridgeInfo(reader); err != nil { return err } ctx.allowedRelayPattern = relayPattern ctx.presumedPatternForLegacyClient = presumedPatternForLegacyClient return nil } func (ctx *BrokerContext) CheckProxyRelayPattern(pattern string, nonSupported bool) bool { if nonSupported { pattern = ctx.presumedPatternForLegacyClient } proxyPattern := namematcher.NewNameMatcher(pattern) brokerPattern := namematcher.NewNameMatcher(ctx.allowedRelayPattern) return proxyPattern.IsSupersetOf(brokerPattern) } // Client offer contains an SDP, bridge fingerprint and the NAT type of the client type ClientOffer struct { natType string sdp []byte fingerprint []byte } func main() { var acmeEmail string var acmeHostnamesCommas string var acmeCertCacheDir string var addr string var geoipDatabase string var geoip6Database string var bridgeListFilePath, allowedRelayPattern, presumedPatternForLegacyClient string var brokerSQSQueueName, brokerSQSQueueRegion 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.StringVar(&bridgeListFilePath, "bridge-list-path", "", "file path for bridgeListFile") flag.StringVar(&allowedRelayPattern, "allowed-relay-pattern", "", "allowed pattern for relay host name") flag.StringVar(&presumedPatternForLegacyClient, "default-relay-pattern", "", "presumed pattern for legacy client") flag.StringVar(&brokerSQSQueueName, "broker-sqs-name", "", "name of broker SQS queue to listen for incoming messages on") flag.StringVar(&brokerSQSQueueRegion, "broker-sqs-region", "", "name of AWS region of broker SQS queue") 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 bridgeListFilePath != "" { bridgeListFile, err := os.Open(bridgeListFilePath) if err != nil { log.Fatal(err.Error()) } err = ctx.InstallBridgeListProfile(bridgeListFile, allowedRelayPattern, presumedPatternForLegacyClient) if err != nil { log.Fatal(err.Error()) } } 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{})) http.Handle("/amp/client/", SnowflakeHandler{i, ampClientOffers}) server := http.Server{ Addr: addr, } // Run SQS Handler to continuously poll and process messages from SQS if brokerSQSQueueName != "" && brokerSQSQueueRegion != "" { log.Printf("Loading SQSHandler using SQS Queue %s in region %s\n", brokerSQSQueueName, brokerSQSQueueRegion) sqsHandlerContext := context.Background() cfg, err := config.LoadDefaultConfig(sqsHandlerContext, config.WithRegion(brokerSQSQueueRegion)) if err != nil { log.Fatal(err) } client := sqs.NewFromConfig(cfg) sqsHandler, err := newSQSHandler(sqsHandlerContext, client, brokerSQSQueueName, brokerSQSQueueRegion, i) if err != nil { log.Fatal(err) } go sqsHandler.PollAndHandleMessages(sqsHandlerContext) } 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) } } 07070100000010000081A400000000000000000000000165F88C50000019FB000000000000000000000000000000000000001F00000000snowflake-2.9.2/broker/http.gopackage main import ( "bytes" "errors" "fmt" "io" "log" "net/http" "os" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/util" ) 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 := io.ReadAll(http.MaxBytesReader(w, r.Body, readLimit)) if err != nil { log.Println("Invalid data.", err.Error()) w.WriteHeader(http.StatusBadRequest) return } arg := messages.Arg{ Body: body, RemoteAddr: util.GetClientIp(r), } 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 := io.ReadAll(http.MaxBytesReader(w, r.Body, readLimit)) if err != nil { log.Printf("Error reading client request: %s", err.Error()) w.WriteHeader(http.StatusBadRequest) return } err = validateSDP(body) if err != nil { log.Println("Error client SDP: ", err.Error()) w.WriteHeader(http.StatusBadRequest) return } // Handle the legacy version // // We support two client message formats. The legacy format is for backwards // compatability 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.EncodeClientPollRequest() if err != nil { log.Printf("Error shimming the legacy request: %s", err.Error()) w.WriteHeader(http.StatusInternalServerError) return } } arg := messages.Arg{ Body: body, RemoteAddr: util.GetClientIp(r), RendezvousMethod: messages.RendezvousHttp, } 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 proxies 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 := io.ReadAll(http.MaxBytesReader(w, r.Body, readLimit)) if err != nil { log.Println("Invalid data.", err.Error()) w.WriteHeader(http.StatusBadRequest) return } err = validateSDP(body) if err != nil { log.Println("Error proxy SDP: ", err.Error()) w.WriteHeader(http.StatusBadRequest) return } arg := messages.Arg{ Body: body, RemoteAddr: util.GetClientIp(r), } 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) } } func validateSDP(SDP []byte) error { // TODO: more validation likely needed if !bytes.Contains(SDP, []byte("a=candidate")) { return fmt.Errorf("SDP contains no candidate") } return nil } 07070100000011000081A400000000000000000000000165F88C5000001F16000000000000000000000000000000000000001E00000000snowflake-2.9.2/broker/ipc.gopackage main import ( "container/heap" "encoding/hex" "fmt" "log" "time" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint" "github.com/prometheus/client_golang/prometheus" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" ) const ( ClientTimeout = 10 ProxyTimeout = 10 NATUnknown = "unknown" NATRestricted = "restricted" NATUnrestricted = "unrestricted" ) type IPC struct { ctx *BrokerContext } func (i *IPC) Debug(_ interface{}, response *string) error { var unknowns int var natRestricted, natUnrestricted, natUnknown int proxyTypes := make(map[string]int) i.ctx.snowflakeLock.Lock() s := fmt.Sprintf("current snowflakes available: %d\n", len(i.ctx.idToSnowflake)) for _, snowflake := range i.ctx.idToSnowflake { if messages.KnownProxyTypes[snowflake.proxyType] { proxyTypes[snowflake.proxyType]++ } else { unknowns++ } switch snowflake.natType { case NATRestricted: natRestricted++ case NATUnrestricted: natUnrestricted++ default: natUnknown++ } } i.ctx.snowflakeLock.Unlock() for pType, num := range proxyTypes { s += fmt.Sprintf("\t%s proxies: %d\n", pType, num) } s += fmt.Sprintf("\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, relayPattern, relayPatternSupported, err := messages.DecodeProxyPollRequestWithRelayPrefix(arg.Body) if err != nil { return messages.ErrBadRequest } if !relayPatternSupported { i.ctx.metrics.lock.Lock() i.ctx.metrics.proxyPollWithoutRelayURLExtension++ i.ctx.metrics.promMetrics.ProxyPollWithoutRelayURLExtensionTotal.With(prometheus.Labels{"nat": natType, "type": proxyType}).Inc() i.ctx.metrics.lock.Unlock() } else { i.ctx.metrics.lock.Lock() i.ctx.metrics.proxyPollWithRelayURLExtension++ i.ctx.metrics.promMetrics.ProxyPollWithRelayURLExtensionTotal.With(prometheus.Labels{"nat": natType, "type": proxyType}).Inc() i.ctx.metrics.lock.Unlock() } if !i.ctx.CheckProxyRelayPattern(relayPattern, !relayPatternSupported) { i.ctx.metrics.lock.Lock() i.ctx.metrics.proxyPollRejectedWithRelayURLExtension++ i.ctx.metrics.promMetrics.ProxyPollRejectedForRelayURLExtensionTotal.With(prometheus.Labels{"nat": natType, "type": proxyType}).Inc() i.ctx.metrics.lock.Unlock() log.Printf("bad request: rejected relay pattern from proxy = %v", messages.ErrBadRequest) b, err := messages.EncodePollResponseWithRelayURL("", false, "", "", "incorrect relay pattern") *response = b if err != nil { return messages.ErrInternal } return nil } // Log geoip stats remoteIP := arg.RemoteAddr if err != nil { log.Println("Warning: cannot process 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() var relayURL string bridgeFingerprint, err := bridgefingerprint.FingerprintFromBytes(offer.fingerprint) if err != nil { return messages.ErrBadRequest } if info, err := i.ctx.bridgeList.GetBridgeInfo(bridgeFingerprint); err != nil { return err } else { relayURL = info.WebSocketAddress } b, err = messages.EncodePollResponseWithRelayURL(string(offer.sdp), true, offer.natType, relayURL, "") 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 { startTime := time.Now() req, err := messages.DecodeClientPollRequest(arg.Body) if err != nil { return sendClientResponse(&messages.ClientPollResponse{Error: err.Error()}, response) } offer := &ClientOffer{ natType: req.NAT, sdp: []byte(req.Offer), } fingerprint, err := hex.DecodeString(req.Fingerprint) if err != nil { return sendClientResponse(&messages.ClientPollResponse{Error: err.Error()}, response) } BridgeFingerprint, err := bridgefingerprint.FingerprintFromBytes(fingerprint) if err != nil { return sendClientResponse(&messages.ClientPollResponse{Error: err.Error()}, response) } if _, err := i.ctx.GetBridgeInfo(BridgeFingerprint); err != nil { return err } offer.fingerprint = BridgeFingerprint.ToBytes() snowflake := i.matchSnowflake(offer.natType) if snowflake != nil { snowflake.offerChannel <- offer } else { i.ctx.metrics.lock.Lock() i.ctx.metrics.UpdateRendezvousStats(arg.RemoteAddr, arg.RendezvousMethod, offer.natType, false) i.ctx.metrics.lock.Unlock() resp := &messages.ClientPollResponse{Error: messages.StrNoProxies} return sendClientResponse(resp, response) } // 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.UpdateRendezvousStats(arg.RemoteAddr, arg.RendezvousMethod, offer.natType, true) i.ctx.metrics.lock.Unlock() resp := &messages.ClientPollResponse{Answer: answer} err = sendClientResponse(resp, response) // 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.") resp := &messages.ClientPollResponse{Error: messages.StrTimedOut} err = sendClientResponse(resp, response) } 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) matchSnowflake(natType string) *Snowflake { i.ctx.snowflakeLock.Lock() defer i.ctx.snowflakeLock.Unlock() // Proiritize known restricted snowflakes for unrestricted clients if natType == NATUnrestricted && i.ctx.restrictedSnowflakes.Len() > 0 { return heap.Pop(i.ctx.restrictedSnowflakes).(*Snowflake) } if i.ctx.snowflakes.Len() > 0 { return heap.Pop(i.ctx.snowflakes).(*Snowflake) } return nil } 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 log.Printf("Warning: matching with snowflake client failed") } 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 } 07070100000012000081A400000000000000000000000165F88C5000003186000000000000000000000000000000000000002200000000snowflake-2.9.2/broker/metrics.go/* 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" "gitlab.torproject.org/tpo/anti-censorship/geoip" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" ) const ( prometheusNamespace = "snowflake" metricsResolution = 60 * 60 * 24 * time.Second //86400 seconds ) var rendezvoudMethodList = [...]messages.RendezvousMethod{ messages.RendezvousHttp, messages.RendezvousAmpCache, messages.RendezvousSqs, } type CountryStats struct { // map[proxyType][address]bool proxies map[string]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 geoipdb *geoip.Geoip countryStats CountryStats clientRoundtripEstimate time.Duration proxyIdleCount uint clientDeniedCount map[messages.RendezvousMethod]uint clientRestrictedDeniedCount map[messages.RendezvousMethod]uint clientUnrestrictedDeniedCount map[messages.RendezvousMethod]uint clientProxyMatchCount map[messages.RendezvousMethod]uint rendezvousCountryStats map[messages.RendezvousMethod]map[string]int proxyPollWithRelayURLExtension uint proxyPollWithoutRelayURLExtension uint proxyPollRejectedWithRelayURLExtension 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 addresses, ok := m.countryStats.proxies[proxyType] if !ok { if m.countryStats.unknown[addr] { return } m.countryStats.unknown[addr] = true } else { if addresses[addr] { return } addresses[addr] = true } ip := net.ParseIP(addr) if m.geoipdb == nil { return } country, ok = m.geoipdb.GetCountryByAddr(ip) if !ok { country = "??" } m.countryStats.counts[country]++ 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) UpdateRendezvousStats(addr string, rendezvousMethod messages.RendezvousMethod, natType string, matched bool) { ip := net.ParseIP(addr) country := "??" if m.geoipdb != nil { country_by_addr, ok := m.geoipdb.GetCountryByAddr(ip) if ok { country = country_by_addr } } var status string if !matched { m.clientDeniedCount[rendezvousMethod]++ if natType == NATUnrestricted { m.clientUnrestrictedDeniedCount[rendezvousMethod]++ } else { m.clientRestrictedDeniedCount[rendezvousMethod]++ } status = "denied" } else { status = "matched" m.clientProxyMatchCount[rendezvousMethod]++ } m.rendezvousCountryStats[rendezvousMethod][country]++ m.promMetrics.ClientPollTotal.With(prometheus.Labels{ "nat": natType, "status": status, "rendezvous_method": string(rendezvousMethod), "cc": country, }).Inc() } func (m *Metrics) DisplayRendezvousStatsByCountry(rendezvoudMethod messages.RendezvousMethod) string { output := "" // Use the records struct to sort our counts map by value. rs := records{} for cc, count := range m.rendezvousCountryStats[rendezvoudMethod] { rs = append(rs, record{cc: cc, count: count}) } sort.Sort(sort.Reverse(rs)) for _, r := range rs { output += fmt.Sprintf("%s=%d,", r.cc, binCount(uint(r.count))) } // cut off trailing "," if len(output) > 0 { return output[:len(output)-1] } return output } func (m *Metrics) LoadGeoipDatabases(geoipDB string, geoip6DB string) error { // Load geoip databases var err error log.Println("Loading geoip databases") m.geoipdb, err = geoip.New(geoipDB, geoip6DB) return err } func NewMetrics(metricsLogger *log.Logger) (*Metrics, error) { m := new(Metrics) m.clientDeniedCount = make(map[messages.RendezvousMethod]uint) m.clientRestrictedDeniedCount = make(map[messages.RendezvousMethod]uint) m.clientUnrestrictedDeniedCount = make(map[messages.RendezvousMethod]uint) m.clientProxyMatchCount = make(map[messages.RendezvousMethod]uint) m.rendezvousCountryStats = make(map[messages.RendezvousMethod]map[string]int) for _, rendezvousMethod := range rendezvoudMethodList { m.rendezvousCountryStats[rendezvousMethod] = make(map[string]int) } m.countryStats = CountryStats{ counts: make(map[string]int), proxies: make(map[string]map[string]bool), unknown: make(map[string]bool), natRestricted: make(map[string]bool), natUnrestricted: make(map[string]bool), natUnknown: make(map[string]bool), } for pType := range messages.KnownProxyTypes { m.countryStats.proxies[pType] = make(map[string]bool) } m.logger = metricsLogger m.promMetrics = initPrometheus() // Write to log file every day 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()) total := len(m.countryStats.unknown) for pType, addresses := range m.countryStats.proxies { m.logger.Printf("snowflake-ips-%s %d\n", pType, len(addresses)) total += len(addresses) } m.logger.Println("snowflake-ips-total", total) m.logger.Println("snowflake-idle-count", binCount(m.proxyIdleCount)) m.logger.Println("snowflake-proxy-poll-with-relay-url-count", binCount(m.proxyPollWithRelayURLExtension)) m.logger.Println("snowflake-proxy-poll-without-relay-url-count", binCount(m.proxyPollWithoutRelayURLExtension)) m.logger.Println("snowflake-proxy-rejected-for-relay-url-count", binCount(m.proxyPollRejectedWithRelayURLExtension)) m.logger.Println("client-denied-count", binCount(sumMapValues(&m.clientDeniedCount))) m.logger.Println("client-restricted-denied-count", binCount(sumMapValues(&m.clientRestrictedDeniedCount))) m.logger.Println("client-unrestricted-denied-count", binCount(sumMapValues(&m.clientUnrestrictedDeniedCount))) m.logger.Println("client-snowflake-match-count", binCount(sumMapValues(&m.clientProxyMatchCount))) for _, rendezvousMethod := range rendezvoudMethodList { m.logger.Printf("client-%s-count %d\n", rendezvousMethod, binCount( m.clientDeniedCount[rendezvousMethod]+m.clientProxyMatchCount[rendezvousMethod], )) m.logger.Printf("client-%s-ips %s\n", rendezvousMethod, m.DisplayRendezvousStatsByCountry(rendezvousMethod)) } 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 = make(map[messages.RendezvousMethod]uint) m.clientRestrictedDeniedCount = make(map[messages.RendezvousMethod]uint) m.clientUnrestrictedDeniedCount = make(map[messages.RendezvousMethod]uint) m.proxyPollRejectedWithRelayURLExtension = 0 m.proxyPollWithRelayURLExtension = 0 m.proxyPollWithoutRelayURLExtension = 0 m.clientProxyMatchCount = make(map[messages.RendezvousMethod]uint) m.rendezvousCountryStats = make(map[messages.RendezvousMethod]map[string]int) for _, rendezvousMethod := range rendezvoudMethodList { m.rendezvousCountryStats[rendezvousMethod] = make(map[string]int) } m.countryStats.counts = make(map[string]int) for pType := range m.countryStats.proxies { m.countryStats.proxies[pType] = 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) } func sumMapValues(m *map[messages.RendezvousMethod]uint) uint { var s uint = 0 for _, v := range *m { s += v } return s } type PromMetrics struct { registry *prometheus.Registry ProxyTotal *prometheus.CounterVec ProxyPollTotal *RoundedCounterVec ClientPollTotal *RoundedCounterVec AvailableProxies *prometheus.GaugeVec ProxyPollWithRelayURLExtensionTotal *RoundedCounterVec ProxyPollWithoutRelayURLExtensionTotal *RoundedCounterVec ProxyPollRejectedForRelayURLExtensionTotal *RoundedCounterVec } // 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.ProxyPollWithRelayURLExtensionTotal = NewRoundedCounterVec( prometheus.CounterOpts{ Namespace: prometheusNamespace, Name: "rounded_proxy_poll_with_relay_url_extension_total", Help: "The number of snowflake proxy polls with Relay URL Extension, rounded up to a multiple of 8", }, []string{"nat", "type"}, ) promMetrics.ProxyPollWithoutRelayURLExtensionTotal = NewRoundedCounterVec( prometheus.CounterOpts{ Namespace: prometheusNamespace, Name: "rounded_proxy_poll_without_relay_url_extension_total", Help: "The number of snowflake proxy polls without Relay URL Extension, rounded up to a multiple of 8", }, []string{"nat", "type"}, ) promMetrics.ProxyPollRejectedForRelayURLExtensionTotal = NewRoundedCounterVec( prometheus.CounterOpts{ Namespace: prometheusNamespace, Name: "rounded_proxy_poll_rejected_relay_url_extension_total", Help: "The number of snowflake proxy polls rejected by Relay URL Extension, rounded up to a multiple of 8", }, []string{"nat", "type"}, ) 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", "cc", "rendezvous_method"}, ) // We need to register our metrics so they can be exported. promMetrics.registry.MustRegister( promMetrics.ClientPollTotal, promMetrics.ProxyPollTotal, promMetrics.ProxyTotal, promMetrics.AvailableProxies, promMetrics.ProxyPollWithRelayURLExtensionTotal, promMetrics.ProxyPollWithoutRelayURLExtensionTotal, promMetrics.ProxyPollRejectedForRelayURLExtensionTotal, ) return promMetrics } 07070100000013000081A400000000000000000000000165F88C500000081A000000000000000000000000000000000000002500000000snowflake-2.9.2/broker/prometheus.go/* 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) } 07070100000014000081A400000000000000000000000165F88C5000007C35000000000000000000000000000000000000003000000000snowflake-2.9.2/broker/snowflake-broker_test.gopackage main import ( "bytes" "container/heap" "encoding/hex" "fmt" "io" "log" "net/http" "net/http/httptest" "os" "sync" "testing" "time" . "github.com/smartystreets/goconvey/convey" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/amp" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" ) func NullLogger() *log.Logger { logger := log.New(os.Stdout, "", 0) logger.SetOutput(io.Discard) return logger } var promOnce sync.Once var ( sdp = "v=0\r\n" + "o=- 123456789 987654321 IN IP4 0.0.0.0\r\n" + "s=-\r\n" + "t=0 0\r\n" + "a=fingerprint:sha-256 12:34\r\n" + "a=extmap-allow-mixed\r\n" + "a=group:BUNDLE 0\r\n" + "m=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\n" + "c=IN IP4 0.0.0.0\r\n" + "a=setup:actpass\r\n" + "a=mid:0\r\n" + "a=sendrecv\r\n" + "a=sctp-port:5000\r\n" + "a=ice-ufrag:CoVEaiFXRGVzshXG\r\n" + "a=ice-pwd:aOrOZXraTfFKzyeBxIXYYKjSgRVPGhUx\r\n" + "a=candidate:1000 1 udp 2000 8.8.8.8 3000 typ host\r\n" + "a=end-of-candidates\r\n" sid = "ymbcCMto7KHNGYlp" ) func createClientOffer(sdp, nat, fingerprint string) (*bytes.Reader, error) { clientRequest := &messages.ClientPollRequest{ Offer: sdp, NAT: nat, Fingerprint: fingerprint, } encOffer, err := clientRequest.EncodeClientPollRequest() if err != nil { return nil, err } offer := bytes.NewReader(encOffer) return offer, nil } func createProxyAnswer(sdp, sid string) (*bytes.Reader, error) { proxyRequest, err := messages.EncodeAnswerRequest(sdp, sid) if err != nil { return nil, err } answer := bytes.NewReader(proxyRequest) return answer, nil } func decodeAMPArmorToString(r io.Reader) (string, error) { dec, err := amp.NewArmorDecoder(r) if err != nil { return "", err } p, err := io.ReadAll(dec) return string(p), err } func TestBroker(t *testing.T) { defaultBridgeValue, _ := hex.DecodeString("2B280B23E1107BB62ABFC40DDCC8824814F80A72") var defaultBridge [20]byte copy(defaultBridge[:], defaultBridgeValue) Convey("Context", t, func() { buf := new(bytes.Buffer) ctx := NewBrokerContext(log.New(buf, "", 0)) 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 HTTP client offers...", func() { w := httptest.NewRecorder() data, err := createClientOffer(sdp, NATUnknown, "") r, err := http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) Convey("with HTTP Bad Request when client offer contains invalid SDP", func() { data, err = createClientOffer("fake", NATUnknown, "") invalidRequest, err := http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, invalidRequest) So(w.Code, ShouldEqual, http.StatusBadRequest) }) 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"}`) // Ensure that denial is correctly recorded in metrics ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, `client-denied-count 8 client-restricted-denied-count 8 client-unrestricted-denied-count 0 client-snowflake-match-count 0 client-http-count 8 client-http-ips ??=8 client-ampcache-count 0 client-ampcache-ips client-sqs-count 0 client-sqs-ips `) }) Convey("with a proxy answer if available.", func() { done := make(chan bool) // Prepare a fake proxy to respond with. snowflake := ctx.AddSnowflake("test", "", NATUnrestricted, 0) go func() { clientOffers(i, w, r) done <- true }() offer := <-snowflake.offerChannel So(offer.sdp, ShouldResemble, []byte(sdp)) snowflake.answerChannel <- "test answer" <-done So(w.Body.String(), ShouldEqual, `{"answer":"test answer"}`) So(w.Code, ShouldEqual, http.StatusOK) // Ensure that match is correctly recorded in metrics ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, `client-denied-count 0 client-restricted-denied-count 0 client-unrestricted-denied-count 0 client-snowflake-match-count 8 client-http-count 8 client-http-ips ??=8 client-ampcache-count 0 client-ampcache-ips client-sqs-count 0 client-sqs-ips `) }) Convey("with unrestricted proxy to unrestricted client if there are no restricted proxies", func() { snowflake := ctx.AddSnowflake("test", "", NATUnrestricted, 0) offerData, err := createClientOffer(sdp, NATUnrestricted, "") So(err, ShouldBeNil) r, err := http.NewRequest("POST", "snowflake.broker/client", offerData) done := make(chan bool) go func() { clientOffers(i, w, r) done <- true }() select { case <-snowflake.offerChannel: case <-time.After(250 * time.Millisecond): So(false, ShouldBeTrue) return } snowflake.answerChannel <- "test answer" <-done So(w.Body.String(), ShouldEqual, `{"answer":"test answer"}`) }) 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(sdp)) <-done So(w.Code, ShouldEqual, http.StatusOK) So(w.Body.String(), ShouldEqual, `{"error":"timed out waiting for answer!"}`) }) }) Convey("Responds to HTTP legacy client offers...", func() { w := httptest.NewRecorder() // legacy offer starts with { offer := bytes.NewReader([]byte(fmt.Sprintf(`{%v}`, sdp))) r, err := http.NewRequest("POST", "snowflake.broker/client", offer) So(err, ShouldBeNil) r.Header.Set("Snowflake-NAT-TYPE", "restricted") Convey("with HTTP Bad Request when client offer contains invalid SDP", func() { invalidOffer := bytes.NewReader([]byte("{test}")) invalidRequest, err := http.NewRequest("POST", "snowflake.broker/client", invalidOffer) So(err, ShouldBeNil) clientOffers(i, w, invalidRequest) So(w.Code, ShouldEqual, http.StatusBadRequest) }) Convey("with 503 when no snowflakes are available.", func() { clientOffers(i, w, r) So(w.Code, ShouldEqual, http.StatusServiceUnavailable) So(w.Body.String(), ShouldEqual, "") // Ensure that denial is correctly recorded in metrics ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, `client-denied-count 8 client-restricted-denied-count 8 client-unrestricted-denied-count 0 client-snowflake-match-count 0 client-http-count 8 client-http-ips ??=8 client-ampcache-count 0 client-ampcache-ips client-sqs-count 0 client-sqs-ips `) }) 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(fmt.Sprintf(`{%v}`, sdp))) snowflake.answerChannel <- "fake answer" <-done So(w.Body.String(), ShouldEqual, "fake answer") So(w.Code, ShouldEqual, http.StatusOK) // Ensure that match is correctly recorded in metrics ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, `client-denied-count 0 client-restricted-denied-count 0 client-unrestricted-denied-count 0 client-snowflake-match-count 8 client-http-count 8 client-http-ips ??=8 client-ampcache-count 0 client-ampcache-ips client-sqs-count 0 client-sqs-ips `) }) 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(fmt.Sprintf(`{%v}`, sdp))) <-done So(w.Code, ShouldEqual, http.StatusGatewayTimeout) }) }) Convey("Responds to AMP client offers...", func() { w := httptest.NewRecorder() encPollReq := []byte("1.0\n{\"offer\": \"fake\", \"nat\": \"unknown\"}") r, err := http.NewRequest("GET", "/amp/client/"+amp.EncodePath(encPollReq), nil) So(err, ShouldBeNil) Convey("with status 200 when request is badly formatted.", func() { r, err := http.NewRequest("GET", "/amp/client/bad", nil) So(err, ShouldBeNil) ampClientOffers(i, w, r) body, err := decodeAMPArmorToString(w.Body) So(err, ShouldBeNil) So(body, ShouldEqual, `{"error":"cannot decode URL path"}`) }) Convey("with error when no snowflakes are available.", func() { ampClientOffers(i, w, r) So(w.Code, ShouldEqual, http.StatusOK) body, err := decodeAMPArmorToString(w.Body) So(err, ShouldBeNil) So(body, ShouldEqual, `{"error":"no snowflake proxies currently available"}`) // Ensure that denial is correctly recorded in metrics ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, `client-denied-count 8 client-restricted-denied-count 8 client-unrestricted-denied-count 0 client-snowflake-match-count 0 client-http-count 0 client-http-ips client-ampcache-count 8 client-ampcache-ips ??=8 client-sqs-count 0 client-sqs-ips `) }) 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() { ampClientOffers(i, w, r) done <- true }() offer := <-snowflake.offerChannel So(offer.sdp, ShouldResemble, []byte("fake")) snowflake.answerChannel <- "fake answer" <-done body, err := decodeAMPArmorToString(w.Body) So(err, ShouldBeNil) So(body, ShouldEqual, `{"answer":"fake answer"}`) So(w.Code, ShouldEqual, http.StatusOK) // Ensure that match is correctly recorded in metrics ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, `client-denied-count 0 client-restricted-denied-count 0 client-unrestricted-denied-count 0 client-snowflake-match-count 8 client-http-count 0 client-http-ips client-ampcache-count 8 client-ampcache-ips ??=8 client-sqs-count 0 client-sqs-ips `) }) Convey("Times out when no proxy responds.", func() { if testing.Short() { return } done := make(chan bool) snowflake := ctx.AddSnowflake("fake", "", NATUnrestricted, 0) go func() { ampClientOffers(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) body, err := decodeAMPArmorToString(w.Body) So(err, ShouldBeNil) So(body, ShouldEqual, `{"error":"timed out waiting for answer!"}`) }) }) 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"), fingerprint: defaultBridge[:]} <-done So(w.Code, ShouldEqual, http.StatusOK) So(w.Body.String(), ShouldEqual, `{"Status":"client match","Offer":"fake offer","NAT":"","RelayURL":"wss://snowflake.torproject.net/"}`) }) 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":"","RelayURL":""}`) So(w.Code, ShouldEqual, http.StatusOK) }) }) Convey("Responds to proxy answers...", func() { done := make(chan bool) s := ctx.AddSnowflake(sid, "", NATUnrestricted, 0) w := httptest.NewRecorder() data, err := createProxyAnswer(sdp, sid) So(err, ShouldBeNil) 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, sdp) }) Convey("with client gone status if the proxy ID is not recognized", func() { data, err := createProxyAnswer(sdp, "invalid") r, err := http.NewRequest("POST", "snowflake.broker/answer", data) So(err, ShouldBeNil) proxyAnswers(i, w, r) So(w.Code, ShouldEqual, http.StatusOK) b, err := io.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, err := createClientOffer(sdp, NATUnknown, "") So(err, ShouldBeNil) 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, err = createProxyAnswer(sdp, sid) So(err, ShouldBeNil) 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. wC := httptest.NewRecorder() dataC, err := createClientOffer(sdp, NATUnknown, "") So(err, ShouldBeNil) 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, fmt.Sprintf(`{"Status":"client match","Offer":%#q,"NAT":"unknown","RelayURL":"wss://snowflake.torproject.net/"}`, sdp)) So(ctx.idToSnowflake[sid], ShouldNotBeNil) // Follow up with the answer request afterwards wA := httptest.NewRecorder() dataA, err := createProxyAnswer(sdp, sid) So(err, ShouldBeNil) 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, fmt.Sprintf(`{"answer":%#q}`, sdp)) }) }) } 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 TestInvalidGeoipFile(t *testing.T) { Convey("Geoip", t, func() { // 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.geoipdb, ShouldBeNil) }) } 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, ShouldBeNil) //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() metricsStr := buf.String() So(metricsStr, ShouldStartWith, "snowflake-stats-end "+time.Now().UTC().Format("2006-01-02 15:04:05")+" (86400 s)\nsnowflake-ips CA=4\n") So(metricsStr, ShouldContainSubstring, "\nsnowflake-ips-standalone 1\n") So(metricsStr, ShouldContainSubstring, "\nsnowflake-ips-badge 1\n") So(metricsStr, ShouldContainSubstring, "\nsnowflake-ips-webext 1\n") So(metricsStr, ShouldEndWith, `snowflake-ips-total 4 snowflake-idle-count 8 snowflake-proxy-poll-with-relay-url-count 0 snowflake-proxy-poll-without-relay-url-count 8 snowflake-proxy-rejected-for-relay-url-count 0 client-denied-count 0 client-restricted-denied-count 0 client-unrestricted-denied-count 0 client-snowflake-match-count 0 client-http-count 0 client-http-ips client-ampcache-count 0 client-ampcache-ips client-sqs-count 0 client-sqs-ips snowflake-ips-nat-restricted 0 snowflake-ips-nat-unrestricted 0 snowflake-ips-nat-unknown 1 `) }) //Test addition of client failures Convey("for no proxies available", func() { w := httptest.NewRecorder() data, err := createClientOffer(sdp, NATUnknown, "") So(err, ShouldBeNil) r, err := http.NewRequest("POST", "snowflake.broker/client", data) r.RemoteAddr = "129.97.208.23:8888" //CA geoip So(err, ShouldBeNil) clientOffers(i, w, r) ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, `client-denied-count 8 client-restricted-denied-count 8 client-unrestricted-denied-count 0 client-snowflake-match-count 0 client-http-count 8 client-http-ips CA=8 client-ampcache-count 0 client-ampcache-ips client-sqs-count 0 client-sqs-ips `) // Test reset buf.Reset() ctx.metrics.zeroMetrics() ctx.metrics.printMetrics() So(buf.String(), ShouldContainSubstring, "\nsnowflake-ips \n") So(buf.String(), ShouldContainSubstring, "\nsnowflake-ips-standalone 0\n") So(buf.String(), ShouldContainSubstring, "\nsnowflake-ips-badge 0\n") So(buf.String(), ShouldContainSubstring, "\nsnowflake-ips-webext 0\n") So(buf.String(), ShouldContainSubstring, `snowflake-ips-total 0 snowflake-idle-count 0 snowflake-proxy-poll-with-relay-url-count 0 snowflake-proxy-poll-without-relay-url-count 0 snowflake-proxy-rejected-for-relay-url-count 0 client-denied-count 0 client-restricted-denied-count 0 client-unrestricted-denied-count 0 client-snowflake-match-count 0 client-http-count 0 client-http-ips client-ampcache-count 0 client-ampcache-ips client-sqs-count 0 client-sqs-ips snowflake-ips-nat-restricted 0 snowflake-ips-nat-unrestricted 0 snowflake-ips-nat-unknown 0 `) }) //Test addition of client matches Convey("for client-proxy match", func() { w := httptest.NewRecorder() data, err := createClientOffer(sdp, NATUnknown, "") So(err, ShouldBeNil) 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(sdp)) 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, err := createClientOffer(sdp, NATRestricted, "") So(err, ShouldBeNil) r, err := http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data, err = createClientOffer(sdp, NATRestricted, "") So(err, ShouldBeNil) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data, err = createClientOffer(sdp, NATRestricted, "") So(err, ShouldBeNil) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data, err = createClientOffer(sdp, NATRestricted, "") So(err, ShouldBeNil) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data, err = createClientOffer(sdp, NATRestricted, "") So(err, ShouldBeNil) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data, err = createClientOffer(sdp, NATRestricted, "") So(err, ShouldBeNil) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data, err = createClientOffer(sdp, NATRestricted, "") So(err, ShouldBeNil) r, err = http.NewRequest("POST", "snowflake.broker/client", data) So(err, ShouldBeNil) clientOffers(i, w, r) w = httptest.NewRecorder() data, err = createClientOffer(sdp, NATRestricted, "") So(err, ShouldBeNil) 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, err = createClientOffer(sdp, NATRestricted, "") So(err, ShouldBeNil) 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() metricsStr := buf.String() So(metricsStr, ShouldContainSubstring, "snowflake-ips CA=1\n") So(metricsStr, ShouldContainSubstring, "snowflake-ips-total 1\n") }) //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") }) Convey("client failures by NAT type", func() { w := httptest.NewRecorder() data, err := createClientOffer(sdp, NATRestricted, "") So(err, ShouldBeNil) 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, err = createClientOffer(sdp, NATUnrestricted, "") So(err, ShouldBeNil) 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, err = createClientOffer(sdp, NATUnknown, "") So(err, ShouldBeNil) 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") }) }) } 07070100000015000081A400000000000000000000000165F88C5000000445000000000000000000000000000000000000002900000000snowflake-2.9.2/broker/snowflake-heap.go/* 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 } 07070100000016000081A400000000000000000000000165F88C5000001C0B000000000000000000000000000000000000001E00000000snowflake-2.9.2/broker/sqs.gopackage main import ( "context" "log" "strconv" "strings" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/aws-sdk-go-v2/service/sqs/types" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/sqsclient" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/util" ) const ( cleanupThreshold = -2 * time.Minute ) type sqsHandler struct { SQSClient sqsclient.SQSClient SQSQueueURL *string IPC *IPC cleanupInterval time.Duration } func (r *sqsHandler) pollMessages(ctx context.Context, chn chan<- *types.Message) { for { select { case <-ctx.Done(): // if context is cancelled return default: res, err := r.SQSClient.ReceiveMessage(ctx, &sqs.ReceiveMessageInput{ QueueUrl: r.SQSQueueURL, MaxNumberOfMessages: 10, WaitTimeSeconds: 15, MessageAttributeNames: []string{ string(types.QueueAttributeNameAll), }, }) if err != nil { log.Printf("SQSHandler: encountered error while polling for messages: %v\n", err) continue } for _, message := range res.Messages { chn <- &message } } } } func (r *sqsHandler) cleanupClientQueues(ctx context.Context) { for range time.NewTicker(r.cleanupInterval).C { // Runs at fixed intervals to clean up any client queues that were last changed more than 2 minutes ago select { case <-ctx.Done(): // if context is cancelled return default: queueURLsList := []string{} var nextToken *string for { res, err := r.SQSClient.ListQueues(ctx, &sqs.ListQueuesInput{ QueueNamePrefix: aws.String("snowflake-client-"), MaxResults: aws.Int32(1000), NextToken: nextToken, }) if err != nil { log.Printf("SQSHandler: encountered error while retrieving client queues to clean up: %v\n", err) // client queues will be cleaned up the next time the cleanup operation is triggered automatically break } queueURLsList = append(queueURLsList, res.QueueUrls...) if res.NextToken == nil { break } else { nextToken = res.NextToken } } numDeleted := 0 cleanupCutoff := time.Now().Add(cleanupThreshold) for _, queueURL := range queueURLsList { if !strings.Contains(queueURL, "snowflake-client-") { continue } res, err := r.SQSClient.GetQueueAttributes(ctx, &sqs.GetQueueAttributesInput{ QueueUrl: aws.String(queueURL), AttributeNames: []types.QueueAttributeName{types.QueueAttributeNameLastModifiedTimestamp}, }) if err != nil { // According to the AWS SQS docs, the deletion process for a queue can take up to 60 seconds. So the queue // can be in the process of being deleted, but will still be returned by the ListQueues operation, but // fail when we try to GetQueueAttributes for the queue log.Printf("SQSHandler: encountered error while getting attribute of client queue %s. queue may already be deleted.\n", queueURL) continue } lastModifiedInt64, err := strconv.ParseInt(res.Attributes[string(types.QueueAttributeNameLastModifiedTimestamp)], 10, 64) if err != nil { log.Printf("SQSHandler: encountered invalid lastModifiedTimetamp value from client queue %s: %v\n", queueURL, err) continue } lastModified := time.Unix(lastModifiedInt64, 0) if lastModified.Before(cleanupCutoff) { _, err := r.SQSClient.DeleteQueue(ctx, &sqs.DeleteQueueInput{ QueueUrl: aws.String(queueURL), }) if err != nil { log.Printf("SQSHandler: encountered error when deleting client queue %s: %v\n", queueURL, err) continue } else { numDeleted += 1 } } } log.Printf("SQSHandler: finished running iteration of client queue cleanup. found and deleted %d client queues.\n", numDeleted) } } } func (r *sqsHandler) handleMessage(context context.Context, message *types.Message) { var encPollReq []byte var response []byte var err error clientID := message.MessageAttributes["ClientID"].StringValue if clientID == nil { log.Println("SQSHandler: got SDP offer in SQS message with no client ID. ignoring this message.") return } res, err := r.SQSClient.CreateQueue(context, &sqs.CreateQueueInput{ QueueName: aws.String("snowflake-client-" + *clientID), }) if err != nil { log.Printf("SQSHandler: error encountered when creating answer queue for client %s: %v\n", *clientID, err) return } answerSQSURL := res.QueueUrl encPollReq = []byte(*message.Body) // Get best guess Client IP for geolocating remoteAddr := "" req, err := messages.DecodeClientPollRequest(encPollReq) if err != nil { log.Printf("SQSHandler: error encounted when decoding client poll request %s: %v\n", *clientID, err) } else { sdp, err := util.DeserializeSessionDescription(req.Offer) if err != nil { log.Printf("SQSHandler: error encounted when deserializing session desc %s: %v\n", *clientID, err) } else { candidateAddrs := util.GetCandidateAddrs(sdp.SDP) if len(candidateAddrs) > 0 { remoteAddr = candidateAddrs[0].String() } } } arg := messages.Arg{ Body: encPollReq, RemoteAddr: remoteAddr, RendezvousMethod: messages.RendezvousSqs, } err = r.IPC.ClientOffers(arg, &response) if err != nil { log.Printf("SQSHandler: error encountered when handling message: %v\n", err) return } r.SQSClient.SendMessage(context, &sqs.SendMessageInput{ QueueUrl: answerSQSURL, MessageBody: aws.String(string(response)), }) } func (r *sqsHandler) deleteMessage(context context.Context, message *types.Message) { r.SQSClient.DeleteMessage(context, &sqs.DeleteMessageInput{ QueueUrl: r.SQSQueueURL, ReceiptHandle: message.ReceiptHandle, }) } func newSQSHandler(context context.Context, client sqsclient.SQSClient, sqsQueueName string, region string, i *IPC) (*sqsHandler, error) { // Creates the queue if a queue with the same name doesn't exist. If a queue with the same name and attributes // already exists, then nothing will happen. If a queue with the same name, but different attributes exists, then // an error will be returned res, err := client.CreateQueue(context, &sqs.CreateQueueInput{ QueueName: aws.String(sqsQueueName), Attributes: map[string]string{ "MessageRetentionPeriod": strconv.FormatInt(int64((5 * time.Minute).Seconds()), 10), }, }) if err != nil { return nil, err } return &sqsHandler{ SQSClient: client, SQSQueueURL: res.QueueUrl, IPC: i, cleanupInterval: time.Second * 30, }, nil } func (r *sqsHandler) PollAndHandleMessages(ctx context.Context) { log.Println("SQSHandler: Starting to poll for messages at: " + *r.SQSQueueURL) messagesChn := make(chan *types.Message, 2) go r.pollMessages(ctx, messagesChn) go r.cleanupClientQueues(ctx) for message := range messagesChn { select { case <-ctx.Done(): // if context is cancelled return default: r.handleMessage(ctx, message) r.deleteMessage(ctx, message) } } } 07070100000017000081A400000000000000000000000165F88C500000319C000000000000000000000000000000000000002300000000snowflake-2.9.2/broker/sqs_test.gopackage main import ( "bytes" "context" "errors" "log" "strconv" "sync" "testing" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/aws-sdk-go-v2/service/sqs/types" "github.com/golang/mock/gomock" . "github.com/smartystreets/goconvey/convey" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/sqsclient" ) func TestSQS(t *testing.T) { Convey("Context", t, func() { buf := new(bytes.Buffer) ipcCtx := NewBrokerContext(log.New(buf, "", 0)) i := &IPC{ipcCtx} var logBuffer bytes.Buffer log.SetOutput(&logBuffer) Convey("Responds to SQS client offers...", func() { ctrl := gomock.NewController(t) mockSQSClient := sqsclient.NewMockSQSClient(ctrl) brokerSQSQueueName := "example-name" responseQueueURL := aws.String("https://sqs.us-east-1.amazonaws.com/testing") runSQSHandler := func(sqsHandlerContext context.Context) { mockSQSClient.EXPECT().CreateQueue(sqsHandlerContext, &sqs.CreateQueueInput{ QueueName: aws.String(brokerSQSQueueName), Attributes: map[string]string{ "MessageRetentionPeriod": strconv.FormatInt(int64((5 * time.Minute).Seconds()), 10), }, }).Return(&sqs.CreateQueueOutput{ QueueUrl: responseQueueURL, }, nil).Times(1) sqsHandler, err := newSQSHandler(sqsHandlerContext, mockSQSClient, brokerSQSQueueName, "example-region", i) So(err, ShouldBeNil) go sqsHandler.PollAndHandleMessages(sqsHandlerContext) } messageBody := aws.String("1.0\n{\"offer\": \"fake\", \"nat\": \"unknown\"}") receiptHandle := "fake-receipt-handle" sqsReceiveMessageInput := sqs.ReceiveMessageInput{ QueueUrl: responseQueueURL, MaxNumberOfMessages: 10, WaitTimeSeconds: 15, MessageAttributeNames: []string{ string(types.QueueAttributeNameAll), }, } sqsDeleteMessageInput := sqs.DeleteMessageInput{ QueueUrl: responseQueueURL, ReceiptHandle: &receiptHandle, } Convey("by ignoring it if no client id specified", func(c C) { var wg sync.WaitGroup wg.Add(1) sqsHandlerContext, sqsCancelFunc := context.WithCancel(context.Background()) defer sqsCancelFunc() defer wg.Wait() mockSQSClient.EXPECT().ReceiveMessage(sqsHandlerContext, &sqsReceiveMessageInput).MinTimes(1).DoAndReturn( func(ctx context.Context, input *sqs.ReceiveMessageInput, optFns ...func(*sqs.Options)) (*sqs.ReceiveMessageOutput, error) { return &sqs.ReceiveMessageOutput{ Messages: []types.Message{ { Body: messageBody, ReceiptHandle: &receiptHandle, }, }, }, nil }, ) mockSQSClient.EXPECT().DeleteMessage(sqsHandlerContext, &sqsDeleteMessageInput).Times(1).Do( func(ctx context.Context, input *sqs.DeleteMessageInput, optFns ...func(*sqs.Options)) { defer wg.Done() c.So(logBuffer.String(), ShouldContainSubstring, "SQSHandler: got SDP offer in SQS message with no client ID. ignoring this message.") mockSQSClient.EXPECT().DeleteMessage(sqsHandlerContext, &sqsDeleteMessageInput).AnyTimes() }, ) runSQSHandler(sqsHandlerContext) }) Convey("by doing nothing if an error occurs upon receipt of the message", func(c C) { var wg sync.WaitGroup wg.Add(2) sqsHandlerContext, sqsCancelFunc := context.WithCancel(context.Background()) defer sqsCancelFunc() defer wg.Wait() numTimes := 0 // When ReceiveMessage is called for the first time, the error has not had a chance to be logged yet. // Therefore, we opt to wait for the second call because we are guaranteed that the error was logged // by then. mockSQSClient.EXPECT().ReceiveMessage(sqsHandlerContext, &sqsReceiveMessageInput).MinTimes(2).DoAndReturn( func(ctx context.Context, input *sqs.ReceiveMessageInput, optFns ...func(*sqs.Options)) (*sqs.ReceiveMessageOutput, error) { numTimes += 1 if numTimes <= 2 { wg.Done() if numTimes == 2 { c.So(logBuffer.String(), ShouldContainSubstring, "SQSHandler: encountered error while polling for messages: error") } } return nil, errors.New("error") }, ) runSQSHandler(sqsHandlerContext) }) Convey("by attempting to create a new sqs queue...", func() { clientId := "fake-id" sqsCreateQueueInput := sqs.CreateQueueInput{ QueueName: aws.String("snowflake-client-fake-id"), } expectReceiveMessageReturnsValidMessage := func(sqsHandlerContext context.Context) { mockSQSClient.EXPECT().ReceiveMessage(sqsHandlerContext, &sqsReceiveMessageInput).AnyTimes().DoAndReturn( func(ctx context.Context, input *sqs.ReceiveMessageInput, optFns ...func(*sqs.Options)) (*sqs.ReceiveMessageOutput, error) { return &sqs.ReceiveMessageOutput{ Messages: []types.Message{ { Body: messageBody, MessageAttributes: map[string]types.MessageAttributeValue{ "ClientID": {StringValue: &clientId}, }, ReceiptHandle: &receiptHandle, }, }, }, nil }, ) } Convey("and does not attempt to send a message via SQS if queue creation fails.", func(c C) { var wg sync.WaitGroup wg.Add(2) sqsHandlerContext, sqsCancelFunc := context.WithCancel(context.Background()) defer sqsCancelFunc() defer wg.Wait() expectReceiveMessageReturnsValidMessage(sqsHandlerContext) mockSQSClient.EXPECT().CreateQueue(sqsHandlerContext, &sqsCreateQueueInput).Return(nil, errors.New("error")).AnyTimes() numTimes := 0 mockSQSClient.EXPECT().DeleteMessage(sqsHandlerContext, &sqsDeleteMessageInput).MinTimes(2).Do( func(ctx context.Context, input *sqs.DeleteMessageInput, optFns ...func(*sqs.Options)) { numTimes += 1 if numTimes <= 2 { wg.Done() if numTimes == 2 { c.So(logBuffer.String(), ShouldContainSubstring, "SQSHandler: error encountered when creating answer queue for client fake-id: error") } } }, ) runSQSHandler(sqsHandlerContext) }) Convey("and responds with a proxy answer if available.", func(c C) { var wg sync.WaitGroup wg.Add(1) sqsHandlerContext, sqsCancelFunc := context.WithCancel(context.Background()) defer sqsCancelFunc() defer wg.Wait() expectReceiveMessageReturnsValidMessage(sqsHandlerContext) mockSQSClient.EXPECT().CreateQueue(sqsHandlerContext, &sqsCreateQueueInput).Return(&sqs.CreateQueueOutput{ QueueUrl: responseQueueURL, }, nil).AnyTimes() mockSQSClient.EXPECT().DeleteMessage(sqsHandlerContext, &sqsDeleteMessageInput).AnyTimes() numTimes := 0 mockSQSClient.EXPECT().SendMessage(sqsHandlerContext, gomock.Any()).MinTimes(1).DoAndReturn( func(ctx context.Context, input *sqs.SendMessageInput, optFns ...func(*sqs.Options)) (*sqs.SendMessageOutput, error) { numTimes += 1 if numTimes == 1 { c.So(input.MessageBody, ShouldEqual, aws.String("{\"answer\":\"fake answer\"}")) // Ensure that match is correctly recorded in metrics ipcCtx.metrics.printMetrics() c.So(buf.String(), ShouldContainSubstring, `client-denied-count 0 client-restricted-denied-count 0 client-unrestricted-denied-count 0 client-snowflake-match-count 8 client-http-count 0 client-http-ips client-ampcache-count 0 client-ampcache-ips client-sqs-count 8 client-sqs-ips ??=8 `) wg.Done() } return &sqs.SendMessageOutput{}, nil }, ) runSQSHandler(sqsHandlerContext) snowflake := ipcCtx.AddSnowflake("fake", "", NATUnrestricted, 0) offer := <-snowflake.offerChannel So(offer.sdp, ShouldResemble, []byte("fake")) snowflake.answerChannel <- "fake answer" }) }) }) Convey("Cleans up SQS client queues...", func() { brokerSQSQueueName := "example-name" responseQueueURL := aws.String("https://sqs.us-east-1.amazonaws.com/testing") ctrl := gomock.NewController(t) mockSQSClient := sqsclient.NewMockSQSClient(ctrl) runSQSHandler := func(sqsHandlerContext context.Context) { mockSQSClient.EXPECT().CreateQueue(sqsHandlerContext, &sqs.CreateQueueInput{ QueueName: aws.String(brokerSQSQueueName), Attributes: map[string]string{ "MessageRetentionPeriod": strconv.FormatInt(int64((5 * time.Minute).Seconds()), 10), }, }).Return(&sqs.CreateQueueOutput{ QueueUrl: responseQueueURL, }, nil).Times(1) mockSQSClient.EXPECT().ReceiveMessage(sqsHandlerContext, gomock.Any()).AnyTimes().Return( &sqs.ReceiveMessageOutput{ Messages: []types.Message{}, }, nil, ) sqsHandler, err := newSQSHandler(sqsHandlerContext, mockSQSClient, brokerSQSQueueName, "example-region", i) So(err, ShouldBeNil) // Set the cleanup interval to 1 ns so we can immediately test the cleanup logic sqsHandler.cleanupInterval = time.Nanosecond go sqsHandler.PollAndHandleMessages(sqsHandlerContext) } Convey("does nothing if there are no open queues.", func() { var wg sync.WaitGroup wg.Add(1) sqsHandlerContext, sqsCancelFunc := context.WithCancel(context.Background()) defer wg.Wait() mockSQSClient.EXPECT().ListQueues(sqsHandlerContext, &sqs.ListQueuesInput{ QueueNamePrefix: aws.String("snowflake-client-"), MaxResults: aws.Int32(1000), NextToken: nil, }).DoAndReturn(func(ctx context.Context, input *sqs.ListQueuesInput, optFns ...func(*sqs.Options)) (*sqs.ListQueuesOutput, error) { wg.Done() // Cancel the handler context since we are only interested in testing one iteration of the cleanup sqsCancelFunc() return &sqs.ListQueuesOutput{ QueueUrls: []string{}, }, nil }) runSQSHandler(sqsHandlerContext) }) Convey("deletes open queue when there is one open queue.", func(c C) { var wg sync.WaitGroup wg.Add(1) sqsHandlerContext, sqsCancelFunc := context.WithCancel(context.Background()) clientQueueUrl1 := "https://sqs.us-east-1.amazonaws.com/snowflake-client-1" clientQueueUrl2 := "https://sqs.us-east-1.amazonaws.com/snowflake-client-2" gomock.InOrder( mockSQSClient.EXPECT().ListQueues(sqsHandlerContext, &sqs.ListQueuesInput{ QueueNamePrefix: aws.String("snowflake-client-"), MaxResults: aws.Int32(1000), NextToken: nil, }).Times(1).Return(&sqs.ListQueuesOutput{ QueueUrls: []string{ clientQueueUrl1, clientQueueUrl2, }, }, nil), mockSQSClient.EXPECT().ListQueues(sqsHandlerContext, &sqs.ListQueuesInput{ QueueNamePrefix: aws.String("snowflake-client-"), MaxResults: aws.Int32(1000), NextToken: nil, }).Times(1).DoAndReturn(func(ctx context.Context, input *sqs.ListQueuesInput, optFns ...func(*sqs.Options)) (*sqs.ListQueuesOutput, error) { // Executed on second iteration of cleanupClientQueues loop. This means that one full iteration has completed and we can verify the results of that iteration wg.Done() sqsCancelFunc() c.So(logBuffer.String(), ShouldContainSubstring, "SQSHandler: finished running iteration of client queue cleanup. found and deleted 2 client queues.") return &sqs.ListQueuesOutput{ QueueUrls: []string{}, }, nil }), ) gomock.InOrder( mockSQSClient.EXPECT().GetQueueAttributes(sqsHandlerContext, &sqs.GetQueueAttributesInput{ QueueUrl: aws.String(clientQueueUrl1), AttributeNames: []types.QueueAttributeName{types.QueueAttributeNameLastModifiedTimestamp}, }).Times(1).Return(&sqs.GetQueueAttributesOutput{ Attributes: map[string]string{ string(types.QueueAttributeNameLastModifiedTimestamp): "0", }}, nil), mockSQSClient.EXPECT().GetQueueAttributes(sqsHandlerContext, &sqs.GetQueueAttributesInput{ QueueUrl: aws.String(clientQueueUrl2), AttributeNames: []types.QueueAttributeName{types.QueueAttributeNameLastModifiedTimestamp}, }).Times(1).Return(&sqs.GetQueueAttributesOutput{ Attributes: map[string]string{ string(types.QueueAttributeNameLastModifiedTimestamp): "0", }}, nil), ) gomock.InOrder( mockSQSClient.EXPECT().DeleteQueue(sqsHandlerContext, &sqs.DeleteQueueInput{ QueueUrl: aws.String(clientQueueUrl1), }).Return(&sqs.DeleteQueueOutput{}, nil), mockSQSClient.EXPECT().DeleteQueue(sqsHandlerContext, &sqs.DeleteQueueInput{ QueueUrl: aws.String(clientQueueUrl2), }).Return(&sqs.DeleteQueueOutput{}, nil), ) runSQSHandler(sqsHandlerContext) wg.Wait() }) }) }) } 07070100000018000081A400000000000000000000000165F88C50000078EA000000000000000000000000000000000000002200000000snowflake-2.9.2/broker/test_geoip# 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 07070100000019000081A400000000000000000000000165F88C50000094D8000000000000000000000000000000000000002300000000snowflake-2.9.2/broker/test_geoip6# 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 0707010000001A000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001700000000snowflake-2.9.2/client0707010000001B000081A400000000000000000000000165F88C5000001442000000000000000000000000000000000000002100000000snowflake-2.9.2/client/README.md<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> **Table of Contents** - [Dependencies](#dependencies) - [Building the Snowflake client](#building-the-snowflake-client) - [Running the Snowflake client with Tor](#running-the-snowflake-client-with-tor) <!-- END doctoc generated TOC please keep comment here to allow auto update --> This is the Tor client component of Snowflake. It is based on the [goptlib](https://gitweb.torproject.org/pluggable-transports/goptlib.git/) pluggable transports library for Tor. ### Dependencies - Go 1.15+ - We use the [pion/webrtc](https://github.com/pion/webrtc) library for WebRTC communication with Snowflake proxies. Note: running `go get` will fetch this dependency automatically during the build process. ### Building the Snowflake client To build the Snowflake client, make sure you are in the `client/` directory, and then run: ``` go get go build ``` ### Running the Snowflake client with Tor The Snowflake client can be configured with SOCKS options. We have a few example `torrc` files in this directory. We recommend the following `torrc` options by default: ``` UseBridges 1 ClientTransportPlugin snowflake exec ./client -log snowflake.log Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ fronts=foursquare.com,github.githubassets.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn ``` `fingerprint=` is the fingerprint of bridge that the client will ultimately be connecting to. `url=` is the URL of a broker instance. If you would like to try out Snowflake with your own broker, simply provide the URL of your broker instance with this option. `fronts=` is an optional, comma-seperated list front domains for the broker request. `ice=` is a comma-separated list of ICE servers. These must be STUN (over UDP) servers with the form stun:<var>host</var>[:<var>port</var>]. We recommend using servers that have implemented NAT discovery. See our wiki page on [NAT traversal](https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/wikis/NAT-matching) for more information. `utls-imitate=` configuration instructs the client to use fingerprinting resistance when connecting when rendez-vous'ing with the broker. To bootstrap Tor, run: ``` tor -f torrc ``` This should start the client plugin, bootstrapping to 100% using WebRTC. ### Registration methods The Snowflake client supports a few different ways of communicating with the broker. This initial step is sometimes called rendezvous. #### Domain fronting HTTPS For domain fronting rendezvous, use the `-url` and `-front` command-line options together. [Domain fronting](https://www.bamsoftware.com/papers/fronting/) hides the externally visible domain name from an external observer, making it appear that the Snowflake client is communicating with some server other than the Snowflake broker. * `-url` is the HTTPS URL of a forwarder to the broker, on some service that supports domain fronting, such as a CDN. * `-front` is the domain name to show externally. It must be another domain on the same service. Example: ``` -url https://snowflake-broker.torproject.net.global.prod.fastly.net/ \ -front cdn.sstatic.net \ ``` #### AMP cache For AMP cache rendezvous, use the `-url`, `-ampcache`, and `-front` command-line options together. [AMP](https://amp.dev/documentation/) is a standard for web pages for mobile computers. An [AMP cache](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/how_amp_pages_are_cached/) is a cache and proxy specialized for AMP pages. The Snowflake broker has the ability to make its client registration responses look like AMP pages, so it can be accessed through an AMP cache. When you use AMP cache rendezvous, it appears to an observer that the Snowflake client is accessing an AMP cache, or some other domain operated by the same organization. You still need to use the `-front` command-line option, because the [format of AMP cache URLs](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/) would otherwise reveal the domain name of the broker. There is only one AMP cache that works with this option, the Google AMP cache at https://cdn.ampproject.org/. * `-url` is the HTTPS URL of the broker. * `-ampcache` is `https://cdn.ampproject.org/`. * `-front` is any Google domain, such as `www.google.com`. Example: ``` -url https://snowflake-broker.torproject.net/ \ -ampcache https://cdn.ampproject.org/ \ -front www.google.com \ ``` #### Direct access It is also possible to access the broker directly using HTTPS, without domain fronting, for testing purposes. This mode is not suitable for circumvention, because the broker is easily blocked by its address. 0707010000001C000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001B00000000snowflake-2.9.2/client/lib0707010000001D000081A400000000000000000000000165F88C5000000333000000000000000000000000000000000000002900000000snowflake-2.9.2/client/lib/interfaces.gopackage snowflake_client // Tongue is an interface for catching Snowflakes. (aka the remote dialer) type Tongue interface { // Catch makes a connection to a new snowflake. Catch() (*WebRTCPeer, error) // GetMax returns the maximum number of snowflakes a client can have. GetMax() int } // SnowflakeCollector is an interface for managing a client's collection of snowflakes. type SnowflakeCollector interface { // Collect adds a snowflake to the collection. // The implementation of Collect should decide how to connect to and maintain // the connection to the WebRTCPeer. Collect() (*WebRTCPeer, error) // Pop removes and returns the most available snowflake from the collection. Pop() *WebRTCPeer // Melted returns a channel that will signal when the collector has stopped. Melted() <-chan struct{} } 0707010000001E000081A400000000000000000000000165F88C50000013B2000000000000000000000000000000000000002700000000snowflake-2.9.2/client/lib/lib_test.gopackage snowflake_client import ( "fmt" "net" "testing" "time" . "github.com/smartystreets/goconvey/convey" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/event" ) 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 } 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{} d := NewWebRTCDialer(broker, nil, 1) So(d, ShouldNotBeNil) So(d.BrokerChannel, ShouldNotBeNil) }) SkipConvey("WebRTCDialer can Catch a snowflake.", func() { broker := &BrokerChannel{} d := NewWebRTCDialer(broker, nil, 1) conn, err := d.Catch() So(conn, ShouldBeNil) So(err, ShouldNotBeNil) }) }) } func TestWebRTCPeer(t *testing.T) { Convey("WebRTCPeer", t, func(c C) { p := &WebRTCPeer{closed: make(chan struct{}), eventsLogger: event.NewSnowflakeEventDispatcher()} 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", "stun:stun.ekiga.net"}, [][]string{[]string{"stun:stun.l.google.com:19302"}, []string{"stun:stun.ekiga.net:3478"}}, 2, }, { []string{"stun:stun1.l.google.com:19302", "stun.ekiga.net", "stun:stun.example.com:1234/path?query", "https://example.com", "turn:relay.metered.ca:80?transport=udp"}, [][]string{[]string{"stun:stun1.l.google.com:19302"}}, 1, }, } { 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) } } }) } 0707010000001F000081A400000000000000000000000165F88C5000000EB7000000000000000000000000000000000000002400000000snowflake-2.9.2/client/lib/peers.gopackage snowflake_client import ( "container/list" "errors" "fmt" "log" "sync" ) // Peers is a container that 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 } // NewPeers constructs 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 } // Collect connects to and adds a new remote peer 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. // Pop will return 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 } } // Melted returns a channel that will close when peers stop being collected. // Melted is a necessary part of |SnowflakeCollector| interface. func (p *Peers) Melted() <-chan struct{} { return p.melt } // Count returns the total available Snowflakes (including the active ones) // 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 } } // End closes all active connections to Peers contained here, and stops the // collection of future Peers. 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) } 07070100000020000081A400000000000000000000000165F88C5000001ED3000000000000000000000000000000000000002900000000snowflake-2.9.2/client/lib/rendezvous.go// WebRTC rendezvous requires the exchange of SessionDescriptions between // peers in order to establish a PeerConnection. package snowflake_client import ( "crypto/tls" "errors" "fmt" "net/url" "log" "net/http" "sync" "time" "github.com/pion/webrtc/v3" utls "github.com/refraction-networking/utls" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/certs" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/event" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/nat" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/util" utlsutil "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/utls" ) const ( brokerErrorUnexpected string = "Unexpected error, no answer." rendezvousErrorMsg string = "One of SQS, AmpCache, or Domain Fronting rendezvous methods must be used." readLimit = 100000 //Maximum number of bytes to be read from an HTTP response ) // RendezvousMethod represents a way of communicating with the broker: sending // an encoded client poll request (SDP offer) and receiving an encoded client // poll response (SDP answer) in return. RendezvousMethod is used by // BrokerChannel, which is in charge of encoding and decoding, and all other // tasks that are independent of the rendezvous method. type RendezvousMethod interface { Exchange([]byte) ([]byte, error) } // BrokerChannel uses a RendezvousMethod to communicate with the Snowflake broker. // The BrokerChannel is responsible for encoding and decoding SDP offers and answers; // RendezvousMethod is responsible for the exchange of encoded information. type BrokerChannel struct { Rendezvous RendezvousMethod keepLocalAddresses bool natType string lock sync.Mutex BridgeFingerprint string } // 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(proxy *url.URL) http.RoundTripper { tlsConfig := &tls.Config{ RootCAs: certs.GetRootCAs(), } transport := &http.Transport{TLSClientConfig: tlsConfig} transport.Proxy = nil if proxy != nil { transport.Proxy = http.ProxyURL(proxy) } transport.ResponseHeaderTimeout = 15 * time.Second return transport } func newBrokerChannelFromConfig(config ClientConfig) (*BrokerChannel, error) { log.Println("Rendezvous using Broker at:", config.BrokerURL) if len(config.FrontDomains) != 0 { log.Printf("Domain fronting using a randomly selected domain from: %v", config.FrontDomains) } brokerTransport := createBrokerTransport(config.CommunicationProxy) if config.UTLSClientID != "" { utlsClientHelloID, err := utlsutil.NameToUTLSID(config.UTLSClientID) if err != nil { return nil, fmt.Errorf("unable to create broker channel: %v", err) } utlsConfig := &utls.Config{ RootCAs: certs.GetRootCAs(), } brokerTransport = utlsutil.NewUTLSHTTPRoundTripperWithProxy(utlsClientHelloID, utlsConfig, brokerTransport, config.UTLSRemoveSNI, config.CommunicationProxy) } var rendezvous RendezvousMethod var err error if config.SQSQueueURL != "" { if config.AmpCacheURL != "" || config.BrokerURL != "" { log.Fatalln("Multiple rendezvous methods specified. " + rendezvousErrorMsg) } if config.SQSCredsStr == "" { log.Fatalln("sqscreds must be specified to use SQS rendezvous method.") } log.Println("Through SQS queue at:", config.SQSQueueURL) rendezvous, err = newSQSRendezvous(config.SQSQueueURL, config.SQSCredsStr, brokerTransport) } else if config.AmpCacheURL != "" && config.BrokerURL != "" { log.Println("Through AMP cache at:", config.AmpCacheURL) rendezvous, err = newAMPCacheRendezvous( config.BrokerURL, config.AmpCacheURL, config.FrontDomains, brokerTransport) } else if config.BrokerURL != "" { rendezvous, err = newHTTPRendezvous( config.BrokerURL, config.FrontDomains, brokerTransport) } else { log.Fatalln("No rendezvous method was specified. " + rendezvousErrorMsg) } if err != nil { return nil, err } return &BrokerChannel{ Rendezvous: rendezvous, keepLocalAddresses: config.KeepLocalAddresses, natType: nat.NATUnknown, BridgeFingerprint: config.BridgeFingerprint, }, nil } // Negotiate uses a RendezvousMethod to send the client's WebRTC SDP offer // and receive a snowflake proxy WebRTC SDP answer in return. func (bc *BrokerChannel) Negotiate(offer *webrtc.SessionDescription) ( *webrtc.SessionDescription, error) { // 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 the client poll request. bc.lock.Lock() req := &messages.ClientPollRequest{ Offer: offerSDP, NAT: bc.natType, Fingerprint: bc.BridgeFingerprint, } encReq, err := req.EncodeClientPollRequest() bc.lock.Unlock() if err != nil { return nil, err } // Do the exchange using our RendezvousMethod. encResp, err := bc.Rendezvous.Exchange(encReq) if err != nil { return nil, err } log.Printf("Received answer: %s", string(encResp)) // Decode the client poll response. resp, err := messages.DecodeClientPollResponse(encResp) if err != nil { return nil, err } if resp.Error != "" { return nil, errors.New(resp.Error) } return util.DeserializeSessionDescription(resp.Answer) } // SetNATType sets the NAT type of the client so we can send it to the WebRTC broker. func (bc *BrokerChannel) SetNATType(NATType string) { bc.lock.Lock() bc.natType = NATType bc.lock.Unlock() log.Printf("NAT Type: %s", NATType) } // WebRTCDialer implements the |Tongue| interface to catch snowflakes, using BrokerChannel. type WebRTCDialer struct { *BrokerChannel webrtcConfig *webrtc.Configuration max int eventLogger event.SnowflakeEventReceiver proxy *url.URL } // Deprecated: Use NewWebRTCDialerWithEventsAndProxy instead func NewWebRTCDialer(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int) *WebRTCDialer { return NewWebRTCDialerWithEventsAndProxy(broker, iceServers, max, nil, nil) } // Deprecated: Use NewWebRTCDialerWithEventsAndProxy instead func NewWebRTCDialerWithEvents(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int, eventLogger event.SnowflakeEventReceiver) *WebRTCDialer { return NewWebRTCDialerWithEventsAndProxy(broker, iceServers, max, eventLogger, nil) } // NewWebRTCDialerWithEventsAndProxy constructs a new WebRTCDialer. func NewWebRTCDialerWithEventsAndProxy(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int, eventLogger event.SnowflakeEventReceiver, proxy *url.URL) *WebRTCDialer { config := webrtc.Configuration{ ICEServers: iceServers, } return &WebRTCDialer{ BrokerChannel: broker, webrtcConfig: &config, max: max, eventLogger: eventLogger, proxy: proxy, } } // Catch initializes a WebRTC Connection by signaling through the BrokerChannel. func (w WebRTCDialer) Catch() (*WebRTCPeer, error) { // TODO: [#25591] Fetch ICE server information from Broker. // TODO: [#25596] Consider TURN servers here too. return NewWebRTCPeerWithEventsAndProxy(w.webrtcConfig, w.BrokerChannel, w.eventLogger, w.proxy) } // GetMax returns the maximum number of snowflakes to collect. func (w WebRTCDialer) GetMax() int { return w.max } 07070100000021000081A400000000000000000000000165F88C5000000F65000000000000000000000000000000000000003200000000snowflake-2.9.2/client/lib/rendezvous_ampcache.gopackage snowflake_client import ( "errors" "io" "log" "math/rand" "net/http" "net/url" "time" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/amp" ) // ampCacheRendezvous is a RendezvousMethod that communicates with the // .../amp/client route of the broker, optionally over an AMP cache proxy, and // with optional domain fronting. type ampCacheRendezvous struct { brokerURL *url.URL cacheURL *url.URL // Optional AMP cache URL. fronts []string // Optional front domains to replace url.Host in requests. transport http.RoundTripper // Used to make all requests. } // newAMPCacheRendezvous creates a new ampCacheRendezvous that contacts the // broker at the given URL, optionally proxying through an AMP cache, and with // an optional front domain. transport is the http.RoundTripper used to make all // requests. func newAMPCacheRendezvous(broker, cache string, fronts []string, transport http.RoundTripper) (*ampCacheRendezvous, error) { brokerURL, err := url.Parse(broker) if err != nil { return nil, err } var cacheURL *url.URL if cache != "" { var err error cacheURL, err = url.Parse(cache) if err != nil { return nil, err } } return &CacheRendezvous{ brokerURL: brokerURL, cacheURL: cacheURL, fronts: fronts, transport: transport, }, nil } func (r *ampCacheRendezvous) Exchange(encPollReq []byte) ([]byte, error) { log.Println("Negotiating via AMP cache rendezvous...") log.Println("Broker URL:", r.brokerURL) log.Println("AMP cache URL:", r.cacheURL) // We cannot POST a body through an AMP cache, so instead we GET and // encode the client poll request message into the URL. reqURL := r.brokerURL.ResolveReference(&url.URL{ Path: "amp/client/" + amp.EncodePath(encPollReq), }) if r.cacheURL != nil { // Rewrite reqURL to its AMP cache version. var err error reqURL, err = amp.CacheURL(reqURL, r.cacheURL, "c") if err != nil { return nil, err } } req, err := http.NewRequest("GET", reqURL.String(), nil) if err != nil { return nil, err } if len(r.fronts) != 0 { // Do domain fronting. Replace the domain in the URL's with a randomly // selected front, and store the original domain the HTTP Host header. rand.Seed(time.Now().UnixNano()) front := r.fronts[rand.Intn(len(r.fronts))] log.Println("Front domain:", front) req.Host = req.URL.Host req.URL.Host = front } resp, err := r.transport.RoundTrip(req) if err != nil { return nil, err } defer resp.Body.Close() log.Printf("AMP cache rendezvous response: %s", resp.Status) if resp.StatusCode != http.StatusOK { // A non-200 status indicates an error: // * If the broker returns a page with invalid AMP, then the AMP // cache returns a redirect that would bypass the cache. // * If the broker returns a 5xx status, the AMP cache // translates it to a 404. // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#redirect-%26-error-handling return nil, errors.New(brokerErrorUnexpected) } if _, err := resp.Location(); err == nil { // The Google AMP Cache may return a "silent redirect" with // status 200, a Location header set, and a JavaScript redirect // in the body. The redirect points directly at the origin // server for the request (bypassing the AMP cache). We do not // follow redirects nor execute JavaScript, but in any case we // cannot extract information from this response and can only // treat it as an error. return nil, errors.New(brokerErrorUnexpected) } lr := io.LimitReader(resp.Body, readLimit+1) dec, err := amp.NewArmorDecoder(lr) if err != nil { return nil, err } encPollResp, err := io.ReadAll(dec) if err != nil { return nil, err } if lr.(*io.LimitedReader).N == 0 { // We hit readLimit while decoding AMP armor, that's an error. return nil, io.ErrUnexpectedEOF } return encPollResp, err } 07070100000022000081A400000000000000000000000165F88C50000008DA000000000000000000000000000000000000002E00000000snowflake-2.9.2/client/lib/rendezvous_http.gopackage snowflake_client import ( "bytes" "errors" "io" "log" "math/rand" "net/http" "net/url" "time" ) // httpRendezvous is a RendezvousMethod that communicates with the .../client // route of the broker over HTTP or HTTPS, with optional domain fronting. type httpRendezvous struct { brokerURL *url.URL fronts []string // Optional front domain to replace url.Host in requests. transport http.RoundTripper // Used to make all requests. } // newHTTPRendezvous creates a new httpRendezvous that contacts the broker at // the given URL, with an optional front domain. transport is the // http.RoundTripper used to make all requests. func newHTTPRendezvous(broker string, fronts []string, transport http.RoundTripper) (*httpRendezvous, error) { brokerURL, err := url.Parse(broker) if err != nil { return nil, err } return &httpRendezvous{ brokerURL: brokerURL, fronts: fronts, transport: transport, }, nil } func (r *httpRendezvous) Exchange(encPollReq []byte) ([]byte, error) { log.Println("Negotiating via HTTP rendezvous...") log.Println("Target URL: ", r.brokerURL.Host) // Suffix the path with the broker's client registration handler. reqURL := r.brokerURL.ResolveReference(&url.URL{Path: "client"}) req, err := http.NewRequest("POST", reqURL.String(), bytes.NewReader(encPollReq)) if err != nil { return nil, err } if len(r.fronts) != 0 { // Do domain fronting. Replace the domain in the URL's with a randomly // selected front, and store the original domain the HTTP Host header. rand.Seed(time.Now().UnixNano()) front := r.fronts[rand.Intn(len(r.fronts))] log.Println("Front URL: ", front) req.Host = req.URL.Host req.URL.Host = front } resp, err := r.transport.RoundTrip(req) if err != nil { return nil, err } defer resp.Body.Close() log.Printf("HTTP rendezvous response: %s", resp.Status) if resp.StatusCode != http.StatusOK { return nil, errors.New(brokerErrorUnexpected) } return limitedRead(resp.Body, readLimit) } func limitedRead(r io.Reader, limit int64) ([]byte, error) { p, err := io.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 } 07070100000023000081A400000000000000000000000165F88C5000000F90000000000000000000000000000000000000002D00000000snowflake-2.9.2/client/lib/rendezvous_sqs.gopackage snowflake_client import ( "context" "crypto/rand" "encoding/hex" "log" "net/http" "net/url" "regexp" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/aws-sdk-go-v2/service/sqs/types" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/sqsclient" sqscreds "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/sqscreds/lib" ) type sqsRendezvous struct { transport http.RoundTripper sqsClient sqsclient.SQSClient sqsURL *url.URL timeout time.Duration numRetries int } func newSQSRendezvous(sqsQueue string, sqsCredsStr string, transport http.RoundTripper) (*sqsRendezvous, error) { sqsURL, err := url.Parse(sqsQueue) if err != nil { return nil, err } sqsCreds, err := sqscreds.AwsCredsFromBase64(sqsCredsStr) if err != nil { return nil, err } queueURL := sqsURL.String() hostName := sqsURL.Hostname() regionRegex, _ := regexp.Compile(`^sqs\.([\w-]+)\.amazonaws\.com$`) res := regionRegex.FindStringSubmatch(hostName) if len(res) < 2 { log.Fatal("Could not extract AWS region from SQS URL. Ensure that the SQS Queue URL provided is valid.") } region := res[1] cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithCredentialsProvider( credentials.NewStaticCredentialsProvider(sqsCreds.AwsAccessKeyId, sqsCreds.AwsSecretKey, ""), ), config.WithRegion(region), ) if err != nil { log.Fatal(err) } client := sqs.NewFromConfig(cfg) log.Println("Queue URL: ", queueURL) return &sqsRendezvous{ transport: transport, sqsClient: client, sqsURL: sqsURL, timeout: time.Second, numRetries: 5, }, nil } func (r *sqsRendezvous) Exchange(encPollReq []byte) ([]byte, error) { log.Println("Negotiating via SQS Queue rendezvous...") var id [8]byte _, err := rand.Read(id[:]) if err != nil { return nil, err } sqsClientID := hex.EncodeToString(id[:]) log.Println("SQS Client ID for rendezvous: " + sqsClientID) _, err = r.sqsClient.SendMessage(context.TODO(), &sqs.SendMessageInput{ MessageAttributes: map[string]types.MessageAttributeValue{ "ClientID": { DataType: aws.String("String"), StringValue: aws.String(sqsClientID), }, }, MessageBody: aws.String(string(encPollReq)), QueueUrl: aws.String(r.sqsURL.String()), }) if err != nil { return nil, err } time.Sleep(r.timeout) // wait for client queue to be created by the broker var responseQueueURL *string for i := 0; i < r.numRetries; i++ { // The SQS queue corresponding to the client where the SDP Answer will be placed // may not be created yet. We will retry up to 5 times before we error out. var res *sqs.GetQueueUrlOutput res, err = r.sqsClient.GetQueueUrl(context.TODO(), &sqs.GetQueueUrlInput{ QueueName: aws.String("snowflake-client-" + sqsClientID), }) if err != nil { log.Println(err) log.Printf("Attempt %d of %d to retrieve URL of response SQS queue failed.\n", i+1, r.numRetries) time.Sleep(r.timeout) } else { responseQueueURL = res.QueueUrl break } } if err != nil { return nil, err } var answer string for i := 0; i < r.numRetries; i++ { // Waiting for SDP Answer from proxy to be placed in SQS queue. // We will retry upt to 5 times before we error out. res, err := r.sqsClient.ReceiveMessage(context.TODO(), &sqs.ReceiveMessageInput{ QueueUrl: responseQueueURL, MaxNumberOfMessages: 1, WaitTimeSeconds: 20, }) if err != nil { return nil, err } if len(res.Messages) == 0 { log.Printf("Attempt %d of %d to receive message from response SQS queue failed. No message found in queue.\n", i+1, r.numRetries) delay := float64(i)/2.0 + 1 time.Sleep(time.Duration(delay*1000) * (r.timeout / 1000)) } else { answer = *res.Messages[0].Body break } } return []byte(answer), nil } 07070100000024000081A400000000000000000000000165F88C50000038B9000000000000000000000000000000000000002E00000000snowflake-2.9.2/client/lib/rendezvous_test.gopackage snowflake_client import ( "bytes" "errors" "fmt" "io" "net/http" "net/url" "testing" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/aws-sdk-go-v2/service/sqs/types" "github.com/golang/mock/gomock" . "github.com/smartystreets/goconvey/convey" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/amp" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/nat" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/sqsclient" ) // mockTransport's RoundTrip method returns a response with a fake status and // body. type mockTransport struct { statusCode int body []byte } func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) { return &http.Response{ Status: fmt.Sprintf("%d %s", t.statusCode, http.StatusText(t.statusCode)), StatusCode: t.statusCode, Body: io.NopCloser(bytes.NewReader(t.body)), }, nil } // errorTransport's RoundTrip method returns an error. type errorTransport struct { err error } func (t errorTransport) RoundTrip(req *http.Request) (*http.Response, error) { return nil, t.err } // makeEncPollReq returns an encoded client poll request containing a given // offer. func makeEncPollReq(offer string) []byte { encPollReq, err := (&messages.ClientPollRequest{ Offer: offer, NAT: nat.NATUnknown, }).EncodeClientPollRequest() if err != nil { panic(err) } return encPollReq } // makeEncPollResp returns an encoded client poll response with given answer and // error strings. func makeEncPollResp(answer, errorStr string) []byte { encPollResp, err := (&messages.ClientPollResponse{ Answer: answer, Error: errorStr, }).EncodePollResponse() if err != nil { panic(err) } return encPollResp } var fakeEncPollReq = makeEncPollReq(`{"type":"offer","sdp":"test"}`) func TestHTTPRendezvous(t *testing.T) { Convey("HTTP rendezvous", t, func() { Convey("Construct httpRendezvous with no front domain", func() { transport := &mockTransport{http.StatusOK, []byte{}} rend, err := newHTTPRendezvous("http://test.broker", []string{}, transport) So(err, ShouldBeNil) So(rend.brokerURL, ShouldNotBeNil) So(rend.brokerURL.Host, ShouldResemble, "test.broker") So(rend.fronts, ShouldEqual, []string{}) So(rend.transport, ShouldEqual, transport) }) Convey("Construct httpRendezvous *with* front domain", func() { transport := &mockTransport{http.StatusOK, []byte{}} rend, err := newHTTPRendezvous("http://test.broker", []string{"front"}, transport) So(err, ShouldBeNil) So(rend.brokerURL, ShouldNotBeNil) So(rend.brokerURL.Host, ShouldResemble, "test.broker") So(rend.fronts, ShouldContain, "front") So(rend.transport, ShouldEqual, transport) }) Convey("httpRendezvous.Exchange responds with answer", func() { fakeEncPollResp := makeEncPollResp( `{"answer": "{\"type\":\"answer\",\"sdp\":\"fake\"}" }`, "", ) rend, err := newHTTPRendezvous("http://test.broker", []string{}, &mockTransport{http.StatusOK, fakeEncPollResp}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) So(err, ShouldBeNil) So(answer, ShouldResemble, fakeEncPollResp) }) Convey("httpRendezvous.Exchange responds with no answer", func() { fakeEncPollResp := makeEncPollResp( "", `{"error": "no snowflake proxies currently available"}`, ) rend, err := newHTTPRendezvous("http://test.broker", []string{}, &mockTransport{http.StatusOK, fakeEncPollResp}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) So(err, ShouldBeNil) So(answer, ShouldResemble, fakeEncPollResp) }) Convey("httpRendezvous.Exchange fails with unexpected HTTP status code", func() { rend, err := newHTTPRendezvous("http://test.broker", []string{}, &mockTransport{http.StatusInternalServerError, []byte{}}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) So(err, ShouldNotBeNil) So(answer, ShouldBeNil) So(err.Error(), ShouldResemble, brokerErrorUnexpected) }) Convey("httpRendezvous.Exchange fails with error", func() { transportErr := errors.New("error") rend, err := newHTTPRendezvous("http://test.broker", []string{}, &errorTransport{err: transportErr}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) So(err, ShouldEqual, transportErr) So(answer, ShouldBeNil) }) Convey("httpRendezvous.Exchange fails with large read", func() { rend, err := newHTTPRendezvous("http://test.broker", []string{}, &mockTransport{http.StatusOK, make([]byte, readLimit+1)}) So(err, ShouldBeNil) _, err = rend.Exchange(fakeEncPollReq) So(err, ShouldEqual, io.ErrUnexpectedEOF) }) }) } func ampArmorEncode(p []byte) []byte { var buf bytes.Buffer enc, err := amp.NewArmorEncoder(&buf) if err != nil { panic(err) } _, err = enc.Write(p) if err != nil { panic(err) } err = enc.Close() if err != nil { panic(err) } return buf.Bytes() } func TestAMPCacheRendezvous(t *testing.T) { Convey("AMP cache rendezvous", t, func() { Convey("Construct ampCacheRendezvous with no cache and no front domain", func() { transport := &mockTransport{http.StatusOK, []byte{}} rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{}, transport) So(err, ShouldBeNil) So(rend.brokerURL, ShouldNotBeNil) So(rend.brokerURL.String(), ShouldResemble, "http://test.broker") So(rend.cacheURL, ShouldBeNil) So(rend.fronts, ShouldResemble, []string{}) So(rend.transport, ShouldEqual, transport) }) Convey("Construct ampCacheRendezvous with cache and no front domain", func() { transport := &mockTransport{http.StatusOK, []byte{}} rend, err := newAMPCacheRendezvous("http://test.broker", "https://amp.cache/", []string{}, transport) So(err, ShouldBeNil) So(rend.brokerURL, ShouldNotBeNil) So(rend.brokerURL.String(), ShouldResemble, "http://test.broker") So(rend.cacheURL, ShouldNotBeNil) So(rend.cacheURL.String(), ShouldResemble, "https://amp.cache/") So(rend.fronts, ShouldResemble, []string{}) So(rend.transport, ShouldEqual, transport) }) Convey("Construct ampCacheRendezvous with no cache and front domain", func() { transport := &mockTransport{http.StatusOK, []byte{}} rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{"front"}, transport) So(err, ShouldBeNil) So(rend.brokerURL, ShouldNotBeNil) So(rend.brokerURL.String(), ShouldResemble, "http://test.broker") So(rend.cacheURL, ShouldBeNil) So(rend.fronts, ShouldContain, "front") So(rend.transport, ShouldEqual, transport) }) Convey("Construct ampCacheRendezvous with cache and front domain", func() { transport := &mockTransport{http.StatusOK, []byte{}} rend, err := newAMPCacheRendezvous("http://test.broker", "https://amp.cache/", []string{"front"}, transport) So(err, ShouldBeNil) So(rend.brokerURL, ShouldNotBeNil) So(rend.brokerURL.String(), ShouldResemble, "http://test.broker") So(rend.cacheURL, ShouldNotBeNil) So(rend.cacheURL.String(), ShouldResemble, "https://amp.cache/") So(rend.fronts, ShouldContain, "front") So(rend.transport, ShouldEqual, transport) }) Convey("ampCacheRendezvous.Exchange responds with answer", func() { fakeEncPollResp := makeEncPollResp( `{"answer": "{\"type\":\"answer\",\"sdp\":\"fake\"}" }`, "", ) rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{}, &mockTransport{http.StatusOK, ampArmorEncode(fakeEncPollResp)}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) So(err, ShouldBeNil) So(answer, ShouldResemble, fakeEncPollResp) }) Convey("ampCacheRendezvous.Exchange responds with no answer", func() { fakeEncPollResp := makeEncPollResp( "", `{"error": "no snowflake proxies currently available"}`, ) rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{}, &mockTransport{http.StatusOK, ampArmorEncode(fakeEncPollResp)}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) So(err, ShouldBeNil) So(answer, ShouldResemble, fakeEncPollResp) }) Convey("ampCacheRendezvous.Exchange fails with unexpected HTTP status code", func() { rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{}, &mockTransport{http.StatusInternalServerError, []byte{}}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) So(err, ShouldNotBeNil) So(answer, ShouldBeNil) So(err.Error(), ShouldResemble, brokerErrorUnexpected) }) Convey("ampCacheRendezvous.Exchange fails with error", func() { transportErr := errors.New("error") rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{}, &errorTransport{err: transportErr}) So(err, ShouldBeNil) answer, err := rend.Exchange(fakeEncPollReq) So(err, ShouldEqual, transportErr) So(answer, ShouldBeNil) }) Convey("ampCacheRendezvous.Exchange fails with large read", func() { // readLimit should apply to the raw HTTP body, not the // encoded bytes. Encode readLimit bytes—the encoded // size will be larger—and try to read the body. It // should fail. rend, err := newAMPCacheRendezvous("http://test.broker", "", []string{}, &mockTransport{http.StatusOK, ampArmorEncode(make([]byte, readLimit))}) So(err, ShouldBeNil) _, err = rend.Exchange(fakeEncPollReq) // We may get io.ErrUnexpectedEOF here, or something // like "missing </pre> tag". So(err, ShouldNotBeNil) }) }) } func TestSQSRendezvous(t *testing.T) { Convey("SQS Rendezvous", t, func() { var sendMessageInput *sqs.SendMessageInput var getQueueUrlInput *sqs.GetQueueUrlInput Convey("Construct SQS queue rendezvous", func() { transport := &mockTransport{http.StatusOK, []byte{}} rend, err := newSQSRendezvous("https://sqs.us-east-1.amazonaws.com", "eyJhd3MtYWNjZXNzLWtleS1pZCI6InRlc3QtYWNjZXNzLWtleSIsImF3cy1zZWNyZXQta2V5IjoidGVzdC1zZWNyZXQta2V5In0=", transport) So(err, ShouldBeNil) So(rend.sqsClient, ShouldNotBeNil) So(rend.sqsURL, ShouldNotBeNil) So(rend.sqsURL.String(), ShouldResemble, "https://sqs.us-east-1.amazonaws.com") }) ctrl := gomock.NewController(t) mockSqsClient := sqsclient.NewMockSQSClient(ctrl) responseQueueURL := "https://sqs.us-east-1.amazonaws.com/testing" sqsUrl, _ := url.Parse("https://sqs.us-east-1.amazonaws.com/broker") fakeEncPollResp := makeEncPollResp( `{"answer": "{\"type\":\"answer\",\"sdp\":\"fake\"}" }`, "", ) sqsRendezvous := sqsRendezvous{ transport: &mockTransport{http.StatusOK, []byte{}}, sqsClient: mockSqsClient, sqsURL: sqsUrl, timeout: 0, numRetries: 5, } Convey("sqsRendezvous.Exchange responds with answer", func() { sqsClientId := "" mockSqsClient.EXPECT().SendMessage(gomock.Any(), gomock.AssignableToTypeOf(sendMessageInput)).Do(func(ctx interface{}, input *sqs.SendMessageInput, optFns ...interface{}) { So(*input.MessageBody, ShouldEqual, string(fakeEncPollResp)) So(*input.QueueUrl, ShouldEqual, sqsUrl.String()) sqsClientId = *input.MessageAttributes["ClientID"].StringValue }) mockSqsClient.EXPECT().GetQueueUrl(gomock.Any(), gomock.AssignableToTypeOf(getQueueUrlInput)).DoAndReturn(func(ctx interface{}, input *sqs.GetQueueUrlInput, optFns ...interface{}) (*sqs.GetQueueUrlOutput, error) { So(*input.QueueName, ShouldEqual, "snowflake-client-"+sqsClientId) return &sqs.GetQueueUrlOutput{ QueueUrl: aws.String(responseQueueURL), }, nil }) mockSqsClient.EXPECT().ReceiveMessage(gomock.Any(), gomock.Eq(&sqs.ReceiveMessageInput{ QueueUrl: &responseQueueURL, MaxNumberOfMessages: 1, WaitTimeSeconds: 20, })).Return(&sqs.ReceiveMessageOutput{ Messages: []types.Message{{Body: aws.String("answer")}}, }, nil) answer, err := sqsRendezvous.Exchange(fakeEncPollResp) So(answer, ShouldEqual, []byte("answer")) So(err, ShouldBeNil) }) Convey("sqsRendezvous.Exchange cannot get queue url", func() { sqsClientId := "" mockSqsClient.EXPECT().SendMessage(gomock.Any(), gomock.AssignableToTypeOf(sendMessageInput)).Do(func(ctx interface{}, input *sqs.SendMessageInput, optFns ...interface{}) { So(*input.MessageBody, ShouldEqual, string(fakeEncPollResp)) So(*input.QueueUrl, ShouldEqual, sqsUrl.String()) sqsClientId = *input.MessageAttributes["ClientID"].StringValue }) for i := 0; i < sqsRendezvous.numRetries; i++ { mockSqsClient.EXPECT().GetQueueUrl(gomock.Any(), gomock.AssignableToTypeOf(getQueueUrlInput)).DoAndReturn(func(ctx interface{}, input *sqs.GetQueueUrlInput, optFns ...interface{}) (*sqs.GetQueueUrlOutput, error) { So(*input.QueueName, ShouldEqual, "snowflake-client-"+sqsClientId) return nil, errors.New("test error") }) } answer, err := sqsRendezvous.Exchange(fakeEncPollResp) So(answer, ShouldBeNil) So(err, ShouldNotBeNil) So(err, ShouldEqual, errors.New("test error")) }) Convey("sqsRendezvous.Exchange does not receive answer", func() { sqsClientId := "" mockSqsClient.EXPECT().SendMessage(gomock.Any(), gomock.AssignableToTypeOf(sendMessageInput)).Do(func(ctx interface{}, input *sqs.SendMessageInput, optFns ...interface{}) { So(*input.MessageBody, ShouldEqual, string(fakeEncPollResp)) So(*input.QueueUrl, ShouldEqual, sqsUrl.String()) sqsClientId = *input.MessageAttributes["ClientID"].StringValue }) mockSqsClient.EXPECT().GetQueueUrl(gomock.Any(), gomock.AssignableToTypeOf(getQueueUrlInput)).DoAndReturn(func(ctx interface{}, input *sqs.GetQueueUrlInput, optFns ...interface{}) (*sqs.GetQueueUrlOutput, error) { So(*input.QueueName, ShouldEqual, "snowflake-client-"+sqsClientId) return &sqs.GetQueueUrlOutput{ QueueUrl: aws.String(responseQueueURL), }, nil }) for i := 0; i < sqsRendezvous.numRetries; i++ { mockSqsClient.EXPECT().ReceiveMessage(gomock.Any(), gomock.Eq(&sqs.ReceiveMessageInput{ QueueUrl: &responseQueueURL, MaxNumberOfMessages: 1, WaitTimeSeconds: 20, })).Return(&sqs.ReceiveMessageOutput{ Messages: []types.Message{}, }, nil) } answer, err := sqsRendezvous.Exchange(fakeEncPollResp) So(answer, ShouldEqual, []byte{}) So(err, ShouldBeNil) }) }) } 07070100000025000081A400000000000000000000000165F88C500000373C000000000000000000000000000000000000002800000000snowflake-2.9.2/client/lib/snowflake.go/* Package snowflake_client implements functionality necessary for a client to establish a connection to a server using Snowflake. Included in the package is a Transport type that implements the Pluggable Transports v2.1 Go API specification. To use Snowflake, you must first create a client from a configuration: config := snowflake_client.ClientConfig{ BrokerURL: "https://snowflake-broker.example.com", FrontDomain: "https://friendlyfrontdomain.net", // ... } transport, err := snowflake_client.NewSnowflakeClient(config) if err != nil { // handle error } The Dial function connects to a Snowflake server: conn, err := transport.Dial() if err != nil { // handle error } defer conn.Close() */ package snowflake_client import ( "context" "errors" "log" "math/rand" "net" "net/url" "strings" "time" "github.com/pion/ice/v2" "github.com/pion/webrtc/v3" "github.com/xtaci/kcp-go/v5" "github.com/xtaci/smux" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/event" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/nat" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" ) const ( // ReconnectTimeout is the time a Snowflake client will wait before collecting // more snowflakes. ReconnectTimeout = 10 * time.Second // SnowflakeTimeout is the time a Snowflake client will wait before determining that // a remote snowflake has been disconnected. If no new messages are sent or received // in this time period, the client will terminate the connection with the remote // peer and collect a new snowflake. SnowflakeTimeout = 20 * time.Second // DataChannelTimeout is how long the client will wait for the OnOpen callback // on a newly created DataChannel. DataChannelTimeout = 10 * time.Second // WindowSize is the number of packets in the send and receive window of a KCP connection. WindowSize = 65535 // StreamSize controls the maximum amount of in flight data between a client and server. StreamSize = 1048576 // 1MB ) 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 // EventDispatcher is the event bus for snowflake events. // When an important event happens, it will be distributed here. eventDispatcher event.SnowflakeEventDispatcher } // ClientConfig defines how the SnowflakeClient will connect to the broker and Snowflake proxies. type ClientConfig struct { // BrokerURL is the full URL of the Snowflake broker that the client will connect to. BrokerURL string // AmpCacheURL is the full URL of a valid AMP cache. A nonzero value indicates // that AMP cache will be used as the rendezvous method with the broker. AmpCacheURL string // SQSQueueURL is the full URL of an AWS SQS Queue. A nonzero value indicates // that SQS queue will be used as the rendezvous method with the broker. SQSQueueURL string // Base64 encoded string of the credentials containing access Key ID and secret key used to access the AWS SQS Qeueue SQSCredsStr string // FrontDomain is the full URL of an optional front domain that can be used with either // the AMP cache or HTTP domain fronting rendezvous method. FrontDomain string // ICEAddresses are a slice of ICE server URLs that will be used for NAT traversal and // the creation of the client's WebRTC SDP offer. FrontDomains []string // ICEAddresses are a slice of ICE server URLs that will be used for NAT traversal and // the creation of the client's WebRTC SDP offer. ICEAddresses []string // KeepLocalAddresses is an optional setting that will prevent the removal of local or // invalid addresses from the client's SDP offer. This is useful for local deployments // and testing. KeepLocalAddresses bool // Max is the maximum number of snowflake proxy peers that the client should attempt to // connect to. Defaults to 1. Max int // UTLSClientID is the type of user application that snowflake should imitate. // If an empty value is provided, it will use Go's default TLS implementation UTLSClientID string // UTLSRemoveSNI is the flag to control whether SNI should be removed from Client Hello // when uTLS is used. UTLSRemoveSNI bool // BridgeFingerprint is the fingerprint of the bridge that the client will eventually // connect to, as specified in the Bridge line of the torrc. BridgeFingerprint string // CommunicationProxy is the proxy address for network communication CommunicationProxy *url.URL } // NewSnowflakeClient creates 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(config ClientConfig) (*Transport, error) { log.Println("\n\n\n --- Starting Snowflake Client ---") iceServers := parseIceServers(config.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, " ")) } // Maintain backwards compatability with old FrontDomain field of ClientConfig if (len(config.FrontDomains) == 0) && (config.FrontDomain != "") { config.FrontDomains = []string{config.FrontDomain} } // Rendezvous with broker using the given parameters. broker, err := newBrokerChannelFromConfig(config) if err != nil { return nil, err } go updateNATType(iceServers, broker, config.CommunicationProxy) max := 1 if config.Max > max { max = config.Max } eventsLogger := event.NewSnowflakeEventDispatcher() transport := &Transport{dialer: NewWebRTCDialerWithEventsAndProxy(broker, iceServers, max, eventsLogger, config.CommunicationProxy), eventDispatcher: eventsLogger} return transport, nil } // Dial creates a new Snowflake connection. // Dial starts the collection of snowflakes and returns a SnowflakeConn that is a // wrapper around a smux.Stream that will reliably deliver data to a Snowflake // server through one or more snowflake proxies. 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 } func (t *Transport) AddSnowflakeEventListener(receiver event.SnowflakeEventReceiver) { t.eventDispatcher.AddSnowflakeEventListener(receiver) } func (t *Transport) RemoveSnowflakeEventListener(receiver event.SnowflakeEventReceiver) { t.eventDispatcher.RemoveSnowflakeEventListener(receiver) } // SetRendezvousMethod sets the rendezvous method to the Snowflake broker. func (t *Transport) SetRendezvousMethod(r RendezvousMethod) { t.dialer.Rendezvous = r } // SnowflakeConn is a reliable connection to a snowflake server that implements net.Conn. type SnowflakeConn struct { *smux.Stream sess *smux.Session pconn net.PacketConn snowflakes *Peers } // Close closes the connection. // // The collection of snowflake proxies for this connection is stopped. 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 compatible with RFC 5780 func updateNATType(servers []webrtc.ICEServer, broker *BrokerChannel, proxy *url.URL) { var restrictedNAT bool var err error for _, server := range servers { addr := strings.TrimPrefix(server.URLs[0], "stun:") restrictedNAT, err = nat.CheckIfRestrictedNATWithProxy(addr, proxy) if err != nil { log.Printf("Warning: NAT checking failed for server at %s: %s", addr, err) } else { 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 _, address := range addresses { address = strings.TrimSpace(address) // ice.ParseURL recognizes many types of ICE servers, // but we only support stun over UDP currently u, err := url.Parse(address) if err != nil { log.Printf("Warning: Parsing ICE server %v resulted in error: %v, skipping", address, err) continue } if u.Scheme != "stun" { log.Printf("Warning: Only stun: (STUN over UDP) servers are supported currently, skipping %v", address) continue } // add default port, other sanity checks parsedURL, err := ice.ParseURL(address) if err != nil { log.Printf("Warning: Parsing ICE server %v resulted in error: %v, skipping", address, err) continue } servers = append(servers, webrtc.ICEServer{ URLs: []string{parsedURL.String()}, }) } 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(WindowSize, WindowSize) // 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 smuxConfig.MaxStreamBuffer = StreamSize 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 } } } 07070100000026000081A400000000000000000000000165F88C500000076F000000000000000000000000000000000000002A00000000snowflake-2.9.2/client/lib/turbotunnel.gopackage snowflake_client import ( "bufio" "errors" "io" "net" "time" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/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) { n, err := encapsulation.ReadData(c.ReadWriteCloser, p) if err == io.ErrShortBuffer { err = nil } return n, c.remoteAddr, err } // 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 } 07070100000027000081A400000000000000000000000165F88C50000005E6000000000000000000000000000000000000002300000000snowflake-2.9.2/client/lib/util.gopackage snowflake_client import ( "log" "time" ) const ( LogTimeInterval = 5 * time.Second ) type bytesLogger interface { addOutbound(int64) addInbound(int64) } // Default bytesLogger does nothing. type bytesNullLogger struct{} func (b bytesNullLogger) addOutbound(amount int64) {} func (b bytesNullLogger) addInbound(amount int64) {} // bytesSyncLogger uses channels to safely log from multiple sources with output // occuring at reasonable intervals. type bytesSyncLogger struct { outboundChan chan int64 inboundChan chan int64 } // newBytesSyncLogger returns a new bytesSyncLogger and starts it loggin. func newBytesSyncLogger() *bytesSyncLogger { b := &bytesSyncLogger{ outboundChan: make(chan int64, 5), inboundChan: make(chan int64, 5), } go b.log() return b } func (b *bytesSyncLogger) log() { var outbound, inbound int64 var 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 int64) { b.outboundChan <- amount } func (b *bytesSyncLogger) addInbound(amount int64) { b.inboundChan <- amount } 07070100000028000081A400000000000000000000000165F88C500000249F000000000000000000000000000000000000002500000000snowflake-2.9.2/client/lib/webrtc.gopackage snowflake_client import ( "crypto/rand" "encoding/hex" "errors" "fmt" "io" "log" "net/url" "strings" "sync" "time" "github.com/pion/ice/v2" "github.com/pion/transport/v2" "github.com/pion/transport/v2/stdnet" "github.com/pion/webrtc/v3" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/event" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/proxy" ) // WebRTCPeer represents a WebRTC connection to a remote snowflake proxy. // // Each WebRTCPeer only ever has one DataChannel that is used as the peer's transport. 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 eventsLogger event.SnowflakeEventReceiver proxy *url.URL } // Deprecated: Use NewWebRTCPeerWithEventsAndProxy Instead. func NewWebRTCPeer( config *webrtc.Configuration, broker *BrokerChannel, ) (*WebRTCPeer, error) { return NewWebRTCPeerWithEventsAndProxy(config, broker, nil, nil) } // Deprecated: Use NewWebRTCPeerWithEventsAndProxy Instead. func NewWebRTCPeerWithEvents( config *webrtc.Configuration, broker *BrokerChannel, eventsLogger event.SnowflakeEventReceiver, ) (*WebRTCPeer, error) { return NewWebRTCPeerWithEventsAndProxy(config, broker, eventsLogger, nil) } // NewWebRTCPeerWithEventsAndProxy constructs a WebRTC PeerConnection to a snowflake proxy. // // The creation of the peer handles the signaling to the Snowflake broker, including // the exchange of SDP information, the creation of a PeerConnection, and the establishment // of a DataChannel to the Snowflake proxy. func NewWebRTCPeerWithEventsAndProxy( config *webrtc.Configuration, broker *BrokerChannel, eventsLogger event.SnowflakeEventReceiver, proxy *url.URL, ) (*WebRTCPeer, error) { if eventsLogger == nil { eventsLogger = event.NewSnowflakeEventDispatcher() } 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() connection.eventsLogger = eventsLogger connection.proxy = proxy 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(int64(len(b))) return len(b), nil } // Closed returns a boolean indicated whether the peer is closed. func (c *WebRTCPeer) Closed() bool { select { case <-c.closed: return true default: } return false } // Close closes the connection the snowflake proxy. 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) err := errors.New("no messages received, closing stale connection") c.eventsLogger.OnNewSnowflakeEvent(event.EventOnSnowflakeConnectionFailed{Error: err}) c.Close() return } select { case <-c.closed: return case <-time.After(time.Second): } } } // connect does the bulk of the work: gather ICE candidates, send the SDP offer to broker, // receive an answer from broker, and wait for data channel to open func (c *WebRTCPeer) connect(config *webrtc.Configuration, broker *BrokerChannel) error { log.Println(c.id, " connecting...") err := c.preparePeerConnection(config) localDescription := c.pc.LocalDescription() c.eventsLogger.OnNewSnowflakeEvent(event.EventOnOfferCreated{ WebRTCLocalDescription: localDescription, Error: err, }) if err != nil { return err } answer, err := broker.Negotiate(localDescription) c.eventsLogger.OnNewSnowflakeEvent(event.EventOnBrokerRendezvous{ WebRTCRemoteDescription: answer, Error: err, }) 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() err = errors.New("timeout waiting for DataChannel.OnOpen") c.eventsLogger.OnNewSnowflakeEvent(event.EventOnSnowflakeConnectionFailed{Error: err}) return err } go c.checkForStaleness(SnowflakeTimeout) return nil } // preparePeerConnection creates a new WebRTC PeerConnection and returns it // after non-trickle ICE candidate gathering is complete. func (c *WebRTCPeer) preparePeerConnection(config *webrtc.Configuration) error { var err error s := webrtc.SettingEngine{} s.SetICEMulticastDNSMode(ice.MulticastDNSModeDisabled) // Use the SetNet setting https://pkg.go.dev/github.com/pion/webrtc/v3#SettingEngine.SetNet // to get snowflake working in shadow (where the AF_NETLINK family is not implemented). // These two lines of code functionally revert a new change in pion by silently ignoring // when net.Interfaces() fails, rather than throwing an error var vnet transport.Net vnet, _ = stdnet.NewNet() if c.proxy != nil { if err = proxy.CheckProxyProtocolSupport(c.proxy); err != nil { return err } socksClient := proxy.NewSocks5UDPClient(c.proxy) vnet = proxy.NewTransportWrapper(&socksClient, vnet) } s.SetNet(vnet) api := webrtc.NewAPI(webrtc.WithSettingEngine(s)) c.pc, err = api.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#a-data-channel-is-no-longer-implicitly-created-with-a-peerconnection dc, err := c.pc.CreateDataChannel(c.id, dataChannelOptions) if err != nil { log.Printf("CreateDataChannel ERROR: %s", err) return err } dc.OnOpen(func() { c.eventsLogger.OnNewSnowflakeEvent(event.EventOnSnowflakeConnected{}) log.Println("WebRTC: DataChannel.OnOpen") close(c.open) }) dc.OnClose(func() { log.Println("WebRTC: DataChannel.OnClose") c.Close() }) dc.OnError(func(err error) { c.eventsLogger.OnNewSnowflakeEvent(event.EventOnSnowflakeConnectionFailed{Error: err}) }) 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(int64(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") 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") // Allow candidates to accumulate until ICEGatheringStateComplete. done := webrtc.GatheringCompletePromise(c.pc) // Start gathering candidates err = c.pc.SetLocalDescription(offer) if err != nil { log.Println("Failed to apply offer", err) c.pc.Close() return err } log.Println("WebRTC: Set local description") <-done // Wait for ICE candidate gathering to complete. if !strings.Contains(c.pc.LocalDescription().SDP, "\na=candidate:") { return fmt.Errorf("SDP offer contains no candidate") } return nil } // cleanup closes 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...") } } } 07070100000029000081A400000000000000000000000165F88C50000024DB000000000000000000000000000000000000002400000000snowflake-2.9.2/client/snowflake.go// Client transport plugin for the Snowflake pluggable transport. package main import ( "flag" "fmt" "io" "log" "net" "os" "os/signal" "path/filepath" "strconv" "strings" "sync" "syscall" pt "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib" sf "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/client/lib" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/event" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/proxy" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/safelog" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/version" ) const ( DefaultSnowflakeCapacity = 1 ) type ptEventLogger struct { } func NewPTEventLogger() event.SnowflakeEventReceiver { return &ptEventLogger{} } func (p ptEventLogger) OnNewSnowflakeEvent(e event.SnowflakeEvent) { pt.Log(pt.LogSeverityNotice, e.String()) } // 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, config sf.ClientConfig, 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() // Check to see if our command line options are overriden by SOCKS options if arg, ok := conn.Req.Args.Get("ampcache"); ok { config.AmpCacheURL = arg } if arg, ok := conn.Req.Args.Get("sqsqueue"); ok { config.SQSQueueURL = arg } if arg, ok := conn.Req.Args.Get("sqscreds"); ok { config.SQSCredsStr = arg } if arg, ok := conn.Req.Args.Get("fronts"); ok { if arg != "" { config.FrontDomains = strings.Split(strings.TrimSpace(arg), ",") } } else if arg, ok := conn.Req.Args.Get("front"); ok { config.FrontDomains = strings.Split(strings.TrimSpace(arg), ",") } if arg, ok := conn.Req.Args.Get("ice"); ok { config.ICEAddresses = strings.Split(strings.TrimSpace(arg), ",") } if arg, ok := conn.Req.Args.Get("max"); ok { max, err := strconv.Atoi(arg) if err != nil { conn.Reject() log.Println("Invalid SOCKS arg: max=", arg) return } config.Max = max } if arg, ok := conn.Req.Args.Get("url"); ok { config.BrokerURL = arg } if arg, ok := conn.Req.Args.Get("utls-nosni"); ok { switch strings.ToLower(arg) { case "true": fallthrough case "yes": config.UTLSRemoveSNI = true } } if arg, ok := conn.Req.Args.Get("utls-imitate"); ok { config.UTLSClientID = arg } if arg, ok := conn.Req.Args.Get("fingerprint"); ok { config.BridgeFingerprint = arg } transport, err := sf.NewSnowflakeClient(config) if err != nil { conn.Reject() log.Println("Failed to start snowflake transport: ", err) return } transport.AddSnowflakeEventListener(NewPTEventLogger()) 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") frontDomainsCommas := flag.String("fronts", "", "comma-separated list of front domains") ampCacheURL := flag.String("ampcache", "", "URL of AMP cache to use as a proxy for signaling") sqsQueueURL := flag.String("sqsqueue", "", "URL of SQS Queue to use as a proxy for signaling") sqsCredsStr := flag.String("sqscreds", "", "credentials to access SQS Queue") 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") versionFlag := flag.Bool("version", false, "display version info to stderr and quit") // Deprecated oldLogToStateDir := flag.Bool("logToStateDir", false, "use -log-to-state-dir instead") oldKeepLocalAddresses := flag.Bool("keepLocalAddresses", false, "use -keep-local-addresses instead") flag.Parse() if *versionFlag { fmt.Fprintf(os.Stderr, "snowflake-client %s", version.ConstructResult()) os.Exit(0) } 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 = io.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}) } log.Printf("snowflake-client %s\n", version.GetVersion()) iceAddresses := strings.Split(strings.TrimSpace(*iceServersCommas), ",") var frontDomains []string if *frontDomainsCommas != "" { frontDomains = strings.Split(strings.TrimSpace(*frontDomainsCommas), ",") } // Maintain backwards compatability with legacy commandline option if (len(frontDomains) == 0) && (*frontDomain != "") { frontDomains = []string{*frontDomain} } config := sf.ClientConfig{ BrokerURL: *brokerURL, AmpCacheURL: *ampCacheURL, SQSQueueURL: *sqsQueueURL, SQSCredsStr: *sqsCredsStr, FrontDomains: frontDomains, ICEAddresses: iceAddresses, KeepLocalAddresses: *keepLocalAddresses || *oldKeepLocalAddresses, Max: *max, } // Begin goptlib client process. ptInfo, err := pt.ClientSetup(nil) if err != nil { log.Fatal(err) } if ptInfo.ProxyURL != nil { if err := proxy.CheckProxyProtocolSupport(ptInfo.ProxyURL); err != nil { pt.ProxyError("proxy is not supported:" + err.Error()) os.Exit(1) } else { config.CommunicationProxy = ptInfo.ProxyURL client := proxy.NewSocks5UDPClient(config.CommunicationProxy) conn, err := client.ListenPacket("udp", nil) if err != nil { pt.ProxyError("proxy test failure:" + err.Error()) os.Exit(1) } conn.Close() pt.ProxyDone() } } 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, config, 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(io.Discard, os.Stdin); err != nil { log.Printf("calling io.Copy(io.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.") } 0707010000002A000081A400000000000000000000000165F88C5000000470000000000000000000000000000000000000001D00000000snowflake-2.9.2/client/torrcUseBridges 1 DataDirectory datadir ClientTransportPlugin snowflake exec ./client -log snowflake.log Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ fronts=foursquare.com,github.githubassets.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn Bridge snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ fronts=foursquare.com,github.githubassets.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn SocksPort auto 0707010000002B000081A400000000000000000000000165F88C50000000A1000000000000000000000000000000000000002700000000snowflake-2.9.2/client/torrc.localhostUseBridges 1 DataDirectory datadir ClientTransportPlugin snowflake exec ./client -keep-local-addresses Bridge snowflake 192.0.2.3:1 url=http://localhost:8080/ 0707010000002C000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001700000000snowflake-2.9.2/common0707010000002D000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001B00000000snowflake-2.9.2/common/amp0707010000002E000081A400000000000000000000000165F88C5000000C9C000000000000000000000000000000000000002C00000000snowflake-2.9.2/common/amp/armor_decoder.gopackage amp import ( "bufio" "bytes" "encoding/base64" "fmt" "io" "golang.org/x/net/html" ) // ErrUnknownVersion is the error returned when the first character inside the // element encoding (but outside the base64 encoding) is not '0'. type ErrUnknownVersion byte func (err ErrUnknownVersion) Error() string { return fmt.Sprintf("unknown armor version indicator %+q", byte(err)) } func isASCIIWhitespace(b byte) bool { switch b { // https://infra.spec.whatwg.org/#ascii-whitespace case '\x09', '\x0a', '\x0c', '\x0d', '\x20': return true default: return false } } func splitASCIIWhitespace(data []byte, atEOF bool) (advance int, token []byte, err error) { var i, j int // Skip initial whitespace. for i = 0; i < len(data); i++ { if !isASCIIWhitespace(data[i]) { break } } // Look for next whitespace. for j = i; j < len(data); j++ { if isASCIIWhitespace(data[j]) { return j + 1, data[i:j], nil } } // We reached the end of data without finding more whitespace. Only // consider it a token if we are at EOF. if atEOF && i < j { return j, data[i:j], nil } // Otherwise, request more data. return i, nil, nil } func decodeToWriter(w io.Writer, r io.Reader) (int64, error) { tokenizer := html.NewTokenizer(r) // Set a memory limit on token sizes, otherwise the tokenizer will // buffer text indefinitely if it is not broken up by other token types. tokenizer.SetMaxBuf(elementSizeLimit) active := false total := int64(0) for { tt := tokenizer.Next() switch tt { case html.ErrorToken: err := tokenizer.Err() if err == io.EOF { err = nil } if err == nil && active { return total, fmt.Errorf("missing </pre> tag") } return total, err case html.TextToken: if active { // Re-join the separate chunks of text and // feed them to the decoder. scanner := bufio.NewScanner(bytes.NewReader(tokenizer.Text())) scanner.Split(splitASCIIWhitespace) for scanner.Scan() { n, err := w.Write(scanner.Bytes()) total += int64(n) if err != nil { return total, err } } if err := scanner.Err(); err != nil { return total, err } } case html.StartTagToken: tn, _ := tokenizer.TagName() if string(tn) == "pre" { if active { // nesting not allowed return total, fmt.Errorf("unexpected %s", tokenizer.Token()) } active = true } case html.EndTagToken: tn, _ := tokenizer.TagName() if string(tn) == "pre" { if !active { // stray end tag return total, fmt.Errorf("unexpected %s", tokenizer.Token()) } active = false } } } } // NewArmorDecoder returns a new AMP armor decoder. func NewArmorDecoder(r io.Reader) (io.Reader, error) { pr, pw := io.Pipe() go func() { _, err := decodeToWriter(pw, r) pw.CloseWithError(err) }() // The first byte inside the element encoding is a server–client // protocol version indicator. var version [1]byte _, err := pr.Read(version[:]) if err != nil { pr.CloseWithError(err) return nil, err } switch version[0] { case '0': return base64.NewDecoder(base64.StdEncoding, pr), nil default: err := ErrUnknownVersion(version[0]) pr.CloseWithError(err) return nil, err } } 0707010000002F000081A400000000000000000000000165F88C5000001540000000000000000000000000000000000000002C00000000snowflake-2.9.2/common/amp/armor_encoder.gopackage amp import ( "encoding/base64" "io" ) // https://amp.dev/boilerplate/ // https://amp.dev/documentation/guides-and-tutorials/learn/spec/amp-boilerplate/?format=websites // https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml/?format=websites#the-amp-html-format const ( boilerplateStart = `<!doctype html> <html amp> <head> <meta charset="utf-8"> <script async src="https://cdn.ampproject.org/v0.js"></script> <link rel="canonical" href="#"> <meta name="viewport" content="width=device-width"> <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript> </head> <body> ` boilerplateEnd = `</body> </html>` ) const ( // We restrict the amount of text may go inside an HTML element, in // order to limit the amount a decoder may have to buffer. elementSizeLimit = 32 * 1024 // The payload is conceptually a long base64-encoded string, but we // break the string into short chunks separated by whitespace. This is // to protect against modification by AMP caches, which reportedly may // truncate long words in text: // https://bugs.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/25985#note_2592348 bytesPerChunk = 32 // We set the number of chunks per element so as to stay under // elementSizeLimit. Here, we assume that there is 1 byte of whitespace // after each chunk (with an additional whitespace byte at the beginning // of the element). chunksPerElement = (elementSizeLimit - 1) / (bytesPerChunk + 1) ) // The AMP armor encoder is a chain of a base64 encoder (base64.NewEncoder) and // an HTML element encoder (elementEncoder). A top-level encoder (armorEncoder) // coordinates these two, and handles prepending and appending the AMP // boilerplate. armorEncoder's Write method writes data into the base64 encoder, // where it makes its way through the chain. // NewArmorEncoder returns a new AMP armor encoder. Anything written to the // returned io.WriteCloser will be encoded and written to w. The caller must // call Close to flush any partially written data and output the AMP boilerplate // trailer. func NewArmorEncoder(w io.Writer) (io.WriteCloser, error) { // Immediately write the AMP boilerplate header. _, err := w.Write([]byte(boilerplateStart)) if err != nil { return nil, err } element := &elementEncoder{w: w} // Write a server–client protocol version indicator, outside the base64 // layer. _, err = element.Write([]byte{'0'}) if err != nil { return nil, err } base64 := base64.NewEncoder(base64.StdEncoding, element) return &armorEncoder{ w: w, element: element, base64: base64, }, nil } type armorEncoder struct { base64 io.WriteCloser element *elementEncoder w io.Writer } func (enc *armorEncoder) Write(p []byte) (int, error) { // Write into the chain base64 | element | w. return enc.base64.Write(p) } func (enc *armorEncoder) Close() error { // Close the base64 encoder first, to flush out any buffered data and // the final padding. err := enc.base64.Close() if err != nil { return err } // Next, close the element encoder, to close any open elements. err = enc.element.Close() if err != nil { return err } // Finally, output the AMP boilerplate trailer. _, err = enc.w.Write([]byte(boilerplateEnd)) if err != nil { return err } return nil } // elementEncoder arranges written data into pre elements, with the text within // separated into chunks. It does no HTML encoding, so data written must not // contain any bytes that are meaningful in HTML. type elementEncoder struct { w io.Writer chunkCounter int elementCounter int } func (enc *elementEncoder) Write(p []byte) (n int, err error) { total := 0 for len(p) > 0 { if enc.elementCounter == 0 && enc.chunkCounter == 0 { _, err := enc.w.Write([]byte("<pre>\n")) if err != nil { return total, err } } n := bytesPerChunk - enc.chunkCounter if n > len(p) { n = len(p) } nn, err := enc.w.Write(p[:n]) if err != nil { return total, err } total += nn p = p[n:] enc.chunkCounter += n if enc.chunkCounter >= bytesPerChunk { enc.chunkCounter = 0 enc.elementCounter += 1 nn, err = enc.w.Write([]byte("\n")) if err != nil { return total, err } total += nn } if enc.elementCounter >= chunksPerElement { enc.elementCounter = 0 nn, err = enc.w.Write([]byte("</pre>\n")) if err != nil { return total, err } total += nn } } return total, nil } func (enc *elementEncoder) Close() error { var err error if !(enc.elementCounter == 0 && enc.chunkCounter == 0) { if enc.chunkCounter == 0 { _, err = enc.w.Write([]byte("</pre>\n")) } else { _, err = enc.w.Write([]byte("\n</pre>\n")) } } return err } 07070100000030000081A400000000000000000000000165F88C5000000E61000000000000000000000000000000000000002900000000snowflake-2.9.2/common/amp/armor_test.gopackage amp import ( "io" "math/rand" "strings" "testing" ) func armorDecodeToString(src string) (string, error) { dec, err := NewArmorDecoder(strings.NewReader(src)) if err != nil { return "", err } p, err := io.ReadAll(dec) return string(p), err } func TestArmorDecoder(t *testing.T) { for _, test := range []struct { input string expectedOutput string expectedErr bool }{ {` <pre> 0 </pre> `, "", false, }, {` <pre> 0aGVsbG8gd29ybGQK </pre> `, "hello world\n", false, }, // bad version indicator {` <pre> 1aGVsbG8gd29ybGQK </pre> `, "", true, }, // text outside <pre> elements {` 0aGVsbG8gd29ybGQK blah blah blah <pre> 0aGVsbG8gd29ybGQK </pre> 0aGVsbG8gd29ybGQK blah blah blah `, "hello world\n", false, }, {` <pre> 0QUJDREV GR0hJSkt MTU5PUFF SU1RVVld </pre> junk <pre> YWVowMTI zNDU2Nzg 5Cg = </pre> <pre> = </pre> `, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n", false, }, // no <pre> elements, hence no version indicator {` aGVsbG8gd29ybGQK blah blah blah aGVsbG8gd29ybGQK aGVsbG8gd29ybGQK blah blah blah `, "", true, }, // empty <pre> elements, hence no version indicator {` aGVsbG8gd29ybGQK blah blah blah <pre> </pre> aGVsbG8gd29ybGQK aGVsbG8gd29ybGQK<pre></pre> blah blah blah `, "", true, }, // other elements inside <pre> { "blah <pre>0aGVsb<p>G8gd29</p>ybGQK</pre>", "hello world\n", false, }, // HTML comment { "blah <!-- <pre>aGVsbG8gd29ybGQK</pre> -->", "", true, }, // all kinds of ASCII whitespace { "blah <pre>\x200\x09aG\x0aV\x0csb\x0dG8\x20gd29ybGQK</pre>", "hello world\n", false, }, // bad padding {` <pre> 0QUJDREV GR0hJSkt MTU5PUFF SU1RVVld </pre> junk <pre> YWVowMTI zNDU2Nzg 5Cg = </pre> `, "", true, }, /* // per-chunk base64 // test disabled because Go stdlib handles this incorrectly: // https://github.com/golang/go/issues/31626 { "<pre>QQ==</pre><pre>Qg==</pre>", "", true, }, */ // missing </pre> { "blah <pre></pre><pre>0aGVsbG8gd29ybGQK", "", true, }, // nested <pre> { "blah <pre>0aGVsb<pre>G8gd29</pre>ybGQK</pre>", "", true, }, } { output, err := armorDecodeToString(test.input) if test.expectedErr && err == nil { t.Errorf("%+q → (%+q, %v), expected error", test.input, output, err) continue } if !test.expectedErr && err != nil { t.Errorf("%+q → (%+q, %v), expected no error", test.input, output, err) continue } if !test.expectedErr && output != test.expectedOutput { t.Errorf("%+q → (%+q, %v), expected (%+q, %v)", test.input, output, err, test.expectedOutput, nil) continue } } } func armorRoundTrip(s string) (string, error) { var encoded strings.Builder enc, err := NewArmorEncoder(&encoded) if err != nil { return "", err } _, err = io.Copy(enc, strings.NewReader(s)) if err != nil { return "", err } err = enc.Close() if err != nil { return "", err } return armorDecodeToString(encoded.String()) } func TestArmorRoundTrip(t *testing.T) { lengths := make([]int, 0) // Test short strings and lengths around elementSizeLimit thresholds. for i := 0; i < bytesPerChunk*2; i++ { lengths = append(lengths, i) } for i := -10; i < +10; i++ { lengths = append(lengths, elementSizeLimit+i) lengths = append(lengths, 2*elementSizeLimit+i) } for _, n := range lengths { buf := make([]byte, n) rand.Read(buf) input := string(buf) output, err := armorRoundTrip(input) if err != nil { t.Errorf("length %d → error %v", n, err) continue } if output != input { t.Errorf("length %d → %+q", n, output) continue } } } 07070100000031000081A400000000000000000000000165F88C5000001C93000000000000000000000000000000000000002400000000snowflake-2.9.2/common/amp/cache.gopackage amp import ( "crypto/sha256" "encoding/base32" "fmt" "net" "net/url" "path" "strings" "golang.org/x/net/idna" ) // domainPrefixBasic does the basic domain prefix conversion. Does not do any // IDNA mapping, such as https://www.unicode.org/reports/tr46/. // // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#basic-algorithm func domainPrefixBasic(domain string) (string, error) { // 1. Punycode Decode the publisher domain. prefix, err := idna.ToUnicode(domain) if err != nil { return "", err } // 2. Replace any "-" (hyphen) character in the output of step 1 with // "--" (two hyphens). prefix = strings.Replace(prefix, "-", "--", -1) // 3. Replace any "." (dot) character in the output of step 2 with "-" // (hyphen). prefix = strings.Replace(prefix, ".", "-", -1) // 4. If the output of step 3 has a "-" (hyphen) at both positions 3 and // 4, then to the output of step 3, add a prefix of "0-" and add a // suffix of "-0". if len(prefix) >= 4 && prefix[2] == '-' && prefix[3] == '-' { prefix = "0-" + prefix + "-0" } // 5. Punycode Encode the output of step 3. return idna.ToASCII(prefix) } // Lower-case base32 without padding. var fallbackBase32Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding) // domainPrefixFallback does the fallback domain prefix conversion. The returned // base32 domain uses lower-case letters. // // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#fallback-algorithm func domainPrefixFallback(domain string) string { // The algorithm specification does not say what, exactly, we are to // take the SHA-256 of. domain is notionally an abstract Unicode // string, not a byte sequence. While // https://github.com/ampproject/amp-toolbox/blob/84cb3057e5f6c54d64369ddd285db1cb36237ee8/packages/cache-url/lib/AmpCurlUrlGenerator.js#L62 // says "Take the SHA256 of the punycode view of the domain," in reality // it hashes the UTF-8 encoding of the domain, without Punycode: // https://github.com/ampproject/amp-toolbox/blob/84cb3057e5f6c54d64369ddd285db1cb36237ee8/packages/cache-url/lib/AmpCurlUrlGenerator.js#L141 // https://github.com/ampproject/amp-toolbox/blob/84cb3057e5f6c54d64369ddd285db1cb36237ee8/packages/cache-url/lib/browser/Sha256.js#L24 // We do the same here, hashing the raw bytes of domain, presumed to be // UTF-8. // 1. Hash the publisher's domain using SHA256. h := sha256.Sum256([]byte(domain)) // 2. Base32 Escape the output of step 1. // 3. Remove the last 4 characters from the output of step 2, which are // always "=" (equals) characters. return fallbackBase32Encoding.EncodeToString(h[:]) } // domainPrefix computes the domain prefix of an AMP cache URL. // // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#domain-name-prefix func domainPrefix(domain string) string { // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#combined-algorithm // 1. Run the Basic Algorithm. If the output is a valid DNS label, // [append the Cache domain suffix and] return. Otherwise continue to // step 2. prefix, err := domainPrefixBasic(domain) // "A domain prefix is not a valid DNS label if it is longer than 63 // characters" if err == nil && len(prefix) <= 63 { return prefix } // 2. Run the Fallback Algorithm. [Append the Cache domain suffix and] // return. return domainPrefixFallback(domain) } // CacheURL computes the AMP cache URL for the publisher URL pubURL, using the // AMP cache at cacheURL. contentType is a string such as "c" or "i" that // indicates what type of serving the AMP cache is to perform. The Scheme of // pubURL must be "http" or "https". The Port of pubURL, if any, must match the // default for the scheme. cacheURL may not have RawQuery, Fragment, or // RawFragment set, because the resulting URL's query and fragment are taken // from the publisher URL. // // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/ func CacheURL(pubURL, cacheURL *url.URL, contentType string) (*url.URL, error) { // The cache URL subdomain, including the domain prefix corresponding to // the publisher URL's domain. resultHost := domainPrefix(pubURL.Hostname()) + "." + cacheURL.Hostname() if cacheURL.Port() != "" { resultHost = net.JoinHostPort(resultHost, cacheURL.Port()) } // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#url-path // The first part of the path is the cache URL's own path, if any. pathComponents := []string{cacheURL.EscapedPath()} // The next path component is the content type. We cannot encode an // empty content type, because it would result in consecutive path // separators, which would semantically combine into a single separator. if contentType == "" { return nil, fmt.Errorf("invalid content type %+q", contentType) } pathComponents = append(pathComponents, url.PathEscape(contentType)) // Then, we add an "s" path component, if the publisher URL scheme is // "https". switch pubURL.Scheme { case "http": // Do nothing. case "https": pathComponents = append(pathComponents, "s") default: return nil, fmt.Errorf("invalid scheme %+q in publisher URL", pubURL.Scheme) } // The next path component is the publisher URL's host. The AMP cache // URL format specification is not clear about whether other // subcomponents of the authority (namely userinfo and port) may appear // here. We adopt a policy of forbidding userinfo, and requiring that // the port be the default for the scheme (and then we omit the port // entirely from the returned URL). if pubURL.User != nil { return nil, fmt.Errorf("publisher URL may not contain userinfo") } if port := pubURL.Port(); port != "" { if !((pubURL.Scheme == "http" && port == "80") || (pubURL.Scheme == "https" && port == "443")) { return nil, fmt.Errorf("publisher URL port %+q is not the default for scheme %+q", port, pubURL.Scheme) } } // As with the content type, we cannot encode an empty host, because // that would result in an empty path component. if pubURL.Hostname() == "" { return nil, fmt.Errorf("invalid host %+q in publisher URL", pubURL.Hostname()) } pathComponents = append(pathComponents, url.PathEscape(pubURL.Hostname())) // Finally, we append the remainder of the original escaped path from // the publisher URL. pathComponents = append(pathComponents, pubURL.EscapedPath()) resultRawPath := path.Join(pathComponents...) resultPath, err := url.PathUnescape(resultRawPath) if err != nil { return nil, err } // The query and fragment of the returned URL always come from pubURL. // Any query or fragment of cacheURL would be ignored. Return an error // if either is set. if cacheURL.RawQuery != "" { return nil, fmt.Errorf("cache URL may not contain a query") } if cacheURL.Fragment != "" { return nil, fmt.Errorf("cache URL may not contain a fragment") } return &url.URL{ Scheme: cacheURL.Scheme, User: cacheURL.User, Host: resultHost, Path: resultPath, RawPath: resultRawPath, RawQuery: pubURL.RawQuery, Fragment: pubURL.Fragment, }, nil } 07070100000032000081A400000000000000000000000165F88C50000021D7000000000000000000000000000000000000002900000000snowflake-2.9.2/common/amp/cache_test.gopackage amp import ( "bytes" "net/url" "testing" "golang.org/x/net/idna" ) func TestDomainPrefixBasic(t *testing.T) { // Tests expecting no error. for _, test := range []struct { domain, expected string }{ {"", ""}, {"xn--", ""}, {"...", "---"}, // Should not apply mappings such as case folding and // normalization. {"b\u00fccher.de", "xn--bcher-de-65a"}, {"B\u00fccher.de", "xn--Bcher-de-65a"}, {"bu\u0308cher.de", "xn--bucher-de-hkf"}, // Check some that differ between IDNA 2003 and IDNA 2008. // https://unicode.org/reports/tr46/#Deviations // https://util.unicode.org/UnicodeJsps/idna.jsp {"faß.de", "xn--fa-de-mqa"}, {"βόλοσ.com", "xn---com-4ld8c2a6a8e"}, // Lengths of 63 and 64. 64 is too long for a DNS label, but // domainPrefixBasic is not expected to check for that. {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#basic-algorithm {"example.com", "example-com"}, {"foo.example.com", "foo-example-com"}, {"foo-example.com", "foo--example-com"}, {"xn--57hw060o.com", "xn---com-p33b41770a"}, {"\u26a1\U0001f60a.com", "xn---com-p33b41770a"}, {"en-us.example.com", "0-en--us-example-com-0"}, } { output, err := domainPrefixBasic(test.domain) if err != nil || output != test.expected { t.Errorf("%+q → (%+q, %v), expected (%+q, %v)", test.domain, output, err, test.expected, nil) } } // Tests expecting an error. for _, domain := range []string{ "xn---", } { output, err := domainPrefixBasic(domain) if err == nil || output != "" { t.Errorf("%+q → (%+q, %v), expected (%+q, non-nil)", domain, output, err, "") } } } func TestDomainPrefixFallback(t *testing.T) { for _, test := range []struct { domain, expected string }{ { "", "4oymiquy7qobjgx36tejs35zeqt24qpemsnzgtfeswmrw6csxbkq", }, { "example.com", "un42n5xov642kxrxrqiyanhcoupgql5lt4wtbkyt2ijflbwodfdq", }, // These checked against the output of // https://github.com/ampproject/amp-toolbox/tree/84cb3057e5f6c54d64369ddd285db1cb36237ee8/packages/cache-url, // using the widget at // https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cache-urls/#url-format. { "000000000000000000000000000000000000000000000000000000000000.com", "stejanx4hsijaoj4secyecy4nvqodk56kw72whwcmvdbtucibf5a", }, { "00000000000000000000000000000000000000000000000000000000000a.com", "jdcvbsorpnc3hcjrhst56nfm6ymdpovlawdbm2efyxpvlt4cpbya", }, { "00000000000000000000000000000000000000000000000000000000000\u03bb.com", "qhzqeumjkfpcpuic3vqruyjswcr7y7gcm3crqyhhywvn3xrhchfa", }, } { output := domainPrefixFallback(test.domain) if output != test.expected { t.Errorf("%+q → %+q, expected %+q", test.domain, output, test.expected) } } } // Checks that domainPrefix chooses domainPrefixBasic or domainPrefixFallback as // appropriate; i.e., always returns string that is a valid DNS label and is // IDNA-decodable. func TestDomainPrefix(t *testing.T) { // A validating IDNA profile, which checks label length and that the // label contains only certain ASCII characters. It does not do the // ValidateLabels check, because that depends on the input having // certain properties. profile := idna.New( idna.VerifyDNSLength(true), idna.StrictDomainName(true), ) for _, domain := range []string{ "example.com", "\u0314example.com", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // 63 bytes "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // 64 bytes "xn--57hw060o.com", "a b c", } { output := domainPrefix(domain) if bytes.IndexByte([]byte(output), '.') != -1 { t.Errorf("%+q → %+q contains a dot", domain, output) } _, err := profile.ToUnicode(output) if err != nil { t.Errorf("%+q → error %v", domain, err) } } } func mustParseURL(rawurl string) *url.URL { u, err := url.Parse(rawurl) if err != nil { panic(err) } return u } func TestCacheURL(t *testing.T) { // Tests expecting no error. for _, test := range []struct { pub string cache string contentType string expected string }{ // With or without trailing slash on pubURL. { "http://example.com/", "https://amp.cache/", "c", "https://example-com.amp.cache/c/example.com", }, { "http://example.com", "https://amp.cache/", "c", "https://example-com.amp.cache/c/example.com", }, // https pubURL. { "https://example.com/", "https://amp.cache/", "c", "https://example-com.amp.cache/c/s/example.com", }, // The content type should be escaped if necessary. { "http://example.com/", "https://amp.cache/", "/", "https://example-com.amp.cache/%2F/example.com", }, // Retain pubURL path, query, and fragment, including escaping. { "http://example.com/my%2Fpath/index.html?a=1#fragment", "https://amp.cache/", "c", "https://example-com.amp.cache/c/example.com/my%2Fpath/index.html?a=1#fragment", }, // Retain scheme, userinfo, port, and path of cacheURL, escaping // whatever is necessary. { "http://example.com", "http://cache%2Fuser:cache%40pass@amp.cache:123/with/../../path/..%2f../", "c", "http://cache%2Fuser:cache%40pass@example-com.amp.cache:123/path/..%2f../c/example.com", }, // Port numbers in pubURL are allowed, if they're the default // for scheme. { "http://example.com:80/", "https://amp.cache/", "c", "https://example-com.amp.cache/c/example.com", }, { "https://example.com:443/", "https://amp.cache/", "c", "https://example-com.amp.cache/c/s/example.com", }, // "?" at the end of cacheURL is okay, as long as the query is // empty. { "http://example.com/", "https://amp.cache/?", "c", "https://example-com.amp.cache/c/example.com", }, // https://developers.google.com/amp/cache/overview#example-requesting-document-using-tls { "https://example.com/amp_document.html", "https://cdn.ampproject.org/", "c", "https://example-com.cdn.ampproject.org/c/s/example.com/amp_document.html", }, // https://developers.google.com/amp/cache/overview#example-requesting-image-using-plain-http { "http://example.com/logo.png", "https://cdn.ampproject.org/", "i", "https://example-com.cdn.ampproject.org/i/example.com/logo.png", }, // https://developers.google.com/amp/cache/overview#query-parameter-example { "https://example.com/g?value=Hello%20World", "https://cdn.ampproject.org/", "c", "https://example-com.cdn.ampproject.org/c/s/example.com/g?value=Hello%20World", }, } { pubURL := mustParseURL(test.pub) cacheURL := mustParseURL(test.cache) outputURL, err := CacheURL(pubURL, cacheURL, test.contentType) if err != nil { t.Errorf("%+q %+q %+q → error %v", test.pub, test.cache, test.contentType, err) continue } if outputURL.String() != test.expected { t.Errorf("%+q %+q %+q → %+q, expected %+q", test.pub, test.cache, test.contentType, outputURL, test.expected) continue } } // Tests expecting an error. for _, test := range []struct { pub string cache string contentType string }{ // Empty content type. { "http://example.com/", "https://amp.cache/", "", }, // Empty host. { "http:///index.html", "https://amp.cache/", "c", }, // Empty scheme. { "//example.com/", "https://amp.cache/", "c", }, // Unrecognized scheme. { "ftp://example.com/", "https://amp.cache/", "c", }, // Wrong port number for scheme. { "http://example.com:443/", "https://amp.cache/", "c", }, // userinfo in pubURL. { "http://user@example.com/", "https://amp.cache/", "c", }, { "http://user:pass@example.com/", "https://amp.cache/", "c", }, // cacheURL may not contain a query. { "http://example.com/", "https://amp.cache/?a=1", "c", }, // cacheURL may not contain a fragment. { "http://example.com/", "https://amp.cache/#fragment", "c", }, } { pubURL := mustParseURL(test.pub) cacheURL := mustParseURL(test.cache) outputURL, err := CacheURL(pubURL, cacheURL, test.contentType) if err == nil { t.Errorf("%+q %+q %+q → %+q, expected error", test.pub, test.cache, test.contentType, outputURL) continue } } } 07070100000033000081A400000000000000000000000165F88C50000011AD000000000000000000000000000000000000002200000000snowflake-2.9.2/common/amp/doc.go/* Package amp provides functions for working with the AMP (Accelerated Mobile Pages) subset of HTML, and conveying binary data through an AMP cache. # AMP cache The CacheURL function takes a plain URL and converts it to be accessed through a given AMP cache. The EncodePath and DecodePath functions provide a way to encode data into the suffix of a URL path. AMP caches do not support HTTP POST, but encoding data into a URL path with GET is an alternative means of sending data to the server. The format of an encoded path is: 0<0 or more bytes, including slash>/<base64 of data> That is: * "0", a format version number, which controls the interpretation of the rest of the path. Only the first byte matters as a version indicator (not the whole first path component). * Any number of slash or non-slash bytes. These may be used as padding or to prevent cache collisions in the AMP cache. * A final slash. * base64 encoding of the data, using the URL-safe alphabet (which does not include slash). For example, an encoding of the string "This is path-encoded data." is the following. The "lgWHcwhXFjUm" following the format version number is random padding that will be ignored on decoding. 0lgWHcwhXFjUm/VGhpcyBpcyBwYXRoLWVuY29kZWQgZGF0YS4 It is the caller's responsibility to add or remove any directory path prefix before calling EncodePath or DecodePath. # AMP armor AMP armor is a data encoding scheme that that satisfies the requirements of the AMP (Accelerated Mobile Pages) subset of HTML, and survives modification by an AMP cache. For the requirements of AMP HTML, see https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml/. For modifications that may be made by an AMP cache, see https://github.com/ampproject/amphtml/blob/main/docs/spec/amp-cache-modifications.md. The encoding is based on ones created by Ivan Markin. See codec/amp/ in https://github.com/nogoegst/amper and discussion at https://bugs.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/25985. The encoding algorithm works as follows. Base64-encode the input. Prepend the input with the byte '0'; this is a protocol version indicator that the decoder can use to determine how to interpret the bytes that follow. Split the base64 into fixed-size chunks separated by whitespace. Take up to 1024 chunks at a time, and wrap them in a pre element. Then, situate the markup so far within the body of the AMP HTML boilerplate. The decoding algorithm is to scan the HTML for pre elements, split their text contents on whitespace and concatenate, then base64 decode. The base64 encoding uses the standard alphabet, with normal "=" padding (https://tools.ietf.org/html/rfc4648#section-4). The reason for splitting the base64 into chunks is that AMP caches reportedly truncate long strings that are not broken by whitespace: https://bugs.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/25985#note_2592348. The characters that may separate the chunks are the ASCII whitespace characters (https://infra.spec.whatwg.org/#ascii-whitespace) "\x09", "\x0a", "\x0c", "\x0d", and "\x20". The reason for separating the chunks into pre elements is to limit the amount of text a decoder may have to buffer while parsing the HTML. Each pre element may contain at most 64 KB of text. pre elements may not be nested. # Example The following is the result of encoding the string "This was encoded with AMP armor.": <!doctype html> <html amp> <head> <meta charset="utf-8"> <script async src="https://cdn.ampproject.org/v0.js"></script> <link rel="canonical" href="#"> <meta name="viewport" content="width=device-width"> <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript> </head> <body> <pre> 0VGhpcyB3YXMgZW5jb2RlZCB3aXRoIEF NUCBhcm1vci4= </pre> </body> </html> */ package amp 07070100000034000081A400000000000000000000000165F88C50000004BC000000000000000000000000000000000000002300000000snowflake-2.9.2/common/amp/path.gopackage amp import ( "crypto/rand" "encoding/base64" "fmt" "strings" ) // EncodePath encodes data in a way that is suitable for the suffix of an AMP // cache URL. func EncodePath(data []byte) string { var cacheBreaker [9]byte _, err := rand.Read(cacheBreaker[:]) if err != nil { panic(err) } b64 := base64.RawURLEncoding.EncodeToString return "0" + b64(cacheBreaker[:]) + "/" + b64(data) } // DecodePath decodes data from a path suffix as encoded by EncodePath. The path // must have already been trimmed of any directory prefix (as might be present // in, e.g., an HTTP request). That is, the first character of path should be // the "0" message format indicator. func DecodePath(path string) ([]byte, error) { if len(path) < 1 { return nil, fmt.Errorf("missing format indicator") } version := path[0] rest := path[1:] switch version { case '0': // Ignore everything else up to and including the final slash // (there must be at least one slash). i := strings.LastIndexByte(rest, '/') if i == -1 { return nil, fmt.Errorf("missing data") } return base64.RawURLEncoding.DecodeString(rest[i+1:]) default: return nil, fmt.Errorf("unknown format indicator %q", version) } } 07070100000035000081A400000000000000000000000165F88C5000000513000000000000000000000000000000000000002800000000snowflake-2.9.2/common/amp/path_test.gopackage amp import ( "testing" ) func TestDecodePath(t *testing.T) { for _, test := range []struct { path string expectedData string expectedErrStr string }{ {"", "", "missing format indicator"}, {"0", "", "missing data"}, {"0foobar", "", "missing data"}, {"/0/YWJj", "", "unknown format indicator '/'"}, {"0/", "", ""}, {"0foobar/", "", ""}, {"0/YWJj", "abc", ""}, {"0///YWJj", "abc", ""}, {"0foobar/YWJj", "abc", ""}, {"0/foobar/YWJj", "abc", ""}, } { data, err := DecodePath(test.path) if test.expectedErrStr != "" { if err == nil || err.Error() != test.expectedErrStr { t.Errorf("%+q expected error %+q, got %+q", test.path, test.expectedErrStr, err) } } else if err != nil { t.Errorf("%+q expected no error, got %+q", test.path, err) } else if string(data) != test.expectedData { t.Errorf("%+q expected data %+q, got %+q", test.path, test.expectedData, data) } } } func TestPathRoundTrip(t *testing.T) { for _, data := range []string{ "", "\x00", "/", "hello world", } { decoded, err := DecodePath(EncodePath([]byte(data))) if err != nil { t.Errorf("%+q roundtripped with error %v", data, err) } else if string(decoded) != data { t.Errorf("%+q roundtripped to %+q", data, decoded) } } } 07070100000036000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000002900000000snowflake-2.9.2/common/bridgefingerprint07070100000037000081A400000000000000000000000165F88C5000000269000000000000000000000000000000000000003800000000snowflake-2.9.2/common/bridgefingerprint/fingerprint.gopackage bridgefingerprint import ( "encoding/hex" "errors" ) type Fingerprint string var ErrBridgeFingerprintInvalid = errors.New("bridge fingerprint invalid") func FingerprintFromBytes(bytes []byte) (Fingerprint, error) { n := len(bytes) if n != 20 && n != 32 { return Fingerprint(""), ErrBridgeFingerprintInvalid } return Fingerprint(bytes), nil } func FingerprintFromHexString(hexString string) (Fingerprint, error) { decoded, err := hex.DecodeString(hexString) if err != nil { return "", err } return FingerprintFromBytes(decoded) } func (f Fingerprint) ToBytes() []byte { return []byte(f) } 07070100000038000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001D00000000snowflake-2.9.2/common/certs07070100000039000081A400000000000000000000000165F88C50000009E9000000000000000000000000000000000000002600000000snowflake-2.9.2/common/certs/certs.gopackage certs import ( "crypto/x509" "log" ) // https://crt.sh/?id=3958242236 const LetsEncryptRootCert = `-----BEGIN CERTIFICATE----- MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/ MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK 4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5 bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4 FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1 c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx +tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC 5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW 9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5 -----END CERTIFICATE-----` // GetRootCAs is a workaround for older versions of Android that do not trust // Let's Encrypt's ISRG Root X1. This manually adds the ISRG root to the device's // existing cert pool. func GetRootCAs() *x509.CertPool { rootCerts, err := x509.SystemCertPool() if err != nil { rootCerts = x509.NewCertPool() } if ok := rootCerts.AppendCertsFromPEM([]byte(LetsEncryptRootCert)); !ok { log.Println("Error appending Let's Encrypt root certificate to cert poool") return nil } return rootCerts } 0707010000003A000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000002500000000snowflake-2.9.2/common/encapsulation0707010000003B000081A400000000000000000000000165F88C5000001A2C000000000000000000000000000000000000003600000000snowflake-2.9.2/common/encapsulation/encapsulation.go// 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" ) // 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 the next available data chunk, skipping over any padding chunks that // may come first, and copies the data into p. If p is shorter than the length // of the data chunk, only the first len(p) bytes are copied into p, and the // error return is io.ErrShortBuffer. 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, p []byte) (int, 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 0, 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 0, ErrTooLong } _, err := r.Read(b[:]) if err == io.EOF { err = io.ErrUnexpectedEOF } if err != nil { return 0, err } moreLength = (b[0] & 0x80) != 0 n = (n << 7) | int(b[0]&0x7f) } if isData { if len(p) > n { p = p[:n] } numData, err := io.ReadFull(r, p) if err == nil && numData < n { // If the caller's buffer was too short, discard // the rest of the data and return // io.ErrShortBuffer. _, err = io.CopyN(io.Discard, r, int64(n-numData)) if err == nil { err = io.ErrShortBuffer } } if err == io.EOF { err = io.ErrUnexpectedEOF } return numData, err } else if n > 0 { _, err := io.CopyN(io.Discard, r, int64(n)) if err == io.EOF { err = io.ErrUnexpectedEOF } if err != nil { return 0, 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 [1024]byte // 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) } 0707010000003C000081A400000000000000000000000165F88C5000003019000000000000000000000000000000000000003B00000000snowflake-2.9.2/common/encapsulation/encapsulation_test.gopackage 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 := make([]byte, i) n, err = ReadData(&enc, inverse) if err != nil { t.Fatalf("size %d, ReadData returned error %v", i, err) } if !bytes.Equal(inverse[:n], original) { t.Fatalf("size %d, got <%x>, expected <%x>", i, inverse[:n], 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 { var actual [10]byte n, err := ReadData(&enc, actual[:]) if err != nil { t.Fatalf("slice %d, got error %v, expected %v", i, err, nil) } if !bytes.Equal(actual[:n], expected) { t.Fatalf("slice %d, got <%x>, expected <%x>", i, actual[:n], expected) } } n, err := ReadData(&enc, nil) if n != 0 || err != io.EOF { t.Fatalf("got (%v, %v), expected (%v, %v)", n, err, 0, io.EOF) } } // Test that EOF before a length prefix returns io.EOF. func TestEOF(t *testing.T) { n, err := ReadData(bytes.NewReader(nil), nil) if n != 0 || err != io.EOF { t.Fatalf("got (%v, %v), expected (%v, %v)", n, err, 0, 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 } { n, err := ReadData(bytes.NewReader(test), nil) if n != 0 || err != io.ErrUnexpectedEOF { t.Fatalf("<%x> got (%v, %v), expected (%v, %v)", test, n, err, 0, 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")}, } { var p [10]byte n, err := ReadData(bytes.NewReader(test.enc), p[:]) if err != nil { t.Fatalf("<%x> got error %v, expected %v", test.enc, err, nil) } if !bytes.Equal(p[:n], test.expected) { t.Fatalf("<%x> got <%x>, expected <%x>", test.enc, p[:n], 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 var p [0xfffff]byte n, err := ReadData(bytes.NewReader(append(prefix, data...)), p[:]) if err != nil { t.Fatalf("got error %v, expected %v", err, nil) } if !bytes.Equal(p[:n], 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) n, err = ReadData(bytes.NewReader(append(prefix, data...)), nil) if n != 0 || err != ErrTooLong { t.Fatalf("got (%v, %v), expected (%v, %v)", n, err, 0, 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{} n, err = ReadData(bytes.NewReader(append(prefix, data...)), nil) if n != 0 || err != ErrTooLong { t.Fatalf("got (%v, %v), expected (%v, %v)", n, err, 0, 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) n, err = ReadData(enc, p[:]) if err != nil { t.Fatalf("got error %v, expected %v", err, nil) } if !bytes.Equal(p[:n], data) { t.Fatalf("got <%x>, expected <%x>", p[:n], 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) n, err = ReadData(enc, nil) if n != 0 || err != ErrTooLong { t.Fatalf("got (%v, %v), expected (%v, %v)", n, err, 0, ErrTooLong) } prefix = []byte{0x40, 0x80, 0x80, 0x80} // encodes 0x0 padding = []byte{} enc = bytes.NewBuffer(append(prefix, padding...)) mustWriteData(enc, data) n, err = ReadData(enc, nil) if n != 0 || err != ErrTooLong { t.Fatalf("got (%v, %v), expected (%v, %v)", n, err, 0, 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) } } } // Test that ReadData truncates the data when the destination slice is too // short. func TestReadDataTruncate(t *testing.T) { var enc bytes.Buffer mustWriteData(&enc, []byte("12345678")) mustWriteData(&enc, []byte("abcdefgh")) var p [4]byte // First ReadData should return truncated "1234". n, err := ReadData(&enc, p[:]) if err != io.ErrShortBuffer { t.Fatalf("got error %v, expected %v", err, io.ErrShortBuffer) } if !bytes.Equal(p[:n], []byte("1234")) { t.Fatalf("got <%x>, expected <%x>", p[:n], []byte("1234")) } // Second ReadData should return truncated "abcd", not the rest of // "12345678". n, err = ReadData(&enc, p[:]) if err != io.ErrShortBuffer { t.Fatalf("got error %v, expected %v", err, io.ErrShortBuffer) } if !bytes.Equal(p[:n], []byte("abcd")) { t.Fatalf("got <%x>, expected <%x>", p[:n], []byte("abcd")) } // Last ReadData should give io.EOF. n, err = ReadData(&enc, p[:]) if err != io.EOF { t.Fatalf("got error %v, expected %v", err, io.EOF) } } // Test that even when the result is truncated, ReadData fills the provided // buffer as much as possible (and not stop at the boundary of an internal Read, // say). func TestReadDataTruncateFull(t *testing.T) { pr, pw := io.Pipe() go func() { // Send one data chunk that will be delivered across two Read // calls. pw.Write([]byte{0x8a, 'h', 'e', 'l', 'l', 'o'}) pw.Write([]byte{'w', 'o', 'r', 'l', 'd'}) }() var p [8]byte n, err := ReadData(pr, p[:]) if err != io.ErrShortBuffer { t.Fatalf("got error %v, expected %v", err, io.ErrShortBuffer) } // Should not stop after "hello". if !bytes.Equal(p[:n], []byte("hellowor")) { t.Fatalf("got <%x>, expected <%x>", p[:n], []byte("hellowor")) } } // Benchmark the ReadData function when reading from a stream of data packets of // different sizes. func BenchmarkReadData(b *testing.B) { pr, pw := io.Pipe() go func() { for { for length := 0; length < 128; length++ { WriteData(pw, paddingBuffer[:length]) } } }() var p [128]byte for i := 0; i < b.N; i++ { _, err := ReadData(pr, p[:]) if err != nil { b.Fatal(err) } } } 0707010000003D000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001D00000000snowflake-2.9.2/common/event0707010000003E000081A400000000000000000000000165F88C500000035D000000000000000000000000000000000000002400000000snowflake-2.9.2/common/event/bus.gopackage event import "sync" func NewSnowflakeEventDispatcher() SnowflakeEventDispatcher { return &eventBus{lock: &sync.Mutex{}} } type eventBus struct { lock *sync.Mutex listeners []SnowflakeEventReceiver } func (e *eventBus) OnNewSnowflakeEvent(event SnowflakeEvent) { e.lock.Lock() defer e.lock.Unlock() for _, v := range e.listeners { v.OnNewSnowflakeEvent(event) } } func (e *eventBus) AddSnowflakeEventListener(receiver SnowflakeEventReceiver) { e.lock.Lock() defer e.lock.Unlock() e.listeners = append(e.listeners, receiver) } func (e *eventBus) RemoveSnowflakeEventListener(receiver SnowflakeEventReceiver) { e.lock.Lock() defer e.lock.Unlock() var newListeners []SnowflakeEventReceiver for _, v := range e.listeners { if v != receiver { newListeners = append(newListeners, v) } } e.listeners = newListeners return } 0707010000003F000081A400000000000000000000000165F88C5000000375000000000000000000000000000000000000002900000000snowflake-2.9.2/common/event/bus_test.gopackage event import ( "github.com/stretchr/testify/assert" "testing" ) type stubReceiver struct { counter int } func (s *stubReceiver) OnNewSnowflakeEvent(event SnowflakeEvent) { s.counter++ } func TestBusDispatch(t *testing.T) { EventBus := NewSnowflakeEventDispatcher() StubReceiverA := &stubReceiver{} StubReceiverB := &stubReceiver{} EventBus.AddSnowflakeEventListener(StubReceiverA) EventBus.AddSnowflakeEventListener(StubReceiverB) assert.Equal(t, 0, StubReceiverA.counter) assert.Equal(t, 0, StubReceiverB.counter) EventBus.OnNewSnowflakeEvent(EventOnSnowflakeConnected{}) assert.Equal(t, 1, StubReceiverA.counter) assert.Equal(t, 1, StubReceiverB.counter) EventBus.RemoveSnowflakeEventListener(StubReceiverB) EventBus.OnNewSnowflakeEvent(EventOnSnowflakeConnected{}) assert.Equal(t, 2, StubReceiverA.counter) assert.Equal(t, 1, StubReceiverB.counter) } 07070100000040000081A400000000000000000000000165F88C5000000BC5000000000000000000000000000000000000002A00000000snowflake-2.9.2/common/event/interface.gopackage event import ( "fmt" "time" "github.com/pion/webrtc/v3" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/safelog" ) type SnowflakeEvent interface { IsSnowflakeEvent() String() string } type EventOnOfferCreated struct { SnowflakeEvent WebRTCLocalDescription *webrtc.SessionDescription Error error } func (e EventOnOfferCreated) String() string { if e.Error != nil { scrubbed := safelog.Scrub([]byte(e.Error.Error())) return fmt.Sprintf("offer creation failure %s", scrubbed) } return "offer created" } type EventOnBrokerRendezvous struct { SnowflakeEvent WebRTCRemoteDescription *webrtc.SessionDescription Error error } func (e EventOnBrokerRendezvous) String() string { if e.Error != nil { scrubbed := safelog.Scrub([]byte(e.Error.Error())) return fmt.Sprintf("broker failure %s", scrubbed) } return "broker rendezvous peer received" } type EventOnSnowflakeConnected struct { SnowflakeEvent } func (e EventOnSnowflakeConnected) String() string { return "connected" } type EventOnSnowflakeConnectionFailed struct { SnowflakeEvent Error error } func (e EventOnSnowflakeConnectionFailed) String() string { scrubbed := safelog.Scrub([]byte(e.Error.Error())) return fmt.Sprintf("trying a new proxy: %s", scrubbed) } type EventOnProxyStarting struct { SnowflakeEvent } func (e EventOnProxyStarting) String() string { return "Proxy starting" } type EventOnProxyConnectionOver struct { SnowflakeEvent InboundTraffic int64 OutboundTraffic int64 } func (e EventOnProxyConnectionOver) String() string { return fmt.Sprintf("Proxy connection closed") } type EventOnProxyStats struct { SnowflakeEvent ConnectionCount int InboundBytes, OutboundBytes int64 InboundUnit, OutboundUnit string SummaryInterval time.Duration } func (e EventOnProxyStats) String() string { statString := fmt.Sprintf("In the last %v, there were %v completed connections. Traffic Relayed ↓ %v %v, ↑ %v %v.", e.SummaryInterval.String(), e.ConnectionCount, e.InboundBytes, e.InboundUnit, e.OutboundBytes, e.OutboundUnit) return statString } type EventOnCurrentNATTypeDetermined struct { SnowflakeEvent CurNATType string } func (e EventOnCurrentNATTypeDetermined) String() string { return fmt.Sprintf("NAT type: %v", e.CurNATType) } type SnowflakeEventReceiver interface { // OnNewSnowflakeEvent notify receiver about a new event // This method MUST not block OnNewSnowflakeEvent(event SnowflakeEvent) } type SnowflakeEventDispatcher interface { SnowflakeEventReceiver // AddSnowflakeEventListener allow receiver(s) to receive event notification // when OnNewSnowflakeEvent is called on the dispatcher. // Every event listener added will be called when an event is received by the dispatcher. // The order each listener is called is undefined. AddSnowflakeEventListener(receiver SnowflakeEventReceiver) RemoveSnowflakeEventListener(receiver SnowflakeEventReceiver) } 07070100000041000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000002000000000snowflake-2.9.2/common/messages07070100000042000081A400000000000000000000000165F88C5000001063000000000000000000000000000000000000002A00000000snowflake-2.9.2/common/messages/client.go//Package for communication with the snowflake broker // import "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" package messages import ( "bytes" "encoding/json" "fmt" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/nat" ) 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 <message> := <version>\n<body> <version> := <digit>.<digit> <body> := <poll request>|<poll response> There are two different types of body messages, each encoded in JSON format == ClientPollRequest == <poll request> := { offer: <sdp offer> [nat: (unknown|restricted|unrestricted)] [fingerprint: <fingerprint string>] } The NAT field is optional, and if it is missing a value of "unknown" will be assumed. The fingerprint is also optional and, if absent, will be assigned the fingerprint of the default bridge. == ClientPollResponse == <poll response> := { [answer: <sdp answer>] [error: <error string>] } 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. */ // The bridge fingerprint to assume, for client poll requests that do not // specify a fingerprint. Before #28651, there was only one bridge with one // fingerprint, which all clients expected to be connected to implicitly. // If a client is old enough that it does not specify a fingerprint, this is // the fingerprint it expects. Clients that do set a fingerprint in the // SOCKS params will also be assumed to want to connect to the default bridge. const defaultBridgeFingerprint = "2B280B23E1107BB62ABFC40DDCC8824814F80A72" type ClientPollRequest struct { Offer string `json:"offer"` NAT string `json:"nat"` Fingerprint string `json:"fingerprint"` } // Encodes a poll message from a snowflake client func (req *ClientPollRequest) EncodeClientPollRequest() ([]byte, error) { if req.Fingerprint == "" { req.Fingerprint = defaultBridgeFingerprint } 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) { parts := bytes.SplitN(data, []byte("\n"), 2) if len(parts) < 2 { // no version number found return nil, fmt.Errorf("unsupported message version") } var message ClientPollRequest if string(parts[0]) != ClientVersion { return nil, fmt.Errorf("unsupported message version") } err := json.Unmarshal(parts[1], &message) if err != nil { return nil, err } if message.Offer == "" { return nil, fmt.Errorf("no supplied offer") } if message.Fingerprint == "" { message.Fingerprint = defaultBridgeFingerprint } if _, err := bridgefingerprint.FingerprintFromHexString(message.Fingerprint); err != nil { return nil, fmt.Errorf("cannot decode fingerprint") } switch message.NAT { case "": message.NAT = nat.NATUnknown case nat.NATUnknown: case nat.NATRestricted: case nat.NATUnrestricted: default: return nil, fmt.Errorf("invalid NAT type") } 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 } 07070100000043000081A400000000000000000000000165F88C500000024B000000000000000000000000000000000000002700000000snowflake-2.9.2/common/messages/ipc.gopackage messages import ( "errors" ) type RendezvousMethod string const ( RendezvousHttp RendezvousMethod = "http" RendezvousAmpCache RendezvousMethod = "ampcache" RendezvousSqs RendezvousMethod = "sqs" ) type Arg struct { Body []byte RemoteAddr string RendezvousMethod RendezvousMethod } var ( ErrBadRequest = errors.New("bad request") ErrInternal = errors.New("internal error") ErrExtraInfo = errors.New("client sent extra info") StrTimedOut = "timed out waiting for answer!" StrNoProxies = "no snowflake proxies currently available" ) 07070100000044000081A400000000000000000000000165F88C5000002DD3000000000000000000000000000000000000003100000000snowflake-2.9.2/common/messages/messages_test.gopackage messages import ( "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 acceptedRelayPattern string }{ { //Version 1.0 proxy message sid: "ymbcCMto7KHNGYlp", proxyType: "unknown", natType: "unknown", clients: 0, data: `{"Sid":"ymbcCMto7KHNGYlp","Version":"1.0"}`, err: nil, }, { //Version 1.1 proxy message sid: "ymbcCMto7KHNGYlp", proxyType: "standalone", natType: "unknown", clients: 0, data: `{"Sid":"ymbcCMto7KHNGYlp","Version":"1.1","Type":"standalone"}`, err: nil, }, { //Version 1.2 proxy message sid: "ymbcCMto7KHNGYlp", proxyType: "standalone", natType: "restricted", clients: 0, data: `{"Sid":"ymbcCMto7KHNGYlp","Version":"1.2","Type":"standalone", "NAT":"restricted"}`, err: nil, }, { //Version 1.2 proxy message with clients sid: "ymbcCMto7KHNGYlp", proxyType: "standalone", natType: "restricted", clients: 24, data: `{"Sid":"ymbcCMto7KHNGYlp","Version":"1.2","Type":"standalone", "NAT":"restricted","Clients":24}`, err: nil, }, { //Version 1.3 proxy message with clients and proxyURL sid: "ymbcCMto7KHNGYlp", proxyType: "standalone", natType: "restricted", clients: 24, acceptedRelayPattern: "snowfalke.torproject.org", data: `{"Sid":"ymbcCMto7KHNGYlp","Version":"1.2","Type":"standalone", "NAT":"restricted","Clients":24, "AcceptedRelayPattern":"snowfalke.torproject.org"}`, err: nil, }, { //Version 0.X proxy message: sid: "", proxyType: "", natType: "", clients: 0, data: "", err: &json.SyntaxError{}, }, { sid: "", proxyType: "", natType: "", clients: 0, data: `{"Sid":"ymbcCMto7KHNGYlp"}`, err: fmt.Errorf(""), }, { sid: "", proxyType: "", natType: "", clients: 0, data: "{}", err: fmt.Errorf(""), }, { sid: "", proxyType: "", natType: "", clients: 0, data: `{"Version":"1.0"}`, err: fmt.Errorf(""), }, { sid: "", proxyType: "", natType: "", clients: 0, data: `{"Version":"2.0"}`, err: fmt.Errorf(""), }, } { sid, proxyType, natType, clients, relayPattern, _, err := DecodeProxyPollRequestWithRelayPrefix([]byte(test.data)) So(sid, ShouldResemble, test.sid) So(proxyType, ShouldResemble, test.proxyType) So(natType, ShouldResemble, test.natType) So(clients, ShouldEqual, test.clients) So(relayPattern, ShouldResemble, test.acceptedRelayPattern) So(err, ShouldHaveSameTypeAs, test.err) } }) } func TestEncodeProxyPollRequests(t *testing.T) { Convey("Context", t, func() { b, err := EncodeProxyPollRequest("ymbcCMto7KHNGYlp", "standalone", "unknown", 16) So(err, ShouldBeNil) sid, proxyType, natType, clients, err := DecodeProxyPollRequest(b) So(sid, ShouldEqual, "ymbcCMto7KHNGYlp") So(proxyType, ShouldEqual, "standalone") So(natType, ShouldEqual, "unknown") So(clients, ShouldEqual, 16) So(err, ShouldBeNil) }) } func TestDecodeProxyPollResponse(t *testing.T) { Convey("Context", t, func() { for _, test := range []struct { offer string data string relayURL string err error }{ { offer: "fake offer", data: `{"Status":"client match","Offer":"fake offer","NAT":"unknown"}`, err: nil, }, { offer: "fake offer", data: `{"Status":"client match","Offer":"fake offer","NAT":"unknown", "RelayURL":"wss://snowflake.torproject.org/proxy"}`, relayURL: "wss://snowflake.torproject.org/proxy", err: nil, }, { offer: "", data: `{"Status":"no match"}`, err: nil, }, { offer: "", data: `{"Status":"client match"}`, err: fmt.Errorf("no supplied offer"), }, { offer: "", data: `{"Test":"test"}`, err: fmt.Errorf(""), }, } { offer, _, relayURL, err := DecodePollResponseWithRelayURL([]byte(test.data)) So(err, ShouldHaveSameTypeAs, test.err) So(offer, ShouldResemble, test.offer) So(relayURL, ShouldResemble, test.relayURL) } }) } func TestEncodeProxyPollResponse(t *testing.T) { Convey("Context", t, func() { b, err := EncodePollResponse("fake offer", true, "restricted") So(err, ShouldBeNil) offer, natType, err := DecodePollResponse(b) So(offer, ShouldEqual, "fake offer") So(natType, ShouldEqual, "restricted") So(err, ShouldBeNil) b, err = EncodePollResponse("", false, "unknown") So(err, ShouldBeNil) offer, natType, err = DecodePollResponse(b) So(offer, ShouldEqual, "") So(natType, ShouldEqual, "unknown") So(err, ShouldBeNil) }) } func TestEncodeProxyPollResponseWithProxyURL(t *testing.T) { Convey("Context", t, func() { b, err := EncodePollResponseWithRelayURL("fake offer", true, "restricted", "wss://test/", "") So(err, ShouldBeNil) offer, natType, err := DecodePollResponse(b) So(err, ShouldNotBeNil) offer, natType, relay, err := DecodePollResponseWithRelayURL(b) So(offer, ShouldEqual, "fake offer") So(natType, ShouldEqual, "restricted") So(relay, ShouldEqual, "wss://test/") So(err, ShouldBeNil) b, err = EncodePollResponse("", false, "unknown") So(err, ShouldBeNil) offer, natType, relay, err = DecodePollResponseWithRelayURL(b) So(offer, ShouldEqual, "") So(natType, ShouldEqual, "unknown") So(err, ShouldBeNil) b, err = EncodePollResponseWithRelayURL("fake offer", false, "restricted", "wss://test/", "test error reason") So(err, ShouldBeNil) offer, natType, relay, err = DecodePollResponseWithRelayURL(b) So(err, ShouldNotBeNil) So(err.Error(), ShouldContainSubstring, "test error reason") }) } 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, ShouldBeNil) answer, sid, err := DecodeAnswerRequest(b) So(answer, ShouldEqual, "test answer") So(sid, ShouldEqual, "test sid") So(err, ShouldBeNil) }) } 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, ShouldBeNil) success, err := DecodeAnswerResponse(b) So(success, ShouldEqual, true) So(err, ShouldBeNil) b, err = EncodeAnswerResponse(false) So(err, ShouldBeNil) success, err = DecodeAnswerResponse(b) So(success, ShouldEqual, false) So(err, ShouldBeNil) }) } 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", `1.0 {"nat":"unknown","offer":"fake"}`, nil, }, { //version 1.0 client message "unknown", "fake", `1.0 {"offer":"fake"}`, nil, }, { //unknown version "", "", `{"version":"2.0"}`, fmt.Errorf(""), }, { //no offer "", "", `1.0 {"nat":"unknown"}`, fmt.Errorf(""), }, } { req, err := DecodeClientPollRequest([]byte(test.data)) So(err, ShouldHaveSameTypeAs, test.err) if test.err == nil { So(req.NAT, ShouldResemble, test.natType) So(req.Offer, ShouldResemble, test.offer) } } }) } func TestEncodeClientPollRequests(t *testing.T) { Convey("Context", t, func() { for i, test := range []struct { natType string offer string fingerprint string err error }{ { "unknown", "fake", "", nil, }, { "unknown", "fake", defaultBridgeFingerprint, nil, }, { "unknown", "fake", "123123", fmt.Errorf(""), }, } { req1 := &ClientPollRequest{ NAT: test.natType, Offer: test.offer, Fingerprint: test.fingerprint, } b, err := req1.EncodeClientPollRequest() So(err, ShouldBeNil) req2, err := DecodeClientPollRequest(b) So(err, ShouldHaveSameTypeAs, test.err) if test.err == nil { So(req2.Offer, ShouldEqual, req1.Offer) So(req2.NAT, ShouldEqual, req1.NAT) fingerprint := test.fingerprint if i == 0 { fingerprint = defaultBridgeFingerprint } So(req2.Fingerprint, ShouldEqual, fingerprint) } } }) } 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, ShouldBeNil) resp2, err := DecodeClientPollResponse(b) So(err, ShouldBeNil) So(resp1, ShouldResemble, resp2) resp1 = &ClientPollResponse{ Error: "failed", } b, err = resp1.EncodePollResponse() So(err, ShouldBeNil) resp2, err = DecodeClientPollResponse(b) So(err, ShouldBeNil) So(resp1, ShouldResemble, resp2) }) } 07070100000045000081A400000000000000000000000165F88C5000001B9A000000000000000000000000000000000000002900000000snowflake-2.9.2/common/messages/proxy.go//Package for communication with the snowflake broker // import "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" package messages import ( "encoding/json" "errors" "fmt" "strings" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/nat" ) const ( version = "1.3" ProxyUnknown = "unknown" ) var KnownProxyTypes = map[string]bool{ "standalone": true, "webext": true, "badge": true, "iptproxy": true, } /* Version 1.3 specification: == ProxyPollRequest == { Sid: [generated session id of proxy], Version: 1.3, Type: ["badge"|"webext"|"standalone"], NAT: ["unknown"|"restricted"|"unrestricted"], Clients: [number of current clients, rounded down to multiples of 8], AcceptedRelayPattern: [a pattern representing accepted set of relay domains] } == ProxyPollResponse == 1) If a client is matched: HTTP 200 OK { Status: "client match", { type: offer, sdp: [WebRTC SDP] }, NAT: ["unknown"|"restricted"|"unrestricted"], RelayURL: [the WebSocket URL proxy should connect to relay Snowflake traffic] } 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.3, 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 AcceptedRelayPattern *string } func EncodeProxyPollRequest(sid string, proxyType string, natType string, clients int) ([]byte, error) { return EncodeProxyPollRequestWithRelayPrefix(sid, proxyType, natType, clients, "") } func EncodeProxyPollRequestWithRelayPrefix(sid string, proxyType string, natType string, clients int, relayPattern string) ([]byte, error) { return json.Marshal(ProxyPollRequest{ Sid: sid, Version: version, Type: proxyType, NAT: natType, Clients: clients, AcceptedRelayPattern: &relayPattern, }) } func DecodeProxyPollRequest(data []byte) (sid string, proxyType string, natType string, clients int, err error) { var relayPrefix string sid, proxyType, natType, clients, relayPrefix, _, err = DecodeProxyPollRequestWithRelayPrefix(data) if relayPrefix != "" { return "", "", "", 0, ErrExtraInfo } return } // 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 DecodeProxyPollRequestWithRelayPrefix(data []byte) ( sid string, proxyType string, natType string, clients int, relayPrefix string, relayPrefixAware bool, 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 } switch message.NAT { case "": message.NAT = nat.NATUnknown case nat.NATUnknown: case nat.NATRestricted: case nat.NATUnrestricted: default: err = fmt.Errorf("invalid NAT type") return } // we don't reject polls with an unknown proxy type because we encourage // projects that embed proxy code to include their own type if !KnownProxyTypes[message.Type] { message.Type = ProxyUnknown } var acceptedRelayPattern = "" if message.AcceptedRelayPattern != nil { acceptedRelayPattern = *message.AcceptedRelayPattern } return message.Sid, message.Type, message.NAT, message.Clients, acceptedRelayPattern, message.AcceptedRelayPattern != nil, nil } type ProxyPollResponse struct { Status string Offer string NAT string RelayURL string } func EncodePollResponse(offer string, success bool, natType string) ([]byte, error) { return EncodePollResponseWithRelayURL(offer, success, natType, "", "no match") } func EncodePollResponseWithRelayURL(offer string, success bool, natType, relayURL, failReason string) ([]byte, error) { if success { return json.Marshal(ProxyPollResponse{ Status: "client match", Offer: offer, NAT: natType, RelayURL: relayURL, }) } return json.Marshal(ProxyPollResponse{ Status: failReason, }) } func DecodePollResponse(data []byte) (string, string, error) { offer, natType, relayURL, err := DecodePollResponseWithRelayURL(data) if relayURL != "" { return "", "", ErrExtraInfo } return offer, natType, err } // 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 DecodePollResponseWithRelayURL(data []byte) (string, 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") } err = nil if message.Status == "client match" { if message.Offer == "" { return "", "", "", fmt.Errorf("no supplied offer") } } else { message.Offer = "" if message.Status != "no match" { err = errors.New(message.Status) } } natType := message.NAT if natType == "" { natType = "unknown" } return message.Offer, natType, message.RelayURL, err } 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 } 07070100000046000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000002300000000snowflake-2.9.2/common/namematcher07070100000047000081A400000000000000000000000165F88C50000002A2000000000000000000000000000000000000002E00000000snowflake-2.9.2/common/namematcher/matcher.gopackage namematcher import "strings" func NewNameMatcher(rule string) NameMatcher { rule = strings.TrimSuffix(rule, "$") return NameMatcher{suffix: strings.TrimPrefix(rule, "^"), exact: strings.HasPrefix(rule, "^")} } func IsValidRule(rule string) bool { return strings.HasSuffix(rule, "$") } type NameMatcher struct { exact bool suffix string } func (m *NameMatcher) IsSupersetOf(matcher NameMatcher) bool { if m.exact { return matcher.exact && m.suffix == matcher.suffix } return strings.HasSuffix(matcher.suffix, m.suffix) } func (m *NameMatcher) IsMember(s string) bool { if m.exact { return s == m.suffix } return strings.HasSuffix(s, m.suffix) } 07070100000048000081A400000000000000000000000165F88C5000000889000000000000000000000000000000000000003300000000snowflake-2.9.2/common/namematcher/matcher_test.gopackage namematcher import "testing" import . "github.com/smartystreets/goconvey/convey" func TestMatchMember(t *testing.T) { testingVector := []struct { matcher string target string expects bool }{ {matcher: "", target: "", expects: true}, {matcher: "^snowflake.torproject.net$", target: "snowflake.torproject.net", expects: true}, {matcher: "^snowflake.torproject.net$", target: "faketorproject.net", expects: false}, {matcher: "snowflake.torproject.net$", target: "faketorproject.net", expects: false}, {matcher: "snowflake.torproject.net$", target: "snowflake.torproject.net", expects: true}, {matcher: "snowflake.torproject.net$", target: "imaginary-01-snowflake.torproject.net", expects: true}, {matcher: "snowflake.torproject.net$", target: "imaginary-aaa-snowflake.torproject.net", expects: true}, {matcher: "snowflake.torproject.net$", target: "imaginary-aaa-snowflake.faketorproject.net", expects: false}, } for _, v := range testingVector { t.Run(v.matcher+"<>"+v.target, func(t *testing.T) { Convey("test", t, func() { matcher := NewNameMatcher(v.matcher) So(matcher.IsMember(v.target), ShouldEqual, v.expects) }) }) } } func TestMatchSubset(t *testing.T) { testingVector := []struct { matcher string target string expects bool }{ {matcher: "", target: "", expects: true}, {matcher: "^snowflake.torproject.net$", target: "^snowflake.torproject.net$", expects: true}, {matcher: "snowflake.torproject.net$", target: "^snowflake.torproject.net$", expects: true}, {matcher: "snowflake.torproject.net$", target: "snowflake.torproject.net$", expects: true}, {matcher: "snowflake.torproject.net$", target: "testing-snowflake.torproject.net$", expects: true}, {matcher: "snowflake.torproject.net$", target: "^testing-snowflake.torproject.net$", expects: true}, {matcher: "snowflake.torproject.net$", target: "", expects: false}, } for _, v := range testingVector { t.Run(v.matcher+"<>"+v.target, func(t *testing.T) { Convey("test", t, func() { matcher := NewNameMatcher(v.matcher) target := NewNameMatcher(v.target) So(matcher.IsSupersetOf(target), ShouldEqual, v.expects) }) }) } } 07070100000049000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001B00000000snowflake-2.9.2/common/nat0707010000004A000081A400000000000000000000000165F88C5000001E39000000000000000000000000000000000000002200000000snowflake-2.9.2/common/nat/nat.go/* 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" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/proxy" "log" "net" "net/url" "time" "github.com/pion/stun" ) var ErrTimedOut = errors.New("timed out waiting for response") const ( NATUnknown = "unknown" NATRestricted = "restricted" NATUnrestricted = "unrestricted" ) // Deprecated: Use CheckIfRestrictedNATWithProxy Instead. func CheckIfRestrictedNAT(server string) (bool, error) { return CheckIfRestrictedNATWithProxy(server, nil) } // CheckIfRestrictedNATWithProxy 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 CheckIfRestrictedNATWithProxy(server string, proxy *url.URL) (bool, error) { return isRestrictedMapping(server, proxy) } // 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, proxy *url.URL) (bool, error) { var xorAddr1 stun.XORMappedAddress var xorAddr2 stun.XORMappedAddress mapTestConn, err := connect(addrStr, proxy) if err != nil { return false, fmt.Errorf("Error creating STUN connection: %w", 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 != nil { return false, fmt.Errorf("Error completing roundtrip map test: %w", err) } // Decoding XOR-MAPPED-ADDRESS attribute from message. if err = xorAddr1.GetFrom(resp); err != nil { return false, fmt.Errorf("Error retrieving XOR-MAPPED-ADDRESS resonse: %w", err) } // Decoding OTHER-ADDRESS attribute from message. var otherAddr stun.OtherAddress if err = otherAddr.GetFrom(resp); err != nil { return false, fmt.Errorf("NAT discovery feature not supported: %w", err) } if err = mapTestConn.AddOtherAddr(otherAddr.String()); err != nil { return false, fmt.Errorf("Error resolving address %s: %w", otherAddr.String(), err) } // Test II: Send binding request to other address resp, err = mapTestConn.RoundTrip(message, mapTestConn.OtherAddr) if err != nil { return false, fmt.Errorf("Error retrieveing server response: %w", err) } // Decoding XOR-MAPPED-ADDRESS attribute from message. if err = xorAddr2.GetFrom(resp); err != nil { return false, fmt.Errorf("Error retrieving XOR-MAPPED-ADDRESS resonse: %w", 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, proxy *url.URL) (bool, error) { var xorAddr stun.XORMappedAddress mapTestConn, err := connect(addrStr, proxy) 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, proxyAddr *url.URL) (*StunServerConn, error) { // Creating a "connection" to STUN server. var conn net.PacketConn ResolveUDPAddr := net.ResolveUDPAddr if proxyAddr != nil { socksClient := proxy.NewSocks5UDPClient(proxyAddr) ResolveUDPAddr = socksClient.ResolveUDPAddr } addr, err := ResolveUDPAddr("udp4", addrStr) if err != nil { log.Printf("Error resolving address: %s\n", err.Error()) return nil, err } if proxyAddr == nil { c, err := net.ListenUDP("udp4", nil) if err != nil { return nil, err } conn = c } else { socksClient := proxy.NewSocks5UDPClient(proxyAddr) c, err := socksClient.ListenPacket("udp", nil) if err != nil { return nil, err } conn = c } mChan := listen(conn) return &StunServerConn{ conn: conn, 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.PacketConn) chan *stun.Message { messages := make(chan *stun.Message) go func() { for { buf := make([]byte, 1024) n, _, err := conn.ReadFrom(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 } 0707010000004B000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001D00000000snowflake-2.9.2/common/proxy0707010000004C000081A400000000000000000000000165F88C500000012B000000000000000000000000000000000000002600000000snowflake-2.9.2/common/proxy/check.gopackage proxy import ( "errors" "net/url" "strings" ) var errUnsupportedProxyType = errors.New("unsupported proxy type") func CheckProxyProtocolSupport(proxy *url.URL) error { switch strings.ToLower(proxy.Scheme) { case "socks5": return nil default: return errUnsupportedProxyType } } 0707010000004D000081A400000000000000000000000165F88C50000018A0000000000000000000000000000000000000002700000000snowflake-2.9.2/common/proxy/client.gopackage proxy import ( "context" "errors" "log" "net" "net/url" "strconv" "time" "github.com/miekg/dns" "github.com/pion/transport/v2" "github.com/txthinking/socks5" ) func NewSocks5UDPClient(addr *url.URL) SocksClient { return SocksClient{addr: addr} } type SocksClient struct { addr *url.URL } type SocksConn struct { net.Conn socks5Client *socks5.Client } func (s SocksConn) SetReadBuffer(bytes int) error { return nil } func (s SocksConn) SetWriteBuffer(bytes int) error { return nil } func (s SocksConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) { var buf [2000]byte n, err = s.Conn.Read(buf[:]) if err != nil { return 0, nil, err } Datagram, err := socks5.NewDatagramFromBytes(buf[:n]) if err != nil { return 0, nil, err } addr, err = net.ResolveUDPAddr("udp", Datagram.Address()) if err != nil { return 0, nil, err } n = copy(b, Datagram.Data) if n < len(Datagram.Data) { return 0, nil, errors.New("short buffer") } return len(Datagram.Data), addr, nil } func (s SocksConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) { panic("unimplemented") } func (s SocksConn) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) { a, addrb, portb, err := socks5.ParseAddress(addr.String()) if err != nil { return 0, err } packet := socks5.NewDatagram(a, addrb, portb, b) _, err = s.Conn.Write(packet.Bytes()) if err != nil { return 0, err } return len(b), nil } func (s SocksConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) { panic("unimplemented") } func (sc *SocksClient) ListenPacket(network string, locAddr *net.UDPAddr) (transport.UDPConn, error) { conn, err := sc.listenPacket() if err != nil { log.Println("[SOCKS5 Client Error] cannot listen packet", err) } return conn, err } func (sc *SocksClient) listenPacket() (transport.UDPConn, error) { var username, password string if sc.addr.User != nil { username = sc.addr.User.Username() password, _ = sc.addr.User.Password() } client, err := socks5.NewClient( sc.addr.Host, username, password, 300, 300) if err != nil { return nil, err } err = client.Negotiate(nil) if err != nil { return nil, err } udpRequest := socks5.NewRequest(socks5.CmdUDP, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}) reply, err := client.Request(udpRequest) if err != nil { return nil, err } udpServerAddr := socks5.ToAddress(reply.Atyp, reply.BndAddr, reply.BndPort) conn, err := net.Dial("udp", udpServerAddr) if err != nil { return nil, err } return &SocksConn{conn, client}, nil } func (s SocksConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { return s.WriteToUDP(p, addr.(*net.UDPAddr)) } func (s SocksConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { return s.ReadFromUDP(p) } func (s SocksConn) Read(b []byte) (int, error) { panic("implement me") } func (s SocksConn) RemoteAddr() net.Addr { panic("implement me") } func (s SocksConn) Write(b []byte) (int, error) { panic("implement me") } func (sc *SocksClient) ResolveUDPAddr(network string, address string) (*net.UDPAddr, error) { dnsServer, err := net.ResolveUDPAddr("udp", "1.1.1.1:53") if err != nil { return nil, err } proxiedResolver := newDnsResolver(sc, dnsServer) ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() host, port, err := net.SplitHostPort(address) if err != nil { return nil, err } ip, err := proxiedResolver.lookupIPAddr(ctx, host, network == "udp6") if err != nil { return nil, err } if len(ip) <= 0 { return nil, errors.New("cannot resolve hostname: NXDOMAIN") } switch network { case "udp4": var v4IPAddr []net.IPAddr for _, v := range ip { if v.IP.To4() != nil { v4IPAddr = append(v4IPAddr, v) } } ip = v4IPAddr case "udp6": var v6IPAddr []net.IPAddr for _, v := range ip { if v.IP.To4() == nil { v6IPAddr = append(v6IPAddr, v) } } ip = v6IPAddr case "udp": default: return nil, errors.New("unknown network") } if len(ip) <= 0 { return nil, errors.New("cannot resolve hostname: so suitable address") } portInInt, err := strconv.ParseInt(port, 10, 32) return &net.UDPAddr{ IP: ip[0].IP, Port: int(portInInt), Zone: "", }, nil } func newDnsResolver(sc *SocksClient, serverAddress net.Addr) *dnsResolver { return &dnsResolver{sc: sc, serverAddress: serverAddress} } type dnsResolver struct { sc *SocksClient serverAddress net.Addr } func (r *dnsResolver) lookupIPAddr(ctx context.Context, host string, ipv6 bool) ([]net.IPAddr, error) { packetConn, err := r.sc.listenPacket() if err != nil { return nil, err } msg := new(dns.Msg) if !ipv6 { msg.SetQuestion(dns.Fqdn(host), dns.TypeA) } else { msg.SetQuestion(dns.Fqdn(host), dns.TypeAAAA) } encodedMsg, err := msg.Pack() if err != nil { log.Println(err.Error()) } for i := 2; i >= 0; i-- { _, err := packetConn.WriteTo(encodedMsg, r.serverAddress) if err != nil { log.Println(err.Error()) } } ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() go func() { <-ctx.Done() packetConn.Close() }() var dataBuf [1600]byte n, _, err := packetConn.ReadFrom(dataBuf[:]) if err != nil { return nil, err } err = msg.Unpack(dataBuf[:n]) if err != nil { return nil, err } var returnedIPs []net.IPAddr for _, resp := range msg.Answer { switch respTyped := resp.(type) { case *dns.A: returnedIPs = append(returnedIPs, net.IPAddr{IP: respTyped.A}) case *dns.AAAA: returnedIPs = append(returnedIPs, net.IPAddr{IP: respTyped.AAAA}) } } return returnedIPs, nil } func NewTransportWrapper(sc *SocksClient, innerNet transport.Net) transport.Net { return &transportWrapper{sc: sc, Net: innerNet} } type transportWrapper struct { transport.Net sc *SocksClient } func (t *transportWrapper) ListenUDP(network string, locAddr *net.UDPAddr) (transport.UDPConn, error) { return t.sc.ListenPacket(network, nil) } func (t *transportWrapper) ListenPacket(network string, address string) (net.PacketConn, error) { return t.sc.ListenPacket(network, nil) } func (t *transportWrapper) ResolveUDPAddr(network string, address string) (*net.UDPAddr, error) { return t.sc.ResolveUDPAddr(network, address) } 0707010000004E000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001F00000000snowflake-2.9.2/common/safelog0707010000004F000081A400000000000000000000000165F88C5000000982000000000000000000000000000000000000002600000000snowflake-2.9.2/common/safelog/log.go//Package for a safer logging wrapper around the standard logging package // import "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/safelog" package safelog import ( "bytes" "io" "regexp" "sync" ) const ipv4Address = `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}` // %3A and %3a are for matching : in URL-encoded IPv6 addresses const colon = `(:|%3a|%3A)` const ipv6Address = `([0-9a-fA-F]{0,4}` + colon + `){5,7}([0-9a-fA-F]{0,4})?` const ipv6Compressed = `([0-9a-fA-F]{0,4}` + colon + `){0,5}([0-9a-fA-F]{0,4})?(` + colon + `){2}([0-9a-fA-F]{0,4}` + colon + `){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. var newBytes []byte index := 0 for { loc := pattern.FindSubmatchIndex(scrubbedBytes[index:]) if loc == nil { break } newBytes = append(newBytes, scrubbedBytes[index:index+loc[2]]...) newBytes = append(newBytes, []byte("[scrubbed]")...) index = index + loc[3] } scrubbedBytes = append(newBytes, scrubbedBytes[index:]...) } 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:] } } 07070100000050000081A400000000000000000000000165F88C500000178E000000000000000000000000000000000000002B00000000snowflake-2.9.2/common/safelog/log_test.gopackage 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", }, { //Make sure ipv6 addresses where : are encoded as %3A or %3a are scrubbed "error dialing relay: wss://snowflake.torproject.net/?client_ip=6201%3ac8%3A3004%3A%3A1234", "error dialing relay: wss://snowflake.torproject.net/?client_ip=[scrubbed]\n", }, { // make sure url encoded IPv6 IPs get scrubbed (%3a) "http2: panic serving [fd00%3a111%3af000%3a777%3a9999%3abbbb%3affff%3adddd]:58344: xxx", "http2: panic serving [scrubbed]: xxx\n", }, { // make sure url encoded IPv6 IPs get scrubbed (%3A) "http2: panic serving [fd00%3a111%3af000%3a777%3a9999%3abbbb%3affff%3adddd]:58344: xxx", "http2: panic serving [scrubbed]: xxx\n", }, { // make sure url encoded IPv6 IPs get scrubbed, different URL (%3A) "error dialing relay: wss://snowflake.torproject.net/?client_ip=fd00%3A8888%3Abbbb%3Acccc%3Adddd%3Aeeee%3A2222%3A123 = dial tcp xxx", "error dialing relay: wss://snowflake.torproject.net/?client_ip=[scrubbed] = dial tcp xxx\n", }, { // make sure url encoded IPv6 IPs get scrubbed (%3A), compressed "http2: panic serving [1%3A2%3A3%3A%3Ad%3Ae%3Af]:55: xxx", "http2: panic serving [scrubbed]: xxx\n", }, { // make sure url encoded IPv6 IPs get scrubbed (%3A), compressed "error dialing relay: wss://snowflake.torproject.net/?client_ip=1%3A2%3A3%3A%3Ad%3Ae%3Af = dial tcp xxx", "error dialing relay: wss://snowflake.torproject.net/?client_ip=[scrubbed] = dial tcp xxx\n", }, { // multiple space-separated IP addresses "Allowed stations: [10.0.1.1 10.0.1.2 10.0.1.3 10.0.1.4]\n", "Allowed stations: [[scrubbed] [scrubbed] [scrubbed] [scrubbed]]\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") } } } 07070100000051000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000002100000000snowflake-2.9.2/common/sqsclient07070100000052000081A400000000000000000000000165F88C500000047D000000000000000000000000000000000000002E00000000snowflake-2.9.2/common/sqsclient/sqsclient.gopackage sqsclient import ( "context" "github.com/aws/aws-sdk-go-v2/service/sqs" ) type SQSClient interface { ReceiveMessage(ctx context.Context, input *sqs.ReceiveMessageInput, optFns ...func(*sqs.Options)) (*sqs.ReceiveMessageOutput, error) ListQueues(ctx context.Context, input *sqs.ListQueuesInput, optFns ...func(*sqs.Options)) (*sqs.ListQueuesOutput, error) GetQueueAttributes(ctx context.Context, input *sqs.GetQueueAttributesInput, optFns ...func(*sqs.Options)) (*sqs.GetQueueAttributesOutput, error) DeleteQueue(ctx context.Context, input *sqs.DeleteQueueInput, optFns ...func(*sqs.Options)) (*sqs.DeleteQueueOutput, error) CreateQueue(ctx context.Context, input *sqs.CreateQueueInput, optFns ...func(*sqs.Options)) (*sqs.CreateQueueOutput, error) SendMessage(ctx context.Context, input *sqs.SendMessageInput, optFns ...func(*sqs.Options)) (*sqs.SendMessageOutput, error) DeleteMessage(ctx context.Context, input *sqs.DeleteMessageInput, optFns ...func(*sqs.Options)) (*sqs.DeleteMessageOutput, error) GetQueueUrl(ctx context.Context, input *sqs.GetQueueUrlInput, optFns ...func(*sqs.Options)) (*sqs.GetQueueUrlOutput, error) } 07070100000053000081A400000000000000000000000165F88C5000001E3D000000000000000000000000000000000000003300000000snowflake-2.9.2/common/sqsclient/sqsclient_mock.go// Code generated by MockGen. DO NOT EDIT. // Source: common/sqsclient/sqsclient.go // Package mock_sqsclient is a generated GoMock package. package sqsclient import ( context "context" reflect "reflect" sqs "github.com/aws/aws-sdk-go-v2/service/sqs" gomock "github.com/golang/mock/gomock" ) // MockSQSClient is a mock of SQSClient interface. type MockSQSClient struct { ctrl *gomock.Controller recorder *MockSQSClientMockRecorder } // MockSQSClientMockRecorder is the mock recorder for MockSQSClient. type MockSQSClientMockRecorder struct { mock *MockSQSClient } // NewMockSQSClient creates a new mock instance. func NewMockSQSClient(ctrl *gomock.Controller) *MockSQSClient { mock := &MockSQSClient{ctrl: ctrl} mock.recorder = &MockSQSClientMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockSQSClient) EXPECT() *MockSQSClientMockRecorder { return m.recorder } // CreateQueue mocks base method. func (m *MockSQSClient) CreateQueue(ctx context.Context, input *sqs.CreateQueueInput, optFns ...func(*sqs.Options)) (*sqs.CreateQueueOutput, error) { m.ctrl.T.Helper() varargs := []interface{}{ctx, input} for _, a := range optFns { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "CreateQueue", varargs...) ret0, _ := ret[0].(*sqs.CreateQueueOutput) ret1, _ := ret[1].(error) return ret0, ret1 } // CreateQueue indicates an expected call of CreateQueue. func (mr *MockSQSClientMockRecorder) CreateQueue(ctx, input interface{}, optFns ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{ctx, input}, optFns...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateQueue", reflect.TypeOf((*MockSQSClient)(nil).CreateQueue), varargs...) } // DeleteMessage mocks base method. func (m *MockSQSClient) DeleteMessage(ctx context.Context, input *sqs.DeleteMessageInput, optFns ...func(*sqs.Options)) (*sqs.DeleteMessageOutput, error) { m.ctrl.T.Helper() varargs := []interface{}{ctx, input} for _, a := range optFns { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "DeleteMessage", varargs...) ret0, _ := ret[0].(*sqs.DeleteMessageOutput) ret1, _ := ret[1].(error) return ret0, ret1 } // DeleteMessage indicates an expected call of DeleteMessage. func (mr *MockSQSClientMockRecorder) DeleteMessage(ctx, input interface{}, optFns ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{ctx, input}, optFns...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessage", reflect.TypeOf((*MockSQSClient)(nil).DeleteMessage), varargs...) } // DeleteQueue mocks base method. func (m *MockSQSClient) DeleteQueue(ctx context.Context, input *sqs.DeleteQueueInput, optFns ...func(*sqs.Options)) (*sqs.DeleteQueueOutput, error) { m.ctrl.T.Helper() varargs := []interface{}{ctx, input} for _, a := range optFns { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "DeleteQueue", varargs...) ret0, _ := ret[0].(*sqs.DeleteQueueOutput) ret1, _ := ret[1].(error) return ret0, ret1 } // DeleteQueue indicates an expected call of DeleteQueue. func (mr *MockSQSClientMockRecorder) DeleteQueue(ctx, input interface{}, optFns ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{ctx, input}, optFns...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteQueue", reflect.TypeOf((*MockSQSClient)(nil).DeleteQueue), varargs...) } // GetQueueAttributes mocks base method. func (m *MockSQSClient) GetQueueAttributes(ctx context.Context, input *sqs.GetQueueAttributesInput, optFns ...func(*sqs.Options)) (*sqs.GetQueueAttributesOutput, error) { m.ctrl.T.Helper() varargs := []interface{}{ctx, input} for _, a := range optFns { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetQueueAttributes", varargs...) ret0, _ := ret[0].(*sqs.GetQueueAttributesOutput) ret1, _ := ret[1].(error) return ret0, ret1 } // GetQueueAttributes indicates an expected call of GetQueueAttributes. func (mr *MockSQSClientMockRecorder) GetQueueAttributes(ctx, input interface{}, optFns ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{ctx, input}, optFns...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQueueAttributes", reflect.TypeOf((*MockSQSClient)(nil).GetQueueAttributes), varargs...) } // GetQueueUrl mocks base method. func (m *MockSQSClient) GetQueueUrl(ctx context.Context, input *sqs.GetQueueUrlInput, optFns ...func(*sqs.Options)) (*sqs.GetQueueUrlOutput, error) { m.ctrl.T.Helper() varargs := []interface{}{ctx, input} for _, a := range optFns { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetQueueUrl", varargs...) ret0, _ := ret[0].(*sqs.GetQueueUrlOutput) ret1, _ := ret[1].(error) return ret0, ret1 } // GetQueueUrl indicates an expected call of GetQueueUrl. func (mr *MockSQSClientMockRecorder) GetQueueUrl(ctx, input interface{}, optFns ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{ctx, input}, optFns...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQueueUrl", reflect.TypeOf((*MockSQSClient)(nil).GetQueueUrl), varargs...) } // ListQueues mocks base method. func (m *MockSQSClient) ListQueues(ctx context.Context, input *sqs.ListQueuesInput, optFns ...func(*sqs.Options)) (*sqs.ListQueuesOutput, error) { m.ctrl.T.Helper() varargs := []interface{}{ctx, input} for _, a := range optFns { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "ListQueues", varargs...) ret0, _ := ret[0].(*sqs.ListQueuesOutput) ret1, _ := ret[1].(error) return ret0, ret1 } // ListQueues indicates an expected call of ListQueues. func (mr *MockSQSClientMockRecorder) ListQueues(ctx, input interface{}, optFns ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{ctx, input}, optFns...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListQueues", reflect.TypeOf((*MockSQSClient)(nil).ListQueues), varargs...) } // ReceiveMessage mocks base method. func (m *MockSQSClient) ReceiveMessage(ctx context.Context, input *sqs.ReceiveMessageInput, optFns ...func(*sqs.Options)) (*sqs.ReceiveMessageOutput, error) { m.ctrl.T.Helper() varargs := []interface{}{ctx, input} for _, a := range optFns { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "ReceiveMessage", varargs...) ret0, _ := ret[0].(*sqs.ReceiveMessageOutput) ret1, _ := ret[1].(error) return ret0, ret1 } // ReceiveMessage indicates an expected call of ReceiveMessage. func (mr *MockSQSClientMockRecorder) ReceiveMessage(ctx, input interface{}, optFns ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{ctx, input}, optFns...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReceiveMessage", reflect.TypeOf((*MockSQSClient)(nil).ReceiveMessage), varargs...) } // SendMessage mocks base method. func (m *MockSQSClient) SendMessage(ctx context.Context, input *sqs.SendMessageInput, optFns ...func(*sqs.Options)) (*sqs.SendMessageOutput, error) { m.ctrl.T.Helper() varargs := []interface{}{ctx, input} for _, a := range optFns { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "SendMessage", varargs...) ret0, _ := ret[0].(*sqs.SendMessageOutput) ret1, _ := ret[1].(error) return ret0, ret1 } // SendMessage indicates an expected call of SendMessage. func (mr *MockSQSClientMockRecorder) SendMessage(ctx, input interface{}, optFns ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{ctx, input}, optFns...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockSQSClient)(nil).SendMessage), varargs...) } 07070100000054000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000002000000000snowflake-2.9.2/common/sqscreds07070100000055000081A400000000000000000000000165F88C5000000353000000000000000000000000000000000000003200000000snowflake-2.9.2/common/sqscreds/generate_creds.gopackage main import ( "fmt" sqscreds "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/sqscreds/lib" ) // This script can be run to generate the encoded SQS credentials to pass as a CLI param or SOCKS option to the client func main() { var accessKey, secretKey string fmt.Print("Enter Access Key: ") _, err := fmt.Scanln(&accessKey) if err != nil { fmt.Println("Error reading access key:", err) return } fmt.Print("Enter Secret Key: ") _, err = fmt.Scanln(&secretKey) if err != nil { fmt.Println("Error reading access key:", err) return } awsCreds := sqscreds.AwsCreds{AwsAccessKeyId: accessKey, AwsSecretKey: secretKey} println() println("Encoded Credentials:") res, err := awsCreds.Base64() if err != nil { fmt.Println("Error encoding credentials:", err) return } println(res) } 07070100000056000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000002400000000snowflake-2.9.2/common/sqscreds/lib07070100000057000081A400000000000000000000000165F88C50000002A9000000000000000000000000000000000000003100000000snowflake-2.9.2/common/sqscreds/lib/sqs_creds.gopackage sqscreds import ( "encoding/base64" "encoding/json" ) type AwsCreds struct { AwsAccessKeyId string `json:"aws-access-key-id"` AwsSecretKey string `json:"aws-secret-key"` } func (awsCreds AwsCreds) Base64() (string, error) { jsonData, err := json.Marshal(awsCreds) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(jsonData), nil } func AwsCredsFromBase64(base64Str string) (AwsCreds, error) { var awsCreds AwsCreds jsonData, err := base64.StdEncoding.DecodeString(base64Str) if err != nil { return awsCreds, err } err = json.Unmarshal(jsonData, &awsCreds) if err != nil { return awsCreds, err } return awsCreds, nil } 07070100000058000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001C00000000snowflake-2.9.2/common/task07070100000059000081A400000000000000000000000165F88C5000000A23000000000000000000000000000000000000002800000000snowflake-2.9.2/common/task/periodic.go// Package task // Reused from https://github.com/v2fly/v2ray-core/blob/784775f68922f07d40c9eead63015b2026af2ade/common/task/periodic.go /* The MIT License (MIT) Copyright (c) 2015-2021 V2Ray & V2Fly Community 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 task import ( "sync" "time" ) // Periodic is a task that runs periodically. type Periodic struct { // Interval of the task being run Interval time.Duration // Execute is the task function Execute func() error access sync.Mutex timer *time.Timer running bool } func (t *Periodic) hasClosed() bool { t.access.Lock() defer t.access.Unlock() return !t.running } func (t *Periodic) checkedExecute() error { if t.hasClosed() { return nil } if err := t.Execute(); err != nil { t.access.Lock() t.running = false t.access.Unlock() return err } t.access.Lock() defer t.access.Unlock() if !t.running { return nil } t.timer = time.AfterFunc(t.Interval, func() { t.checkedExecute() }) return nil } // Start implements common.Runnable. func (t *Periodic) Start() error { t.access.Lock() if t.running { t.access.Unlock() return nil } t.running = true t.access.Unlock() if err := t.checkedExecute(); err != nil { t.access.Lock() t.running = false t.access.Unlock() return err } return nil } func (t *Periodic) WaitThenStart() { time.AfterFunc(t.Interval, func() { t.Start() }) } // Close implements common.Closable. func (t *Periodic) Close() error { t.access.Lock() defer t.access.Unlock() t.running = false if t.timer != nil { t.timer.Stop() t.timer = nil } return nil } 0707010000005A000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000002300000000snowflake-2.9.2/common/turbotunnel0707010000005B000081A400000000000000000000000165F88C50000003A5000000000000000000000000000000000000002F00000000snowflake-2.9.2/common/turbotunnel/clientid.gopackage 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[:]) } 0707010000005C000081A400000000000000000000000165F88C50000010C4000000000000000000000000000000000000003000000000snowflake-2.9.2/common/turbotunnel/clientmap.gopackage 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 smux's internal idle // timeout. If a client is removed from the client map while the smux session is // still live, the worst that can happen is a loss of whatever packets were in // the send queue at the time. If smux 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() queue := m.inner.SendQueue(addr, time.Now()) m.lock.Unlock() return queue } // 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 } 0707010000005D000081A400000000000000000000000165F88C500000019D000000000000000000000000000000000000003500000000snowflake-2.9.2/common/turbotunnel/clientmap_test.gopackage turbotunnel import ( "testing" "time" ) // Benchmark the ClientMap.SendQueue function. This is mainly measuring the cost // of the mutex operations around the call to clientMapInner.SendQueue. func BenchmarkSendQueue(b *testing.B) { m := NewClientMap(1 * time.Hour) id := NewClientID() m.SendQueue(id) // populate the entry for id b.ResetTimer() for i := 0; i < b.N; i++ { m.SendQueue(id) } } 0707010000005E000081A400000000000000000000000165F88C500000023C000000000000000000000000000000000000002D00000000snowflake-2.9.2/common/turbotunnel/consts.go// 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 = 512 var errClosedPacketConn = errors.New("operation on closed connection") var errNotImplemented = errors.New("not implemented") 0707010000005F000081A400000000000000000000000165F88C5000001580000000000000000000000000000000000000003600000000snowflake-2.9.2/common/turbotunnel/queuepacketconn.gopackage 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{} mtu int // Pool of reusable mtu-sized buffers. bufPool sync.Pool // 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. The maximum packet size is mtu. func NewQueuePacketConn(localAddr net.Addr, timeout time.Duration, mtu int) *QueuePacketConn { return &QueuePacketConn{ clients: NewClientMap(timeout), localAddr: localAddr, recvQueue: make(chan taggedPacket, queueSize), closed: make(chan struct{}), mtu: mtu, bufPool: sync.Pool{New: func() interface{} { return make([]byte, mtu) }}, } } // QueueIncoming queues an incoming packet and its source address, to be // returned in a future call to ReadFrom. If p is longer than the MTU, only its // first MTU bytes will be used. 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 := c.bufPool.Get().([]byte) if len(p) < cap(buf) { buf = buf[:len(p)] } else { buf = buf[:cap(buf)] } copy(buf, p) select { case c.recvQueue <- taggedPacket{buf, addr}: default: // Drop the incoming packet if the receive queue is full. c.Restore(buf) } } // 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) } // Restore adds a slice to the internal pool of packet buffers. Typically you // will call this with a slice from the OutgoingQueue channel once you are done // using it. (It is not an error to fail to do so, it will just result in more // allocations.) func (c *QueuePacketConn) Restore(p []byte) { if cap(p) >= c.mtu { c.bufPool.Put(p) } } // 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: n := copy(p, packet.P) c.Restore(packet.P) return n, packet.Addr, nil } } // WriteTo queues an outgoing packet for the given address. The queue can later // be retrieved using the OutgoingQueue method. If p is longer than the MTU, // only its first MTU bytes will be used. 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 := c.bufPool.Get().([]byte) if len(p) < cap(buf) { buf = buf[:len(p)] } else { buf = buf[:cap(buf)] } 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. c.Restore(buf) return len(p), 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 } 07070100000060000081A400000000000000000000000165F88C5000001ABA000000000000000000000000000000000000003B00000000snowflake-2.9.2/common/turbotunnel/queuepacketconn_test.gopackage turbotunnel import ( "bytes" "fmt" "net" "sync" "testing" "time" "github.com/xtaci/kcp-go/v5" ) type emptyAddr struct{} func (_ emptyAddr) Network() string { return "empty" } func (_ emptyAddr) String() string { return "empty" } type intAddr int func (i intAddr) Network() string { return "int" } func (i intAddr) String() string { return fmt.Sprintf("%d", i) } // Run with -benchmem to see memory allocations. func BenchmarkQueueIncoming(b *testing.B) { conn := NewQueuePacketConn(emptyAddr{}, 1*time.Hour, 500) defer conn.Close() b.ResetTimer() var p [500]byte for i := 0; i < b.N; i++ { conn.QueueIncoming(p[:], emptyAddr{}) } b.StopTimer() } // BenchmarkWriteTo benchmarks the QueuePacketConn.WriteTo function. func BenchmarkWriteTo(b *testing.B) { conn := NewQueuePacketConn(emptyAddr{}, 1*time.Hour, 500) defer conn.Close() b.ResetTimer() var p [500]byte for i := 0; i < b.N; i++ { conn.WriteTo(p[:], emptyAddr{}) } b.StopTimer() } // TestQueueIncomingOversize tests that QueueIncoming truncates packets that are // larger than the MTU. func TestQueueIncomingOversize(t *testing.T) { const payload = "abcdefghijklmnopqrstuvwxyz" conn := NewQueuePacketConn(emptyAddr{}, 1*time.Hour, len(payload)-1) defer conn.Close() conn.QueueIncoming([]byte(payload), emptyAddr{}) var p [500]byte n, _, err := conn.ReadFrom(p[:]) if err != nil { t.Fatal(err) } if !bytes.Equal(p[:n], []byte(payload[:len(payload)-1])) { t.Fatalf("payload was %+q, expected %+q", p[:n], payload[:len(payload)-1]) } } // TestWriteToOversize tests that WriteTo truncates packets that are larger than // the MTU. func TestWriteToOversize(t *testing.T) { const payload = "abcdefghijklmnopqrstuvwxyz" conn := NewQueuePacketConn(emptyAddr{}, 1*time.Hour, len(payload)-1) defer conn.Close() conn.WriteTo([]byte(payload), emptyAddr{}) p := <-conn.OutgoingQueue(emptyAddr{}) if !bytes.Equal(p, []byte(payload[:len(payload)-1])) { t.Fatalf("payload was %+q, expected %+q", p, payload[:len(payload)-1]) } } // TestRestoreMTU tests that Restore ignores any inputs that are not at least // MTU-sized. func TestRestoreMTU(t *testing.T) { const mtu = 500 const payload = "hello" conn := NewQueuePacketConn(emptyAddr{}, 1*time.Hour, mtu) defer conn.Close() conn.Restore(make([]byte, mtu-1)) // This WriteTo may use the short slice we just gave to Restore. conn.WriteTo([]byte(payload), emptyAddr{}) // Read the queued slice and ensure its capacity is at least the MTU. p := <-conn.OutgoingQueue(emptyAddr{}) if cap(p) != mtu { t.Fatalf("cap was %v, expected %v", cap(p), mtu) } // Check the payload while we're at it. if !bytes.Equal(p, []byte(payload)) { t.Fatalf("payload was %+q, expected %+q", p, payload) } } // TestRestoreCap tests that Restore can use slices whose cap is at least the // MTU, even if the len is shorter. func TestRestoreCap(t *testing.T) { const mtu = 500 const payload = "hello" conn := NewQueuePacketConn(emptyAddr{}, 1*time.Hour, mtu) defer conn.Close() conn.Restore(make([]byte, 0, mtu)) conn.WriteTo([]byte(payload), emptyAddr{}) p := <-conn.OutgoingQueue(emptyAddr{}) if !bytes.Equal(p, []byte(payload)) { t.Fatalf("payload was %+q, expected %+q", p, payload) } } // DiscardPacketConn is a net.PacketConn whose ReadFrom method block forever and // whose WriteTo method discards whatever it is called with. type DiscardPacketConn struct{} func (_ DiscardPacketConn) ReadFrom(_ []byte) (int, net.Addr, error) { select {} } // block forever func (_ DiscardPacketConn) WriteTo(p []byte, _ net.Addr) (int, error) { return len(p), nil } func (_ DiscardPacketConn) Close() error { return nil } func (_ DiscardPacketConn) LocalAddr() net.Addr { return emptyAddr{} } func (_ DiscardPacketConn) SetDeadline(t time.Time) error { return nil } func (_ DiscardPacketConn) SetReadDeadline(t time.Time) error { return nil } func (_ DiscardPacketConn) SetWriteDeadline(t time.Time) error { return nil } // TranscriptPacketConn keeps a log of the []byte argument to every call to // WriteTo. type TranscriptPacketConn struct { Transcript [][]byte lock sync.Mutex net.PacketConn } func NewTranscriptPacketConn(inner net.PacketConn) *TranscriptPacketConn { return &TranscriptPacketConn{ PacketConn: inner, } } func (c *TranscriptPacketConn) WriteTo(p []byte, addr net.Addr) (int, error) { c.lock.Lock() defer c.lock.Unlock() p2 := make([]byte, len(p)) copy(p2, p) c.Transcript = append(c.Transcript, p2) return c.PacketConn.WriteTo(p, addr) } // Tests that QueuePacketConn.WriteTo is compatible with the way kcp-go uses // PacketConn, allocating source buffers in a sync.Pool. // // https://bugs.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/40260 func TestQueuePacketConnWriteToKCP(t *testing.T) { // Start a goroutine to constantly exercise kcp UDPSession.tx, writing // packets with payload "XXXX". done := make(chan struct{}, 0) defer close(done) ready := make(chan struct{}, 0) go func() { var readyClose sync.Once defer readyClose.Do(func() { close(ready) }) pconn := DiscardPacketConn{} defer pconn.Close() loop: for { select { case <-done: break loop default: } // Create a new UDPSession, send once, then discard the // UDPSession. conn, err := kcp.NewConn2(intAddr(2), nil, 0, 0, pconn) if err != nil { panic(err) } _, err = conn.Write([]byte("XXXX")) if err != nil { panic(err) } conn.Close() // Signal the main test to start once we have done one // iterator of this noisy loop. readyClose.Do(func() { close(ready) }) } }() pconn := NewQueuePacketConn(emptyAddr{}, 1*time.Hour, 500) defer pconn.Close() addr1 := intAddr(1) outgoing := pconn.OutgoingQueue(addr1) // Once the "XXXX" goroutine is started, repeatedly send a packet, wait, // then retrieve it and check whether it has changed since being sent. <-ready for i := 0; i < 10; i++ { transcript := NewTranscriptPacketConn(pconn) conn, err := kcp.NewConn2(addr1, nil, 0, 0, transcript) if err != nil { panic(err) } _, err = conn.Write([]byte("hello world")) if err != nil { panic(err) } err = conn.Close() if err != nil { panic(err) } // A sleep after the Write makes buffer reuse more likely. time.Sleep(100 * time.Millisecond) if len(transcript.Transcript) == 0 { panic("empty transcript") } for j, tr := range transcript.Transcript { p := <-outgoing // This test is meant to detect unsynchronized memory // changes, so freeze the slice we just read. p2 := make([]byte, len(p)) copy(p2, p) if !bytes.Equal(p2, tr) { t.Fatalf("%d %d packet changed between send and recv\nsend: %+q\nrecv: %+q", i, j, tr, p2) } } } } 07070100000061000081A400000000000000000000000165F88C50000016B7000000000000000000000000000000000000003700000000snowflake-2.9.2/common/turbotunnel/redialpacketconn.gopackage 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 } // NewRedialPacketConn 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 } 07070100000062000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001C00000000snowflake-2.9.2/common/util07070100000063000081A400000000000000000000000165F88C50000014FD000000000000000000000000000000000000002400000000snowflake-2.9.2/common/util/util.gopackage util import ( "encoding/json" "errors" "log" "net" "net/http" "slices" "sort" "github.com/pion/ice/v2" "github.com/pion/sdp/v3" "github.com/pion/webrtc/v3" "github.com/realclientip/realclientip-go" ) 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) } // Attempts to retrieve the client IP of where the HTTP request originating. // There is no standard way to do this since the original client IP can be included in a number of different headers, // depending on the proxies and load balancers between the client and the server. We attempt to check as many of these // headers as possible to determine a "best guess" of the client IP // Using this as a reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded func GetClientIp(req *http.Request) string { // We check the "Fowarded" header first, followed by the "X-Forwarded-For" header, and then use the "RemoteAddr" as // a last resort. We use the leftmost address since it is the closest one to the client. strat := realclientip.NewChainStrategy( realclientip.Must(realclientip.NewLeftmostNonPrivateStrategy("Forwarded")), realclientip.Must(realclientip.NewLeftmostNonPrivateStrategy("X-Forwarded-For")), realclientip.RemoteAddrStrategy{}, ) clientIp := strat.ClientIP(req.Header, req.RemoteAddr) return clientIp } // Returns a list of IP addresses of ICE candidates, roughly in descending order for accuracy for geolocation func GetCandidateAddrs(sdpStr string) []net.IP { var desc sdp.SessionDescription err := desc.Unmarshal([]byte(sdpStr)) if err != nil { log.Printf("GetCandidateAddrs: failed to unmarshal SDP: %v\n", err) return []net.IP{} } iceCandidates := make([]ice.Candidate, 0) for _, m := range desc.MediaDescriptions { for _, a := range m.Attributes { if a.IsICECandidate() { c, err := ice.UnmarshalCandidate(a.Value) if err == nil { iceCandidates = append(iceCandidates, c) } } } } // ICE candidates are first sorted in asecending order of priority, to match convention of providing a custom Less // function to sort sort.Slice(iceCandidates, func(i, j int) bool { if iceCandidates[i].Type() != iceCandidates[j].Type() { // Sort by candidate type first, in the order specified in https://datatracker.ietf.org/doc/html/rfc8445#section-5.1.2.2 // Higher priority candidate types are more efficient, which likely means they are closer to the client // itself, providing a more accurate result for geolocation return ice.CandidateType(iceCandidates[i].Type().Preference()) < ice.CandidateType(iceCandidates[j].Type().Preference()) } // Break ties with the ICE candidate's priority property return iceCandidates[i].Priority() < iceCandidates[j].Priority() }) slices.Reverse(iceCandidates) sortedIpAddr := make([]net.IP, 0) for _, c := range iceCandidates { ip := net.ParseIP(c.Address()) if ip != nil { sortedIpAddr = append(sortedIpAddr, ip) } } return sortedIpAddr } 07070100000064000081A400000000000000000000000165F88C5000001160000000000000000000000000000000000000002900000000snowflake-2.9.2/common/util/util_test.gopackage util import ( "net" "net/http" "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) }) Convey("GetClientIp", t, func() { // Should use Forwarded header req1, _ := http.NewRequest("GET", "https://example.com", nil) req1.Header.Add("X-Forwarded-For", "1.1.1.1, 2001:db8:cafe::99%eth0, 3.3.3.3, 192.168.1.1") req1.Header.Add("Forwarded", `For=fe80::abcd;By=fe80::1234, Proto=https;For=::ffff:188.0.2.128, For="[2001:db8:cafe::17]:4848", For=fc00::1`) req1.RemoteAddr = "192.168.1.2:8888" So(GetClientIp(req1), ShouldEqual, "188.0.2.128") // Should use X-Forwarded-For header req2, _ := http.NewRequest("GET", "https://example.com", nil) req2.Header.Add("X-Forwarded-For", "1.1.1.1, 2001:db8:cafe::99%eth0, 3.3.3.3, 192.168.1.1") req2.RemoteAddr = "192.168.1.2:8888" So(GetClientIp(req2), ShouldEqual, "1.1.1.1") // Should use RemoteAddr req3, _ := http.NewRequest("GET", "https://example.com", nil) req3.RemoteAddr = "192.168.1.2:8888" So(GetClientIp(req3), ShouldEqual, "192.168.1.2") // Should return empty client IP req4, _ := http.NewRequest("GET", "https://example.com", nil) So(GetClientIp(req4), ShouldEqual, "") }) Convey("GetCandidateAddrs", t, func() { // Should prioritize type in the following order: https://datatracker.ietf.org/doc/html/rfc8445#section-5.1.2.2 // Break ties using priority value 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 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" const sdp = offerStart + "a=candidate:3769337065 1 udp 2122260223 8.8.8.8 56688 typ prflx\r\n" + "a=candidate:3769337065 1 udp 2122260223 129.97.124.13 56688 typ relay\r\n" + "a=candidate:3769337065 1 udp 2122260223 129.97.124.14 56688 typ srflx\r\n" + "a=candidate:3769337065 1 udp 2122260223 129.97.124.15 56688 typ host\r\n" + "a=candidate:3769337065 1 udp 2122260224 129.97.124.16 56688 typ host\r\n" + offerEnd So(GetCandidateAddrs(sdp), ShouldEqual, []net.IP{ net.ParseIP("129.97.124.16"), net.ParseIP("129.97.124.15"), net.ParseIP("8.8.8.8"), net.ParseIP("129.97.124.14"), net.ParseIP("129.97.124.13"), }) }) } 07070100000065000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001C00000000snowflake-2.9.2/common/utls07070100000066000081A400000000000000000000000165F88C50000005EF000000000000000000000000000000000000002F00000000snowflake-2.9.2/common/utls/client_hello_id.gopackage utls import ( "errors" utls "github.com/refraction-networking/utls" "strings" ) // ported from https://github.com/max-b/snowflake/commit/9dded063cb74c6941a16ad90b9dd0e06e618e55e var clientHelloIDMap = map[string]utls.ClientHelloID{ // No HelloCustom: not useful for external configuration. // No HelloRandomized: doesn't negotiate consistent ALPN. "hellorandomizedalpn": utls.HelloRandomizedALPN, "hellorandomizednoalpn": utls.HelloRandomizedNoALPN, "hellofirefox_auto": utls.HelloFirefox_Auto, "hellofirefox_55": utls.HelloFirefox_55, "hellofirefox_56": utls.HelloFirefox_56, "hellofirefox_63": utls.HelloFirefox_63, "hellofirefox_65": utls.HelloFirefox_65, "hellochrome_auto": utls.HelloChrome_Auto, "hellochrome_58": utls.HelloChrome_58, "hellochrome_62": utls.HelloChrome_62, "hellochrome_70": utls.HelloChrome_70, "hellochrome_72": utls.HelloChrome_72, "helloios_auto": utls.HelloIOS_Auto, "helloios_11_1": utls.HelloIOS_11_1, "helloios_12_1": utls.HelloIOS_12_1, } var errNameNotFound = errors.New("client hello name is unrecognized") func NameToUTLSID(name string) (utls.ClientHelloID, error) { normalizedName := strings.ToLower(name) if id, ok := clientHelloIDMap[normalizedName]; ok { return id, nil } return utls.ClientHelloID{}, errNameNotFound } func ListAllNames() []string { var names []string for k, _ := range clientHelloIDMap { names = append(names, k) } return names } 07070100000067000081A400000000000000000000000165F88C5000000204000000000000000000000000000000000000003700000000snowflake-2.9.2/common/utls/client_hello_id_version.gopackage utls import ( "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/version" "strings" ) func generateVersionOutput() string { var versionOutputBuilder strings.Builder versionOutputBuilder.WriteString(`Known utls-imitate values: (empty) `) for _, name := range ListAllNames() { versionOutputBuilder.WriteString(name) versionOutputBuilder.WriteRune('\n') } return versionOutputBuilder.String() } func init() { version.AddVersionDetail(generateVersionOutput()) } 07070100000068000081A400000000000000000000000165F88C5000001C9B000000000000000000000000000000000000002C00000000snowflake-2.9.2/common/utls/roundtripper.gopackage utls import ( "context" "crypto/tls" "errors" "fmt" "golang.org/x/net/proxy" "net" "net/http" "net/url" "sync" "time" utls "github.com/refraction-networking/utls" "golang.org/x/net/http2" ) // Deprecated: use NewUTLSHTTPRoundTripperWithProxy instead func NewUTLSHTTPRoundTripper(clientHelloID utls.ClientHelloID, uTlsConfig *utls.Config, backdropTransport http.RoundTripper, removeSNI bool) http.RoundTripper { return NewUTLSHTTPRoundTripperWithProxy(clientHelloID, uTlsConfig, backdropTransport, removeSNI, nil) } // NewUTLSHTTPRoundTripperWithProxy creates an instance of RoundTripper that dial to remote HTTPS endpoint with // an alternative version of TLS implementation that attempts to imitate browsers' fingerprint. // clientHelloID is the clientHello that uTLS attempts to imitate // uTlsConfig is the TLS Configuration template // backdropTransport is the transport that will be used for non-https traffic // removeSNI indicates not to send Server Name Indication Extension // returns a RoundTripper: its behaviour is documented at // https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/merge_requests/76#note_2777161 func NewUTLSHTTPRoundTripperWithProxy(clientHelloID utls.ClientHelloID, uTlsConfig *utls.Config, backdropTransport http.RoundTripper, removeSNI bool, proxy *url.URL) http.RoundTripper { rtImpl := &uTLSHTTPRoundTripperImpl{ clientHelloID: clientHelloID, config: uTlsConfig, connectWithH1: map[string]bool{}, backdropTransport: backdropTransport, pendingConn: map[pendingConnKey]*unclaimedConnection{}, removeSNI: removeSNI, proxyAddr: proxy, } rtImpl.init() return rtImpl } type uTLSHTTPRoundTripperImpl struct { clientHelloID utls.ClientHelloID config *utls.Config accessConnectWithH1 sync.Mutex connectWithH1 map[string]bool httpsH1Transport http.RoundTripper httpsH2Transport http.RoundTripper backdropTransport http.RoundTripper accessDialingConnection sync.Mutex pendingConn map[pendingConnKey]*unclaimedConnection removeSNI bool proxyAddr *url.URL } type pendingConnKey struct { isH2 bool dest string } var errEAGAIN = errors.New("incorrect ALPN negotiated, try again with another ALPN") var errEAGAINTooMany = errors.New("incorrect ALPN negotiated") var errExpired = errors.New("connection have expired") func (r *uTLSHTTPRoundTripperImpl) RoundTrip(req *http.Request) (*http.Response, error) { if req.URL.Scheme != "https" { return r.backdropTransport.RoundTrip(req) } for retryCount := 0; retryCount < 5; retryCount++ { effectivePort := req.URL.Port() if effectivePort == "" { effectivePort = "443" } if r.getShouldConnectWithH1(fmt.Sprintf("%v:%v", req.URL.Hostname(), effectivePort)) { resp, err := r.httpsH1Transport.RoundTrip(req) if errors.Is(err, errEAGAIN) { continue } return resp, err } resp, err := r.httpsH2Transport.RoundTrip(req) if errors.Is(err, errEAGAIN) { continue } return resp, err } return nil, errEAGAINTooMany } func (r *uTLSHTTPRoundTripperImpl) getShouldConnectWithH1(domainName string) bool { r.accessConnectWithH1.Lock() defer r.accessConnectWithH1.Unlock() if value, set := r.connectWithH1[domainName]; set { return value } return false } func (r *uTLSHTTPRoundTripperImpl) setShouldConnectWithH1(domainName string) { r.accessConnectWithH1.Lock() defer r.accessConnectWithH1.Unlock() r.connectWithH1[domainName] = true } func (r *uTLSHTTPRoundTripperImpl) clearShouldConnectWithH1(domainName string) { r.accessConnectWithH1.Lock() defer r.accessConnectWithH1.Unlock() r.connectWithH1[domainName] = false } func getPendingConnectionID(dest string, alpnIsH2 bool) pendingConnKey { return pendingConnKey{isH2: alpnIsH2, dest: dest} } func (r *uTLSHTTPRoundTripperImpl) putConn(addr string, alpnIsH2 bool, conn net.Conn) { connId := getPendingConnectionID(addr, alpnIsH2) r.pendingConn[connId] = NewUnclaimedConnection(conn, time.Minute) } func (r *uTLSHTTPRoundTripperImpl) getConn(addr string, alpnIsH2 bool) net.Conn { connId := getPendingConnectionID(addr, alpnIsH2) if conn, ok := r.pendingConn[connId]; ok { delete(r.pendingConn, connId) if claimedConnection, err := conn.claimConnection(); err == nil { return claimedConnection } } return nil } func (r *uTLSHTTPRoundTripperImpl) dialOrGetTLSWithExpectedALPN(ctx context.Context, addr string, expectedH2 bool) (net.Conn, error) { r.accessDialingConnection.Lock() defer r.accessDialingConnection.Unlock() if r.getShouldConnectWithH1(addr) == expectedH2 { return nil, errEAGAIN } //Get a cached connection if possible to reduce preflight connection closed without sending data if gconn := r.getConn(addr, expectedH2); gconn != nil { return gconn, nil } conn, err := r.dialTLS(ctx, addr) if err != nil { return nil, err } protocol := conn.ConnectionState().NegotiatedProtocol protocolIsH2 := protocol == http2.NextProtoTLS if protocolIsH2 == expectedH2 { return conn, err } r.putConn(addr, protocolIsH2, conn) if protocolIsH2 { r.clearShouldConnectWithH1(addr) } else { r.setShouldConnectWithH1(addr) } return nil, errEAGAIN } // based on https://repo.or.cz/dnstt.git/commitdiff/d92a791b6864901f9263f7d73d97cfd30ac53b09..98bdffa1706dfc041d1e99b86c47f29d72ad3a0c // by dcf1 func (r *uTLSHTTPRoundTripperImpl) dialTLS(ctx context.Context, addr string) (*utls.UConn, error) { config := r.config.Clone() host, _, err := net.SplitHostPort(addr) if err != nil { return nil, err } config.ServerName = host systemDialer := &net.Dialer{} var dialer proxy.ContextDialer dialer = systemDialer if r.proxyAddr != nil { proxyDialer, err := proxy.FromURL(r.proxyAddr, systemDialer) if err != nil { return nil, err } dialer = proxyDialer.(proxy.ContextDialer) } conn, err := dialer.DialContext(ctx, "tcp", addr) if err != nil { return nil, err } uconn := utls.UClient(conn, config, r.clientHelloID) if (net.ParseIP(config.ServerName) != nil) || r.removeSNI { err := uconn.RemoveSNIExtension() if err != nil { uconn.Close() return nil, err } } err = uconn.Handshake() if err != nil { return nil, err } return uconn, nil } func (r *uTLSHTTPRoundTripperImpl) init() { r.httpsH2Transport = &http2.Transport{ DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { return r.dialOrGetTLSWithExpectedALPN(context.Background(), addr, true) }, } r.httpsH1Transport = &http.Transport{ DialTLSContext: func(ctx context.Context, network string, addr string) (net.Conn, error) { return r.dialOrGetTLSWithExpectedALPN(ctx, addr, false) }, } } func NewUnclaimedConnection(conn net.Conn, expireTime time.Duration) *unclaimedConnection { c := &unclaimedConnection{ Conn: conn, } time.AfterFunc(expireTime, c.tick) return c } type unclaimedConnection struct { net.Conn claimed bool access sync.Mutex } func (c *unclaimedConnection) claimConnection() (net.Conn, error) { c.access.Lock() defer c.access.Unlock() if !c.claimed { c.claimed = true return c.Conn, nil } return nil, errExpired } func (c *unclaimedConnection) tick() { c.access.Lock() defer c.access.Unlock() if !c.claimed { c.claimed = true c.Conn.Close() c.Conn = nil } } 07070100000069000081A400000000000000000000000165F88C5000001284000000000000000000000000000000000000003100000000snowflake-2.9.2/common/utls/roundtripper_test.gopackage utls import ( "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "math/big" "math/rand" "net/http" "os" "testing" "time" stdcontext "context" utls "github.com/refraction-networking/utls" "golang.org/x/net/http2" . "github.com/smartystreets/goconvey/convey" ) func TestRoundTripper(t *testing.T) { runRoundTripperTest(t, "127.0.0.1:23802", "127.0.0.1:23801", "https://127.0.0.1:23802/", "https://127.0.0.1:23801/") } func TestRoundTripperOnH1DefaultPort(t *testing.T) { if os.Getuid() != 0 { t.SkipNow() } runRoundTripperTest(t, "127.0.0.1:23802", "127.0.0.1:443", "https://127.0.0.1:23802/", "https://127.0.0.1/") } func TestRoundTripperOnH2DefaultPort(t *testing.T) { if os.Getuid() != 0 { t.SkipNow() } runRoundTripperTest(t, "127.0.0.1:443", "127.0.0.1:23801", "https://127.0.0.1/", "https://127.0.0.1:23801/") } func runRoundTripperTest(t *testing.T, h2listen, h1listen, h2addr, h1addr string) { var selfSignedCert []byte var selfSignedPrivateKey *rsa.PrivateKey httpServerContext, cancel := stdcontext.WithCancel(stdcontext.Background()) Convey("[Test]Set up http servers", t, func(c C) { c.Convey("[Test]Generate Self-Signed Cert", func(c C) { // Ported from https://gist.github.com/samuel/8b500ddd3f6118d052b5e6bc16bc4c09 // note that we use the insecure math/rand here because some platforms // fail the test suite at build time in Debian, due to entropy starvation. // since that's not a problem at test time, we do *not* use a secure // mechanism for key generation. // // DO NOT REUSE THIS CODE IN PRODUCTION, IT IS DANGEROUS insecureRandReader := rand.New(rand.NewSource(1337)) priv, err := rsa.GenerateKey(insecureRandReader, 4096) c.So(err, ShouldBeNil) template := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ CommonName: "Testing Certificate", }, NotBefore: time.Now(), NotAfter: time.Now().Add(time.Hour * 24 * 180), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } derBytes, err := x509.CreateCertificate(insecureRandReader, &template, &template, priv.Public(), priv) c.So(err, ShouldBeNil) selfSignedPrivateKey = priv selfSignedCert = derBytes }) c.Convey("[Test]Setup http2 server", func(c C) { listener, err := tls.Listen("tcp", h2listen, &tls.Config{ NextProtos: []string{http2.NextProtoTLS}, Certificates: []tls.Certificate{ tls.Certificate{Certificate: [][]byte{selfSignedCert}, PrivateKey: selfSignedPrivateKey}, }, }) c.So(err, ShouldBeNil) s := http.Server{} go s.Serve(listener) go func() { <-httpServerContext.Done() s.Close() }() }) c.Convey("[Test]Setup http1 server", func(c C) { listener, err := tls.Listen("tcp", h1listen, &tls.Config{ NextProtos: []string{"http/1.1"}, Certificates: []tls.Certificate{ tls.Certificate{Certificate: [][]byte{selfSignedCert}, PrivateKey: selfSignedPrivateKey}, }, }) c.So(err, ShouldBeNil) s := http.Server{} go s.Serve(listener) go func() { <-httpServerContext.Done() s.Close() }() }) }) for _, v := range []struct { id utls.ClientHelloID name string }{ { id: utls.HelloChrome_58, name: "HelloChrome_58", }, { id: utls.HelloChrome_62, name: "HelloChrome_62", }, { id: utls.HelloChrome_70, name: "HelloChrome_70", }, { id: utls.HelloChrome_72, name: "HelloChrome_72", }, { id: utls.HelloChrome_83, name: "HelloChrome_83", }, { id: utls.HelloFirefox_55, name: "HelloFirefox_55", }, { id: utls.HelloFirefox_55, name: "HelloFirefox_55", }, { id: utls.HelloFirefox_63, name: "HelloFirefox_63", }, { id: utls.HelloFirefox_65, name: "HelloFirefox_65", }, { id: utls.HelloIOS_11_1, name: "HelloIOS_11_1", }, { id: utls.HelloIOS_12_1, name: "HelloIOS_12_1", }, } { t.Run("Testing fingerprint for "+v.name, func(t *testing.T) { rtter := NewUTLSHTTPRoundTripper(v.id, &utls.Config{ InsecureSkipVerify: true, }, http.DefaultTransport, false) for count := 0; count <= 10; count++ { Convey("HTTP 1.1 Test", t, func(c C) { { req, err := http.NewRequest("GET", h2addr, nil) So(err, ShouldBeNil) _, err = rtter.RoundTrip(req) So(err, ShouldBeNil) } }) Convey("HTTP 2 Test", t, func(c C) { { req, err := http.NewRequest("GET", h1addr, nil) So(err, ShouldBeNil) _, err = rtter.RoundTrip(req) So(err, ShouldBeNil) } }) } }) } cancel() } 0707010000006A000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001F00000000snowflake-2.9.2/common/version0707010000006B000081A400000000000000000000000165F88C5000000064000000000000000000000000000000000000002B00000000snowflake-2.9.2/common/version/combined.gopackage version func ConstructResult() string { return GetVersion() + "\n" + GetVersionDetail() } 0707010000006C000081A400000000000000000000000165F88C50000000D5000000000000000000000000000000000000002900000000snowflake-2.9.2/common/version/detail.gopackage version import "strings" var detailBuilder strings.Builder func AddVersionDetail(detail string) { detailBuilder.WriteString(detail) } func GetVersionDetail() string { return detailBuilder.String() } 0707010000006D000081A400000000000000000000000165F88C5000000163000000000000000000000000000000000000002A00000000snowflake-2.9.2/common/version/version.gopackage version import ( "fmt" "runtime/debug" ) var version = func() string { ver := "2.9.2" if info, ok := debug.ReadBuildInfo(); ok { for _, setting := range info.Settings { if setting.Key == "vcs.revision" { return fmt.Sprintf("%v (%v)", ver, setting.Value[:8]) } } } return ver }() func GetVersion() string { return version } 0707010000006E000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000002500000000snowflake-2.9.2/common/websocketconn0707010000006F000081A400000000000000000000000165F88C5000000B67000000000000000000000000000000000000003600000000snowflake-2.9.2/common/websocketconn/websocketconn.gopackage 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 { var buf [2048]byte for { messageType, r, err := ws.NextReader() if err != nil { return err } if messageType != websocket.BinaryMessage && messageType != websocket.TextMessage { continue } _, err = io.CopyBuffer(w, r, buf[:]) if err != nil { return err } } } func writeLoop(ws *websocket.Conn, r io.Reader) error { var buf [2048]byte for { n, err := r.Read(buf[:]) if err != nil { return err } err = ws.WriteMessage(websocket.BinaryMessage, buf[:n]) 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 (WriteMessage, 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, } } 07070100000070000081A400000000000000000000000165F88C5000002133000000000000000000000000000000000000003B00000000snowflake-2.9.2/common/websocketconn/websocketconn_test.gopackage websocketconn import ( "bytes" "fmt" "io" "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 := io.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(io.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(io.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) } } // Benchmark creating a server websocket.Conn (without the websocketconn.Conn // wrapper) for different read/write buffer sizes. func BenchmarkUpgradeBufferSize(b *testing.B) { // Buffer size of 0 would mean the default of 4096: // https://github.com/gorilla/websocket/blob/v1.5.0/conn.go#L37 // But a size of zero also has the effect of causing reuse of the HTTP // server's buffers. So we test 4096 separately from 0. // https://github.com/gorilla/websocket/blob/v1.5.0/server.go#L32 for _, bufSize := range []int{0, 128, 1024, 2048, 4096, 8192} { upgrader := websocket.Upgrader{ CheckOrigin: func(*http.Request) bool { return true }, ReadBufferSize: bufSize, WriteBufferSize: bufSize, } b.Run(fmt.Sprintf("%d", bufSize), func(b *testing.B) { // Start up a web server to receive the request. ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { b.Fatal(err) } defer ln.Close() wsCh := make(chan *websocket.Conn) errCh := make(chan error) server := http.Server{ Handler: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { ws, err := upgrader.Upgrade(rw, req, nil) if err != nil { errCh <- err return } wsCh <- ws }), } defer server.Close() go func() { err := server.Serve(ln) if err != nil && err != http.ErrServerClosed { errCh <- err } }() // Make a request to the web server. dialer := &websocket.Dialer{ ReadBufferSize: bufSize, WriteBufferSize: bufSize, } urlStr := (&url.URL{Scheme: "ws", Host: ln.Addr().String()}).String() b.ResetTimer() for i := 0; i < b.N; i++ { ws, _, err := dialer.Dial(urlStr, nil) if err != nil { b.Fatal(err) } ws.Close() select { case <-wsCh: case err := <-errCh: b.Fatal(err) } } b.StopTimer() }) } } // Benchmark read/write in the client←server and server←client directions, with // messages of different sizes. Run with -benchmem to see memory allocations. func BenchmarkReadWrite(b *testing.B) { trial := func(b *testing.B, readConn, writeConn *Conn, msgSize int) { go func() { io.Copy(io.Discard, readConn) }() data := make([]byte, msgSize) b.ResetTimer() for i := 0; i < b.N; i++ { n, err := writeConn.Write(data[:]) b.SetBytes(int64(n)) if err != nil { b.Fatal(err) } } } for _, msgSize := range []int{150, 3000} { s, c, err := connPair() if err != nil { b.Fatal(err) } b.Run(fmt.Sprintf("c←s %d", msgSize), func(b *testing.B) { trial(b, c, s, msgSize) }) b.Run(fmt.Sprintf("s←c %d", msgSize), func(b *testing.B) { trial(b, s, c, msgSize) }) err = s.Close() if err != nil { b.Fatal(err) } err = c.Close() if err != nil { b.Fatal(err) } } } 07070100000071000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001400000000snowflake-2.9.2/doc07070100000072000081A400000000000000000000000165F88C50000026E5000000000000000000000000000000000000002400000000snowflake-2.9.2/doc/broker-spec.txt 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. "client-http-count" NUM NL [At most once.] A count of the number of times a client has requested a proxy using the HTTP rendezvous method from the broker, rounded up to the nearest multiple of 8. "client-http-ips" [CC=NUM,CC=NUM,...,CC=NUM] NL [At most once.] List of mappings from two-letter country codes to the number of times a client has requested a proxy using the HTTP rendezvous method, rounded up to the nearest multiple of 8. Each country code only appears once. "client-ampcache-count" NUM NL [At most once.] A count of the number of times a client has requested a proxy using the ampcache rendezvous method from the broker, rounded up to the nearest multiple of 8. "client-ampcache-ips" [CC=NUM,CC=NUM,...,CC=NUM] NL [At most once.] List of mappings from two-letter country codes to the number of times a client has requested a proxy using the ampcache rendezvous method, rounded up to the nearest multiple of 8. Each country code only appears once. "client-sqs-count" NUM NL [At most once.] A count of the number of times a client has requested a proxy using the sqs rendezvous method from the broker, rounded up to the nearest multiple of 8. "client-sqs-ips" [CC=NUM,CC=NUM,...,CC=NUM] NL [At most once.] List of mappings from two-letter country codes to the number of times a client has requested a proxy using the sqs rendezvous method, rounded up to the nearest multiple of 8. Each country code only appears once. "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. "snowflake-proxy-poll-with-relay-url-count" NUM NL [At most once.] A count of snowflake proxy polls with relay url extension present. This means this proxy understands relay url, and is sending its allowed prefix. "snowflake-proxy-poll-without-relay-url-count" NUM NL [At most once.] A count of snowflake proxy polls with relay url extension absent. This means this proxy is not yet updated. "snowflake-proxy-rejected-for-relay-url-count" NUM NL [At most once.] A count of snowflake proxy polls with relay url extension rejected based on broker's relay url extension policy. This means an incompatible allowed relay pattern is included in the proxy poll message. 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 The broker offers multiple ways for clients to exchange registration messages. 2.1.1. HTTPS POST 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.1.2. AMP The broker's /amp/client endpoint receives client poll messages encoded into the URL path, and sends client poll responses encoded as HTML that conforms to the requirements of AMP (Accelerated Mobile Pages). This endpoint is intended to be accessed through an AMP cache, using the -ampcache option of snowflake-client. The client encodes its poll message into a GET request as follows: ``` GET /amp/client/0[0 or more bytes]/[base64 of client poll message] ``` The components of the path are as follows: * "/amp/client/", the root of the endpoint. * "0", a format version number, which controls the interpretation of the rest of the path. Only the first byte matters as a version indicator (not the whole first path component). * Any number of slash or non-slash bytes. These may be used as padding or to prevent cache collisions in the AMP cache. * A final slash. * base64 encoding of the client poll message, using the URL-safe alphabet (which does not include slash). The broker returns a client poll response message in the HTTP response. The message is encoded using AMP armor, an AMP-compatible HTML encoding. The data stream is notionally a "0" byte (a format version indicator) followed by the base64 encoding of the message (using the standard alphabet, with "=" padding). This stream is broken into whitespace-separated chunks, which are then bundled into HTML <pre> elements. The <pre> elements are then surrounded by AMP boilerplate. To decode, search the HTML for <pre> elements, concatenate their contents and join on whitespace, discard the "0" prefix, and base64 decode. 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.3, Type: ["badge"|"webext"|"standalone"|"mobile"], NAT: ["unknown"|"restricted"|"unrestricted"], Clients: [number of current clients, rounded down to multiples of 8], AcceptedRelayPattern: [a pattern representing accepted set of relay domains] } ``` 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] }, RelayURL: [the WebSocket URL proxy should connect to relay Snowflake traffic] } ``` 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.3, 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 ``` 07070100000073000081A400000000000000000000000165F88C5000001095000000000000000000000000000000000000002B00000000snowflake-2.9.2/doc/rendezvous-with-sqs.md# Rendezvous with Amazon SQS This is a new experimental rendezvous method (in addition to the existing HTTPs and AMP cache methods). It leverages the Amazon SQS Queue service for a client to communicate with the broker server. ## Broker To run the broker with this rendezvous method, use the following CLI flags (they are both required): - `broker-sqs-name` - name of the broker SQS queue to listen for incoming messages - `broker-sqs-region` - name of AWS region of the SQS queue These two parameters determine the SQS queue URL that the client needs to be run with as a CLI flag in order to communicate with the broker. For example, the following values can be used: `-broker-sqs-name snowflake-broker -broker-sqs-region us-east-1` The machine on which the broker is being run must be equiped with the correct AWS configs and credentials that would allow the broker program to create, read from, and write to the SQS queue. These are typically stored at `~/.aws/config` and `~/.aws/credentials`. However, enviornment variables may also be used as described in the [AWS Docs](https://docs.aws.amazon.com/sdkref/latest/guide/creds-config-files.html) ## Client To run the client with this rendezvous method, use the following CLI flags (they are all required): - `sqsqueue` - URL of the SQS queue to use as a proxy for signalling - `sqscreds` - Encoded credentials for accessing the SQS queue `sqsqueue` should correspond to the URL of the SQS queue that the broker is listening on. For the example above, the following value can be used: `-sqsqueue https://sqs.us-east-1.amazonaws.com/893902434899/snowflake-broker -sqscreds some-encoded-sqs-creds` *Public access to SQS queues is not allowed, so there needs to be some form of authentication to be able to access the queue. Limited permission credentials will be provided by the Snowflake team to access the corresponding SQS queue.* ## Implementation Details ``` ╭――――――――――――――――――╮ ╭――――――――――――――――――╮ ╭――――――――――――――――――╮ ╭―――――――――――――――――-―╮ │ Client │ <=> │ Amazon SQS │ <=> │ Broker │ <=> │ Snowflake Proxy │ ╰――――――――――――――――――╯ ╰――――――――――――――――――╯ ╰――――――――――――――――――╯ ╰――――――――――――――――――-╯ ``` 1. On startup, the **broker** ensures that an SQS queue with the name of the `broker-sqs-name` parameter exists. It will create such a queue if it doesn’t exist. Afterwards, it will enter a loop of continuously: - polling for new messages - cleaning up client queues 2. **Client** sends SDP Offer to the SQS queue at the URL provided by the `sqsqueue` parameter using a message with a unique ID (clientID) corresponding to the client along with the contents of the SDP Offer. The client will randomly generate a new ClientID to use each rendezvous attempt. 3. The **broker** will receive this message during its polling and process it. - A client SQS queue with the name `"snowflake-client" + clientID` will be created for the broker to send messages to the client. This is needed because if a queue shared between all clients was used for outgoing messages from the server, then clients would have to pick off the top message, check if it is addressed to them, and then process the message if it is. This means clients would possibly have to check many messages before they find the one addressed to them. - When the broker has a response for the client, it will send a message to the client queue with the details of the SDP answer. - The SDP offer message from the client is then deleted from the broker queue. 4. The **client** will continuously poll its client queue and eventually receive the message with the SDP answer from the broker. 5. The broker server will periodically clean up the unique SQS queues it has created for each client once the queues are no longer needed (it will delete queues that were last modified before a certain amount of time ago)07070100000074000081A400000000000000000000000165F88C5000000442000000000000000000000000000000000000002700000000snowflake-2.9.2/doc/snowflake-client.1.TH SNOWFLAKE-CLIENT "1" "July 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\-ampcache\fR string .IP URL of AMP cache to use as a proxy for signaling .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 07070100000075000081A400000000000000000000000165F88C50000003A9000000000000000000000000000000000000002600000000snowflake-2.9.2/doc/snowflake-proxy.1.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.torproject.net/") .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.torproject.net/") .HP \fB\-stun\fR string .IP stun URL (default "stun:stun.l.google.com:19302") .HP \fB\-unsafe\-logging\fR .IP prevent logs from being scrubbed .SH "SEE ALSO" https://snowflake.torproject.org 07070100000076000081A400000000000000000000000165F88C50000011B9000000000000000000000000000000000000003300000000snowflake-2.9.2/doc/using-the-snowflake-library.mdSnowflake is available as a general-purpose pluggable transports library and adheres to the [pluggable transports v2.1 Go API](https://github.com/Pluggable-Transports/Pluggable-Transports-spec/blob/master/releases/PTSpecV2.1/Pluggable%20Transport%20Specification%20v2.1%20-%20Go%20Transport%20API.pdf). ### Client library The Snowflake client library contains functions for running a Snowflake client. Example usage: ```Golang package main import ( "log" sf "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/client/lib" ) func main() { config := sf.ClientConfig{ BrokerURL: "https://snowflake-broker.example.com", FrontDomain: "https://friendlyfrontdomain.net", ICEAddresses: []string{ "stun:stun.voip.blackberry.com:3478", }, Max: 1, } transport, err := sf.NewSnowflakeClient(config) if err != nil { log.Fatal("Failed to start snowflake transport: ", err) } // transport implements the ClientFactory interface and returns a net.Conn conn, err := transport.Dial() if err != nil { log.Printf("dial error: %s", err) return } defer conn.Close() // ... } ``` #### Using your own rendezvous method You can define and use your own rendezvous method to communicate with a Snowflake broker by implementing the `RendezvousMethod` interface. ```Golang package main import ( "log" sf "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/client/lib" ) type StubMethod struct { } func (m *StubMethod) Exchange(pollReq []byte) ([]byte, error) { var brokerResponse []byte var err error //Implement the logic you need to communicate with the Snowflake broker here return brokerResponse, err } func main() { config := sf.ClientConfig{ ICEAddresses: []string{ "stun:stun.voip.blackberry.com:3478", }, } transport, err := sf.NewSnowflakeClient(config) if err != nil { log.Fatal("Failed to start snowflake transport: ", err) } // custom rendezvous methods can be set with `SetRendezvousMethod` rendezvous := &StubMethod{} transport.SetRendezvousMethod(rendezvous) // transport implements the ClientFactory interface and returns a net.Conn conn, err := transport.Dial() if err != nil { log.Printf("dial error: %s", err) return } defer conn.Close() // ... } ``` ### Server library The Snowflake server library contains functions for running a Snowflake server. Example usage: ```Golang package main import ( "log" "net" sf "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/server/lib" "golang.org/x/crypto/acme/autocert" ) func main() { // The snowflake server runs a websocket server. To run this securely, you will // need a valid certificate. certManager := &autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist("snowflake.yourdomain.com"), Email: "you@yourdomain.com", } transport := sf.NewSnowflakeServer(certManager.GetCertificate) addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:443") if err != nil { log.Printf("error resolving bind address: %s", err.Error()) } ln, err := transport.Listen(addr) if err != nil { log.Printf("error opening listener: %s", err.Error()) } 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() }() } // ... } ``` ### Running your own Snowflake infrastructure At the moment we do not have the ability to share Snowfake infrastructure between different types of applications. If you are planning on using Snowflake as a transport for your application, you will need to: - Run a Snowflake broker. See our [broker documentation](../broker/) and [installation guide](https://gitlab.torproject.org/tpo/anti-censorship/team/-/wikis/Survival-Guides/Snowflake-Broker-Installation-Guide) for more information - Run Snowflake proxies. These can be run as [standalone Go proxies](../proxy/) or [browser-based proxies](https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake-webext). 07070100000077000081A400000000000000000000000165F88C5000000E71000000000000000000000000000000000000001700000000snowflake-2.9.2/go.modmodule gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2 go 1.21 require ( github.com/aws/aws-sdk-go-v2 v1.25.3 github.com/aws/aws-sdk-go-v2/config v1.27.7 github.com/aws/aws-sdk-go-v2/credentials v1.17.7 github.com/aws/aws-sdk-go-v2/service/sqs v1.31.2 github.com/golang/mock v1.6.0 github.com/gorilla/websocket v1.5.1 github.com/miekg/dns v1.1.58 github.com/pion/ice/v2 v2.3.13 github.com/pion/sdp/v3 v3.0.8 github.com/pion/stun v0.6.1 github.com/pion/transport/v2 v2.2.4 github.com/pion/webrtc/v3 v3.2.29 github.com/prometheus/client_golang v1.19.0 github.com/prometheus/client_model v0.6.0 github.com/realclientip/realclientip-go v1.0.0 github.com/refraction-networking/utls v1.6.3 github.com/smartystreets/goconvey v1.8.1 github.com/stretchr/testify v1.9.0 github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 github.com/xtaci/kcp-go/v5 v5.6.8 github.com/xtaci/smux v1.5.24 gitlab.torproject.org/tpo/anti-censorship/geoip v0.0.0-20210928150955-7ce4b3d98d01 gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 golang.org/x/crypto v0.21.0 golang.org/x/net v0.22.0 golang.org/x/sys v0.18.0 google.golang.org/protobuf v1.33.0 ) require ( github.com/andybalholm/brotli v1.0.6 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.3 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.3 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.3 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.5 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.20.2 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.28.4 // indirect github.com/aws/smithy-go v1.20.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/uuid v1.3.1 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/reedsolomon v1.12.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pion/datachannel v1.5.5 // indirect github.com/pion/dtls/v2 v2.2.7 // indirect github.com/pion/interceptor v0.1.25 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/mdns v0.0.12 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.12 // indirect github.com/pion/rtp v1.8.3 // indirect github.com/pion/sctp v1.8.12 // indirect github.com/pion/srtp/v2 v2.0.18 // indirect github.com/pion/turn/v2 v2.1.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/quic-go/quic-go v0.40.1 // indirect github.com/smarty/assertions v1.15.0 // indirect github.com/templexxx/cpu v0.1.0 // indirect github.com/templexxx/xorsimd v0.4.2 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.17.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) 07070100000078000081A400000000000000000000000165F88C5000008B34000000000000000000000000000000000000001700000000snowflake-2.9.2/go.sumcloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/aws/aws-sdk-go-v2 v1.25.3 h1:xYiLpZTQs1mzvz5PaI6uR0Wh57ippuEthxS4iK5v0n0= github.com/aws/aws-sdk-go-v2 v1.25.3/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I= github.com/aws/aws-sdk-go-v2/config v1.27.7 h1:JSfb5nOQF01iOgxFI5OIKWwDiEXWTyTgg1Mm1mHi0A4= github.com/aws/aws-sdk-go-v2/config v1.27.7/go.mod h1:PH0/cNpoMO+B04qET699o5W92Ca79fVtbUnvMIZro4I= github.com/aws/aws-sdk-go-v2/credentials v1.17.7 h1:WJd+ubWKoBeRh7A5iNMnxEOs982SyVKOJD+K8HIezu4= github.com/aws/aws-sdk-go-v2/credentials v1.17.7/go.mod h1:UQi7LMR0Vhvs+44w5ec8Q+VS+cd10cjwgHwiVkE0YGU= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.3 h1:p+y7FvkK2dxS+FEwRIDHDe//ZX+jDhP8HHE50ppj4iI= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.3/go.mod h1:/fYB+FZbDlwlAiynK9KDXlzZl3ANI9JkD0Uhz5FjNT4= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.3 h1:ifbIbHZyGl1alsAhPIYsHOg5MuApgqOvVeI8wIugXfs= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.3/go.mod h1:oQZXg3c6SNeY6OZrDY+xHcF4VGIEoNotX2B4PrDeoJI= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.3 h1:Qvodo9gHG9F3E8SfYOspPeBt0bjSbsevK8WhRAUHcoY= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.3/go.mod h1:vCKrdLXtybdf/uQd/YfVR2r5pcbNuEYKzMQpcxmeSJw= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.5 h1:K/NXvIftOlX+oGgWGIa3jDyYLDNsdVhsjHmsBH2GLAQ= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.5/go.mod h1:cl9HGLV66EnCmMNzq4sYOti+/xo8w34CsgzVtm2GgsY= github.com/aws/aws-sdk-go-v2/service/sqs v1.31.2 h1:A9ihuyTKpS8Z1ou/D4ETfOEFMyokA6JjRsgXWTiHvCk= github.com/aws/aws-sdk-go-v2/service/sqs v1.31.2/go.mod h1:J3XhTE+VsY1jDsdDY+ACFAppZj/gpvygzC5JE0bTLbQ= github.com/aws/aws-sdk-go-v2/service/sso v1.20.2 h1:XOPfar83RIRPEzfihnp+U6udOveKZJvPQ76SKWrLRHc= github.com/aws/aws-sdk-go-v2/service/sso v1.20.2/go.mod h1:Vv9Xyk1KMHXrR3vNQe8W5LMFdTjSeWk0gBZBzvf3Qa0= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.2 h1:pi0Skl6mNl2w8qWZXcdOyg197Zsf4G97U7Sso9JXGZE= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.2/go.mod h1:JYzLoEVeLXk+L4tn1+rrkfhkxl6mLDEVaDSvGq9og90= github.com/aws/aws-sdk-go-v2/service/sts v1.28.4 h1:Ppup1nVNAOWbBOrcoOxaxPeEnSFB2RnnQdguhXpmeQk= github.com/aws/aws-sdk-go-v2/service/sts v1.28.4/go.mod h1:+K1rNPVyGxkRuv9NNiaZ4YhBFuyw2MMA9SlIJ1Zlpz8= github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 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/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 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/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/reedsolomon v1.12.0 h1:I5FEp3xSwVCcEh3F5A7dofEfhXdF/bWhQWPH+XwBFno= github.com/klauspost/reedsolomon v1.12.0/go.mod h1:EPLZJeh4l27pUGC3aXOjheaoh1I9yut7xTURiW3LQ9Y= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c= github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= 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.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8= github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0= github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/ice/v2 v2.3.13 h1:xOxP+4V9nSDlUaGFRf/LvAuGHDXRcjIdsbbXPK/w7c8= github.com/pion/ice/v2 v2.3.13/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw= github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc= github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y= 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.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8= github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk= 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.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I= github.com/pion/rtcp v1.2.12 h1:bKWiX93XKgDZENEXCijvHRU/wRifm6JV5DGcH6twtSM= github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8= github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0= github.com/pion/sctp v1.8.12 h1:2VX50pedElH+is6FI+OKyRTeN5oy4mrk2HjnGa3UCmY= github.com/pion/sctp v1.8.12/go.mod h1:cMLT45jqw3+jiJCrtHVwfQLnfR0MGZ4rgOJwUOIqLkI= github.com/pion/sdp/v3 v3.0.8 h1:yd/wkrS0nzXEAb+uwv1TL3SG/gzsTiXHVOtXtD7EKl0= github.com/pion/sdp/v3 v3.0.8/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M= github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo= github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA= github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40= github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc= github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM= github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= github.com/pion/turn/v2 v2.1.4 h1:2xn8rduI5W6sCZQkEnIUDAkrBQNl2eYIBCHMZ3QMmP8= github.com/pion/turn/v2 v2.1.4/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= github.com/pion/webrtc/v3 v3.2.29 h1:flXjxjlqpp3FjkpSSBKwv7UOfbUvan9+gFY6A5ZaAn4= github.com/pion/webrtc/v3 v3.2.29/go.mod h1:M+5YSvBDPAkHHRwGXlplIFBQI5mXm6Y4byns1OpiX68= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q= github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c= github.com/realclientip/realclientip-go v1.0.0 h1:+yPxeC0mEaJzq1BfCt2h4BxlyrvIIBzR6suDc3BEF1U= github.com/realclientip/realclientip-go v1.0.0/go.mod h1:CXnUdVwFRcXFJIRb/dTYqbT7ud48+Pi2pFm80bxDmcI= github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc= github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40= github.com/templexxx/cpu v0.1.0/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUoVI= github.com/templexxx/xorsimd v0.4.2/go.mod h1:HgwaPoDREdi6OnULpSfxhzaiiSUY4Fi3JPn1wpt28NI= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0= github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM= github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 h1:d/Wr/Vl/wiJHc3AHYbYs5I3PucJvRuw3SvbmlIRf+oM= github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301/go.mod h1:ntmMHL/xPq1WLeKiw8p/eRATaae6PiVRNipHFJxI8PM= github.com/xtaci/kcp-go/v5 v5.6.8 h1:jlI/0jAyjoOjT/SaGB58s4bQMJiNS41A2RKzR6TMWeI= github.com/xtaci/kcp-go/v5 v5.6.8/go.mod h1:oE9j2NVqAkuKO5o8ByKGch3vgVX3BNf8zqP8JiGq0bM= 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.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY= github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.torproject.org/tpo/anti-censorship/geoip v0.0.0-20210928150955-7ce4b3d98d01 h1:4949mHh9Vj2/okk48yG8nhP6TosFWOUfSfSr502sKGE= gitlab.torproject.org/tpo/anti-censorship/geoip v0.0.0-20210928150955-7ce4b3d98d01/go.mod h1:K3LOI4H8fa6j+7E10ViHeGEQV10304FG4j94ypmKLjY= gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 h1:rzdY78Ox2T+VlXcxGxELF+6VyUXlZBhmRqZu5etLm+c= gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0/go.mod h1:70bhd4JKW/+1HLfm+TMrgHJsUHG4coelMWwiVEJ2gAg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-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-20190620200207-3b0461eec859/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-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/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-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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-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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 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/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 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.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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= 07070100000079000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001A00000000snowflake-2.9.2/probetest0707010000007A000081A400000000000000000000000165F88C5000000029000000000000000000000000000000000000002500000000snowflake-2.9.2/probetest/DockerfileFROM golang:1.22 COPY probetest /go/bin 0707010000007B000081A400000000000000000000000165F88C500000074B000000000000000000000000000000000000002400000000snowflake-2.9.2/probetest/README.md<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> **Table of Contents** - [Overview](#overview) - [Running your own](#running-your-own) <!-- END doctoc generated TOC please keep comment here to allow auto update --> This 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 ``` 0707010000007C000081A400000000000000000000000165F88C500000018E000000000000000000000000000000000000002D00000000snowflake-2.9.2/probetest/docker-compose.yml 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"] 0707010000007D000081A400000000000000000000000165F88C5000001EEF000000000000000000000000000000000000002700000000snowflake-2.9.2/probetest/probetest.go/* 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" "log" "net/http" "os" "strings" "time" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/safelog" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/util" "github.com/pion/transport/v2/stdnet" "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 defaultStunUrl = "stun:stun.l.google.com:19302" //default STUN URL ) type ProbeHandler struct { stunURL string handle func(string, http.ResponseWriter, *http.Request) } func (h ProbeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.handle(h.stunURL, w, r) } // 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(stunURL string, sdp *webrtc.SessionDescription, dataChan chan struct{}) (*webrtc.PeerConnection, error) { settingsEngine := webrtc.SettingEngine{} // Use the SetNet setting https://pkg.go.dev/github.com/pion/webrtc/v3#SettingEngine.SetNet // to functionally revert a new change in pion by silently ignoring // when net.Interfaces() fails, rather than throwing an error vnet, _ := stdnet.NewNet() settingsEngine.SetNet(vnet) api := webrtc.NewAPI(webrtc.WithSettingEngine(settingsEngine)) config := webrtc.Configuration{ ICEServers: []webrtc.ICEServer{ { URLs: []string{stunURL}, }, }, } pc, err := api.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(stunURL string, w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") resp, err := io.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(stunURL, 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 var stunURL string 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.StringVar(&stunURL, "stun", defaultStunUrl, "STUN server to use for NAT traversal") 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.Handle("/probe", ProbeHandler{stunURL, 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) } } 0707010000007E000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001600000000snowflake-2.9.2/proxy0707010000007F000081A400000000000000000000000165F88C5000000ABF000000000000000000000000000000000000002000000000snowflake-2.9.2/proxy/README.md<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> **Table of Contents** - [Dependencies](#dependencies) - [Building the standalone Snowflake proxy](#building-the-standalone-snowflake-proxy) - [Running a standalone Snowflake proxy](#running-a-standalone-snowflake-proxy) <!-- END doctoc generated TOC please keep comment here to allow auto update --> This is a standalone (not browser-based) version of the Snowflake proxy. For browser-based versions of the Snowflake proxy, see https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake-webext. ### Dependencies - Go 1.15+ - We use the [pion/webrtc](https://github.com/pion/webrtc) library for WebRTC communication with Snowflake proxies. Note: running `go get` will fetch this dependency automatically during the build process. ### Building the standalone Snowflake proxy To build the Snowflake proxy, make sure you are in the `proxy/` directory, and then run: ``` go get go build ``` ### Running a standalone Snowflake proxy The Snowflake proxy can be run with the following options: ``` Usage of ./proxy: -allow-non-tls-relay allow relay without tls encryption -allowed-relay-hostname-pattern string a pattern to specify allowed hostname pattern for relay URL. (default "snowflake.torproject.net$") -broker string broker URL (default "https://snowflake-broker.torproject.net/") -capacity uint maximum concurrent clients (default is to accept an unlimited number of clients) -ephemeral-ports-range string ICE UDP ephemeral ports range (format:"<min>:<max>") -keep-local-addresses keep local LAN address ICE candidates -log string log filename -nat-retest-interval duration the time interval in second before NAT type is retested, 0s disables retest. Valid time units are "s", "m", "h". (default 24h0m0s) -relay string websocket relay URL (default "wss://snowflake.torproject.net/") -outbound-address string bind a specific outbound address. Replace all host candidates with this address without validation. -stun string stun URL (default "stun:stun.l.google.com:19302") -summary-interval duration the time interval to output summary, 0s disables summaries. Valid time units are "s", "m", "h". (default 1h0m0s) -unsafe-logging prevent logs from being scrubbed -verbose increase log verbosity -version display version info to stderr and quit ``` For more information on how to run a Snowflake proxy in deployment, see our [community documentation](https://community.torproject.org/relay/setup/snowflake/standalone/). 07070100000080000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001A00000000snowflake-2.9.2/proxy/lib07070100000081000081A400000000000000000000000165F88C5000000877000000000000000000000000000000000000002500000000snowflake-2.9.2/proxy/lib/metrics.gopackage snowflake_proxy import ( "net/http" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) const ( // metricNamespace represent prometheus namespace metricNamespace = "tor_snowflake_proxy" ) type Metrics struct { totalInBoundTraffic prometheus.Counter totalOutBoundTraffic prometheus.Counter totalConnections prometheus.Counter } func NewMetrics() *Metrics { return &Metrics{ totalConnections: prometheus.NewCounter(prometheus.CounterOpts{ Namespace: metricNamespace, Name: "connections_total", Help: "The total number of connections handled by the snowflake proxy", }), totalInBoundTraffic: prometheus.NewCounter(prometheus.CounterOpts{ Namespace: metricNamespace, Name: "traffic_inbound_bytes_total", Help: "The total in bound traffic by the snowflake proxy (KB)", }), totalOutBoundTraffic: prometheus.NewCounter(prometheus.CounterOpts{ Namespace: metricNamespace, Name: "traffic_outbound_bytes_total", Help: "The total out bound traffic by the snowflake proxy (KB)", }), } } // Start register the metrics server and serve them on the given address func (m *Metrics) Start(addr string) error { go func() { http.Handle("/internal/metrics", promhttp.Handler()) if err := http.ListenAndServe(addr, nil); err != nil { panic(err) } }() return prometheus.Register(m) } func (m *Metrics) Collect(ch chan<- prometheus.Metric) { m.totalConnections.Collect(ch) m.totalInBoundTraffic.Collect(ch) m.totalOutBoundTraffic.Collect(ch) } func (m *Metrics) Describe(descs chan<- *prometheus.Desc) { prometheus.DescribeByCollect(m, descs) } // TrackInBoundTraffic counts the received traffic by the snowflake proxy func (m *Metrics) TrackInBoundTraffic(value int64) { m.totalInBoundTraffic.Add(float64(value)) } // TrackOutBoundTraffic counts the transmitted traffic by the snowflake proxy func (m *Metrics) TrackOutBoundTraffic(value int64) { m.totalOutBoundTraffic.Add(float64(value)) } // TrackNewConnection counts the new connections func (m *Metrics) TrackNewConnection() { m.totalConnections.Inc() } 07070100000082000081A400000000000000000000000165F88C5000003263000000000000000000000000000000000000002B00000000snowflake-2.9.2/proxy/lib/proxy-go_test.gopackage snowflake_proxy import ( "bytes" "fmt" "io" "net" "net/http" "strconv" "strings" "testing" "github.com/pion/webrtc/v3" . "github.com/smartystreets/goconvey/convey" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/util" ) // 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 := io.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() { var err error broker, err = newSignalingServer("localhost", false) So(err, ShouldBeNil) 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, ShouldBeNil) broker.transport = &MockTransport{ http.StatusOK, b, } sdp, _ := broker.pollOffer(sampleOffer, DefaultProxyType, "", nil) expectedSDP, _ := strconv.Unquote(sampleSDP) So(sdp.SDP, ShouldResemble, expectedSDP) }) Convey("handles poll error", func() { var err error b := []byte("test") So(err, ShouldBeNil) broker.transport = &MockTransport{ http.StatusOK, b, } sdp, _ := broker.pollOffer(sampleOffer, DefaultProxyType, "", nil) So(sdp, ShouldBeNil) }) Convey("sends answer to broker", func() { var err error b, err := messages.EncodeAnswerResponse(true) So(err, ShouldBeNil) broker.transport = &MockTransport{ http.StatusOK, b, } err = broker.sendAnswer(sampleAnswer, pc) So(err, ShouldBeNil) b, err = messages.EncodeAnswerResponse(false) So(err, ShouldBeNil) 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, nil) go func() { bytes := []byte("Hello!") c1.Write(bytes) }() bytes := make([]byte, 6) n, err := c2.Read(bytes) So(n, ShouldEqual, 6) So(err, ShouldBeNil) So(bytes, ShouldResemble, []byte("Hello!")) s1.Close() //Check that copy loop has closed other connection _, err = s2.Write(bytes) So(err, ShouldNotBeNil) }) } 07070100000083000081A400000000000000000000000165F88C500000084C000000000000000000000000000000000000002D00000000snowflake-2.9.2/proxy/lib/pt_event_logger.gopackage snowflake_proxy import ( "io" "log" "time" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/event" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/task" ) func NewProxyEventLogger(output io.Writer, disableStats bool) event.SnowflakeEventReceiver { logger := log.New(output, "", log.LstdFlags|log.LUTC) return &proxyEventLogger{logger: logger, disableStats: disableStats} } type proxyEventLogger struct { logger *log.Logger disableStats bool } func (p *proxyEventLogger) OnNewSnowflakeEvent(e event.SnowflakeEvent) { switch e.(type) { case event.EventOnProxyStats: if !p.disableStats { p.logger.Println(e.String()) } case event.EventOnProxyConnectionOver: // Suppress logs of this event // https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/issues/40310 default: p.logger.Println(e.String()) } } type periodicProxyStats struct { bytesLogger bytesLogger connectionCount int logPeriod time.Duration task *task.Periodic dispatcher event.SnowflakeEventDispatcher } func newPeriodicProxyStats(logPeriod time.Duration, dispatcher event.SnowflakeEventDispatcher, bytesLogger bytesLogger) *periodicProxyStats { el := &periodicProxyStats{logPeriod: logPeriod, dispatcher: dispatcher, bytesLogger: bytesLogger} el.task = &task.Periodic{Interval: logPeriod, Execute: el.logTick} el.task.WaitThenStart() return el } func (p *periodicProxyStats) OnNewSnowflakeEvent(e event.SnowflakeEvent) { switch e.(type) { case event.EventOnProxyConnectionOver: p.connectionCount += 1 } } func (p *periodicProxyStats) logTick() error { inboundSum, outboundSum := p.bytesLogger.GetStat() e := event.EventOnProxyStats{ SummaryInterval: p.logPeriod, ConnectionCount: p.connectionCount, } e.InboundBytes, e.InboundUnit = formatTraffic(inboundSum) e.OutboundBytes, e.OutboundUnit = formatTraffic(outboundSum) p.dispatcher.OnNewSnowflakeEvent(e) p.connectionCount = 0 return nil } func (p *periodicProxyStats) Close() error { return p.task.Close() } 07070100000084000081A400000000000000000000000165F88C50000002F9000000000000000000000000000000000000002E00000000snowflake-2.9.2/proxy/lib/pt_event_metrics.gopackage snowflake_proxy import ( "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/event" ) type EventCollector interface { TrackInBoundTraffic(value int64) TrackOutBoundTraffic(value int64) TrackNewConnection() } type EventMetrics struct { collector EventCollector } func NewEventMetrics(collector EventCollector) *EventMetrics { return &EventMetrics{collector: collector} } func (em *EventMetrics) OnNewSnowflakeEvent(e event.SnowflakeEvent) { switch e.(type) { case event.EventOnProxyStats: e := e.(event.EventOnProxyStats) em.collector.TrackInBoundTraffic(e.InboundBytes) em.collector.TrackOutBoundTraffic(e.OutboundBytes) case event.EventOnProxyConnectionOver: em.collector.TrackNewConnection() } } 07070100000085000081A400000000000000000000000165F88C5000005F12000000000000000000000000000000000000002700000000snowflake-2.9.2/proxy/lib/snowflake.go/* Package snowflake_proxy provides functionality for creating, starting, and stopping a snowflake proxy. To run a proxy, you must first create a proxy configuration. Unconfigured fields will be set to the defined defaults. proxy := snowflake_proxy.SnowflakeProxy{ BrokerURL: "https://snowflake-broker.example.com", STUNURL: "stun:stun.l.google.com:19302", // ... } You may then start and stop the proxy. Stopping the proxy will close existing connections and the proxy will not poll for more clients. go func() { err := proxy.Start() // handle error } // ... proxy.Stop() */ package snowflake_proxy import ( "bytes" "crypto/rand" "encoding/base64" "fmt" "io" "log" "net" "net/http" "net/url" "strings" "sync" "time" "github.com/gorilla/websocket" "github.com/pion/ice/v2" "github.com/pion/transport/v2/stdnet" "github.com/pion/webrtc/v3" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/event" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/namematcher" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/task" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/util" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/websocketconn" ) const ( DefaultBrokerURL = "https://snowflake-broker.torproject.net/" DefaultNATProbeURL = "https://snowflake-broker.torproject.net:8443/probe" DefaultRelayURL = "wss://snowflake.torproject.net/" DefaultSTUNURL = "stun:stun.l.google.com:19302" DefaultProxyType = "standalone" ) const ( // NATUnknown represents a NAT type which is unknown. NATUnknown = "unknown" // NATRestricted represents a restricted NAT. NATRestricted = "restricted" // NATUnrestricted represents an unrestricted NAT. NATUnrestricted = "unrestricted" ) const ( pollInterval = 5 * time.Second // Amount of time after sending an SDP answer before the proxy assumes the // client is not going to connect dataChannelTimeout = 20 * time.Second // Maximum number of bytes to be read from an HTTP request readLimit = 100000 sessionIDLength = 16 ) const bufferedAmountLowThreshold uint64 = 256 * 1024 // 256 KB var broker *SignalingServer var currentNATTypeAccess = &sync.RWMutex{} // currentNATType describes local network environment. // Obtain currentNATTypeAccess before access. var currentNATType = NATUnknown func getCurrentNATType() string { currentNATTypeAccess.RLock() defer currentNATTypeAccess.RUnlock() return currentNATType } var ( tokens *tokens_t config webrtc.Configuration client http.Client ) // SnowflakeProxy is used to configure an embedded // Snowflake in another Go application. type SnowflakeProxy struct { // Capacity is the maximum number of clients a Snowflake will serve. // Proxies with a capacity of 0 will accept an unlimited number of clients. Capacity uint // STUNURL is the URL of the STUN server the proxy will use STUNURL string // BrokerURL is the URL of the Snowflake broker BrokerURL string // KeepLocalAddresses indicates whether local SDP candidates will be sent to the broker KeepLocalAddresses bool // RelayURL is the URL of the Snowflake server that all traffic will be relayed to RelayURL string // OutboundAddress specify an IP address to use as SDP host candidate OutboundAddress string // Ephemeral*Port limits the pool of ports that ICE UDP connections can allocate from EphemeralMinPort uint16 EphemeralMaxPort uint16 // RelayDomainNamePattern is the pattern specify allowed domain name for relay // If the pattern starts with ^ then an exact match is required. // The rest of pattern is the suffix of domain name. // There is no look ahead assertion when matching domain name suffix, // thus the string prepend the suffix does not need to be empty or ends with a dot. RelayDomainNamePattern string AllowNonTLSRelay bool // NATProbeURL is the URL of the probe service we use for NAT checks NATProbeURL string // NATTypeMeasurementInterval is time before NAT type is retested NATTypeMeasurementInterval time.Duration // ProxyType is the type reported to the broker, if not provided it "standalone" will be used ProxyType string EventDispatcher event.SnowflakeEventDispatcher shutdown chan struct{} // SummaryInterval is the time interval at which proxy stats will be logged SummaryInterval time.Duration periodicProxyStats *periodicProxyStats bytesLogger bytesLogger } // 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 := io.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 } // SignalingServer keeps track of the SignalingServer in use by the Snowflake 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 } // Post sends a POST request to the SignalingServer 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) } // pollOffer communicates the proxy's capabilities with broker // and retrieves a compatible SDP offer func (s *SignalingServer) pollOffer(sid string, proxyType string, acceptedRelayPattern string, shutdown chan struct{}) (*webrtc.SessionDescription, string) { brokerPath := s.url.ResolveReference(&url.URL{Path: "proxy"}) ticker := time.NewTicker(pollInterval) defer ticker.Stop() // Run the loop once before hitting the ticker for ; true; <-ticker.C { select { case <-shutdown: return nil, "" default: numClients := int((tokens.count() / 8) * 8) // Round down to 8 currentNATTypeLoaded := getCurrentNATType() body, err := messages.EncodeProxyPollRequestWithRelayPrefix(sid, proxyType, currentNATTypeLoaded, numClients, acceptedRelayPattern) 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, _, relayURL, err := messages.DecodePollResponseWithRelayURL(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, relayURL } } } return nil, "" } // sendAnswer encodes an SDP answer, sends it to the broker // and wait for its response func (s *SignalingServer) sendAnswer(sid string, pc *webrtc.PeerConnection) error { 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 } brokerPath := s.url.ResolveReference(&url.URL{Path: "answer"}) 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, shutdown chan struct{}) { var once sync.Once defer c2.Close() defer c1.Close() done := make(chan struct{}) copyer := func(dst io.ReadWriteCloser, src io.ReadWriteCloser) { // 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) } once.Do(func() { close(done) }) } go copyer(c1, c2) go copyer(c2, c1) select { case <-done: case <-shutdown: } log.Println("copy loop ended") } // 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 conn.pc.RemoteDescription() (called by RemoteAddr). // https://bugs.torproject.org/18628#comment:8 func (sf *SnowflakeProxy) datachannelHandler(conn *webRTCConn, remoteAddr net.Addr, relayURL string) { defer conn.Close() defer tokens.ret() if relayURL == "" { relayURL = sf.RelayURL } 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 = %s", u.String(), err) return } wsConn := websocketconn.New(ws) log.Printf("Connected to relay: %v", relayURL) defer wsConn.Close() copyLoop(conn, wsConn, sf.shutdown) log.Printf("datachannelHandler ends") } type dataChannelHandlerWithRelayURL struct { RelayURL string sf *SnowflakeProxy } func (d dataChannelHandlerWithRelayURL) datachannelHandler(conn *webRTCConn, remoteAddr net.Addr) { d.sf.datachannelHandler(conn, remoteAddr, d.RelayURL) } func (sf *SnowflakeProxy) makeWebRTCAPI() *webrtc.API { settingsEngine := webrtc.SettingEngine{} // Use the SetNet setting https://pkg.go.dev/github.com/pion/webrtc/v3#SettingEngine.SetNet // to get snowflake working in shadow (where the AF_NETLINK family is not implemented). // These two lines of code functionally revert a new change in pion by silently ignoring // when net.Interfaces() fails, rather than throwing an error vnet, _ := stdnet.NewNet() settingsEngine.SetNet(vnet) if sf.EphemeralMinPort != 0 && sf.EphemeralMaxPort != 0 { err := settingsEngine.SetEphemeralUDPPortRange(sf.EphemeralMinPort, sf.EphemeralMaxPort) if err != nil { log.Fatal("Invalid port range: min > max") } } if sf.OutboundAddress != "" { // replace SDP host candidates with the given IP without validation // still have server reflexive candidates to fall back on settingsEngine.SetNAT1To1IPs([]string{sf.OutboundAddress}, webrtc.ICECandidateTypeHost) } settingsEngine.SetICEMulticastDNSMode(ice.MulticastDNSModeDisabled) settingsEngine.SetDTLSInsecureSkipHelloVerify(true) return webrtc.NewAPI(webrtc.WithSettingEngine(settingsEngine)) } // 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 (sf *SnowflakeProxy) makePeerConnectionFromOffer( sdp *webrtc.SessionDescription, config webrtc.Configuration, dataChan chan struct{}, handler func(conn *webRTCConn, remoteAddr net.Addr), ) (*webrtc.PeerConnection, error) { api := sf.makeWebRTCAPI() pc, err := api.NewPeerConnection(config) if err != nil { return nil, fmt.Errorf("accept: NewPeerConnection: %s", err) } pc.OnDataChannel(func(dc *webrtc.DataChannel) { log.Printf("New Data Channel %s-%d\n", dc.Label(), dc.ID()) close(dataChan) pr, pw := io.Pipe() conn := newWebRTCConn(pc, dc, pr, sf.bytesLogger) dc.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold) dc.OnBufferedAmountLow(func() { select { case conn.sendMoreCh <- struct{}{}: default: } }) dc.OnOpen(func() { log.Printf("Data Channel %s-%d open\n", dc.Label(), dc.ID()) if sf.OutboundAddress != "" { selectedCandidatePair, err := pc.SCTP().Transport().ICETransport().GetSelectedCandidatePair() if err != nil { log.Printf("Warning: couldn't get the selected candidate pair") } log.Printf("Selected Local Candidate: %s:%d", selectedCandidatePair.Local.Address, selectedCandidatePair.Local.Port) if sf.OutboundAddress != selectedCandidatePair.Local.Address { log.Printf("Warning: the IP address provided by --outbound-address is not used for establishing peerconnection") } } }) dc.OnClose(func() { conn.lock.Lock() defer conn.lock.Unlock() log.Printf("Data Channel %s-%d close\n", dc.Label(), dc.ID()) sf.EventDispatcher.OnNewSnowflakeEvent(event.EventOnProxyConnectionOver{}) 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) } return } conn.bytesLogger.AddOutbound(int64(n)) if n != len(msg.Data) { // XXX: Maybe don't panic here and log an error instead? 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("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 if !strings.Contains(pc.LocalDescription().SDP, "\na=candidate:") { return nil, fmt.Errorf("SDP answer contains no candidate") } log.Printf("Answer: \n\t%s", strings.ReplaceAll(pc.LocalDescription().SDP, "\n", "\n\t")) return pc, nil } // Create a new PeerConnection. Blocks until the gathering of ICE // candidates is complete and the answer is available in LocalDescription. func (sf *SnowflakeProxy) makeNewPeerConnection( config webrtc.Configuration, dataChan chan struct{}, ) (*webrtc.PeerConnection, error) { api := sf.makeWebRTCAPI() pc, err := api.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#a-data-channel-is-no-longer-implicitly-created-with-a-peerconnection 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("Probetest: 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) // start the gathering of ICE candidates err = pc.SetLocalDescription(offer) if err != nil { log.Println("Failed to apply offer", err) pc.Close() return nil, err } log.Println("Probetest: Set local description") // Wait for ICE candidate gathering to complete <-done if !strings.Contains(pc.LocalDescription().SDP, "\na=candidate:") { return nil, fmt.Errorf("Probetest SDP offer contains no candidate") } return pc, nil } func (sf *SnowflakeProxy) runSession(sid string) { offer, relayURL := broker.pollOffer(sid, sf.ProxyType, sf.RelayDomainNamePattern, sf.shutdown) if offer == nil { log.Printf("bad offer from broker") tokens.ret() return } log.Printf("Received Offer From Broker: \n\t%s", strings.ReplaceAll(offer.SDP, "\n", "\n\t")) matcher := namematcher.NewNameMatcher(sf.RelayDomainNamePattern) parsedRelayURL, err := url.Parse(relayURL) if err != nil { log.Printf("bad offer from broker: bad Relay URL %v", err.Error()) tokens.ret() return } if relayURL != "" && (!matcher.IsMember(parsedRelayURL.Hostname()) || (!sf.AllowNonTLSRelay && parsedRelayURL.Scheme != "wss")) { log.Printf("bad offer from broker: rejected Relay URL") tokens.ret() return } dataChan := make(chan struct{}) dataChannelAdaptor := dataChannelHandlerWithRelayURL{RelayURL: relayURL, sf: sf} pc, err := sf.makePeerConnectionFromOffer(offer, config, dataChan, dataChannelAdaptor.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() } } // Start configures and starts a Snowflake, fully formed and special. Configuration // values that are unset will default to their corresponding default values. func (sf *SnowflakeProxy) Start() error { var err error sf.EventDispatcher.OnNewSnowflakeEvent(event.EventOnProxyStarting{}) sf.shutdown = make(chan struct{}) // blank configurations revert to default if sf.BrokerURL == "" { sf.BrokerURL = DefaultBrokerURL } if sf.RelayURL == "" { sf.RelayURL = DefaultRelayURL } if sf.STUNURL == "" { sf.STUNURL = DefaultSTUNURL } if sf.NATProbeURL == "" { sf.NATProbeURL = DefaultNATProbeURL } if sf.ProxyType == "" { sf.ProxyType = DefaultProxyType } if sf.EventDispatcher == nil { sf.EventDispatcher = event.NewSnowflakeEventDispatcher() } sf.bytesLogger = newBytesSyncLogger() sf.periodicProxyStats = newPeriodicProxyStats(sf.SummaryInterval, sf.EventDispatcher, sf.bytesLogger) sf.EventDispatcher.AddSnowflakeEventListener(sf.periodicProxyStats) broker, err = newSignalingServer(sf.BrokerURL, sf.KeepLocalAddresses) if err != nil { return fmt.Errorf("error configuring broker: %s", err) } _, err = url.Parse(sf.STUNURL) if err != nil { return fmt.Errorf("invalid stun url: %s", err) } _, err = url.Parse(sf.RelayURL) if err != nil { return fmt.Errorf("invalid relay url: %s", err) } if !namematcher.IsValidRule(sf.RelayDomainNamePattern) { return fmt.Errorf("invalid relay domain name pattern") } config = webrtc.Configuration{ ICEServers: []webrtc.ICEServer{ { URLs: []string{sf.STUNURL}, }, }, } tokens = newTokens(sf.Capacity) sf.checkNATType(config, sf.NATProbeURL) currentNATTypeLoaded := getCurrentNATType() sf.EventDispatcher.OnNewSnowflakeEvent(&event.EventOnCurrentNATTypeDetermined{CurNATType: currentNATTypeLoaded}) NatRetestTask := task.Periodic{ Interval: sf.NATTypeMeasurementInterval, Execute: func() error { sf.checkNATType(config, sf.NATProbeURL) return nil }, } if sf.NATTypeMeasurementInterval != 0 { NatRetestTask.WaitThenStart() defer NatRetestTask.Close() } ticker := time.NewTicker(pollInterval) defer ticker.Stop() for ; true; <-ticker.C { select { case <-sf.shutdown: return nil default: tokens.get() sessionID := genSessionID() sf.runSession(sessionID) } } return nil } // Stop closes all existing connections and shuts down the Snowflake. func (sf *SnowflakeProxy) Stop() { close(sf.shutdown) } // checkNATType use probetest to determine NAT compatability by // attempting to connect with a known symmetric NAT. If success, // it is considered "unrestricted". If timeout it is considered "restricted" func (sf *SnowflakeProxy) checkNATType(config webrtc.Configuration, probeURL string) { probe, err := newSignalingServer(probeURL, false) if err != nil { log.Printf("Error parsing url: %s", err.Error()) } dataChan := make(chan struct{}) pc, err := sf.makeNewPeerConnection(config, dataChan) if err != nil { log.Printf("error making WebRTC connection: %s", err) return } offer := pc.LocalDescription() log.Printf("Probetest offer: \n\t%s", strings.ReplaceAll(offer.SDP, "\n", "\n\t")) sdp, err := util.SerializeSessionDescription(offer) 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 } currentNATTypeLoaded := getCurrentNATType() currentNATTypeTestResult := NATUnknown select { case <-dataChan: currentNATTypeTestResult = NATUnrestricted case <-time.After(dataChannelTimeout): currentNATTypeTestResult = NATRestricted } currentNATTypeToStore := NATUnknown switch currentNATTypeLoaded + "->" + currentNATTypeTestResult { case NATUnrestricted + "->" + NATUnknown: currentNATTypeToStore = NATUnrestricted case NATRestricted + "->" + NATUnknown: currentNATTypeToStore = NATRestricted default: currentNATTypeToStore = currentNATTypeTestResult } log.Printf("NAT Type measurement: %v -> %v = %v\n", currentNATTypeLoaded, currentNATTypeTestResult, currentNATTypeToStore) currentNATTypeAccess.Lock() currentNATType = currentNATTypeToStore currentNATTypeAccess.Unlock() if err := pc.Close(); err != nil { log.Printf("error calling pc.Close: %v", err) } } 07070100000086000081A400000000000000000000000165F88C5000000268000000000000000000000000000000000000002400000000snowflake-2.9.2/proxy/lib/tokens.gopackage snowflake_proxy 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) } 07070100000087000081A400000000000000000000000165F88C500000023F000000000000000000000000000000000000002900000000snowflake-2.9.2/proxy/lib/tokens_test.gopackage snowflake_proxy 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) }) } 07070100000088000081A400000000000000000000000165F88C500000093B000000000000000000000000000000000000002200000000snowflake-2.9.2/proxy/lib/util.gopackage snowflake_proxy import ( "time" ) // bytesLogger is an interface which is used to allow logging the throughput // of the Snowflake. A default bytesLogger(bytesNullLogger) does nothing. type bytesLogger interface { AddOutbound(int64) AddInbound(int64) GetStat() (in int64, out int64) } // bytesNullLogger Default bytesLogger does nothing. type bytesNullLogger struct{} // AddOutbound in bytesNullLogger does nothing func (b bytesNullLogger) AddOutbound(amount int64) {} // AddInbound in bytesNullLogger does nothing func (b bytesNullLogger) AddInbound(amount int64) {} func (b bytesNullLogger) GetStat() (in int64, out int64) { return -1, -1 } // bytesSyncLogger uses channels to safely log from multiple sources with output // occuring at reasonable intervals. type bytesSyncLogger struct { outboundChan, inboundChan chan int64 statsChan chan bytesLoggerStats stats bytesLoggerStats outEvents, inEvents int start time.Time } type bytesLoggerStats struct { outbound, inbound int64 } // newBytesSyncLogger returns a new bytesSyncLogger and starts it loggin. func newBytesSyncLogger() *bytesSyncLogger { b := &bytesSyncLogger{ outboundChan: make(chan int64, 5), inboundChan: make(chan int64, 5), statsChan: make(chan bytesLoggerStats), } go b.log() b.start = time.Now() return b } func (b *bytesSyncLogger) log() { for { select { case amount := <-b.outboundChan: b.stats.outbound += amount b.outEvents++ case amount := <-b.inboundChan: b.stats.inbound += amount b.inEvents++ case b.statsChan <- b.stats: b.stats.inbound = 0 b.stats.outbound = 0 b.inEvents = 0 b.outEvents = 0 } } } // AddOutbound add a number of bytes to the outbound total reported by the logger func (b *bytesSyncLogger) AddOutbound(amount int64) { b.outboundChan <- amount } // AddInbound add a number of bytes to the inbound total reported by the logger func (b *bytesSyncLogger) AddInbound(amount int64) { b.inboundChan <- amount } // GetStat returns the current inbound and outbound stats from the logger and then zeros the counts func (b *bytesSyncLogger) GetStat() (in int64, out int64) { stats := <-b.statsChan return stats.inbound, stats.outbound } func formatTraffic(amount int64) (value int64, unit string) { return amount / 1000, "KB" } 07070100000089000081A400000000000000000000000165F88C5000000FBF000000000000000000000000000000000000002800000000snowflake-2.9.2/proxy/lib/webrtcconn.gopackage snowflake_proxy import ( "context" "errors" "fmt" "io" "log" "net" "regexp" "sync" "time" "github.com/pion/ice/v2" "github.com/pion/sdp/v3" "github.com/pion/webrtc/v3" ) const maxBufferedAmount uint64 = 512 * 1024 // 512 KB 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 isClosing bool inactivityTimeout time.Duration activity chan struct{} sendMoreCh chan struct{} cancelTimeoutLoop context.CancelFunc bytesLogger bytesLogger } func newWebRTCConn(pc *webrtc.PeerConnection, dc *webrtc.DataChannel, pr *io.PipeReader, bytesLogger bytesLogger) *webRTCConn { conn := &webRTCConn{pc: pc, dc: dc, pr: pr, bytesLogger: bytesLogger} conn.isClosing = false conn.activity = make(chan struct{}, 100) conn.sendMoreCh = make(chan struct{}, 1) conn.inactivityTimeout = 30 * time.Second ctx, cancel := context.WithCancel(context.Background()) conn.cancelTimeoutLoop = cancel go conn.timeoutLoop(ctx) return conn } func (c *webRTCConn) timeoutLoop(ctx context.Context) { timer := time.NewTimer(c.inactivityTimeout) for { select { case <-timer.C: _ = c.Close() log.Println("Closed connection due to inactivity") return case <-c.activity: if !timer.Stop() { <-timer.C } timer.Reset(c.inactivityTimeout) continue case <-ctx.Done(): return } } } func (c *webRTCConn) Read(b []byte) (int, error) { return c.pr.Read(b) } func (c *webRTCConn) Write(b []byte) (int, error) { c.bytesLogger.AddInbound(int64(len(b))) select { case c.activity <- struct{}{}: default: } c.lock.Lock() defer c.lock.Unlock() if c.dc != nil { _ = c.dc.Send(b) if !c.isClosing && c.dc.BufferedAmount() >= maxBufferedAmount { <-c.sendMoreCh } } return len(b), nil } func (c *webRTCConn) Close() (err error) { c.isClosing = true select { case c.sendMoreCh <- struct{}{}: default: } c.once.Do(func() { c.cancelTimeoutLoop() err = errors.Join(c.pr.Close(), 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 } 0707010000008A000081A400000000000000000000000165F88C5000001521000000000000000000000000000000000000001E00000000snowflake-2.9.2/proxy/main.gopackage main import ( "flag" "fmt" "io" "log" "net" "os" "strconv" "strings" "time" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/event" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/safelog" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/version" sf "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/proxy/lib" ) func main() { capacity := flag.Uint("capacity", 0, "maximum concurrent clients (default is to accept an unlimited number of clients)") stunURL := flag.String("stun", sf.DefaultSTUNURL, "STUN URL") logFilename := flag.String("log", "", "log filename") rawBrokerURL := flag.String("broker", sf.DefaultBrokerURL, "broker URL") unsafeLogging := flag.Bool("unsafe-logging", false, "prevent logs from being scrubbed") keepLocalAddresses := flag.Bool("keep-local-addresses", false, "keep local LAN address ICE candidates") relayURL := flag.String("relay", sf.DefaultRelayURL, "websocket relay URL") probeURL := flag.String("nat-probe-server", sf.DefaultNATProbeURL, "NAT check probe server URL") outboundAddress := flag.String("outbound-address", "", "prefer the given address as outbound address") allowedRelayHostNamePattern := flag.String("allowed-relay-hostname-pattern", "snowflake.torproject.net$", "a pattern to specify allowed hostname pattern for relay URL.") allowNonTLSRelay := flag.Bool("allow-non-tls-relay", false, "allow relay without tls encryption") NATTypeMeasurementInterval := flag.Duration("nat-retest-interval", time.Hour*24, "the time interval in second before NAT type is retested, 0s disables retest. Valid time units are \"s\", \"m\", \"h\". ") summaryInterval := flag.Duration("summary-interval", time.Hour, "the time interval to output summary, 0s disables summaries. Valid time units are \"s\", \"m\", \"h\". ") disableStatsLogger := flag.Bool("disable-stats-logger", false, "disable the exposing mechanism for stats using logs") enableMetrics := flag.Bool("metrics", false, "enable the exposing mechanism for stats using metrics") metricsPort := flag.Int("metrics-port", 9999, "set port for the metrics service") verboseLogging := flag.Bool("verbose", false, "increase log verbosity") ephemeralPortsRangeFlag := flag.String("ephemeral-ports-range", "", "ICE UDP ephemeral ports range (format:\"<min>:<max>\")") versionFlag := flag.Bool("version", false, "display version info to stderr and quit") var ephemeralPortsRange []uint16 = []uint16{0, 0} flag.Parse() if *versionFlag { fmt.Fprintf(os.Stderr, "snowflake-proxy %s", version.ConstructResult()) os.Exit(0) } if *outboundAddress != "" && *keepLocalAddresses { log.Fatal("Cannot keep local address candidates when outbound address is specified") } eventLogger := event.NewSnowflakeEventDispatcher() if *ephemeralPortsRangeFlag != "" { ephemeralPortsRangeParts := strings.Split(*ephemeralPortsRangeFlag, ":") if len(ephemeralPortsRangeParts) == 2 { ephemeralMinPort, err := strconv.ParseUint(ephemeralPortsRangeParts[0], 10, 16) if err != nil { log.Fatal(err) } ephemeralMaxPort, err := strconv.ParseUint(ephemeralPortsRangeParts[1], 10, 16) if err != nil { log.Fatal(err) } if ephemeralMinPort == 0 || ephemeralMaxPort == 0 { log.Fatal("Ephemeral port cannot be zero") } if ephemeralMinPort > ephemeralMaxPort { log.Fatal("Invalid port range: min > max") } ephemeralPortsRange = []uint16{uint16(ephemeralMinPort), uint16(ephemeralMaxPort)} } else { log.Fatalf("Bad range port format: %v", *ephemeralPortsRangeFlag) } } proxy := sf.SnowflakeProxy{ Capacity: uint(*capacity), STUNURL: *stunURL, BrokerURL: *rawBrokerURL, KeepLocalAddresses: *keepLocalAddresses, RelayURL: *relayURL, NATProbeURL: *probeURL, OutboundAddress: *outboundAddress, EphemeralMinPort: ephemeralPortsRange[0], EphemeralMaxPort: ephemeralPortsRange[1], NATTypeMeasurementInterval: *NATTypeMeasurementInterval, EventDispatcher: eventLogger, RelayDomainNamePattern: *allowedRelayHostNamePattern, AllowNonTLSRelay: *allowNonTLSRelay, SummaryInterval: *summaryInterval, } var logOutput = io.Discard var eventlogOutput io.Writer = os.Stderr log.SetFlags(log.LstdFlags | log.LUTC) if *verboseLogging { logOutput = os.Stderr } 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() if *verboseLogging { logOutput = io.MultiWriter(logOutput, f) } eventlogOutput = io.MultiWriter(eventlogOutput, f) } if *unsafeLogging { log.SetOutput(logOutput) } else { log.SetOutput(&safelog.LogScrubber{Output: logOutput}) } proxyEventLogger := sf.NewProxyEventLogger(eventlogOutput, *disableStatsLogger) eventLogger.AddSnowflakeEventListener(proxyEventLogger) if *enableMetrics { metrics := sf.NewMetrics() err := metrics.Start(net.JoinHostPort("localhost", strconv.Itoa(*metricsPort))) if err != nil { log.Fatalf("could not enable metrics: %v", err) } eventLogger.AddSnowflakeEventListener(sf.NewEventMetrics(metrics)) } log.Printf("snowflake-proxy %s\n", version.GetVersion()) err := proxy.Start() if err != nil { log.Fatal(err) } } 0707010000008B000081A400000000000000000000000165F88C50000000CF000000000000000000000000000000000000001E00000000snowflake-2.9.2/renovate.json{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "constraints": { "go": "1.21" }, "postUpdateOptions": ["gomodTidy", "gomodUpdateImportPaths"], "osvVulnerabilityAlerts": true } 0707010000008C000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001700000000snowflake-2.9.2/server0707010000008D000081A400000000000000000000000165F88C5000000FC0000000000000000000000000000000000000002100000000snowflake-2.9.2/server/README.md<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> **Table of Contents** - [Setup](#setup) - [TLS](#tls) <!-- END doctoc generated TOC please keep comment here to allow auto update --> This 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 ``` # Multiple KCP state machines The server internally uses a network protocol called KCP to manage and persist client sessions. Each KCP scheduler runs on a single thread. When there are many simultaneous users (thousands), a single KCP scheduler can be a bottleneck. The `num-turbotunnel` pluggable transport option lets you control the number of KCP instances, which can help with CPU scaling: https://bugs.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/40200 There is currently no way to set this option automatically. You have to tune it manually. ``` ServerTransportOptions snowflake num-turbotunnel=2 ``` # Controlling source addresses Use the `orport-srcaddr` pluggable transport option to control what source addresses are used when connecting to the upstream Tor ExtORPort or ORPort. The value of the option may be a single IP address (e.g. "127.0.0.2") or a CIDR range (e.g. "127.0.2.0/24"). If a range is given, an IP address from the range is randomly chosen for each new connection. Use `ServerTransportOptions` in torrc to set the option: ``` ServerTransportOptions snowflake orport-srcaddr=127.0.2.0/24 ``` You can combine it with other options: ``` ServerTransportOptions snowflake num-turbotunnel=2 orport-srcaddr=127.0.2.0/24 ``` Specifying a source address range other than the default 127.0.0.1 can help with conserving localhost ephemeral ports on servers that receive a lot of connections: https://bugs.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/40198 0707010000008E000081A400000000000000000000000165F88C500000013E000000000000000000000000000000000000001F00000000snowflake-2.9.2/server/dial.go//go:build !linux // +build !linux package main import "syscall" // dialerControl does nothing. // // On Linux, this function would set the IP_BIND_ADDRESS_NO_PORT socket option // in preparation for a future bind-before-connect. func dialerControl(network, address string, c syscall.RawConn) error { return nil } 0707010000008F000081A400000000000000000000000165F88C500000070A000000000000000000000000000000000000002500000000snowflake-2.9.2/server/dial_linux.go//go:build linux // +build linux package main import ( "syscall" "golang.org/x/sys/unix" ) // dialerControl prepares a syscall.RawConn for a future bind-before-connect by // setting the IP_BIND_ADDRESS_NO_PORT socket option. // // On Linux, setting the IP_BIND_ADDRESS_NO_PORT socket option helps conserve // ephemeral ports when binding to a specific IP addresses before connecting // (bind before connect), by not assigning the port number when bind is called, // but waiting until connect. But problems arise if there are multiple processes // doing bind-before-connect, and some of them use IP_BIND_ADDRESS_NO_PORT and // some of them do not. When there is a mix, the ones that do will have their // ephemeral ports reserved by the ones that do not, leading to EADDRNOTAVAIL // errors. // // tor does bind-before-connect when the OutboundBindAddress option is set in // torrc. Since version 0.4.7.13 (January 2023), tor sets // IP_BIND_ADDRESS_NO_PORT unconditionally on platforms that support it, and // therefore we must do the same, to avoid EADDRNOTAVAIL errors. // // # References // // https://bugs.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/40201#note_2839472 // https://forum.torproject.net/t/tor-relays-inet-csk-bind-conflict/5757/10 // https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections/ // https://blog.cloudflare.com/the-quantum-state-of-a-tcp-port/ // https://forum.torproject.net/t/stable-release-0-4-5-16-and-0-4-7-13/6216 func dialerControl(network, address string, c syscall.RawConn) error { var sockErr error err := c.Control(func(fd uintptr) { sockErr = syscall.SetsockoptInt(int(fd), unix.SOL_IP, unix.IP_BIND_ADDRESS_NO_PORT, 1) }) if err == nil { err = sockErr } return err } 07070100000090000041ED00000000000000000000000265F88C5000000000000000000000000000000000000000000000001B00000000snowflake-2.9.2/server/lib07070100000091000081A400000000000000000000000165F88C500000200A000000000000000000000000000000000000002300000000snowflake-2.9.2/server/lib/http.gopackage snowflake_server import ( "bufio" "bytes" "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/binary" "fmt" "io" "log" "net" "net/http" "sync" "time" "github.com/gorilla/websocket" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/encapsulation" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/websocketconn" ) 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 = 98304 // 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) type httpHandler struct { // pconns is the adapter layer between stream-oriented WebSocket // connections and the packet-oriented KCP layer. There are multiple of // these, corresponding to the multiple kcp.ServeConn in // Transport.Listen. Clients are assigned to a particular instance by a // hash of ClientID, indexed by a hash of the ClientID, in order to // distribute KCP processing load across CPU cores. pconns []*turbotunnel.QueuePacketConn // clientIDLookupKey is a secret key used to tweak the hash-based // assignement of ClientID to pconn, in order to avoid manipulation of // hash assignments. clientIDLookupKey []byte } // newHTTPHandler creates a new http.Handler that exchanges encapsulated packets // over incoming WebSocket connections. func newHTTPHandler(localAddr net.Addr, numInstances int, mtu int) *httpHandler { pconns := make([]*turbotunnel.QueuePacketConn, 0, numInstances) for i := 0; i < numInstances; i++ { pconns = append(pconns, turbotunnel.NewQueuePacketConn(localAddr, clientMapTimeout, mtu)) } clientIDLookupKey := make([]byte, 16) _, err := rand.Read(clientIDLookupKey) if err != nil { panic(err) } return &httpHandler{ pconns: pconns, clientIDLookupKey: clientIDLookupKey, } } // lookupPacketConn returns the element of pconns that corresponds to client ID, // according to the hash-based mapping. func (handler *httpHandler) lookupPacketConn(clientID turbotunnel.ClientID) *turbotunnel.QueuePacketConn { s := hmac.New(sha256.New, handler.clientIDLookupKey).Sum(clientID[:]) return handler.pconns[binary.LittleEndian.Uint64(s)%uint64(len(handler.pconns))] } 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 = handler.turbotunnelMode(conn, addr) default: // We didn't find a matching token, which means that we are // dealing with a client that doesn't know about such things. // Close the conn as we no longer support the old // one-session-per-WebSocket mode. log.Println("Received unsupported oneshot connection") return } if err != nil { log.Println(err) return } } // 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 (handler *httpHandler) turbotunnelMode(conn net.Conn, addr net.Addr) 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) pconn := handler.lookupPacketConn(clientID) 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 var p [2048]byte for { n, err := encapsulation.ReadData(conn, p[:]) if err == io.ErrShortBuffer { err = nil } if err != nil { return } pconn.QueueIncoming(p[:n], 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) pconn.Restore(p) if err == nil { err = bw.Flush() } if err != nil { return } } } }() wg.Wait() return nil } // ClientMapAddr is a string that represents a connecting client. 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()) } 07070100000092000081A400000000000000000000000165F88C50000004C6000000000000000000000000000000000000002A00000000snowflake-2.9.2/server/lib/server_test.gopackage snowflake_server 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, "") } } }) } 07070100000093000081A400000000000000000000000165F88C5000002961000000000000000000000000000000000000002800000000snowflake-2.9.2/server/lib/snowflake.go/* Package snowflake_server implements the functionality necessary to accept Snowflake connections from Snowflake clients. Included in the package is a Transport type that implements the Pluggable Transports v2.1 Go API specification. To start a TLS Snowflake server using the golang.org/x/crypto/acme/autocert library, configure a certificate manager for the server's domain name and then create a new Transport as follows: // The snowflake server runs a websocket server. To run this securely, you will // need a valid certificate. certManager := &autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist("snowflake.yourdomain.com"), Email: "you@yourdomain.com", } transport := snowflake_server.NewSnowflakeServer(certManager.GetCertificate) The Listen function starts a new listener, and Accept will return incoming Snowflake connections: ln, err := transport.Listen(addr) if err != nil { // handle error } for { conn, err := ln.Accept() if err != nil { // handle error } // handle conn } */ package snowflake_server import ( "crypto/tls" "errors" "fmt" "io" "log" "net" "net/http" "sync" "time" "github.com/xtaci/kcp-go/v5" "github.com/xtaci/smux" "golang.org/x/net/http2" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/turbotunnel" ) const ( // WindowSize is the number of packets in the send and receive window of a KCP connection. WindowSize = 65535 // StreamSize controls the maximum amount of in flight data between a client and server. StreamSize = 1048576 //1MB ) // 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) } // NewSnowflakeServer returns a new server-side Transport for Snowflake. func NewSnowflakeServer(getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error)) *Transport { return &Transport{getCertificate: getCertificate} } // Listen starts a listener on addr that will accept both turbotunnel // and legacy Snowflake connections. func (t *Transport) Listen(addr net.Addr, numKCPInstances int) (*SnowflakeListener, error) { listener := &SnowflakeListener{ addr: addr, queue: make(chan net.Conn, 65534), closed: make(chan struct{}), ln: make([]*kcp.Listener, 0, numKCPInstances), } // kcp-go doesn't provide an accessor for the current MTU setting (and // anyway we could not create a kcp.Listener without creating a // net.PacketConn for it first), so assume the default kcp.IKCP_MTU_DEF // (1400 bytes) and don't increase it elsewhere. handler := newHTTPHandler(addr, numKCPInstances, kcp.IKCP_MTU_DEF) 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 } if err != nil { return nil, err } listener.server = server // Start the KCP engines, 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. for i, pconn := range handler.pconns { ln, err := kcp.ServeConn(nil, 0, 0, pconn) if err != nil { server.Close() return nil, err } go func() { defer ln.Close() err := listener.acceptSessions(ln) if err != nil { log.Printf("acceptSessions %d: %v", i, err) } }() listener.ln = append(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 } // Accept 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 } } // Addr returns the address of the SnowflakeListener func (l *SnowflakeListener) Addr() net.Addr { return l.addr } // Close closes the Snowflake connection. func (l *SnowflakeListener) Close() error { // Close our HTTP server and our KCP listener l.closeOnce.Do(func() { close(l.closed) l.server.Close() for _, ln := range l.ln { 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 = 4 * time.Minute smuxConfig.MaxStreamBuffer = StreamSize 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{stream: 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(WindowSize, WindowSize) // 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 && !errors.Is(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 } } // SnowflakeClientConn is a wrapper for the underlying turbotunnel conn // (smux.Stream). It implements the net.Conn and io.WriterTo interfaces. The // RemoteAddr method is overridden to refer to a real IP address, looked up from // the client address map, rather than an abstract client ID. type SnowflakeClientConn struct { stream *smux.Stream address net.Addr } // Forward net.Conn methods, other than RemoteAddr, to the inner stream. func (conn *SnowflakeClientConn) Read(b []byte) (int, error) { return conn.stream.Read(b) } func (conn *SnowflakeClientConn) Write(b []byte) (int, error) { return conn.stream.Write(b) } func (conn *SnowflakeClientConn) Close() error { return conn.stream.Close() } func (conn *SnowflakeClientConn) LocalAddr() net.Addr { return conn.stream.LocalAddr() } func (conn *SnowflakeClientConn) SetDeadline(t time.Time) error { return conn.stream.SetDeadline(t) } func (conn *SnowflakeClientConn) SetReadDeadline(t time.Time) error { return conn.stream.SetReadDeadline(t) } func (conn *SnowflakeClientConn) SetWriteDeadline(t time.Time) error { return conn.stream.SetWriteDeadline(t) } // RemoteAddr returns the mapped client address of the Snowflake connection. func (conn *SnowflakeClientConn) RemoteAddr() net.Addr { return conn.address } // WriteTo implements the io.WriterTo interface by passing the call to the // underlying smux.Stream. func (conn *SnowflakeClientConn) WriteTo(w io.Writer) (int64, error) { return conn.stream.WriteTo(w) } 07070100000094000081A400000000000000000000000165F88C5000000C02000000000000000000000000000000000000002A00000000snowflake-2.9.2/server/lib/turbotunnel.gopackage snowflake_server import ( "net" "sync" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/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 } } 07070100000095000081A400000000000000000000000165F88C5000000F5C000000000000000000000000000000000000002F00000000snowflake-2.9.2/server/lib/turbotunnel_test.gopackage snowflake_server import ( "encoding/binary" "net" "testing" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/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) } } 07070100000096000081A400000000000000000000000165F88C5000000442000000000000000000000000000000000000002300000000snowflake-2.9.2/server/randaddr.gopackage main import ( "crypto/rand" "fmt" "net" ) // randIPAddr generates a random IP address within the network represented by // ipnet. func randIPAddr(ipnet *net.IPNet) (net.IP, error) { if len(ipnet.IP) != len(ipnet.Mask) { return nil, fmt.Errorf("IP and mask have unequal lengths (%v and %v)", len(ipnet.IP), len(ipnet.Mask)) } ip := make(net.IP, len(ipnet.IP)) _, err := rand.Read(ip) if err != nil { return nil, err } for i := 0; i < len(ipnet.IP); i++ { ip[i] = (ipnet.IP[i] & ipnet.Mask[i]) | (ip[i] & ^ipnet.Mask[i]) } return ip, nil } // parseIPCIDR parses a CIDR-notation IP address and prefix length; or if that // fails, as a plain IP address (with the prefix length equal to the address // length). func parseIPCIDR(s string) (*net.IPNet, error) { _, ipnet, err := net.ParseCIDR(s) if err == nil { return ipnet, nil } // IP/mask failed; try just IP now, but remember err, to return it in // case that fails too. ip := net.ParseIP(s) if ip != nil { return &net.IPNet{IP: ip, Mask: net.CIDRMask(len(ip)*8, len(ip)*8)}, nil } return nil, err } 07070100000097000081A400000000000000000000000165F88C5000001011000000000000000000000000000000000000002800000000snowflake-2.9.2/server/randaddr_test.gopackage main import ( "bytes" "net" "testing" ) func mustParseCIDR(s string) *net.IPNet { _, ipnet, err := net.ParseCIDR(s) if err != nil { panic(err) } return ipnet } func TestRandAddr(t *testing.T) { outer: for _, ipnet := range []*net.IPNet{ mustParseCIDR("127.0.0.1/0"), mustParseCIDR("127.0.0.1/24"), mustParseCIDR("127.0.0.55/32"), mustParseCIDR("2001:db8::1234/0"), mustParseCIDR("2001:db8::1234/32"), mustParseCIDR("2001:db8::1234/128"), // Non-canonical masks (that don't consist of 1s followed by 0s) // work too, why not. &net.IPNet{ IP: net.IP{1, 2, 3, 4}, Mask: net.IPMask{0x00, 0x07, 0xff, 0xff}, }, } { for i := 0; i < 100; i++ { ip, err := randIPAddr(ipnet) if err != nil { t.Errorf("%v returned error %v", ipnet, err) continue outer } if !ipnet.Contains(ip) { t.Errorf("%v does not contain %v", ipnet, ip) continue outer } } } } func TestRandAddrUnequalLengths(t *testing.T) { for _, ipnet := range []*net.IPNet{ &net.IPNet{ IP: net.IP{1, 2, 3, 4}, Mask: net.CIDRMask(32, 128), }, &net.IPNet{ IP: net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Mask: net.CIDRMask(24, 32), }, &net.IPNet{ IP: net.IP{1, 2, 3, 4}, Mask: net.IPMask{}, }, &net.IPNet{ IP: net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Mask: net.IPMask{}, }, } { _, err := randIPAddr(ipnet) if err == nil { t.Errorf("%v did not result in error, but should have", ipnet) } } } func BenchmarkRandAddr(b *testing.B) { for _, test := range []struct { label string ipnet net.IPNet }{ {"IPv4/32", net.IPNet{IP: net.IP{127, 0, 0, 1}, Mask: net.CIDRMask(32, 32)}}, {"IPv4/24", net.IPNet{IP: net.IP{127, 0, 0, 1}, Mask: net.CIDRMask(32, 32)}}, {"IPv6/64", net.IPNet{ IP: net.IP{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x12, 0x34}, Mask: net.CIDRMask(64, 128), }}, {"IPv6/128", net.IPNet{ IP: net.IP{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x12, 0x34}, Mask: net.CIDRMask(128, 128), }}, } { b.Run(test.label, func(b *testing.B) { for i := 0; i < b.N; i++ { _, err := randIPAddr(&test.ipnet) if err != nil { b.Fatal(err) } } }) } } func ipNetEqual(a, b *net.IPNet) bool { if !a.IP.Equal(b.IP) { return false } // Comparing masks for equality is a little tricky because they may be // different lengths. For masks in canonical form (those for which // Size() returns other than (0, 0)), we consider two masks equal if the // numbers of bits *not* covered by the prefix are equal; e.g. // (120, 128) is equal to (24, 32), because they both have 8 bits not in // the prefix. If either mask is not in canonical form, we require them // to be equal as byte arrays (which includes length). aOnes, aBits := a.Mask.Size() bOnes, bBits := b.Mask.Size() if aBits == 0 || bBits == 0 { return bytes.Equal(a.Mask, b.Mask) } else { return aBits-aOnes == bBits-bOnes } } func TestParseIPCIDR(t *testing.T) { // Well-formed inputs. for _, test := range []struct { input string expected *net.IPNet }{ {"127.0.0.123", mustParseCIDR("127.0.0.123/32")}, {"127.0.0.123/0", mustParseCIDR("127.0.0.123/0")}, {"127.0.0.123/24", mustParseCIDR("127.0.0.123/24")}, {"127.0.0.123/32", mustParseCIDR("127.0.0.123/32")}, {"2001:db8::1234", mustParseCIDR("2001:db8::1234/128")}, {"2001:db8::1234/0", mustParseCIDR("2001:db8::1234/0")}, {"2001:db8::1234/32", mustParseCIDR("2001:db8::1234/32")}, {"2001:db8::1234/128", mustParseCIDR("2001:db8::1234/128")}, } { ipnet, err := parseIPCIDR(test.input) if err != nil { t.Errorf("%q returned error %v", test.input, err) continue } if !ipNetEqual(ipnet, test.expected) { t.Errorf("%q → %v, expected %v", test.input, ipnet, test.expected) } } // Bad inputs. for _, input := range []string{ "", "1.2.3", "1.2.3/16", "2001:db8:1234", "2001:db8:1234/64", "localhost", } { _, err := parseIPCIDR(input) if err == nil { t.Errorf("%q did not result in error, but should have", input) } } } 07070100000098000081A400000000000000000000000165F88C5000002626000000000000000000000000000000000000002100000000snowflake-2.9.2/server/server.go// Snowflake-specific websocket server plugin. It reports the transport name as // "snowflake". package main import ( "errors" "flag" "fmt" "io" "log" "net" "net/http" "os" "os/signal" "path/filepath" "strconv" "strings" "sync" "syscall" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/safelog" "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/version" "golang.org/x/crypto/acme/autocert" pt "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib" sf "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/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 && !errors.Is(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 && !errors.Is(err, io.EOF) && !errors.Is(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 the // ORPort. If orPortSrcAddr is not nil, addresses from the given range are used // when dialing the ORPOrt. func handleConn(conn net.Conn, orPortSrcAddr *net.IPNet) error { addr := conn.RemoteAddr().String() statsChannel <- addr != "" dialer := net.Dialer{ Control: dialerControl, } if orPortSrcAddr != nil { // Use a random source IP address in the given range. ip, err := randIPAddr(orPortSrcAddr) if err != nil { return err } dialer.LocalAddr = &net.TCPAddr{IP: ip} } or, err := pt.DialOrWithDialer(&dialer, &ptInfo, addr, ptMethodName) if err != nil { return fmt.Errorf("failed to connect to ORPort: %s", err) } defer or.Close() proxy(or.(*net.TCPConn), conn) return nil } // acceptLoop accepts incoming client snowflake connections and passes them to // handleConn. If orPortSrcAddr is not nil, addresses from the given range are // used when dialing the ORPOrt. func acceptLoop(ln net.Listener, orPortSrcAddr *net.IPNet) { 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, orPortSrcAddr) 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 var versionFlag 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.BoolVar(&versionFlag, "version", false, "display version info to stderr and quit") flag.Parse() if versionFlag { fmt.Fprintf(os.Stderr, "snowflake-server %s", version.ConstructResult()) os.Exit(0) } 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}) } log.Printf("snowflake-server %s\n", version.GetVersion()) 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) } // Are we requested to use source addresses from a particular // range when dialing the ORPort for this transport? var orPortSrcAddr *net.IPNet if orPortSrcAddrCIDR, ok := bindaddr.Options.Get("orport-srcaddr"); ok { ipnet, err := parseIPCIDR(orPortSrcAddrCIDR) if err != nil { err = fmt.Errorf("parsing srcaddr: %w", err) log.Println(err) pt.SmethodError(bindaddr.MethodName, err.Error()) continue } orPortSrcAddr = ipnet } numKCPInstances := 1 // Are we requested to run a certain number of KCP state // machines? if value, ok := bindaddr.Options.Get("num-turbotunnel"); ok { n, err := strconv.Atoi(value) if err == nil && n < 1 { err = fmt.Errorf("cannot be less than 1") } if err != nil { err = fmt.Errorf("parsing num-turbotunnel: %w", err) log.Println(err) pt.SmethodError(bindaddr.MethodName, err.Error()) continue } numKCPInstances = n } ln, err := transport.Listen(bindaddr.Addr, numKCPInstances) if err != nil { log.Printf("error opening listener: %s", err) pt.SmethodError(bindaddr.MethodName, err.Error()) continue } defer ln.Close() go acceptLoop(ln, orPortSrcAddr) 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(io.Discard, os.Stdin); err != nil { log.Printf("error copying os.Stdin to io.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() } } 07070100000099000081A400000000000000000000000165F88C5000000368000000000000000000000000000000000000002000000000snowflake-2.9.2/server/stats.gopackage main // This code handles 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) } } } 0707010000009A000081A400000000000000000000000165F88C5000000104000000000000000000000000000000000000001D00000000snowflake-2.9.2/server/torrcSocksPort 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 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1326 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor