REPOSITORY / ScuroNeko/mtg

Compare commits

DIFF REPOSITORY

Compare commits

...
107 Commits
Author SHA1 Message Date
9seconds 7d748077e9 Merge remote-tracking branch 'origin/stable' into v2 2026-02-26 13:53:32 +01:00
9seconds f61a63a59b Merge remote-tracking branch 'origin/master' into stable 2026-02-26 13:53:17 +01:00
9seconds 282896be09 Update own dependencies 2026-02-26 13:52:26 +01:00
9seconds 3809f034ad Update go packages 2026-02-26 13:51:29 +01:00
Sergei ArkhipovandGitHub 6feef02d08 Merge pull request #344 from 9seconds/blocked-telegram
Make auto updating optional
2026-02-26 13:46:01 +01:00
Sergei ArkhipovandGitHub 5549a82d19 Merge pull request #345 from 9seconds/fix-no-addresses
Do not panic if no addresses were found
2026-02-26 13:35:47 +01:00
9seconds 189dce07c6 Make auto updating optional 2026-02-26 13:35:05 +01:00
9seconds 449f84133a Do not panic if no addresses were found 2026-02-26 12:31:30 +01:00
9seconds 9c122f942f Always use latest govulncheck to check vulnerabilities 2026-02-24 19:06:16 +01:00
9seconds 136eea551f Merge remote-tracking branch 'origin/stable' into v2 2026-02-24 18:46:18 +01:00
9seconds e6fa5906c9 Merge remote-tracking branch 'origin/master' into stable 2026-02-24 18:45:46 +01:00
9seconds 42f612f49e Use go tag for 1.26 2026-02-24 18:08:45 +01:00
Sergei ArkhipovandGitHub d7db8ca98b Merge pull request #339 from 9seconds/domain-fronting-config-grouping
Domain fronting config grouping
2026-02-24 18:07:02 +01:00
9seconds 1cb225f52c Introduce [domain-fronting] config 2026-02-24 18:05:12 +01:00
9seconds af72b2a574 Delete obsoleted setting 2026-02-24 16:56:42 +01:00
Sergei ArkhipovandGitHub 2cbee5d453 Merge pull request #338 from 9seconds/proxy-proto-front
Add support for domain fronting proxy protocol
2026-02-24 16:53:17 +01:00
9seconds cde313b359 Add support for domain fronting proxy protocol 2026-02-24 16:44:35 +01:00
Sergei ArkhipovandGitHub 58cb0b2caf Merge pull request #336 from 9seconds/obfuscated2
Fetch DC203 from Telegram
2026-02-24 16:41:06 +01:00
9seconds bb320e9d89 Update fuzz tests 2026-02-24 15:27:19 +01:00
Sergei ArkhipovandGitHub f6d2f2ffd8 Merge pull request #337 from 9seconds/govulncheck
Check for vulnerabilities
2026-02-24 15:25:06 +01:00
9seconds 5fe3fdd73c Check for vulnerabilities 2026-02-24 14:21:16 +01:00
9seconds 5b91edf5c4 Fix tests 2026-02-24 13:58:16 +01:00
9seconds 8b34c1b104 Merge remote-tracking branch 'origin/master' into obfuscated2 2026-02-24 13:37:10 +01:00
9seconds 36c766b331 Fix lint issues 2026-02-24 13:35:06 +01:00
9seconds e4a9a96309 Remove mentioning of DC overrides 2026-02-24 13:32:06 +01:00
9seconds 94d46d2c65 Add fetching of addresses from proxyGetConfig endpoint 2026-02-24 12:55:16 +01:00
9seconds 908842063a Do not use additional bytes buffer for faketls 2026-02-23 10:27:01 +01:00
9seconds e50cee5748 Do not use unnecessary lock in connRewind 2026-02-23 10:12:25 +01:00
9seconds ee524abdb5 Remove redundant copyBufferPool from relay 2026-02-23 10:12:25 +01:00
9seconds 3e75e4fa63 Delete old obfuscated2 package 2026-02-23 10:12:25 +01:00
9seconds 140e9dfc2e Integrate obfuscation package 2026-02-23 10:12:25 +01:00
9seconds d0065d35c2 Add new obfuscation package 2026-02-23 10:12:25 +01:00
Sergei ArkhipovandGitHub 45ce5c2f61 Merge pull request #334 from ivulit/master 2026-02-20 20:33:10 +01:00
ivulit 21129b6e00 Add domain-fronting-ip to example config 2026-02-20 12:34:22 +03:00
ivulit bf38f9f8af Add domain-fronting-ip option
Allow specifying an explicit IP address for the domain fronting host
instead of relying on DNS resolution. Useful when DNS resolution of
the fronting hostname is blocked.

The hostname from the secret is still used for SNI in TLS handshake.
2026-02-20 12:34:17 +03:00
9seconds b854b16e1a Merge branch 'stable' into v2 2026-02-19 14:37:52 +01:00
9seconds f4b296d1e0 Merge branch 'master' into stable 2026-02-19 14:37:36 +01:00
Sergei ArkhipovandGitHub 432e530f68 Adjust formatting in readme 2026-02-19 14:35:04 +01:00
Sergei ArkhipovandGitHub a2bf9a269a Merge pull request #333 from 9seconds/proxyproto
Add support of proxy protocol
2026-02-19 14:27:43 +01:00
9seconds cf3437bb63 Add support of proxy protocol 2026-02-19 14:22:00 +01:00
Sergei ArkhipovandGitHub d0e99dda2b Update README.md 2026-02-18 11:16:04 +01:00
Sergei ArkhipovandGitHub ac3bd16e83 Merge pull request #332 from 9seconds/telemt-recommend
Recommend to use telemt for adtag carvings
2026-02-18 11:14:22 +01:00
9seconds 222cef8c41 Recommend to use telemt for adtag carvings 2026-02-18 11:13:55 +01:00
Sergei ArkhipovandGitHub ed5da2864a Merge pull request #331 from 9seconds/go1.26
Chore updates for golang 1.26
2026-02-18 11:04:23 +01:00
9seconds 08393e426a Update mise dependencies 2026-02-18 10:37:41 +01:00
9seconds 0440ccf4ff Rewrite to WaitGroup.Go 2026-02-18 10:34:31 +01:00
9seconds a0aabf2391 Switch to rand/v2 2026-02-18 10:34:31 +01:00
9seconds 3b03c4a90a More idioms related to go 1.26 2026-02-17 23:38:12 +01:00
Sergei ArkhipovandGitHub 80b9159ce9 Merge pull request #330 from 9seconds/telemt
Mention telemt project in README
2026-02-17 22:19:50 +01:00
Sergei ArkhipovandGitHub bdabb0e59a Merge pull request #329 from 9seconds/tools
Tell about mise in readme
2026-02-17 22:19:32 +01:00
9seconds 543f5cde9c Mention telemt project in README 2026-02-17 21:46:15 +01:00
9seconds 1a247d18b1 Tell about mise in README 2026-02-17 21:43:16 +01:00
9seconds 5a63c7d5b4 Lock all mise dependencies 2026-02-17 21:39:12 +01:00
9seconds e7fdb02a29 Merge branch 'v2' into stable 2026-02-16 19:23:45 +01:00
9seconds 7a6ba6d8c6 Merge branch 'master' into v2 2026-02-16 19:23:28 +01:00
9seconds be398f9c79 Use mise lock 2026-02-16 19:22:37 +01:00
9seconds 4c029fc683 Use gofumpt from github 2026-02-16 19:22:37 +01:00
9seconds 3066672353 Add permissions for Docker job to write into packages 2026-02-16 18:01:27 +00:00
9seconds ae88c0cab0 More tags for relevant branches 2026-02-16 17:17:52 +00:00
9seconds 816b7f72b6 Change secret to Github token 2026-02-16 17:17:47 +00:00
Sergei ArkhipovandGitHub 1f7584c108 Merge pull request #326 from 9seconds/dc203
Correctly connect to DC203/CDN
2026-02-16 18:01:26 +01:00
Sergei ArkhipovandGitHub 2fb025bbfd Merge pull request #327 from 9seconds/workflow
Correctly build and push images into registry
2026-02-16 18:01:09 +01:00
Sergey Arkhipov c7ab53134a Correctly build and push images into registry 2026-02-16 16:39:29 +00:00
9seconds f83ee17361 Fix test 2026-02-16 17:30:11 +01:00
9seconds 36546cec2f Change default DOH to cloudflarte 2026-02-16 17:18:31 +01:00
9seconds 074c4017f5 Formatting by gofumpt 2026-02-16 17:10:06 +01:00
9seconds 8e87405d3e Remove integration with gotd 2026-02-16 16:30:10 +01:00
9seconds 82679ec20f Update config 2026-02-16 15:22:03 +01:00
9seconds 308e372a5d Propagate DcUpdateEach setting 2026-02-16 15:07:56 +01:00
9seconds 836a481026 Propagate DCOverrides 2026-02-16 14:58:41 +01:00
9seconds 0a5a45b32d Create internal DC package 2026-02-16 14:15:38 +01:00
9seconds 852ca713c8 Fetch DC ips from Telegram 2026-02-15 23:56:53 +01:00
9seconds 301bde88ac Add gopls to mise 2026-02-11 15:09:43 +01:00
9seconds 25bca76da5 Merge remote-tracking branch 'origin/stable' into v2 2026-02-11 14:26:31 +01:00
9seconds 76109f6204 Merge remote-tracking branch 'origin/master' into stable 2026-02-11 14:25:54 +01:00
9seconds 6db6415798 Enable tests back 2026-02-11 14:25:00 +01:00
9seconds 0faf482e52 Update dockerfile 2026-02-11 13:55:11 +01:00
9seconds 3e4faf6ba6 Test docker 2026-02-11 12:13:55 +01:00
9seconds f7f6a7637c Update for tag semver 2026-02-11 12:09:57 +01:00
9seconds 130b02013b More CI updates 2026-02-11 12:07:45 +01:00
9seconds 1b4c777ed3 Use mise for fuzzing 2026-02-11 11:58:30 +01:00
9seconds 60b4b5ad8f Fix lint issues 2026-02-11 11:54:47 +01:00
9seconds a4930a596a Update linter job 2026-02-11 11:49:51 +01:00
9seconds fb3e921bc6 Update linter job 2026-02-11 11:48:09 +01:00
9seconds afbf4f6f74 Disable push to docker hub 2026-02-11 11:44:59 +01:00
9seconds 592b7c5b87 Update setup-go actions 2026-02-11 11:44:52 +01:00
9seconds c170887499 Update checkouts 2026-02-11 11:44:44 +01:00
9seconds ad76304b43 Correct argument for coverage 2026-02-11 11:43:01 +01:00
9seconds 5e38819506 Update codecov action 2026-02-11 11:42:01 +01:00
9seconds f06fd5ca77 Update CI 2026-02-11 11:21:27 +01:00
9seconds ad88afeecd Remove makefile 2026-02-11 11:13:36 +01:00
9seconds c74c0c92c6 Update CI 2026-02-11 11:13:25 +01:00
9seconds e3dc03b4db Update dependencies 2026-02-11 10:46:07 +01:00
9seconds ecba88d2e3 Update to the latest golangci-lint 2026-02-11 10:20:04 +01:00
9seconds 37de052feb Disable failing real request test for a while 2026-02-11 10:02:39 +01:00
9seconds 2041f3154b Add tests into mise 2026-02-11 09:58:26 +01:00
9seconds b1728c3474 Add task for building image 2026-02-11 09:47:11 +01:00
9seconds b72af2b953 Add task for formatting source code 2026-02-11 09:45:40 +01:00
9seconds 75c317f35e Add task for static build 2026-02-11 09:45:40 +01:00
9seconds 795dabb80b Add docs task 2026-02-11 09:38:36 +01:00
9seconds 01b0166995 Add release mise task 2026-02-11 09:38:36 +01:00
9seconds e9a147c836 Add build task 2026-02-11 09:26:46 +01:00
9seconds de66835657 Update goreleaser 2026-02-11 08:59:17 +01:00
9seconds 75f11965d8 Switch to mise 2026-02-11 08:52:45 +01:00
Sergey ArkhipovandGitHub e68d0c7da5 Merge pull request #289 from arashnm80/arashnm80-fix-docker-command
edit docker command in README.md
2022-11-21 17:53:10 +01:00
Arash Nemat ZadehandGitHub 051180a170 edit docker command in README.md
added the missing slash mentioned in issue #287
2022-11-17 03:42:09 +03:30
9seconds b4e0143cb7 Merge remote-tracking branch 'origin/stable' 2022-08-09 17:57:09 +03:00
116 changed files with 2439 additions and 2281 deletions
+41 -62
View File
@@ -39,35 +39,22 @@ jobs:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
go_version:
- ^1.19
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v6
with:
submodules: recursive
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go_version }}
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- uses: jdx/mise-action@v3
name: Install mise
- name: Run tests
run: go test -coverprofile=./coverage.txt -covermode=atomic -v ./...
run: mise tasks run covtest
- name: Collect coverage
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v5
with:
file: ./coverage.txt
files: ./coverage.txt
fuzz:
name: Fuzzing
@@ -75,31 +62,16 @@ jobs:
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v6
with:
submodules: recursive
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ^1.18
- name: Cache fuzz results
uses: actions/cache@v2
with:
path: ~/.cache/go-build/fuzz
key: ${{ runner.os }}-go-${{ hashFiles('**/*_fuzz_test.go', '**/*_fuzz_internal_test.go') }}
restore-keys: ${{ runner.os }}-go-
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- uses: jdx/mise-action@v3
name: Install mise
- name: Run fuzzing
run: make -j4 fuzz
run: mise tasks run 'test:fuzz:*'
lint:
name: Lint
@@ -107,45 +79,52 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v6
with:
submodules: recursive
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ^1.19
- uses: jdx/mise-action@v3
name: Install mise
- name: Run linter
uses: golangci/golangci-lint-action@v3
with:
version: v1.48.0
run: mise tasks run lint
docker:
name: Docker
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v6
with:
submodules: recursive
- name: Get Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
id: meta
uses: docker/metadata-action@v5
with:
images: nineseconds/mtg,ghcr.io/9seconds/mtg
tag-semver: "{{version}},{{major}},{{major}}.{{minor}}"
images: |
nineseconds/mtg
ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=master,enable=${{ github.ref == 'refs/heads/master' }}
type=raw,value=stable,enable=${{ github.ref == 'refs/heads/stable' }}
- name: Setup QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v3
- name: Setup BuildX
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Setup cache
uses: actions/cache@v2
uses: actions/cache@v5
with:
path: /tmp/buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
@@ -154,18 +133,18 @@ jobs:
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to GHCR.io
- name: Login to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
@@ -174,7 +153,7 @@ jobs:
context: .
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7,linux/arm/v6
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=local,src=/tmp/buildx-cache
cache-to: type=local,dest=/tmp/buildx-cache
+41
View File
@@ -0,0 +1,41 @@
---
name: Vulnerability checks
permissions:
actions: read
checks: read
contents: read
deployments: read
issues: read
discussions: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
on:
push:
pull_request:
schedule: # daily at 10:22 UTC
- cron: '22 10 * * *'
workflow_dispatch:
jobs:
vuln:
name: Test vulnerabilities
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
- name: Check for vulnerabilities
run: |
go run golang.org/x/vuln/cmd/govulncheck@latest ./...
-3
View File
@@ -6,8 +6,5 @@
*.test
*.out
mtg
vendor/
ccbuilds/
.bin/
coverage.txt
dist/
+17 -18
View File
@@ -1,22 +1,21 @@
# https://golangci-lint.run/docs/configuration/file/
version = '2'
[run]
concurrency = 4
deadline = "2m"
tests = true
skip-dirs = ["vendor"]
[output]
format = "colored-line-number"
[linters]
enable-all = true
disable = [
"containedctx",
"exhaustivestruct",
"exhaustruct",
"gas",
"gochecknoglobals",
"goerr113",
"ireturn",
"thelper",
"varnamelen",
]
# [linters]
# enable-all = true
# disable = [
# "containedctx",
# "exhaustivestruct",
# "exhaustruct",
# "gas",
# "gochecknoglobals",
# "goerr113",
# "ireturn",
# "thelper",
# "varnamelen",
# ]
+7 -3
View File
@@ -1,5 +1,7 @@
---
version: 2
project_name: mtg
before:
@@ -41,11 +43,13 @@ builds:
archives:
- name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
format: tar.gz
formats:
- tar.gz
wrap_in_directory: true
format_overrides:
- goos: windows
format: zip
formats:
- zip
files:
- LICENSE
- README.md
@@ -55,7 +59,7 @@ gomod:
proxy: true
snapshot:
name_template: '{{ .Version }}'
version_template: '{{ .Version }}'
checksum:
name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt'
+98
View File
@@ -0,0 +1,98 @@
[tools]
"go:golang.org/x/pkgsite/cmd/pkgsite" = "latest"
"go:golang.org/x/tools/gopls" = "latest"
"go:golang.org/x/vuln/cmd/govulncheck" = "latest"
"go:mvdan.cc/gofumpt" = "latest"
go = "latest"
golangci-lint = "latest"
goreleaser = "latest"
[vars]
fuzzflags = "-fuzztime=120s"
[tasks.build]
description = "Build binary"
sources = ["**/*.go", "go.mod", "go.sum"]
outputs = ["mtg"]
run = "go build"
[tasks.update]
description = "Update dependencies"
run = [
"go get -u",
"go mod tidy -go=1.26"
]
[tasks.lint]
description = "Run linter"
run = "golangci-lint run"
[tasks.vuln]
description = "Test for vulnerabilities"
run = "govulncheck ./..."
[tasks.test]
description = "Run tests"
run = "go test -v ./..."
[tasks.covtest]
description = "Run tests with code coverage"
run = "go test -coverprofile=coverage.txt -covermode=atomic -parallel 2 -race -v ./..."
[tasks.test-all]
description = "Run all tests"
depends = [
"test",
"test:fuzz:*"
]
[tasks."test:fuzz:client-hello"]
description = "Run fuzzy test for ClientHello"
run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzClientHello ./mtglib/internal/faketls"
[tasks."test:fuzz:client-handshake"]
description = "Run fuzzy test for ClientHandshake"
run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzClientServerHandshake ./mtglib/internal/obfuscation"
[tasks."test:fuzz:server-handshake-frame"]
description = "Run fuzzy test for GenerateHandshakeFrame"
run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzGenerateHandshakeFrame ./mtglib/internal/obfuscation"
[tasks.static]
description = "Build static binary"
sources = ["**/*.go", "go.mod", "go.sum"]
outputs = ["mtg"]
run = """
#!/bin/bash
version="$(git describe --exact-match HEAD 2>/dev/null || git describe --tags --always)"
go build \
-trimpath \
-mod=readonly \
-ldflags="-extldflags '-static' -s -w -X 'main.version=$version'" \
-a \
-tags netgo
"""
[tasks.release]
description = "Create release tarballs"
sources = ["**/*.go", "go.mod", "go.sum", ".goreleaser.yml"]
run = [
"goreleaser --snapshot --clean",
"find dist -depth 1 -type d | xargs -r rm -r",
"rm ./dist/config.yaml"
]
[tasks.image]
description = "Build docker image"
sources = ["**/*.go", "go.mod", "go.sum", "Dockerfile"]
run = "docker buildx build --pull -t mtg ."
[tasks.docs]
description = "Run doc server"
run = "pkgsite -http 0.0.0.0:10000"
[tasks.fmt]
description = "Reformat source code"
sources = ["**/*.go"]
run = "gofumpt -w --extra ."
+11 -5
View File
@@ -1,21 +1,27 @@
###############################################################################
# BUILD STAGE
FROM golang:1.19-alpine AS build
FROM golang:1.26-alpine AS build
ENV CGO_ENABLED=0
RUN set -x \
&& apk --no-cache --update add \
bash \
ca-certificates \
curl \
git \
make
git
COPY . /app
WORKDIR /app
RUN set -x \
&& make -j 4 static
&& version="$(git describe --exact-match HEAD 2>/dev/null || git describe --tags --always)" \
&& go build \
-trimpath \
-mod=readonly \
-ldflags="-extldflags '-static' -s -w -X 'main.version=$version'" \
-a \
-tags netgo
###############################################################################
-121
View File
@@ -1,121 +0,0 @@
ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
IMAGE_NAME := mtg
APP_NAME := $(IMAGE_NAME)
GOLANGCI_LINT_VERSION := v1.48.0
VERSION := $(shell git describe --exact-match HEAD 2>/dev/null || git describe --tags --always)
COMMON_BUILD_FLAGS := -trimpath -mod=readonly -ldflags="-extldflags '-static' -s -w -X 'main.version=$(VERSION)'"
FUZZ_FLAGS := -fuzztime=120s
GOBIN := $(ROOT_DIR)/.bin
GOTOOL := env "GOBIN=$(GOBIN)" "PATH=$(ROOT_DIR)/.bin:$(PATH)"
# -----------------------------------------------------------------------------
.PHONY: all
all: build
.PHONY: build
build:
@go build $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)"
$(APP_NAME): build
.PHONY: static
static:
@env CGO_ENABLED=0 GOOS=linux go build \
$(COMMON_BUILD_FLAGS) \
-tags netgo \
-a \
-o "$(APP_NAME)"
vendor: go.mod go.sum
@$(MOD_ON) go mod vendor
.bin:
@mkdir -p "$(GOBIN)" || true
.PHONY: fmt
fmt:
@$(GOTOOL) gofumpt -w -extra "$(ROOT_DIR)"
.PHONY: test
test:
@go test -v ./...
.PHONY: citest
citest:
@go test -coverprofile=coverage.txt -covermode=atomic -parallel 2 -race -v ./...
.PHONY: clean
clean:
@git clean -xfd && \
git reset --hard >/dev/null && \
git submodule foreach --recursive sh -c 'git clean -xfd && git reset --hard' >/dev/null
.PHONY: lint
lint:
@$(GOTOOL) golangci-lint run
.PHONY: release
release:
@$(GOTOOL) goreleaser release --snapshot --rm-dist && \
find "$(ROOT_DIR)/dist" -type d | grep -vP "dist$$" | xargs -r rm -rf && \
rm -f "$(ROOT_DIR)/dist/config.yaml"
.PHONY: docker
docker:
@docker build --pull -t "$(IMAGE_NAME)" "$(ROOT_DIR)"
.PHONY: doc
doc:
@$(GOTOOL) godoc -http 0.0.0.0:10000
.PHONY: install-tools
install-tools: install-tools-lint install-tools-godoc install-tools-gofumpt install-tools-goreleaser
.PHONY: install-tools-lint
install-tools-lint: .bin
@curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \
| bash -s -- -b "$(GOBIN)" "$(GOLANGCI_LINT_VERSION)"
.PHONY: install-tools-godoc
install-tools-godoc: .bin
@$(GOTOOL) go install golang.org/x/tools/cmd/godoc@latest
.PHONY: install-tools-gofumpt
install-tools-gofumpt: .bin
@$(GOTOOL) go install mvdan.cc/gofumpt@latest
.PHONY: goreleaser
install-tools-goreleaser: .bin
@$(GOTOOL) go install github.com/goreleaser/goreleaser@latest
.PHONY: update-deps
update-deps:
@go get -u && go mod tidy -go=1.18
.PHONY: fuzz
fuzz: fuzz-ClientHello fuzz-ServerGenerateHandshakeFrame fuzz-ClientHandshake fuzz-ServerReceive fuzz-ServerSend
.PHONY: fuzz-ClientHello
fuzz-ClientHello:
@go test -fuzz=FuzzClientHello $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/faketls"
.PHONY: fuzz-ServerGenerateHandshakeFrame
fuzz-ServerGenerateHandshakeFrame:
@go test -fuzz=FuzzServerGenerateHandshakeFrame $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
.PHONY: fuzz-ClientHandshake
fuzz-ClientHandshake:
@go test -fuzz=FuzzClientHandshake $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
.PHONY: fuzz-ServerReceive
fuzz-ServerReceive:
@go test -fuzz=FuzzServerReceive $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
.PHONY: fuzz-ServerSend
fuzz-ServerSend:
@go test -fuzz=FuzzServerSend $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
+29 -4
View File
@@ -10,6 +10,17 @@ Highly-opinionated (ex-bullshit-free) MTPROTO proxy for
**If you use v1.0 or upgrade broke you proxy, please read the chapter
[Version 2](#version-2)**
If you want to have a proxy that _supports adtag_ (possibility to promote a
channel with a special Telegram bot), I recommend to use
[telemt](https://github.com/telemt/telemt) project. v1 of mtg supports it
but I do not see any reasonable point of using it: adtag requires communication
via a fragile set of middle proxies, requires complex setup that must expose
a public IPs, has lower bandwidth and latency.
mtg idea is simple: minimal unbloated proxy that can handle a reasonable scale
~10-20k simultaneous connections, has no user management, but ticks all
checkboxes related to its main intent: provide a way to use Telegram.
## Rationale
There are several available proxies for Telegram MTPROTO available. Here
@@ -18,6 +29,7 @@ are the most notable:
* [Official](https://github.com/TelegramMessenger/MTProxy)
* [Python](https://github.com/alexbers/mtprotoproxy)
* [Erlang](https://github.com/seriyps/mtproto_proxy)
* [Telemt (Rust)](https://github.com/telemt/telemt)
You can use any of these. They work great and all implementations have
feature parity now. This includes support of adtag, replay attack
@@ -40,6 +52,12 @@ that probably matter.
way of doing business I suppose. I think the only viable way is to
have a proxy that can be restored anywhere easily.
* **Supports proxy protocol v1/v2**
This makes integration with loadbalancers like HAProxy and ELB a first class
citizen by supporting their
[commuication protocols](https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt).
* **A single secret**
I think that multiple secrets solve no problems and just complex
@@ -162,6 +180,12 @@ This project has several main branches
## Getting started
### Download mise
mtg uses [mise](https://mise.jdx.dev/) to maintain its development
dependencies + replaces a make for building things. Please
[install](https://mise.jdx.dev/getting-started.html) it first.
### Download a tool
#### Download binaries
@@ -198,13 +222,14 @@ go install github.com/9seconds/mtg/v2@latest
```console
git clone https://github.com/9seconds/mtg.git
cd mtg
make static
mise install
mise tasks run build
```
or for the docker image:
```console
make docker
mise tasks run image
```
### Generate secret
@@ -275,7 +300,7 @@ Flags:
-b, --tcp-buffer="4KB" Size of TCP buffer to use.
-i, --prefer-ip="prefer-ipv6" IP preference. By default we prefer IPv6 with fallback to IPv4.
-p, --domain-fronting-port=443 A port to access for domain fronting.
-n, --doh-ip=9.9.9.9 IP address of DNS-over-HTTP to use.
-n, --doh-ip=1.1.1.1 IP address of DNS-over-HTTP to use.
-t, --timeout=10s Network timeout to use
-a, --antireplay-cache-size="1MB" A size of anti-replay cache to use.
```
@@ -338,7 +363,7 @@ $ sudo systemctl start mtg
or you can run a docker image
```console
docker run -d -v $PWD/config.toml:config.toml -p 443:3128 --name mtg-proxy --restart=unless-stopped nineseconds/mtg:2
docker run -d -v $PWD/config.toml:/config.toml -p 443:3128 --name mtg-proxy --restart=unless-stopped nineseconds/mtg:2
```
where _443_ is a host port (a port you want to connect to from a
+1 -1
View File
@@ -42,7 +42,7 @@ func NewStableBloomFilter(byteSize uint, errorRate float64) mtglib.AntiReplayCac
errorRate = DefaultStableBloomFilterErrorRate
}
sf := boom.NewDefaultStableBloomFilter(byteSize*8, errorRate) //nolint: gomnd
sf := boom.NewDefaultStableBloomFilter(byteSize*8, errorRate)
sf.SetHash(xxhash.New64())
return &stableBloomFilter{
+2 -2
View File
@@ -2,7 +2,7 @@ package events
import (
"context"
"math/rand"
"math/rand/v2"
"runtime"
"github.com/9seconds/mtg/v2/mtglib"
@@ -64,7 +64,7 @@ func NewEventStream(observerFactories []ObserverFactory) EventStream {
chans: make([]chan mtglib.Event, runtime.NumCPU()),
}
for i := 0; i < runtime.NumCPU(); i++ {
for i := range runtime.NumCPU() {
rv.chans[i] = make(chan mtglib.Event, 1)
if len(observerFactories) == 1 {
+27 -54
View File
@@ -12,14 +12,11 @@ type multiObserver struct {
func (m multiObserver) EventStart(evt mtglib.EventStart) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventStart(evt)
}(v)
wg.Go(func() {
v.EventStart(evt)
})
}
wg.Wait()
@@ -27,14 +24,11 @@ func (m multiObserver) EventStart(evt mtglib.EventStart) {
func (m multiObserver) EventConnectedToDC(evt mtglib.EventConnectedToDC) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventConnectedToDC(evt)
}(v)
wg.Go(func() {
v.EventConnectedToDC(evt)
})
}
wg.Wait()
@@ -42,14 +36,11 @@ func (m multiObserver) EventConnectedToDC(evt mtglib.EventConnectedToDC) {
func (m multiObserver) EventDomainFronting(evt mtglib.EventDomainFronting) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventDomainFronting(evt)
}(v)
wg.Go(func() {
v.EventDomainFronting(evt)
})
}
wg.Wait()
@@ -57,14 +48,11 @@ func (m multiObserver) EventDomainFronting(evt mtglib.EventDomainFronting) {
func (m multiObserver) EventTraffic(evt mtglib.EventTraffic) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventTraffic(evt)
}(v)
wg.Go(func() {
v.EventTraffic(evt)
})
}
wg.Wait()
@@ -72,14 +60,11 @@ func (m multiObserver) EventTraffic(evt mtglib.EventTraffic) {
func (m multiObserver) EventFinish(evt mtglib.EventFinish) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventFinish(evt)
}(v)
wg.Go(func() {
v.EventFinish(evt)
})
}
wg.Wait()
@@ -87,14 +72,11 @@ func (m multiObserver) EventFinish(evt mtglib.EventFinish) {
func (m multiObserver) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimited) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventConcurrencyLimited(evt)
}(v)
wg.Go(func() {
v.EventConcurrencyLimited(evt)
})
}
wg.Wait()
@@ -102,14 +84,11 @@ func (m multiObserver) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimite
func (m multiObserver) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventIPBlocklisted(evt)
}(v)
wg.Go(func() {
v.EventIPBlocklisted(evt)
})
}
wg.Wait()
@@ -117,14 +96,11 @@ func (m multiObserver) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
func (m multiObserver) EventReplayAttack(evt mtglib.EventReplayAttack) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventReplayAttack(evt)
}(v)
wg.Go(func() {
v.EventReplayAttack(evt)
})
}
wg.Wait()
@@ -132,14 +108,11 @@ func (m multiObserver) EventReplayAttack(evt mtglib.EventReplayAttack) {
func (m multiObserver) EventIPListSize(evt mtglib.EventIPListSize) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventIPListSize(evt)
}(v)
wg.Go(func() {
v.EventIPListSize(evt)
})
}
wg.Wait()
+59 -10
View File
@@ -23,17 +23,19 @@ secret = "ee367a189aee18fa31c190054efd4a8e9573746f726167652e676f6f676c6561706973
# Host:port pair to run proxy on.
bind-to = "0.0.0.0:3128"
# This defines what types of traffic mtg listens to. If you are not sure,
# then definitely keep it disable. Enable it only and only if incoming traffic
# is coming from some sort of load-balancer like HAProxy or ELB.
# https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt
#
# mtg uses a library that supports v1 and v2 versions of ProxyProtocol.
# default value is false.
# proxy-protocol-listener = false
# Defines how many concurrent connections are allowed to this proxy.
# All other incoming connections are going to be dropped.
concurrency = 8192
# A size of user-space buffer for TCP to use. Since we do 2 connections,
# then we have tcp-buffer * (4 + 2) per each connection: read/write for
# each connection + 2 copy buffers to pump the data between sockets.
#
# Deprecated: this setting is no longer makes any effect.
# tcp-buffer = "4kb"
# Sometimes you want to enforce mtg to use some types of
# IP connectivity to Telegram. We have 4 modes:
# - prefer-ipv6:
@@ -46,9 +48,35 @@ concurrency = 8192
# Only ipv4 connectivity is used
prefer-ip = "prefer-ipv6"
# If this setting is set, then mtg will try to get proxy updates from Telegram
# Usually this is completely fine to have it disabled, because mtg has a list
# of some core proxies hardcoded.
auto-update = false
# FakeTLS uses domain fronting protection. So it needs to know a port to
# access.
domain-fronting-port = 443
#
# Deprecated: use [domain-fronting] configuration block. If relevant option
# is defined there, this one would be ignored.
# domain-fronting-port = 443
# By default, mtg resolves the fronting hostname (from the secret) via DNS
# to establish a TCP connection. If DNS resolution of that hostname is blocked,
# you can specify an IP address to connect to directly. The hostname is still
# used for SNI in the TLS handshake.
#
# default value is not set (DNS resolution is used).
#
# Deprecated: use [domain-fronting] configuration block. If relevant option
# is defined there, this one would be ignored.
# domain-fronting-ip = "10.0.0.10"
# This makes a communication between both fronting website and mtg to use
# proxy protocol.
#
# Deprecated: use [domain-fronting] configuration block. If relevant option
# is defined there, this one would be ignored.
# domain-fronting-proxy-protocol = false
# FakeTLS can compare timestamps to prevent probes. Each message has
# encrypted timestamp. So, mtg can compare this timestamp and decide if
@@ -71,6 +99,27 @@ tolerate-time-skewness = "5s"
# Otherwise, chose a new DC.
allow-fallback-on-unknown-dc = false
# This section is relevant to communication with fronting domain. Usually
# you do not need to setup anything here but there are plenty of cases, especially
# if you put mtg behind load balancer, when some specific configuration is
# required.
[domain-fronting]
# By default, mtg resolves the fronting hostname (from the secret) via DNS
# to establish a TCP connection. If DNS resolution of that hostname is blocked,
# you can specify an IP address to connect to directly. The hostname is still
# used for SNI in the TLS handshake.
#
# default value is not set (DNS resolution is used).
# ip = "10.10.10.11"
# FakeTLS uses domain fronting protection. So it needs to know a port to
# access. Default value is 443
# port = 443
# This makes a communication between both fronting website and mtg to use
# proxy protocol.
# proxy-protocol = false
# network defines different network-related settings
[network]
# please be aware that mtg needs to do some external requests. For
@@ -84,8 +133,8 @@ allow-fallback-on-unknown-dc = false
# resolver of the operating system and uses DOH instead. This is a host
# it has to access.
#
# By default we use Quad9.
doh-ip = "9.9.9.9"
# By default we use Cloudflare.
doh-ip = "1.1.1.1"
# mtg can work via proxies (for now, we support only socks5). Proxy
# configuration is done via list. So, you can specify many proxies
+31 -35
View File
@@ -1,58 +1,54 @@
module github.com/9seconds/mtg/v2
go 1.18
go 1.26
require (
github.com/OneOfOne/xxhash v1.2.8
github.com/alecthomas/kong v0.6.1
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137
github.com/alecthomas/kong v1.14.0
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6
github.com/d4l3k/messagediff v1.2.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gotd/td v0.34.0
github.com/jarcoal/httpmock v1.0.8
github.com/mccutchen/go-httpbin v1.1.1
github.com/panjf2000/ants/v2 v2.5.0
github.com/pelletier/go-toml v1.9.5
github.com/prometheus/client_golang v1.13.0
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rs/zerolog v1.27.0
github.com/smira/go-statsd v1.3.2
github.com/stretchr/objx v0.3.0 // indirect
github.com/stretchr/testify v1.7.2
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418
google.golang.org/protobuf v1.28.1 // indirect
github.com/panjf2000/ants/v2 v2.11.5
github.com/prometheus/client_golang v1.23.2
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.20.0 // indirect
github.com/rs/zerolog v1.34.0
github.com/smira/go-statsd v1.3.4
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.11.1
github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b
golang.org/x/crypto v0.48.0
golang.org/x/net v0.49.0 // indirect
golang.org/x/sys v0.41.0
google.golang.org/protobuf v1.36.11 // indirect
)
require (
github.com/txthinking/socks5 v0.0.0-20220615051428-39268faee3e6
github.com/pelletier/go-toml/v2 v2.2.4
github.com/pires/go-proxyproto v0.11.0
github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e
github.com/yl2chen/cidranger v1.0.2
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gotd/ige v0.1.5 // indirect
github.com/gotd/xor v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/klauspost/compress v1.18.3 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/txthinking/runnergroup v0.0.0-20220212043759-8da8edb7dae8 // indirect
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0 // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/txthinking/runnergroup v0.0.0-20250224021307-5864ffeb65ae // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/tools v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+111 -550
View File
@@ -1,597 +1,158 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/alecthomas/kong v0.6.1 h1:1kNhcFepkR+HmasQpbiKDLylIL8yh5B5y1zPp5bJimA=
github.com/alecthomas/kong v0.6.1/go.mod h1:JfHWDzLmbh/puW6I3V7uWenoh56YNVONW+w8eKeUr9I=
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.14.0 h1:gFgEUZWu2ZmZ+UhyZ1bDhuutbKN1nTtJTwh19Wsn21s=
github.com/alecthomas/kong v1.14.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
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.1-0.20191026205805-5f8ba28d4473/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/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/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.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gotd/getdoc v0.6.2/go.mod h1:6Ul8cyaq+Aw0gVyuU+TKZPCmNjdlylzSIWVxjzI5t/U=
github.com/gotd/ige v0.1.5 h1:qITql4hZpqPM/DSeO5IVlxzXJxrs4ZZiKRwPif2K76U=
github.com/gotd/ige v0.1.5/go.mod h1:LbvqjUuGELVuHcKjfJZ2NPNzegICQU4eqrPAkXy0nTs=
github.com/gotd/neo v0.1.2 h1:hayFfKNSH5RP/L+KKKnsKMdDNCdhSfzVuo5TF4wvpa8=
github.com/gotd/neo v0.1.2/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ=
github.com/gotd/td v0.34.0 h1:BLIdpv2sxbCuWI2fyXCECRQuLJdnYk3VMZDRKNog/E8=
github.com/gotd/td v0.34.0/go.mod h1:4s/7cuEscdvZIecM/pVi5L3nTK+djSoVLAhgfk40OE4=
github.com/gotd/tl v0.4.0/go.mod h1:CMIcjPWFS4qxxJ+1Ce7U/ilbtPrkoVo/t8uhN5Y/D7c=
github.com/gotd/xor v0.1.0/go.mod h1:ZTmdgqf6SOHder8/MFp9CNkXIadzID5lIiaZxRZICH0=
github.com/gotd/xor v0.1.1 h1:LSPEeuf7noTo4fi4PrEsAaWXOSwjsY2e+IINPiR+c7s=
github.com/gotd/xor v0.1.1/go.mod h1:ZTmdgqf6SOHder8/MFp9CNkXIadzID5lIiaZxRZICH0=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k=
github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/pp v2.4.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mccutchen/go-httpbin v1.1.1 h1:aEws49HEJEyXHLDnshQVswfUlCVoS8g6h9YaDyaW7RE=
github.com/mccutchen/go-httpbin v1.1.1/go.mod h1:fhpOYavp5g2K74XDl/ao2y4KvhqVtKlkg1e+0UaQv7I=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/panjf2000/ants/v2 v2.5.0 h1:1rWGWSnxCsQBga+nQbA4/iY6VMeNoOIAM0ZWh9u3q2Q=
github.com/panjf2000/ants/v2 v2.5.0/go.mod h1:cU93usDlihJZ5CfRGNDYsiBYvoilLvBF5Qp/BT2GNRE=
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/panjf2000/ants/v2 v2.11.5 h1:a7LMnMEeux/ebqTux140tRiaqcFTV0q2bEHF03nl6Rg=
github.com/panjf2000/ants/v2 v2.11.5/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
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/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
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 v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/quasilyte/go-ruleguard/dsl v0.3.2/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smira/go-statsd v1.3.2 h1:1EeuzxNZ/TD9apbTOFSM9nulqfcsQFmT4u1A2DREabI=
github.com/smira/go-statsd v1.3.2/go.mod h1:1srXJ9/pbnN04G8f4F1jUzsGOnwkPKXciyqpewGlkC4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.20.0 h1:AA7aCvjxwAquZAlonN7888f2u4IN8WVeFgBi4k82M4Q=
github.com/prometheus/procfs v0.20.0/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/smira/go-statsd v1.3.4 h1:kBYWcLSGT+qC6JVbvfz48kX7mQys32fjDOPrfmsSx2c=
github.com/smira/go-statsd v1.3.4/go.mod h1:RjdsESPgDODtg1VpVVf9MJrEW2Hw0wtRNbmB1CAhu6A=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/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 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
github.com/txthinking/runnergroup v0.0.0-20220212043759-8da8edb7dae8 h1:iYc+JnXtzv6sdMx9Q7OTKkDAn7FhDPDogcjeSfEQcLY=
github.com/txthinking/runnergroup v0.0.0-20220212043759-8da8edb7dae8/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
github.com/txthinking/socks5 v0.0.0-20220615051428-39268faee3e6 h1:8DkPbOq/EPxbD5VJajKuvssiYZJSrlpeetcGfrBoBVE=
github.com/txthinking/socks5 v0.0.0-20220615051428-39268faee3e6/go.mod h1:7NloQcrxaZYKURWph5HLxVDlIwMHJXCPkeWPtpftsIg=
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe h1:gMWxZxBFRAXqoGkwkYlPX2zvyyKNWJpxOxCrjqJkm5A=
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4=
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43 h1:QEePdg0ty2r0t1+qwfZmQ4OOl/MB2UXIeJSpIZv56lg=
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43/go.mod h1:OYRfF6eb5wY9VRFkXJH8FFBi3plw2v+giaIu7P054pM=
github.com/txthinking/runnergroup v0.0.0-20250224021307-5864ffeb65ae h1:ArVM1jICfm7g4E4dBet+KHUFMLuxmj1Nxdp/tr3ByCU=
github.com/txthinking/runnergroup v0.0.0-20250224021307-5864ffeb65ae/go.mod h1:cldYm15/XHcGt7ndItnEWHwFZo7dinU+2QoyjfErhsI=
github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e h1:xA7GVlbz6teIF4FdvuqwbX6C4tiqNk2PH7FRPIDerao=
github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e/go.mod h1:ntmMHL/xPq1WLeKiw8p/eRATaae6PiVRNipHFJxI8PM=
github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b h1:p+bJ3v5uUdEVMCoeFUs+BNJPsqt+Y6BLbDaPfTcbcH8=
github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b/go.mod h1:OYRfF6eb5wY9VRFkXJH8FFBi3plw2v+giaIu7P054pM=
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
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.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 h1:9vYwv7OjYaky/tlAeD7C4oC9EsPTlaFl1H2jS++V+ME=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d h1:W07d4xkoAUSNOkOzdzXCdFGxT7o2rW4q8M34tB2i//k=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
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.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+5 -11
View File
@@ -61,11 +61,8 @@ func (a *Access) Run(cli *CLI, version string) error {
}
wg := &sync.WaitGroup{}
wg.Add(2) //nolint: gomnd
go func() {
defer wg.Done()
wg.Go(func() {
ip := a.PublicIPv4
if ip == nil {
ip = a.getIP(ntw, "tcp4")
@@ -76,11 +73,8 @@ func (a *Access) Run(cli *CLI, version string) error {
}
resp.IPv4 = a.makeURLs(conf, ip)
}()
go func() {
defer wg.Done()
})
wg.Go(func() {
ip := a.PublicIPv6
if ip == nil {
ip = a.getIP(ntw, "tcp6")
@@ -91,7 +85,7 @@ func (a *Access) Run(cli *CLI, version string) error {
}
resp.IPv6 = a.makeURLs(conf, ip)
}()
})
wg.Wait()
@@ -129,7 +123,7 @@ func (a *Access) getIP(ntw mtglib.Network, protocol string) net.IP {
defer func() {
io.Copy(io.Discard, resp.Body) //nolint: errcheck
resp.Body.Close()
resp.Body.Close() //nolint: errcheck
}()
data, err := io.ReadAll(resp.Body)
+18 -5
View File
@@ -10,6 +10,7 @@ import (
"github.com/9seconds/mtg/v2/antireplay"
"github.com/9seconds/mtg/v2/events"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/9seconds/mtg/v2/internal/proxyprotocol"
"github.com/9seconds/mtg/v2/internal/utils"
"github.com/9seconds/mtg/v2/ipblocklist"
"github.com/9seconds/mtg/v2/ipblocklist/files"
@@ -17,6 +18,7 @@ import (
"github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/network"
"github.com/9seconds/mtg/v2/stats"
"github.com/pires/go-proxyproto"
"github.com/rs/zerolog"
"github.com/yl2chen/cidranger"
)
@@ -164,7 +166,7 @@ func makeIPAllowlist(conf config.ListConfig,
}
func makeEventStream(conf *config.Config, logger mtglib.Logger) (mtglib.EventStream, error) {
factories := make([]events.ObserverFactory, 0, 2) //nolint: gomnd
factories := make([]events.ObserverFactory, 0, 2)
if conf.Stats.StatsD.Enabled.Get(false) {
statsdFactory, err := stats.NewStatsd(
@@ -248,9 +250,12 @@ func runProxy(conf *config.Config, version string) error { //nolint: funlen
IPAllowlist: allowlist,
EventStream: eventStream,
Secret: conf.Secret,
DomainFrontingPort: conf.DomainFrontingPort.Get(mtglib.DefaultDomainFrontingPort),
PreferIP: conf.PreferIP.Get(mtglib.DefaultPreferIP),
Secret: conf.Secret,
DomainFrontingPort: conf.GetDomainFrontingPort(mtglib.DefaultDomainFrontingPort),
DomainFrontingIP: conf.GetDomainFrontingIP(nil),
DomainFrontingProxyProtocol: conf.GetDomainFrontingProxyProtocol(false),
PreferIP: conf.PreferIP.Get(mtglib.DefaultPreferIP),
AutoUpdate: conf.AutoUpdate.Get(false),
AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false),
TolerateTimeSkewness: conf.TolerateTimeSkewness.Value,
@@ -266,12 +271,20 @@ func runProxy(conf *config.Config, version string) error { //nolint: funlen
return fmt.Errorf("cannot start proxy: %w", err)
}
if conf.ProxyProtocolListener.Get(false) {
listener = &proxyprotocol.ListenerAdapter{
Listener: proxyproto.Listener{
Listener: listener,
},
}
}
ctx := utils.RootContext()
go proxy.Serve(listener) //nolint: errcheck
<-ctx.Done()
listener.Close()
listener.Close() //nolint: errcheck
proxy.Shutdown()
return nil
+10 -3
View File
@@ -18,7 +18,8 @@ type SimpleRun struct {
TCPBuffer string `kong:"name='tcp-buffer',short='b',default='4KB',help='Deprecated and ignored'"` //nolint: lll
PreferIP string `kong:"name='prefer-ip',short='i',default='prefer-ipv6',help='IP preference. By default we prefer IPv6 with fallback to IPv4.'"` //nolint: lll
DomainFrontingPort uint64 `kong:"name='domain-fronting-port',short='p',default='443',help='A port to access for domain fronting.'"` //nolint: lll
DOHIP net.IP `kong:"name='doh-ip',short='n',default='9.9.9.9',help='IP address of DNS-over-HTTP to use.'"` //nolint: lll
DomainFrontingIP string `kong:"name='domain-fronting-ip',help='An IP address to use for domain fronting instead of resolving the hostname via DNS.'"` //nolint: lll
DOHIP net.IP `kong:"name='doh-ip',short='n',default='1.1.1.1',help='IP address of DNS-over-HTTP to use.'"` //nolint: lll
Timeout time.Duration `kong:"name='timeout',short='t',default='10s',help='Network timeout to use'"` //nolint: lll
Socks5Proxies []string `kong:"name='socks5-proxy',short='s',help='Socks5 proxies to use for network access.'"` //nolint: lll
AntiReplayCacheSize string `kong:"name='antireplay-cache-size',short='a',default='1MB',help='A size of anti-replay cache to use.'"` //nolint: lll
@@ -35,7 +36,7 @@ func (s *SimpleRun) Run(cli *CLI, version string) error { //nolint: cyclop,funle
return fmt.Errorf("incorrect secret: %w", err)
}
if err := conf.Concurrency.Set(strconv.FormatUint(s.Concurrency, 10)); err != nil { //nolint: gomnd
if err := conf.Concurrency.Set(strconv.FormatUint(s.Concurrency, 10)); err != nil {
return fmt.Errorf("incorrect concurrency: %w", err)
}
@@ -43,10 +44,16 @@ func (s *SimpleRun) Run(cli *CLI, version string) error { //nolint: cyclop,funle
return fmt.Errorf("incorrect prefer-ip: %w", err)
}
if err := conf.DomainFrontingPort.Set(strconv.FormatUint(s.DomainFrontingPort, 10)); err != nil { //nolint: gomnd
if err := conf.DomainFrontingPort.Set(strconv.FormatUint(s.DomainFrontingPort, 10)); err != nil {
return fmt.Errorf("incorrect domain-fronting-port: %w", err)
}
if s.DomainFrontingIP != "" {
if err := conf.DomainFrontingIP.Set(s.DomainFrontingIP); err != nil {
return fmt.Errorf("incorrect domain-fronting-ip: %w", err)
}
}
if err := conf.Network.DOHIP.Set(s.DOHIP.String()); err != nil {
return fmt.Errorf("incorrect doh-ip: %w", err)
}
+40 -9
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"net"
"github.com/9seconds/mtg/v2/mtglib"
)
@@ -21,15 +22,24 @@ type ListConfig struct {
}
type Config struct {
Debug TypeBool `json:"debug"`
AllowFallbackOnUnknownDC TypeBool `json:"allowFallbackOnUnknownDc"`
Secret mtglib.Secret `json:"secret"`
BindTo TypeHostPort `json:"bindTo"`
PreferIP TypePreferIP `json:"preferIp"`
DomainFrontingPort TypePort `json:"domainFrontingPort"`
TolerateTimeSkewness TypeDuration `json:"tolerateTimeSkewness"`
Concurrency TypeConcurrency `json:"concurrency"`
Defense struct {
Debug TypeBool `json:"debug"`
AllowFallbackOnUnknownDC TypeBool `json:"allowFallbackOnUnknownDc"`
Secret mtglib.Secret `json:"secret"`
BindTo TypeHostPort `json:"bindTo"`
ProxyProtocolListener TypeBool `json:"proxyProtocolListener"`
PreferIP TypePreferIP `json:"preferIp"`
AutoUpdate TypeBool `json:"autoUpdate"`
DomainFrontingPort TypePort `json:"domainFrontingPort"`
DomainFrontingIP TypeIP `json:"domainFrontingIp"`
DomainFrontingProxyProtocol TypeBool `json:"domainFrontingProxyProtocol"`
TolerateTimeSkewness TypeDuration `json:"tolerateTimeSkewness"`
Concurrency TypeConcurrency `json:"concurrency"`
DomainFronting struct {
IP TypeIP `json:"ip"`
Port TypePort `json:"port"`
ProxyProtocol TypeBool `json:"proxyProtocol"`
} `json:"domainFronting"`
Defense struct {
AntiReplay struct {
Optional
@@ -66,6 +76,27 @@ type Config struct {
} `json:"stats"`
}
func (c *Config) GetDomainFrontingPort(defaultValue uint) uint {
if port := c.DomainFronting.Port.Get(0); port != 0 {
return port
}
return c.DomainFrontingPort.Get(defaultValue)
}
func (c *Config) GetDomainFrontingIP(defaultValue net.IP) string {
if ip := c.DomainFronting.IP.Get(nil); ip != nil {
return ip.String()
}
if ip := c.DomainFrontingIP.Get(defaultValue); ip != nil {
return ip.String()
}
return ""
}
func (c *Config) GetDomainFrontingProxyProtocol(defaultValue bool) bool {
return c.DomainFronting.ProxyProtocol.Get(false) || c.DomainFrontingProxyProtocol.Get(defaultValue)
}
func (c *Config) Validate() error {
if !c.Secret.Valid() {
return fmt.Errorf("invalid secret %s", c.Secret.String())
+19 -10
View File
@@ -5,19 +5,28 @@ import (
"encoding/json"
"fmt"
"github.com/pelletier/go-toml"
"github.com/pelletier/go-toml/v2"
)
type tomlConfig struct {
Debug bool `toml:"debug" json:"debug,omitempty"`
AllowFallbackOnUnknownDC bool `toml:"allow-fallback-on-unknown-dc" json:"allowFallbackOnUnknownDc,omitempty"`
Secret string `toml:"secret" json:"secret"`
BindTo string `toml:"bind-to" json:"bindTo"`
PreferIP string `toml:"prefer-ip" json:"preferIp,omitempty"`
DomainFrontingPort uint `toml:"domain-fronting-port" json:"domainFrontingPort,omitempty"`
TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"`
Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"`
Defense struct {
Debug bool `toml:"debug" json:"debug,omitempty"`
AllowFallbackOnUnknownDC bool `toml:"allow-fallback-on-unknown-dc" json:"allowFallbackOnUnknownDc,omitempty"`
Secret string `toml:"secret" json:"secret"`
BindTo string `toml:"bind-to" json:"bindTo"`
ProxyProtocolListener bool `toml:"proxy-protocol-listener" json:"proxyProtocolListener"`
PreferIP string `toml:"prefer-ip" json:"preferIp,omitempty"`
AutoUpdate bool `toml:"auto-update" json:"autoUpdate,omitempty"`
DomainFrontingPort uint `toml:"domain-fronting-port" json:"domainFrontingPort,omitempty"`
DomainFrontingIP string `toml:"domain-fronting-ip" json:"domainFrontingIp,omitempty"`
DomainFrontingProxyProtocol bool `toml:"domain-fronting-proxy-protocol" json:"domainFrontingProxyProtocol,omitempty"`
TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"`
Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"`
DomainFronting struct {
IP string `toml:"ip" json:"ip,omitempty"`
Port uint `toml:"port" json:"port,omitempty"`
ProxyProtocol bool `toml:"proxy-protocol" json:"proxyProtocol,omitempty"`
} `toml:"domain-fronting" json:"domainFronting,omitempty"`
Defense struct {
AntiReplay struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
MaxSize string `toml:"max-size" json:"maxSize,omitempty"`
+2 -2
View File
@@ -20,7 +20,7 @@ type TypeBoolTestSuite struct {
}
func (suite *TypeBoolTestSuite) TestUnmarshalFail() {
testData := []interface{}{
testData := []any{
"",
"np",
"нет",
@@ -29,7 +29,7 @@ func (suite *TypeBoolTestSuite) TestUnmarshalFail() {
}
for _, v := range testData {
data, err := json.Marshal(map[string]interface{}{
data, err := json.Marshal(map[string]any{
"value": v,
})
suite.NoError(err)
+2 -2
View File
@@ -10,7 +10,7 @@ type TypeConcurrency struct {
}
func (t *TypeConcurrency) Set(value string) error {
concurrencyValue, err := strconv.ParseUint(value, 10, 16) //nolint: gomnd
concurrencyValue, err := strconv.ParseUint(value, 10, 16)
if err != nil {
return fmt.Errorf("value is not uint (%s): %w", value, err)
}
@@ -41,5 +41,5 @@ func (t TypeConcurrency) MarshalJSON() ([]byte, error) {
}
func (t TypeConcurrency) String() string {
return strconv.FormatUint(uint64(t.Value), 10) //nolint: gomnd
return strconv.FormatUint(uint64(t.Value), 10)
}
+41
View File
@@ -0,0 +1,41 @@
package config
import (
"fmt"
"strconv"
)
type TypeDC struct {
Value int
}
func (t *TypeDC) Set(value string) error {
parsed, err := strconv.ParseInt(value, 10, 16)
if err != nil {
return fmt.Errorf("cannot parse dc: %w", err)
}
if parsed < 0 {
parsed = -parsed
}
t.Value = int(parsed)
return nil
}
func (t *TypeDC) UnmarshalJSON(data []byte) error {
return t.Set(string(data))
}
func (t TypeDC) MarshalJSON() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeDC) String() string {
return strconv.Itoa(t.Value)
}
func (t TypeDC) Get() int {
return t.Value
}
+96
View File
@@ -0,0 +1,96 @@
package config_test
import (
"encoding/json"
"strconv"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeDCTestStruct struct {
Value config.TypeDC `json:"value"`
}
type TypeDCTestSuite struct {
suite.Suite
}
func (suite *TypeDCTestSuite) TestUnmarshalFail() {
testData := []string{
"-1s",
"1202002020202",
"xxx",
"-11111111111111",
"",
}
for _, v := range testData {
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typeDCTestStruct{}))
})
}
}
func (suite *TypeDCTestSuite) TestUnmarshalOk() {
testData := map[int]int{
1: 1,
-1: 1,
203: 203,
}
for value, expected := range testData {
data, err := json.Marshal(map[string]int{
"value": value,
})
suite.NoError(err)
suite.T().Run(strconv.Itoa(value), func(t *testing.T) {
testStruct := &typeDCTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, expected, testStruct.Value.Value)
assert.Equal(t, expected, testStruct.Value.Get())
})
}
}
func (suite *TypeDCTestSuite) TestMarshalOk() {
testData := map[string]int{
"1": 1,
"203": 203,
}
for k, v := range testData {
value := k
expected := v
suite.T().Run(value, func(t *testing.T) {
testStruct := &typeDCTestStruct{}
assert.NoError(t, testStruct.Value.Set(value))
data, err := json.Marshal(testStruct)
assert.NoError(t, err)
expectedJSON, err := json.Marshal(map[string]int{
"value": expected,
})
assert.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(data))
})
}
}
func TestTypeDC(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeDCTestSuite{})
}
+2 -2
View File
@@ -12,7 +12,7 @@ type TypeErrorRate struct {
}
func (t *TypeErrorRate) Set(value string) error {
parsedValue, err := strconv.ParseFloat(value, 64) //nolint: gomnd
parsedValue, err := strconv.ParseFloat(value, 64)
if err != nil {
return fmt.Errorf("value is not a float (%s): %w", value, err)
}
@@ -43,5 +43,5 @@ func (t TypeErrorRate) MarshalJSON() ([]byte, error) {
}
func (t TypeErrorRate) String() string {
return strconv.FormatFloat(t.Value, 'f', -1, 64) //nolint: gomnd
return strconv.FormatFloat(t.Value, 'f', -1, 64)
}
+1 -1
View File
@@ -18,7 +18,7 @@ func (t *TypeHostPort) Set(value string) error {
return fmt.Errorf("incorrect host:port value (%v): %w", value, err)
}
portValue, err := strconv.ParseUint(port, 10, 16) //nolint: gomnd
portValue, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return fmt.Errorf("incorrect port number (%v): %w", value, err)
}
+1 -1
View File
@@ -10,7 +10,7 @@ type TypePort struct {
}
func (t *TypePort) Set(value string) error {
portValue, err := strconv.ParseUint(value, 10, 16) //nolint: gomnd
portValue, err := strconv.ParseUint(value, 10, 16)
if err != nil {
return fmt.Errorf("incorrect port number (%v): %w", value, err)
}
+20
View File
@@ -0,0 +1,20 @@
package proxyprotocol
import (
"net"
"github.com/pires/go-proxyproto"
)
type ListenerAdapter struct {
proxyproto.Listener
}
func (l *ListenerAdapter) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
return nil, err
}
return connWrapper{conn.(*proxyproto.Conn)}, nil
}
+25
View File
@@ -0,0 +1,25 @@
package proxyprotocol
import "github.com/pires/go-proxyproto"
type connWrapper struct {
*proxyproto.Conn
}
func (c connWrapper) CloseRead() error {
tcpConn, ok := c.TCPConn()
if !ok {
panic("we support only tcp connections")
}
return tcpConn.CloseRead()
}
func (c connWrapper) CloseWrite() error {
tcpConn, ok := c.TCPConn()
if !ok {
panic("we support only tcp connections")
}
return tcpConn.CloseWrite()
}
+1 -1
View File
@@ -35,7 +35,7 @@ func captureOutput(filefp **os.File, callback func()) string {
callback()
writer.Close()
writer.Close() //nolint: errcheck
<-closeChan
return strings.TrimSpace(buf.String())
+1 -1
View File
@@ -18,7 +18,7 @@ func (l Listener) Accept() (net.Conn, error) {
}
if err := network.SetClientSocketOptions(conn, 0); err != nil {
conn.Close()
conn.Close() //nolint: errcheck
return nil, fmt.Errorf("cannot set TCP options: %w", err)
}
+1 -1
View File
@@ -23,7 +23,7 @@ func (h httpFile) Open(ctx context.Context) (io.ReadCloser, error) {
if err != nil {
if response != nil {
io.Copy(io.Discard, response.Body) //nolint: errcheck
response.Body.Close()
response.Body.Close() //nolint: errcheck
}
return nil, fmt.Errorf("cannot get url %s: %w", h.url, err)
+1 -1
View File
@@ -77,7 +77,7 @@ func (suite *HTTPTestSuite) TestOk() {
readCloser, err := file.Open(suite.ctx)
suite.NoError(err)
defer readCloser.Close()
defer readCloser.Close() //nolint: errcheck
data, err := io.ReadAll(readCloser)
suite.NoError(err)
+7 -10
View File
@@ -19,8 +19,8 @@ import (
var (
fireholRegexpComment = regexp.MustCompile(`\s*#.*?$`)
fireholIPv4DefaultCIDR = net.CIDRMask(32, 32) //nolint: gomnd
fireholIPv6DefaultCIDR = net.CIDRMask(128, 128) //nolint: gomnd
fireholIPv4DefaultCIDR = net.CIDRMask(32, 32)
fireholIPv6DefaultCIDR = net.CIDRMask(128, 128)
)
// FireholUpdateCallback defines a signature of the callback that has to be
@@ -112,30 +112,27 @@ func (f *Firehol) update() {
defer cancel()
wg := &sync.WaitGroup{}
wg.Add(len(f.blocklists))
mutex := &sync.Mutex{}
ranger := cidranger.NewPCTrieRanger()
for _, v := range f.blocklists {
go func(file files.File) {
defer wg.Done()
wg.Go(func() {
logger := f.logger.BindStr("filename", v.String())
logger := f.logger.BindStr("filename", file.String())
fileContent, err := file.Open(ctx)
fileContent, err := v.Open(ctx)
if err != nil {
logger.WarningError("update has failed", err)
return
}
defer fileContent.Close()
defer fileContent.Close() //nolint: errcheck
if err := f.updateFromFile(mutex, ranger, bufio.NewScanner(fileContent)); err != nil {
logger.WarningError("update has failed", err)
}
}(v)
})
}
wg.Wait()
+1 -1
View File
@@ -35,7 +35,7 @@ func (suite *FireholTestSuite) SetupSuite() {
panic(err)
}
defer filefp.Close()
defer filefp.Close() //nolint: errcheck
io.Copy(w, filefp) //nolint: errcheck
})
+1 -1
View File
@@ -8,5 +8,5 @@ package logger
// commonly used by many 3pp tools. While mtglib itself does not need it, it is
// always a good idea to support it and have a transient end to end logging.
type StdLikeLogger interface {
Printf(format string, args ...interface{})
Printf(format string, args ...any)
}
+1 -1
View File
@@ -8,7 +8,7 @@ func (n noopLogger) Named(_ string) mtglib.Logger { return n }
func (n noopLogger) BindInt(_ string, _ int) mtglib.Logger { return n }
func (n noopLogger) BindStr(_, _ string) mtglib.Logger { return n }
func (n noopLogger) BindJSON(_, _ string) mtglib.Logger { return n }
func (n noopLogger) Printf(_ string, _ ...interface{}) {}
func (n noopLogger) Printf(_ string, _ ...any) {}
func (n noopLogger) Info(_ string) {}
func (n noopLogger) Warning(_ string) {}
func (n noopLogger) Debug(_ string) {}
+1 -1
View File
@@ -78,7 +78,7 @@ func (z *zeroLogContext) BindJSON(name, value string) mtglib.Logger {
}
}
func (z *zeroLogContext) Printf(format string, args ...interface{}) {
func (z *zeroLogContext) Printf(format string, args ...any) {
z.Debug(fmt.Sprintf(format, args...))
}
-5
View File
@@ -9,16 +9,11 @@
package main
import (
"math/rand"
"time"
"github.com/9seconds/mtg/v2/internal/cli"
"github.com/alecthomas/kong"
)
func main() {
rand.Seed(time.Now().UTC().UnixNano())
cli := &cli.CLI{}
ctx := kong.Parse(cli, kong.Vars{
"version": getVersion(),
+42
View File
@@ -0,0 +1,42 @@
[[tools.go]]
version = "1.26.0"
backend = "core:go"
"platforms.linux-arm64" = { checksum = "sha256:bd03b743eb6eb4193ea3c3fd3956546bf0e3ca5b7076c8226334afe6b75704cd", url = "https://dl.google.com/go/go1.26.0.linux-arm64.tar.gz"}
"platforms.linux-x64" = { checksum = "sha256:aac1b08a0fb0c4e0a7c1555beb7b59180b05dfc5a3d62e40e9de90cd42f88235", url = "https://dl.google.com/go/go1.26.0.linux-amd64.tar.gz"}
"platforms.macos-arm64" = { checksum = "sha256:b1640525dfe68f066d56f200bef7bf4dce955a1a893bd061de6754c211431023", url = "https://dl.google.com/go/go1.26.0.darwin-arm64.tar.gz"}
"platforms.macos-x64" = { checksum = "sha256:1ca28b7703cbea05a65b2a1d92d6b308610ef92f8824578a0874f2e60c9d5a22", url = "https://dl.google.com/go/go1.26.0.darwin-amd64.tar.gz"}
"platforms.windows-x64" = { checksum = "sha256:9bbe0fc64236b2b51f6255c05c4232532b8ecc0e6d2e00950bd3021d8a4d07d4", url = "https://dl.google.com/go/go1.26.0.windows-amd64.zip"}
[[tools."go:golang.org/x/pkgsite/cmd/pkgsite"]]
version = "latest"
backend = "go:golang.org/x/pkgsite/cmd/pkgsite"
[[tools."go:golang.org/x/tools/gopls"]]
version = "0.21.1"
backend = "go:golang.org/x/tools/gopls"
[[tools."go:golang.org/x/vuln/cmd/govulncheck"]]
version = "1.1.4"
backend = "go:golang.org/x/vuln/cmd/govulncheck"
[[tools."go:mvdan.cc/gofumpt"]]
version = "0.9.2"
backend = "go:mvdan.cc/gofumpt"
[[tools.golangci-lint]]
version = "2.10.1"
backend = "aqua:golangci/golangci-lint"
"platforms.linux-arm64" = { checksum = "sha256:6652b42ae02915eb2f9cb2a2e0cac99514c8eded8388d88ae3e06e1a52c00de8", url = "https://github.com/golangci/golangci-lint/releases/download/v2.10.1/golangci-lint-2.10.1-linux-arm64.tar.gz"}
"platforms.linux-x64" = { checksum = "sha256:dfa775874cf0561b404a02a8f4481fc69b28091da95aa697259820d429b09c99", url = "https://github.com/golangci/golangci-lint/releases/download/v2.10.1/golangci-lint-2.10.1-linux-amd64.tar.gz"}
"platforms.macos-arm64" = { checksum = "sha256:03bfadf67e52b441b7ec21305e501c717df93c959836d66c7f97312654acb297", url = "https://github.com/golangci/golangci-lint/releases/download/v2.10.1/golangci-lint-2.10.1-darwin-arm64.tar.gz"}
"platforms.macos-x64" = { checksum = "sha256:66fb0da81b8033b477f97eea420d4b46b230ca172b8bb87c6610109f3772b6b6", url = "https://github.com/golangci/golangci-lint/releases/download/v2.10.1/golangci-lint-2.10.1-darwin-amd64.tar.gz"}
"platforms.windows-x64" = { checksum = "sha256:c60c87695e79db8e320f0e5be885059859de52bb5ee5f11be5577828570bc2a3", url = "https://github.com/golangci/golangci-lint/releases/download/v2.10.1/golangci-lint-2.10.1-windows-amd64.zip"}
[[tools.goreleaser]]
version = "2.14.1"
backend = "aqua:goreleaser/goreleaser"
"platforms.linux-arm64" = { checksum = "sha256:a84d3b27f052c12ad5c8342d7caf1450a7174a305730aed21d72db09301e49a5", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.14.1/goreleaser_Linux_arm64.tar.gz"}
"platforms.linux-x64" = { checksum = "sha256:2df975a7acbfdeaf888d596cab0024d48ec7fb7d747e1d08b90948b791f40a5f", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.14.1/goreleaser_Linux_x86_64.tar.gz"}
"platforms.macos-arm64" = { checksum = "sha256:9f2e47f847b4f4177376fc6aa6914fbc7f673f59720076747e738b578c2e896e", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.14.1/goreleaser_Darwin_all.tar.gz"}
"platforms.macos-x64" = { checksum = "sha256:9f2e47f847b4f4177376fc6aa6914fbc7f673f59720076747e738b578c2e896e", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.14.1/goreleaser_Darwin_all.tar.gz"}
"platforms.windows-x64" = { checksum = "sha256:d7a3d8ba795e97ab8c4f8003630d300da164adf21fde5a4049440c20f15c3137", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.14.1/goreleaser_Windows_x86_64.zip"}
+38 -10
View File
@@ -3,10 +3,12 @@ package mtglib
import (
"bytes"
"context"
"fmt"
"io"
"sync"
"net"
"github.com/9seconds/mtg/v2/essentials"
"github.com/pires/go-proxyproto"
)
type connTraffic struct {
@@ -40,22 +42,15 @@ func (c connTraffic) Write(b []byte) (int, error) {
type connRewind struct {
essentials.Conn
active io.Reader
buf bytes.Buffer
mutex sync.RWMutex
active io.Reader
}
func (c *connRewind) Read(p []byte) (int, error) {
c.mutex.RLock()
defer c.mutex.RUnlock()
return c.active.Read(p) //nolint: wrapcheck
return c.active.Read(p)
}
func (c *connRewind) Rewind() {
c.mutex.Lock()
defer c.mutex.Unlock()
c.active = io.MultiReader(&c.buf, c.Conn)
}
@@ -67,3 +62,36 @@ func newConnRewind(conn essentials.Conn) *connRewind {
return rv
}
type connProxyProtocol struct {
essentials.Conn
sourceAddr net.Addr
headersWritten bool
}
func (c *connProxyProtocol) Write(p []byte) (int, error) {
if !c.headersWritten {
headers := proxyproto.HeaderProxyFromAddrs(2, c.sourceAddr, c.RemoteAddr())
toSend, err := headers.Format()
if err != nil {
panic(err)
}
if _, err := c.Conn.Write(toSend); err != nil {
return 0, fmt.Errorf("cannot send proxy protocol header: %w", err)
}
c.headersWritten = true
}
return c.Conn.Write(p)
}
func newConnProxyProtocol(source, target essentials.Conn) *connProxyProtocol {
return &connProxyProtocol{
Conn: target,
sourceAddr: source.RemoteAddr(),
}
}
+96
View File
@@ -1,14 +1,17 @@
package mtglib
import (
"bufio"
"bytes"
"context"
"errors"
"io"
"net"
"testing"
"time"
"github.com/9seconds/mtg/v2/internal/testlib"
"github.com/pires/go-proxyproto"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)
@@ -200,6 +203,94 @@ func (suite *ConnRewindTestSuite) TestRead() {
suite.Equal([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, data)
}
type ConnProxyProtocolTestSuite struct {
suite.Suite
sourceConnMock *testlib.EssentialsConnMock
targetConnMock *testlib.EssentialsConnMock
conn *connProxyProtocol
}
func (suite *ConnProxyProtocolTestSuite) SetupTest() {
suite.sourceConnMock = &testlib.EssentialsConnMock{}
suite.targetConnMock = &testlib.EssentialsConnMock{}
localAddr := &net.TCPAddr{
IP: net.ParseIP("127.0.0.1").To4(),
}
remoteAddr := &net.TCPAddr{
IP: net.ParseIP("127.0.0.2").To4(),
}
suite.sourceConnMock.
On("RemoteAddr").
Return(localAddr)
suite.targetConnMock.
On("RemoteAddr").
Maybe().
Return(remoteAddr)
suite.conn = newConnProxyProtocol(suite.sourceConnMock, suite.targetConnMock)
}
func (suite *ConnProxyProtocolTestSuite) TestRead() {
value := []byte{1, 2, 3, 4, 5}
toRead := make([]byte, len(value))
suite.targetConnMock.
On("Read", mock.AnythingOfType("[]uint8")).
Once().
Return(len(toRead), nil).
Run(func(args mock.Arguments) {
arr := args.Get(0).([]byte)
copy(arr, value)
})
n, err := suite.conn.Read(toRead)
suite.Equal(len(value), n)
suite.NoError(err)
suite.Equal(value, toRead)
}
func (suite *ConnProxyProtocolTestSuite) TestWrite() {
value := []byte{1, 2, 3, 4, 5}
buf := &bytes.Buffer{}
bufReader := bufio.NewReader(buf)
suite.targetConnMock.
On("Write", mock.AnythingOfType("[]uint8")).
Return(28, nil).
Run(func(args mock.Arguments) {
arr := args.Get(0).([]byte)
buf.Write(arr)
})
_, err := suite.conn.Write(value)
suite.NoError(err)
header, err := proxyproto.Read(bufReader)
suite.NoError(err)
sourceAddr, destAddr, ok := header.TCPAddrs()
suite.True(ok)
suite.Equal(suite.sourceConnMock.RemoteAddr(), sourceAddr)
suite.Equal(suite.targetConnMock.RemoteAddr(), destAddr)
read, _ := io.ReadAll(bufReader)
suite.Equal(value, read)
_, err = suite.conn.Write(value)
suite.NoError(err)
read, _ = io.ReadAll(bufReader)
suite.Equal(value, read)
}
func (suite *ConnProxyProtocolTestSuite) TearDownTest() {
suite.sourceConnMock.AssertExpectations(suite.T())
suite.targetConnMock.AssertExpectations(suite.T())
}
func TestConnTraffic(t *testing.T) {
t.Parallel()
suite.Run(t, &ConnTrafficTestSuite{})
@@ -209,3 +300,8 @@ func TestConnRewind(t *testing.T) {
t.Parallel()
suite.Run(t, &ConnRewindTestSuite{})
}
func TestConnProxyProtocol(t *testing.T) {
t.Parallel()
suite.Run(t, &ConnProxyProtocolTestSuite{})
}
+1 -1
View File
@@ -249,7 +249,7 @@ type Logger interface {
BindJSON(name, value string) Logger
// Printf is to support log.Logger behavior.
Printf(format string, args ...interface{})
Printf(format string, args ...any)
// Info puts a message about some normal situation.
Info(msg string)
+11 -11
View File
@@ -8,17 +8,17 @@ import (
type NoopLogger struct{}
func (n NoopLogger) Named(_ string) Logger { return n }
func (n NoopLogger) BindInt(_ string, _ int) Logger { return n }
func (n NoopLogger) BindStr(_, _ string) Logger { return n }
func (n NoopLogger) BindJSON(_, _ string) Logger { return n }
func (n NoopLogger) Printf(_ string, _ ...interface{}) {}
func (n NoopLogger) Info(_ string) {}
func (n NoopLogger) Warning(_ string) {}
func (n NoopLogger) Debug(_ string) {}
func (n NoopLogger) InfoError(_ string, _ error) {}
func (n NoopLogger) WarningError(_ string, _ error) {}
func (n NoopLogger) DebugError(_ string, _ error) {}
func (n NoopLogger) Named(_ string) Logger { return n }
func (n NoopLogger) BindInt(_ string, _ int) Logger { return n }
func (n NoopLogger) BindStr(_, _ string) Logger { return n }
func (n NoopLogger) BindJSON(_, _ string) Logger { return n }
func (n NoopLogger) Printf(_ string, _ ...any) {}
func (n NoopLogger) Info(_ string) {}
func (n NoopLogger) Warning(_ string) {}
func (n NoopLogger) Debug(_ string) {}
func (n NoopLogger) InfoError(_ string, _ error) {}
func (n NoopLogger) WarningError(_ string, _ error) {}
func (n NoopLogger) DebugError(_ string, _ error) {}
type EventStreamMock struct {
mock.Mock
+17
View File
@@ -0,0 +1,17 @@
package dc
import (
"fmt"
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscation"
)
type Addr struct {
Network string
Address string
Obfuscator obfuscation.Obfuscator
}
func (d Addr) String() string {
return fmt.Sprintf("addr=%s, secret=%v", d.Address, d.Obfuscator.Secret)
}
+33
View File
@@ -0,0 +1,33 @@
package dc
import "math/rand/v2"
type dcAddrSet struct {
v4 map[int][]Addr
v6 map[int][]Addr
}
func (d dcAddrSet) getV4(dc int) []Addr {
if d.v4 == nil {
return nil
}
return d.get(d.v4[dc])
}
func (d dcAddrSet) getV6(dc int) []Addr {
if d.v6 == nil {
return nil
}
return d.get(d.v6[dc])
}
func (d dcAddrSet) get(addrs []Addr) []Addr {
otherSet := make([]Addr, 0, len(addrs))
otherSet = append(otherSet, addrs...)
rand.Shuffle(len(otherSet), func(i, j int) {
otherSet[i], otherSet[j] = otherSet[j], otherSet[i]
})
return otherSet
}
+84
View File
@@ -0,0 +1,84 @@
package dc
import (
"context"
"time"
)
type preferIP uint8
const (
preferIPOnlyIPv4 preferIP = iota
preferIPOnlyIPv6
preferIPPreferIPv4
preferIPPreferIPv6
)
const (
// Default DC to connect to if not sure.
DefaultDC = 2
// How often should we request updates from
// https://core.telegram.org/getProxyConfig
PublicConfigUpdateEach = time.Hour
PublicConfigUpdateURLv4 = "https://core.telegram.org/getProxyConfig"
PublicConfigUpdateURLv6 = "https://core.telegram.org/getProxyConfigV6"
// How often should we extract hosts from Telegram using help.getConfig
// method.
OwnConfigUpdateEach = time.Hour
)
type Logger interface {
Info(msg string)
WarningError(msg string, err error)
}
type Updater interface {
Run(ctx context.Context)
}
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp#L30
var defaultDCAddrSet = dcAddrSet{
v4: map[int][]Addr{
1: {
{Network: "tcp4", Address: "149.154.175.50:443"},
},
2: {
{Network: "tcp4", Address: "149.154.167.51:443"},
{Network: "tcp4", Address: "95.161.76.100:443"},
},
3: {
{Network: "tcp4", Address: "149.154.175.100:443"},
},
4: {
{Network: "tcp4", Address: "149.154.167.91:443"},
},
5: {
{Network: "tcp4", Address: "149.154.171.5:443"},
},
203: {
{Network: "tcp4", Address: "91.105.192.100:443"},
},
},
v6: map[int][]Addr{
1: {
{Network: "tcp6", Address: "[2001:b28:f23d:f001::a]:443"},
},
2: {
{Network: "tcp6", Address: "[2001:67c:04e8:f002::a]:443"},
},
3: {
{Network: "tcp6", Address: "[2001:b28:f23d:f003::a]:443"},
},
4: {
{Network: "tcp6", Address: "[2001:67c:04e8:f004::a]:443"},
},
5: {
{Network: "tcp6", Address: "[2001:b28:f23f:f005::a]:443"},
},
203: {
{Network: "tcp6", Address: "[2a0a:f280:0203:000a:5000:0000:0000:0100]:443"},
},
},
}
+43
View File
@@ -0,0 +1,43 @@
package dc
import (
"context"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)
type LoggerMock struct {
mock.Mock
}
func (m *LoggerMock) Info(msg string) {
m.Called(msg)
}
func (m *LoggerMock) WarningError(msg string, err error) {
m.Called(msg, err)
}
type UpdaterTestSuiteBase struct {
suite.Suite
ctx context.Context
ctxCancel context.CancelFunc
loggerMock *LoggerMock
}
func (s *UpdaterTestSuiteBase) SetupTest() {
ctx, cancel := context.WithCancel(context.Background())
s.loggerMock = &LoggerMock{}
s.loggerMock.On("Info", mock.AnythingOfType("string"))
s.loggerMock.On("WarningError", mock.AnythingOfType("string"), mock.Anything)
s.ctx = ctx
s.ctxCancel = cancel
}
func (s *UpdaterTestSuiteBase) TearDownTest() {
s.ctxCancel()
}
@@ -0,0 +1,93 @@
package dc
import (
"bufio"
"context"
"fmt"
"io"
"net/http"
"regexp"
"strconv"
)
var publicConfigRe = regexp.MustCompile(`^\s*proxy_for\s+(\d+)\s+(\S+?)?;\s*$`)
type PublicConfigUpdater struct {
updater
http *http.Client
tg *Telegram
}
func (p *PublicConfigUpdater) Run(ctx context.Context, url, network string) {
p.run(ctx, func() error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
panic(err)
}
resp, err := p.http.Do(req)
if err != nil {
if resp != nil {
io.Copy(io.Discard, resp.Body) //nolint: errcheck
resp.Body.Close() //nolint: errcheck
}
return fmt.Errorf("cannot fetch url %s: %w", url, err)
}
if resp.StatusCode >= http.StatusBadRequest {
return fmt.Errorf("unexpected status code from %s: %d", url, resp.StatusCode)
}
scanner := bufio.NewScanner(resp.Body)
addrs := map[int][]Addr{}
for scanner.Scan() {
matches := publicConfigRe.FindStringSubmatch(scanner.Text())
if len(matches) != 3 {
continue
}
dc, err := strconv.Atoi(matches[1])
if err != nil {
continue
}
switch dc {
// this is a list of DC we currently support. Other are ignored.
case 203: // CDN DC
p.logger.Info(fmt.Sprintf("found %s address for DC %d", matches[2], dc))
addrs[dc] = append(addrs[dc], Addr{
Network: network,
Address: matches[2],
})
}
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("cannot read response body from %s: %w", url, err)
}
p.tg.lock.Lock()
defer p.tg.lock.Unlock()
if network == "tcp4" {
p.tg.view.publicConfigs.v4 = addrs
} else {
p.tg.view.publicConfigs.v6 = addrs
}
return nil
})
}
func NewPublicConfigUpdater(tg *Telegram, logger Logger, client *http.Client) *PublicConfigUpdater {
return &PublicConfigUpdater{
updater: updater{
logger: logger,
period: PublicConfigUpdateEach,
},
http: client,
tg: tg,
}
}
@@ -0,0 +1,113 @@
package dc
import (
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type PublicConfigUpdaterTestSuite struct {
UpdaterTestSuiteBase
u *PublicConfigUpdater
lock sync.Mutex
srv *httptest.Server
responseHandler func(w http.ResponseWriter)
}
func (s *PublicConfigUpdaterTestSuite) SetupSuite() {
s.srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s.lock.Lock()
s.responseHandler(w)
s.lock.Unlock()
}))
}
func (s *PublicConfigUpdaterTestSuite) TearDownSuite() {
s.srv.Close()
}
func (s *PublicConfigUpdaterTestSuite) SetupTest() {
s.UpdaterTestSuiteBase.SetupTest()
tg, err := New("prefer-ipv4")
require.NoError(s.T(), err)
s.u = NewPublicConfigUpdater(tg, s.loggerMock, s.srv.Client())
}
func (s *PublicConfigUpdaterTestSuite) Test502StatusCode() {
s.responseHandler = func(w http.ResponseWriter) {
w.WriteHeader(http.StatusBadGateway)
}
s.u.Run(s.ctx, s.srv.URL, "tcp4")
time.Sleep(100 * time.Millisecond)
s.ctxCancel()
s.u.Wait()
s.Len(s.u.tg.view.publicConfigs.v4, 0)
}
func (s *PublicConfigUpdaterTestSuite) TestEmptyFile() {
s.responseHandler = func(w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
}
s.u.Run(s.ctx, s.srv.URL, "tcp4")
time.Sleep(100 * time.Millisecond)
s.ctxCancel()
s.u.Wait()
s.Len(s.u.tg.view.publicConfigs.v4, 0)
}
func (s *PublicConfigUpdaterTestSuite) TestGarbage() {
result := `
proxy_for -1 -1;
proxy_for 100 100.10.0.0:3333;
lala 0 0
`
s.responseHandler = func(w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(result)) //nolint: errcheck
}
s.u.Run(s.ctx, s.srv.URL, "tcp4")
time.Sleep(100 * time.Millisecond)
s.ctxCancel()
s.u.Wait()
s.Len(s.u.tg.view.publicConfigs.v4, 0)
}
func (s *PublicConfigUpdaterTestSuite) TestOk() {
result := `
proxy_for 203 100.10.0.0:3333;
proxy_for -100 101.10.0.0:3333;
`
s.responseHandler = func(w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(result)) //nolint: errcheck
}
s.u.Run(s.ctx, s.srv.URL, "tcp4")
time.Sleep(100 * time.Millisecond)
s.ctxCancel()
s.u.Wait()
s.Len(s.u.tg.view.publicConfigs.v4, 1)
s.Len(s.u.tg.view.publicConfigs.v4[203], 1)
s.Equal("100.10.0.0:3333", s.u.tg.view.publicConfigs.v4[203][0].Address)
}
func TestPublicConfigUpdater(t *testing.T) {
suite.Run(t, &PublicConfigUpdaterTestSuite{})
}
+50
View File
@@ -0,0 +1,50 @@
package dc
import (
"fmt"
"strings"
"sync"
)
type Telegram struct {
lock sync.RWMutex
view dcView
preferIP preferIP
}
func (t *Telegram) GetAddresses(dc int) []Addr {
t.lock.RLock()
defer t.lock.RUnlock()
switch t.preferIP {
case preferIPOnlyIPv4:
return t.view.getV4(dc)
case preferIPOnlyIPv6:
return t.view.getV4(dc)
case preferIPPreferIPv4:
return append(t.view.getV4(dc), t.view.getV6(dc)...)
}
return append(t.view.getV6(dc), t.view.getV4(dc)...)
}
func New(ipPreference string) (*Telegram, error) {
var pref preferIP
switch strings.ToLower(ipPreference) {
case "prefer-ipv4":
pref = preferIPPreferIPv4
case "prefer-ipv6":
pref = preferIPPreferIPv6
case "only-ipv4":
pref = preferIPOnlyIPv4
case "only-ipv6":
pref = preferIPOnlyIPv6
default:
return nil, fmt.Errorf("unknown ip preference %s", ipPreference)
}
return &Telegram{
preferIP: pref,
}, nil
}
+47
View File
@@ -0,0 +1,47 @@
package dc
import (
"context"
"sync"
"time"
)
type updater struct {
wg sync.WaitGroup
logger Logger
period time.Duration
}
func (u *updater) Wait() {
u.wg.Wait()
}
func (u *updater) run(ctx context.Context, callback func() error) {
u.wg.Go(func() {
ticker := time.NewTicker(u.period)
defer func() {
ticker.Stop()
select {
case <-ticker.C:
default:
}
}()
for {
u.logger.Info("start update")
if err := callback(); err != nil {
u.logger.WarningError("cannot update", err)
}
u.logger.Info("updated")
select {
case <-ctx.Done():
u.logger.Info("stop updating")
return
case <-ticker.C:
}
}
})
}
+55
View File
@@ -0,0 +1,55 @@
package dc
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/suite"
)
type UpdaterTestSuite struct {
UpdaterTestSuiteBase
u updater
}
func (s *UpdaterTestSuite) SetupTest() {
s.UpdaterTestSuiteBase.SetupTest()
s.u = updater{
logger: s.loggerMock,
period: 100 * time.Millisecond,
}
}
func (s *UpdaterTestSuite) TestPeriodicUpdates() {
ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
lock := &sync.Mutex{}
collected := []time.Time{}
go s.u.run(s.ctx, func() error {
select {
case <-s.ctx.Done():
case value := <-ticker.C:
lock.Lock()
collected = append(collected, value)
lock.Unlock()
}
return nil
})
s.Eventually(func() bool {
lock.Lock()
defer lock.Unlock()
return len(collected) == 3
}, time.Second, 10*time.Millisecond)
}
func TestUpdater(t *testing.T) {
t.Parallel()
suite.Run(t, &UpdaterTestSuite{})
}
+19
View File
@@ -0,0 +1,19 @@
package dc
type dcView struct {
publicConfigs dcAddrSet
}
func (d dcView) getV4(dc int) []Addr {
addrs := d.publicConfigs.getV4(dc)
addrs = append(addrs, defaultDCAddrSet.getV4(dc)...)
return addrs
}
func (d dcView) getV6(dc int) []Addr {
addrs := d.publicConfigs.getV6(dc)
addrs = append(addrs, defaultDCAddrSet.getV6(dc)...)
return addrs
}
+81
View File
@@ -0,0 +1,81 @@
package dc
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type ViewTestSuite struct {
suite.Suite
view dcView
}
func (suite *ViewTestSuite) SetupSuite() {
suite.view = dcView{
publicConfigs: dcAddrSet{
v4: map[int][]Addr{
111: {
{Network: "tcp4", Address: "127.0.0.1:443"},
},
203: {
{Network: "tcp4", Address: "127.0.0.2:443"},
},
},
v6: map[int][]Addr{
203: {
{Network: "tcp6", Address: "xxx"},
},
},
},
}
}
func (suite *ViewTestSuite) TestGetV4() {
testData := map[int][]Addr{
111: {
{Network: "tcp4", Address: "127.0.0.1:443"},
},
203: {
{Network: "tcp4", Address: "127.0.0.2:443"},
{Network: "tcp4", Address: "91.105.192.100:443"},
},
2: {
{Network: "tcp4", Address: "149.154.167.51:443"},
{Network: "tcp4", Address: "95.161.76.100:443"},
},
}
for dc, addresses := range testData {
suite.T().Run(fmt.Sprintf("dc%d", dc), func(t *testing.T) {
assert.ElementsMatch(t, addresses, suite.view.getV4(dc))
})
}
}
func (suite *ViewTestSuite) TestGetV6() {
testData := map[int][]Addr{
111: {},
203: {
{Network: "tcp6", Address: "xxx"},
{Network: "tcp6", Address: "[2a0a:f280:0203:000a:5000:0000:0000:0100]:443"},
},
1: {
{Network: "tcp6", Address: "[2001:b28:f23d:f001::a]:443"},
},
}
for dc, addresses := range testData {
suite.T().Run(fmt.Sprintf("dc%d", dc), func(t *testing.T) {
assert.ElementsMatch(t, addresses, suite.view.getV6(dc))
})
}
}
func TestView(t *testing.T) {
t.Parallel()
suite.Run(t, &ViewTestSuite{})
}
+3 -3
View File
@@ -56,7 +56,7 @@ func ParseClientHello(secret, handshake []byte) (ClientHello, error) {
if len(handshake)-4 != int(handshakeLength) {
return hello,
fmt.Errorf("incorrect handshake size. manifested=%d, real=%d",
handshakeLength, len(handshake)-4) //nolint: gomnd
handshakeLength, len(handshake)-4)
}
copy(hello.Random[:], handshake[ClientHelloRandomOffset:])
@@ -76,7 +76,7 @@ func ParseClientHello(secret, handshake []byte) (ClientHello, error) {
computedRandom := mac.Sum(nil)
for i := 0; i < RandomLen; i++ {
for i := range RandomLen {
computedRandom[i] ^= hello.Random[i]
}
@@ -100,7 +100,7 @@ func parseSessionID(hello *ClientHello, handshake []byte) {
}
func parseCipherSuite(hello *ClientHello, handshake []byte) {
cipherSuiteOffset := ClientHelloSessionIDOffset + len(hello.SessionID) + 3 //nolint: gomnd
cipherSuiteOffset := ClientHelloSessionIDOffset + len(hello.SessionID) + 3
hello.CipherSuite = binary.BigEndian.Uint16(handshake[cipherSuiteOffset : cipherSuiteOffset+2])
}
+11 -12
View File
@@ -3,7 +3,7 @@ package faketls
import (
"bytes"
"fmt"
"math/rand"
"math/rand/v2"
"github.com/9seconds/mtg/v2/essentials"
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
@@ -47,27 +47,26 @@ func (c *Conn) Write(p []byte) (int, error) {
rec.Type = record.TypeApplicationData
rec.Version = record.Version12
sendBuffer := acquireBytesBuffer()
defer releaseBytesBuffer(sendBuffer)
lenP := len(p)
written := 0
for len(p) > 0 {
chunkSize := rand.Intn(record.TLSMaxRecordSize)
chunkSize := rand.IntN(record.TLSMaxRecordSize)
if chunkSize > len(p) || chunkSize == 0 {
chunkSize = len(p)
}
rec.Payload.Reset()
rec.Payload.Write(p[:chunkSize])
rec.Dump(sendBuffer) //nolint: errcheck
err := rec.Dump(c.Conn)
written += chunkSize
if err != nil {
return written, err
}
p = p[chunkSize:]
}
if _, err := c.Conn.Write(sendBuffer.Bytes()); err != nil {
return 0, err //nolint: wrapcheck
}
return lenP, nil
return written, nil
}
+2 -2
View File
@@ -2,9 +2,9 @@ package faketls_test
import (
"bytes"
"crypto/rand"
"errors"
"io"
"math/rand"
"testing"
"github.com/9seconds/mtg/v2/internal/testlib"
@@ -123,7 +123,7 @@ func (suite *ConnTestSuite) TestWrite() {
suite.connMock.On("Write", mock.Anything).Return(0, nil)
dataToRec := make([]byte, record.TLSMaxRecordSize*2)
rand.Read(dataToRec)
rand.Read(dataToRec) //nolint: staticcheck, errcheck
n, err := suite.c.Write(dataToRec)
suite.NoError(err)
-21
View File
@@ -1,21 +0,0 @@
package faketls
import (
"bytes"
"sync"
)
var bytesBufferPool = sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
}
func acquireBytesBuffer() *bytes.Buffer {
return bytesBufferPool.Get().(*bytes.Buffer) //nolint: forcetypeassert
}
func releaseBytesBuffer(b *bytes.Buffer) {
b.Reset()
bytesBufferPool.Put(b)
}
+1 -1
View File
@@ -5,7 +5,7 @@ import (
)
var recordPool = sync.Pool{
New: func() interface{} {
New: func() any {
return &Record{}
},
}
+5 -6
View File
@@ -1,20 +1,20 @@
package faketls
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"io"
mrand "math/rand"
mrand "math/rand/v2"
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
"golang.org/x/crypto/curve25519"
)
func SendWelcomePacket(writer io.Writer, secret []byte, clientHello ClientHello) error {
buf := acquireBytesBuffer()
defer releaseBytesBuffer(buf)
buf := &bytes.Buffer{}
rec := record.AcquireRecord()
defer record.ReleaseRecord(rec)
@@ -36,7 +36,7 @@ func SendWelcomePacket(writer io.Writer, secret []byte, clientHello ClientHello)
rec.Type = record.TypeApplicationData
rec.Version = record.Version12
if _, err := io.CopyN(&rec.Payload, rand.Reader, int64(1024+mrand.Intn(3092))); err != nil { //nolint: gomnd
if _, err := io.CopyN(&rec.Payload, rand.Reader, int64(1024+mrand.IntN(3092))); err != nil {
panic(err)
}
@@ -58,8 +58,7 @@ func SendWelcomePacket(writer io.Writer, secret []byte, clientHello ClientHello)
}
func generateServerHello(writer io.Writer, clientHello ClientHello) {
bodyBuf := acquireBytesBuffer()
defer releaseBytesBuffer(bodyBuf)
bodyBuf := &bytes.Buffer{}
sliceBuf := [2]byte{}
digest := [RandomLen]byte{}
+3 -3
View File
@@ -3,8 +3,8 @@ package faketls_test
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"math/rand"
"testing"
"time"
@@ -30,10 +30,10 @@ func (suite *WelcomeTestSuite) SetupTest() {
SessionID: make([]byte, 32),
}
_, err := rand.Read(suite.h.SessionID)
_, err := rand.Read(suite.h.SessionID) //nolint: staticcheck
suite.NoError(err)
_, err = rand.Read(suite.h.Random[:])
_, err = rand.Read(suite.h.Random[:]) //nolint: staticcheck
suite.NoError(err)
suite.buf = &bytes.Buffer{}
@@ -1,54 +0,0 @@
package obfuscated2
import (
"crypto/cipher"
"crypto/subtle"
"encoding/hex"
"fmt"
"io"
)
type clientHandhakeFrame struct {
handshakeFrame
}
func (c *clientHandhakeFrame) decryptor(secret []byte) cipher.Stream {
hasher := acquireSha256Hasher()
defer releaseSha256Hasher(hasher)
hasher.Write(c.key())
hasher.Write(secret)
return makeAesCtr(hasher.Sum(nil), c.iv())
}
func (c *clientHandhakeFrame) encryptor(secret []byte) cipher.Stream {
invertedHandshake := c.invert()
hasher := acquireSha256Hasher()
defer releaseSha256Hasher(hasher)
hasher.Write(invertedHandshake.key())
hasher.Write(secret)
return makeAesCtr(hasher.Sum(nil), invertedHandshake.iv())
}
func ClientHandshake(secret []byte, reader io.Reader) (int, cipher.Stream, cipher.Stream, error) {
handshake := clientHandhakeFrame{}
if _, err := io.ReadFull(reader, handshake.data[:]); err != nil {
return 0, nil, nil, fmt.Errorf("cannot read frame: %w", err)
}
decryptor := handshake.decryptor(secret)
encryptor := handshake.encryptor(secret)
decryptor.XORKeyStream(handshake.data[:], handshake.data[:])
if val := handshake.connectionType(); subtle.ConstantTimeCompare(handshakeConnectionType, val) != 1 {
return 0, nil, nil, fmt.Errorf("unsupported connection type: %s", hex.EncodeToString(val))
}
return handshake.dc(), encryptor, decryptor, nil
}
@@ -1,32 +0,0 @@
package obfuscated2
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
var FuzzClientHandshakeSecret = []byte{1, 2, 3}
func FuzzClientHandshake(f *testing.F) {
f.Add([]byte{1, 2, 3})
f.Fuzz(func(t *testing.T, frame []byte) {
data := bytes.NewReader(frame)
if _, _, _, err := ClientHandshake(FuzzClientHandshakeSecret, data); err != nil {
return
}
handshake := clientHandhakeFrame{}
require.Len(t, frame, handshakeFrameLen)
copy(handshake.data[:], frame)
decryptor := handshake.decryptor(FuzzClientHandshakeSecret)
decryptor.XORKeyStream(handshake.data[:], handshake.data[:])
require.Equal(t, handshakeConnectionType, handshake.connectionType())
})
}
@@ -1,89 +0,0 @@
package obfuscated2_test
import (
"bytes"
"testing"
"github.com/9seconds/mtg/v2/internal/testlib"
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)
type ClientHandshakeTestSuite struct {
suite.Suite
SnapshotTestSuite
}
func (suite *ClientHandshakeTestSuite) SetupSuite() {
suite.NoError(suite.IngestSnapshots(".", "client-handshake-snapshot-"))
}
func (suite *ClientHandshakeTestSuite) TestCannotRead() {
buf := bytes.NewBuffer([]byte{1, 2, 3})
_, _, _, err := obfuscated2.ClientHandshake([]byte{1, 2, 3}, buf) //nolint: dogsled
suite.Error(err)
}
func (suite *ClientHandshakeTestSuite) TestOk() {
for nameV, snapshotV := range suite.snapshots {
snapshot := snapshotV
suite.T().Run(nameV, func(t *testing.T) {
buf := bytes.NewBuffer(snapshot.Frame.data)
dc, encryptor, decryptor, err := obfuscated2.ClientHandshake(
snapshot.Secret.data, buf)
assert.NoError(t, err)
assert.EqualValues(t, snapshot.DC, dc)
writeData := make([]byte, len(snapshot.Encrypted.Text.data))
readData := make([]byte, len(snapshot.Decrypted.Text.data))
connMock := &testlib.EssentialsConnMock{}
connMock.On("Read", mock.Anything).
Once().
Return(len(snapshot.Decrypted.Text.data), nil).
Run(func(args mock.Arguments) {
arr, ok := args.Get(0).([]byte)
suite.True(ok)
copy(arr, snapshot.Decrypted.Cipher.data)
})
connMock.On("Write", mock.Anything).
Once().
Return(len(snapshot.Encrypted.Text.data), nil).
Run(func(args mock.Arguments) {
arr, ok := args.Get(0).([]byte)
suite.True(ok)
copy(writeData, arr)
})
conn := obfuscated2.Conn{
Conn: connMock,
Encryptor: encryptor,
Decryptor: decryptor,
}
n, err := conn.Read(readData)
assert.Equal(t, len(readData), n)
assert.NoError(t, err)
assert.Equal(t, snapshot.Decrypted.Text.data, readData)
n, err = conn.Write(snapshot.Encrypted.Text.data)
assert.Equal(t, len(writeData), n)
assert.NoError(t, err)
assert.Equal(t, snapshot.Encrypted.Cipher.data, writeData)
connMock.AssertExpectations(t)
})
}
}
func TestClientHandshake(t *testing.T) {
t.Parallel()
suite.Run(t, &ClientHandshakeTestSuite{})
}
-37
View File
@@ -1,37 +0,0 @@
package obfuscated2
import (
"crypto/cipher"
"github.com/9seconds/mtg/v2/essentials"
)
type Conn struct {
essentials.Conn
Encryptor cipher.Stream
Decryptor cipher.Stream
}
func (c Conn) Read(p []byte) (int, error) {
n, err := c.Conn.Read(p)
if err != nil {
return n, err //nolint: wrapcheck
}
c.Decryptor.XORKeyStream(p, p[:n])
return n, nil
}
func (c Conn) Write(p []byte) (int, error) {
buf := acquireBytesBuffer()
defer releaseBytesBuffer(buf)
buf.Write(p)
payload := buf.Bytes()
c.Encryptor.XORKeyStream(payload, payload)
return c.Conn.Write(payload) //nolint: wrapcheck
}
@@ -1,71 +0,0 @@
package obfuscated2
const (
// DefaultDC defines a number of the default DC to use. This value used
// only if a value from obfuscated2 handshake frame is 0 (default).
DefaultDC = 2
handshakeFrameLen = 64
handshakeFrameLenKey = 32
handshakeFrameLenIV = 16
handshakeFrameLenConnectionType = 4
handshakeFrameOffsetStart = 8
handshakeFrameOffsetKey = handshakeFrameOffsetStart
handshakeFrameOffsetIV = handshakeFrameOffsetKey + handshakeFrameLenKey
handshakeFrameOffsetConnectionType = handshakeFrameOffsetIV + handshakeFrameLenIV
handshakeFrameOffsetDC = handshakeFrameOffsetConnectionType + handshakeFrameLenConnectionType
)
// Connection-Type: Secure. We support only fake tls.
var handshakeConnectionType = []byte{0xdd, 0xdd, 0xdd, 0xdd}
// A structure of obfuscated2 handshake frame is following:
//
// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd].
//
// - 8 bytes of noise
// - 32 bytes of AES Key
// - 16 bytes of AES IV
// - 4 bytes of 'connection type' - this has some setting like a connection type
// - 2 bytes of 'DC'. DC is little endian int16
// - 2 bytes of noise
type handshakeFrame struct {
data [handshakeFrameLen]byte
}
func (h *handshakeFrame) dc() int {
idx := int16(h.data[handshakeFrameOffsetDC]) | int16(h.data[handshakeFrameOffsetDC+1])<<8 //nolint: gomnd, lll // little endian for int16 is here
switch {
case idx > 0:
return int(idx)
case idx < 0:
return -int(idx)
default:
return DefaultDC
}
}
func (h *handshakeFrame) key() []byte {
return h.data[handshakeFrameOffsetKey:handshakeFrameOffsetIV]
}
func (h *handshakeFrame) iv() []byte {
return h.data[handshakeFrameOffsetIV:handshakeFrameOffsetConnectionType]
}
func (h *handshakeFrame) connectionType() []byte {
return h.data[handshakeFrameOffsetConnectionType:handshakeFrameOffsetDC]
}
func (h *handshakeFrame) invert() handshakeFrame {
copyFrame := *h
for i := 0; i < handshakeFrameLenKey+handshakeFrameLenIV; i++ {
copyFrame.data[handshakeFrameOffsetKey+i] = h.data[handshakeFrameOffsetConnectionType-1-i]
}
return copyFrame
}
@@ -1,73 +0,0 @@
package obfuscated2
import (
"crypto/rand"
"encoding/base64"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type HandshakeFrameTestSuite struct {
suite.Suite
}
func (suite *HandshakeFrameTestSuite) Decode(value string) []byte {
v, err := base64.RawStdEncoding.DecodeString(value)
suite.NoError(err)
return v
}
func (suite *HandshakeFrameTestSuite) Encode(value []byte) string {
return base64.RawStdEncoding.EncodeToString(value)
}
func (suite *HandshakeFrameTestSuite) TestOk() {
hf := handshakeFrame{}
testFrame := suite.Decode(
"L9TmCzzxl9bPKODBpZeVM/qqNUxQ/axxBup1S2ymbIfUd6f7YSyzzM9EmTFv2/XzGqJGEHuj2zofmUGBLghu5g")
copy(hf.data[:], testFrame)
suite.Equal("zyjgwaWXlTP6qjVMUP2scQbqdUtspmyH1Hen+2Ess8w", suite.Encode(hf.key()))
suite.Equal("z0SZMW/b9fMaokYQe6PbOg", suite.Encode(hf.iv()))
suite.Equal("H5lBgQ", suite.Encode(hf.connectionType()))
suite.EqualValues(2094, hf.dc())
inverted := hf.invert()
suite.Equal("OtujexBGohrz9dtvMZlEz8yzLGH7p3fUh2ymbEt16gY", suite.Encode(inverted.key()))
suite.Equal("caz9UEw1qvozlZelweAozw", suite.Encode(inverted.iv()))
suite.Equal("H5lBgQ", suite.Encode(inverted.connectionType()))
suite.EqualValues(2094, inverted.dc())
}
func (suite *HandshakeFrameTestSuite) TestDC() {
testData := map[int16]int{
1: 1,
-1: 1,
0: DefaultDC,
}
for k, v := range testData {
incoming := k
expected := v
suite.T().Run(strconv.Itoa(int(incoming)), func(t *testing.T) {
frame := handshakeFrame{}
rand.Read(frame.data[:]) //nolint: errcheck
frame.data[handshakeFrameOffsetDC] = byte(incoming)
frame.data[handshakeFrameOffsetDC+1] = byte(incoming >> 8)
assert.Equal(t, expected, frame.dc())
})
}
}
func TestHandshakeFrame(t *testing.T) {
t.Parallel()
suite.Run(t, &HandshakeFrameTestSuite{})
}
-137
View File
@@ -1,137 +0,0 @@
package obfuscated2_test
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/9seconds/mtg/v2/internal/testlib"
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
"github.com/stretchr/testify/require"
)
type snapshotBytes struct {
data []byte
}
func (s snapshotBytes) MarshalText() ([]byte, error) {
if len(s.data) == 0 {
return nil, nil
}
return []byte(base64.RawStdEncoding.EncodeToString(s.data)), nil
}
func (s *snapshotBytes) UnmarshalText(data []byte) error {
val, err := base64.RawStdEncoding.DecodeString(string(data))
if err != nil {
return fmt.Errorf("cannot unmarshal %v: %w", len(val), err)
}
s.data = val
return nil
}
type Obfuscated2Snapshot struct {
Secret snapshotBytes `json:"secret"`
Frame snapshotBytes `json:"frame"`
DC int16 `json:"dc"`
Encrypted struct {
Text snapshotBytes `json:"text"`
Cipher snapshotBytes `json:"cipher"`
} `json:"encrypted"`
Decrypted struct {
Text snapshotBytes `json:"text"`
Cipher snapshotBytes `json:"cipher"`
} `json:"decrypted"`
}
type SnapshotTestSuite struct {
snapshots map[string]*Obfuscated2Snapshot
}
type ServerHandshakeTestData struct {
connMock *testlib.EssentialsConnMock
proxyConn obfuscated2.Conn
encryptor cipher.Stream
decryptor cipher.Stream
}
func (suite *SnapshotTestSuite) IngestSnapshots(dirname, namePrefix string) error {
suite.snapshots = map[string]*Obfuscated2Snapshot{}
files, err := os.ReadDir(filepath.Join("testdata", dirname))
if err != nil {
return fmt.Errorf("cannot ingest snapshots: %w", err)
}
for _, v := range files {
if !strings.HasPrefix(v.Name(), namePrefix) {
continue
}
filename := filepath.Join("testdata", dirname, v.Name())
contents, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("cannot read %s: %w", filename, err)
}
value := &Obfuscated2Snapshot{}
if err := json.Unmarshal(contents, value); err != nil {
return fmt.Errorf("cannot unmarshal %s: %w", filename, err)
}
suite.snapshots[v.Name()] = value
}
return nil
}
func NewServerHandshakeTestData(t *testing.T) ServerHandshakeTestData {
buf := &bytes.Buffer{}
connMock := &testlib.EssentialsConnMock{}
handshakeEnc, handshakeDec, err := obfuscated2.ServerHandshake(buf)
require.NoError(t, err)
serverEncrypted := buf.Bytes()
decBlock, _ := aes.NewCipher(serverEncrypted[8 : 8+32])
decryptor := cipher.NewCTR(decBlock, serverEncrypted[8+32:8+32+16])
serverDecrypted := make([]byte, len(serverEncrypted))
decryptor.XORKeyStream(serverDecrypted, serverEncrypted)
require.Equal(t, "3d3d3Q",
base64.RawStdEncoding.EncodeToString(serverDecrypted[8+32+16:8+32+16+4]))
serverEncryptedReverted := make([]byte, len(serverEncrypted))
for i := 0; i < 32+16; i++ {
serverEncryptedReverted[8+i] = serverEncrypted[8+32+16-1-i]
}
encBlock, _ := aes.NewCipher(serverEncryptedReverted[8 : 8+32])
encryptor := cipher.NewCTR(encBlock, serverEncryptedReverted[8+32:8+32+16])
return ServerHandshakeTestData{
connMock: connMock,
proxyConn: obfuscated2.Conn{
Conn: connMock,
Encryptor: handshakeEnc,
Decryptor: handshakeDec,
},
encryptor: encryptor,
decryptor: decryptor,
}
}
-39
View File
@@ -1,39 +0,0 @@
package obfuscated2
import (
"bytes"
"crypto/sha256"
"hash"
"sync"
)
var (
sha256HasherPool = sync.Pool{
New: func() interface{} {
return sha256.New()
},
}
bytesBufferPool = sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
}
)
func acquireSha256Hasher() hash.Hash {
return sha256HasherPool.Get().(hash.Hash) //nolint: forcetypeassert
}
func releaseSha256Hasher(h hash.Hash) {
h.Reset()
sha256HasherPool.Put(h)
}
func acquireBytesBuffer() *bytes.Buffer {
return bytesBufferPool.Get().(*bytes.Buffer) //nolint: forcetypeassert
}
func releaseBytesBuffer(buf *bytes.Buffer) {
buf.Reset()
bytesBufferPool.Put(buf)
}
@@ -1,67 +0,0 @@
package obfuscated2
import (
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"fmt"
"io"
)
type serverHandshakeFrame struct {
handshakeFrame
}
func (s *serverHandshakeFrame) decryptor() cipher.Stream {
invertedHandshake := s.invert()
return makeAesCtr(invertedHandshake.key(), invertedHandshake.iv())
}
func (s *serverHandshakeFrame) encryptor() cipher.Stream {
return makeAesCtr(s.key(), s.iv())
}
func ServerHandshake(writer io.Writer) (cipher.Stream, cipher.Stream, error) {
handshake := generateServerHanshakeFrame()
copyHandshake := handshake
encryptor := handshake.encryptor()
decryptor := handshake.decryptor()
encryptor.XORKeyStream(handshake.data[:], handshake.data[:])
copy(handshake.key(), copyHandshake.key())
copy(handshake.iv(), copyHandshake.iv())
if _, err := writer.Write(handshake.data[:]); err != nil {
return nil, nil, fmt.Errorf("cannot send a handshake frame to telegram: %w", err)
}
return encryptor, decryptor, nil
}
func generateServerHanshakeFrame() serverHandshakeFrame {
frame := serverHandshakeFrame{}
for {
if _, err := rand.Read(frame.data[:]); err != nil {
panic(err)
}
if frame.data[0] == 0xef { //nolint: gomnd // taken from tg sources
continue
}
switch binary.LittleEndian.Uint32(frame.data[:4]) {
case 0x44414548, 0x54534f50, 0x20544547, 0x4954504f, 0xeeeeeeee: //nolint: gomnd // taken from tg sources
continue
}
if frame.data[4]|frame.data[5]|frame.data[6]|frame.data[7] == 0 {
continue
}
copy(frame.connectionType(), handshakeConnectionType)
return frame
}
}
@@ -1,58 +0,0 @@
package obfuscated2_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func FuzzServerSend(f *testing.F) {
f.Add([]byte{1, 2, 3, 4, 5})
f.Fuzz(func(t *testing.T, data []byte) {
handshakeData := NewServerHandshakeTestData(t)
handshakeData.connMock.
On("Write", mock.Anything).
Return(len(data), nil).
Once().
Run(func(args mock.Arguments) {
message := make([]byte, len(data))
handshakeData.decryptor.XORKeyStream(message, args.Get(0).([]byte)) //nolint: forcetypeassert
assert.Equal(t, message, data)
})
n, err := handshakeData.proxyConn.Write(data)
assert.EqualValues(t, len(data), n)
assert.NoError(t, err)
handshakeData.connMock.AssertExpectations(t)
})
}
func FuzzServerReceive(f *testing.F) {
f.Add([]byte{1, 2, 3, 4, 5})
f.Fuzz(func(t *testing.T, data []byte) {
handshakeData := NewServerHandshakeTestData(t)
buffer := make([]byte, len(data))
handshakeData.connMock.
On("Read", mock.Anything).
Return(len(data), nil).
Once().
Run(func(args mock.Arguments) {
message := make([]byte, len(data))
handshakeData.encryptor.XORKeyStream(message, data)
copy(args.Get(0).([]byte), message) //nolint: forcetypeassert
})
n, err := handshakeData.proxyConn.Read(buffer)
assert.EqualValues(t, len(data), n)
assert.NoError(t, err)
assert.Equal(t, data, buffer)
handshakeData.connMock.AssertExpectations(t)
})
}
@@ -1,65 +0,0 @@
package obfuscated2_test
import (
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)
type ServerHandshakeTestSuite struct {
suite.Suite
data ServerHandshakeTestData
}
func (suite *ServerHandshakeTestSuite) SetupTest() {
suite.data = NewServerHandshakeTestData(suite.T())
}
func (suite *ServerHandshakeTestSuite) TearDownTest() {
suite.data.connMock.AssertExpectations(suite.T())
}
func (suite *ServerHandshakeTestSuite) TestSendToTelegram() {
messageToTelegram := []byte{10, 11, 12, 13, 14, 'a'}
suite.data.connMock.
On("Write", mock.Anything).
Return(len(messageToTelegram), nil).
Once().
Run(func(args mock.Arguments) {
message := make([]byte, len(messageToTelegram))
suite.data.decryptor.XORKeyStream(message, args.Get(0).([]byte)) //nolint: forcetypeassert
suite.Equal(messageToTelegram, message)
})
n, err := suite.data.proxyConn.Write(messageToTelegram)
suite.EqualValues(len(messageToTelegram), n)
suite.NoError(err)
}
func (suite *ServerHandshakeTestSuite) TestRecieveFromTelegram() {
messageFromTelegram := []byte{10, 11, 12, 13, 14, 'a'}
buffer := make([]byte, len(messageFromTelegram))
suite.data.connMock.
On("Read", mock.Anything).
Return(len(messageFromTelegram), nil).
Once().
Run(func(args mock.Arguments) {
message := make([]byte, len(messageFromTelegram))
suite.data.encryptor.XORKeyStream(message, messageFromTelegram)
copy(args.Get(0).([]byte), message) //nolint: forcetypeassert
})
n, err := suite.data.proxyConn.Read(buffer)
suite.EqualValues(len(messageFromTelegram), n)
suite.NoError(err)
suite.Equal(messageFromTelegram, buffer)
}
func TestServerHandshake(t *testing.T) {
t.Parallel()
suite.Run(t, &ServerHandshakeTestSuite{})
}
-15
View File
@@ -1,15 +0,0 @@
package obfuscated2
import (
"crypto/aes"
"crypto/cipher"
)
func makeAesCtr(key, iv []byte) cipher.Stream {
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
return cipher.NewCTR(block, iv)
}
+34
View File
@@ -0,0 +1,34 @@
package obfuscation
import (
"crypto/cipher"
"github.com/9seconds/mtg/v2/essentials"
)
type conn struct {
essentials.Conn
sendCipher cipher.Stream
recvCipher cipher.Stream
}
func (c conn) Read(p []byte) (int, error) {
n, err := c.Conn.Read(p)
if err != nil {
return n, err
}
c.recvCipher.XORKeyStream(p, p[:n])
return n, nil
}
func (c conn) Write(p []byte) (int, error) {
// yes, this is a bit violent and goes against a contract in io.Writer
// but we do it to avoid creating a new buffer just to perform this
// encryption.
c.sendCipher.XORKeyStream(p, p)
return c.Conn.Write(p)
}
+102
View File
@@ -0,0 +1,102 @@
package obfuscation
import (
"crypto/aes"
"crypto/cipher"
"encoding/hex"
"testing"
"github.com/9seconds/mtg/v2/essentials"
"github.com/9seconds/mtg/v2/internal/testlib"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)
type ConnTestSuite struct {
suite.Suite
secret []byte
}
func (s *ConnTestSuite) SetupSuite() {
secret := [32]byte{}
s.secret = secret[:]
}
func (s *ConnTestSuite) TestRead() {
testData := map[string]string{
"data1": "b8f4b41993",
"": "",
"___": "83ca9f",
}
for incoming, outgoing := range testData {
s.T().Run(incoming, func(t *testing.T) {
connMock := &testlib.EssentialsConnMock{}
testConn := s.makeConn(connMock)
data := make([]byte, len(incoming))
connMock.On("Read", make([]byte, len(incoming))).Return(len(incoming), nil).Run(func(args mock.Arguments) {
arg := args.Get(0).([]byte)
copy(arg, []byte(incoming))
})
n, err := testConn.Read(data)
assert.Equal(t, len(data), n)
assert.NoError(t, err)
assert.Equal(t, outgoing, hex.EncodeToString(data))
connMock.AssertExpectations(t)
})
}
}
func (s *ConnTestSuite) TestWrite() {
testData := map[string]string{
"b8f4b41993": "data1",
"": "",
"83ca9f": "___",
}
for incoming, outgoing := range testData {
s.T().Run(incoming, func(t *testing.T) {
connMock := &testlib.EssentialsConnMock{}
testConn := s.makeConn(connMock)
toWrite, _ := hex.DecodeString(incoming)
data := make([]byte, len(toWrite))
connMock.On("Write", []byte(outgoing)).Return(len(toWrite), nil)
n, err := testConn.Write(toWrite)
assert.Equal(t, len(data), n)
assert.NoError(t, err)
connMock.AssertExpectations(t)
})
}
}
func (s *ConnTestSuite) makeConn(rawConn *testlib.EssentialsConnMock) essentials.Conn {
rblock, err := aes.NewCipher(s.secret)
if err != nil {
panic(err)
}
wblock, err := aes.NewCipher(s.secret)
if err != nil {
panic(err)
}
return conn{
Conn: rawConn,
sendCipher: cipher.NewCTR(wblock, s.secret[:aes.BlockSize]),
recvCipher: cipher.NewCTR(rblock, s.secret[:aes.BlockSize]),
}
}
func TestConn(t *testing.T) {
t.Parallel()
suite.Run(t, &ConnTestSuite{})
}
@@ -0,0 +1,111 @@
package obfuscation
import (
"crypto/rand"
"encoding/binary"
"slices"
)
// https://core.telegram.org/mtproto/mtproto-transports#transport-obfuscation
const (
// default DC is nothing is selected
defaultDC = 2
// the length of the handshake frame. Always 64 bytes
hfLen = 64
hfLenKey = 32
hfLenIV = 16
hfLenConnectionType = 4
// A structure of obfuscated handshake frame is following:
//
// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd].
//
// - 8 bytes of noise
// - 32 bytes of AES Key
// - 16 bytes of AES IV
// - 4 bytes of 'connection type' - this has some setting like a connection type
// - 2 bytes of 'DC'. DC is little endian int16
// - 2 bytes of noise
hfOffsetKey = 8
hfOffsetIV = hfOffsetKey + hfLenKey
hfOffsetConnectionType = hfOffsetIV + hfLenIV
hfOffsetDC = hfOffsetConnectionType + hfLenConnectionType
)
// Connection-Type: Secure. We support only fake tls.
var hfConnectionType = [hfLenConnectionType]byte{0xdd, 0xdd, 0xdd, 0xdd}
type handshakeFrame struct {
data [hfLen]byte
}
func (h *handshakeFrame) key() []byte {
return h.data[hfOffsetKey : hfOffsetKey+hfLenKey]
}
func (h *handshakeFrame) iv() []byte {
return h.data[hfOffsetIV : hfOffsetIV+hfLenIV]
}
func (h *handshakeFrame) connectionType() []byte {
return h.data[hfOffsetConnectionType : hfOffsetConnectionType+hfLenConnectionType]
}
func (h *handshakeFrame) dcSlice() []byte {
return h.data[hfOffsetDC : hfOffsetDC+2]
}
func (h *handshakeFrame) dc() int {
idx := int16(binary.LittleEndian.Uint16(h.dcSlice()))
switch {
case idx > 0:
return int(idx)
case idx < 0:
return -int(idx)
}
return defaultDC
}
func (h *handshakeFrame) revert() {
slices.Reverse(h.data[hfOffsetKey:hfOffsetConnectionType])
}
func generateHandshake(dc int) handshakeFrame {
frame := handshakeFrame{}
for {
if _, err := rand.Read(frame.data[:]); err != nil {
panic(err)
}
// https://github.com/tdlib/td/blob/master/td/mtproto/TcpTransport.cpp#L157-L158.
if frame.data[0] == 0xef { // abridged header
// https://core.telegram.org/mtproto/mtproto-transports#abridged
continue
}
switch binary.LittleEndian.Uint32(frame.data[:4]) {
case 0x44414548, // HEAD
0x54534f50, // POST
0x20544547, // GET
0x4954504f, // OPTI
0x02010316, // ????
0xdddddddd, // PaddedIntermediate header
0xeeeeeeee: // Intermediate header
continue
}
if frame.data[4]|frame.data[5]|frame.data[6]|frame.data[7] == 0 {
continue
}
copy(frame.connectionType(), hfConnectionType[:])
binary.LittleEndian.PutUint16(frame.dcSlice(), uint16(dc))
return frame
}
}
@@ -1,4 +1,4 @@
package obfuscated2
package obfuscation
import (
"encoding/binary"
@@ -7,9 +7,9 @@ import (
"github.com/stretchr/testify/assert"
)
func FuzzServerGenerateHandshakeFrame(f *testing.F) {
f.Fuzz(func(t *testing.T, arg int) {
frame := generateServerHanshakeFrame()
func FuzzGenerateHandshakeFrame(f *testing.F) {
f.Fuzz(func(t *testing.T, arg int16) {
frame := generateHandshake(int(arg))
assert.NotEqualValues(t, 0xef, frame.data[0])
@@ -18,13 +18,23 @@ func FuzzServerGenerateHandshakeFrame(f *testing.F) {
assert.NotEqualValues(t, 0x54534f50, firstBytes)
assert.NotEqualValues(t, 0x20544547, firstBytes)
assert.NotEqualValues(t, 0x4954504f, firstBytes)
assert.NotEqualValues(t, 0x02010316, firstBytes)
assert.NotEqualValues(t, 0xeeeeeeee, firstBytes)
assert.NotEqualValues(t, 0xdddddddd, firstBytes)
assert.NotEqualValues(
t,
0,
frame.data[4]|frame.data[5]|frame.data[6]|frame.data[7])
assert.Equal(t, handshakeConnectionType, frame.connectionType())
assert.Equal(t, hfConnectionType[:], frame.connectionType())
if arg < 0 {
arg = -arg
} else if arg == 0 {
arg = defaultDC
}
assert.EqualValues(t, arg, frame.dc())
})
}
@@ -0,0 +1,66 @@
package obfuscation
import (
"testing"
"github.com/stretchr/testify/suite"
)
type HandshakeFrameTestSuite struct {
suite.Suite
frame handshakeFrame
reverted handshakeFrame
}
func (h *HandshakeFrameTestSuite) SetupSuite() {
for i := range hfLen {
h.frame.data[i] = byte(i + 1)
h.reverted.data[i] = byte(hfLen - i)
}
}
func (h *HandshakeFrameTestSuite) TestKey() {
key := h.frame.key()
h.EqualValues(8+1, key[0])
h.EqualValues(8+hfLenKey, key[len(key)-1])
h.Len(key, hfLenKey)
}
func (h *HandshakeFrameTestSuite) TestIV() {
iv := h.frame.iv()
h.EqualValues(40+1, iv[0])
h.EqualValues(40+hfLenIV, iv[len(iv)-1])
h.Len(iv, hfLenIV)
}
func (h *HandshakeFrameTestSuite) TestConnectionType() {
connectionType := h.frame.connectionType()
h.EqualValues(56+1, connectionType[0])
h.EqualValues(56+hfLenConnectionType, connectionType[len(connectionType)-1])
h.Len(connectionType, hfLenConnectionType)
}
func (h *HandshakeFrameTestSuite) TestDCSlice() {
dcSlice := h.frame.dcSlice()
h.EqualValues(61, dcSlice[0])
h.EqualValues(61+1, dcSlice[1])
h.Len(dcSlice, 2)
}
func (h *HandshakeFrameTestSuite) TestDC() {
h.Equal(15933, h.frame.dc())
}
func (h *HandshakeFrameTestSuite) TestRevert() {
fr := h.frame
fr.revert()
h.Equal(h.reverted.key(), fr.key())
h.Equal(h.reverted.iv(), fr.iv())
}
func TestHandshakeFrame(t *testing.T) {
t.Parallel()
suite.Run(t, &HandshakeFrameTestSuite{})
}
+79
View File
@@ -0,0 +1,79 @@
package obfuscation_test
import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type snapshotBytes struct {
data []byte
}
func (s snapshotBytes) MarshalText() ([]byte, error) {
if len(s.data) == 0 {
return nil, nil
}
return []byte(base64.RawStdEncoding.EncodeToString(s.data)), nil
}
func (s *snapshotBytes) UnmarshalText(data []byte) error {
val, err := base64.RawStdEncoding.DecodeString(string(data))
if err != nil {
return fmt.Errorf("cannot unmarshal %v: %w", len(val), err)
}
s.data = val
return nil
}
type ObfuscatedSnapshot struct {
Secret snapshotBytes `json:"secret"`
Frame snapshotBytes `json:"frame"`
DC int16 `json:"dc"`
Encrypted struct {
Text snapshotBytes `json:"text"`
Cipher snapshotBytes `json:"cipher"`
} `json:"encrypted"`
Decrypted struct {
Text snapshotBytes `json:"text"`
Cipher snapshotBytes `json:"cipher"`
} `json:"decrypted"`
}
type SnapshotTestSuite struct {
suite.Suite
snapshots map[string]*ObfuscatedSnapshot
}
func (s *SnapshotTestSuite) Setup(dirname, namePrefix string) {
s.snapshots = make(map[string]*ObfuscatedSnapshot)
files, err := os.ReadDir("testdata")
require.NoError(s.T(), err)
for _, v := range files {
if !strings.HasPrefix(v.Name(), namePrefix) {
continue
}
filename := filepath.Join("testdata", v.Name())
contents, err := os.ReadFile(filename)
require.NoError(s.T(), err)
value := &ObfuscatedSnapshot{}
require.NoError(s.T(), json.Unmarshal(contents, value))
s.snapshots[v.Name()] = value
}
}
+87
View File
@@ -0,0 +1,87 @@
package obfuscation
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"fmt"
"hash"
"io"
"github.com/9seconds/mtg/v2/essentials"
)
type Obfuscator struct {
Secret []byte
}
func (o Obfuscator) ReadHandshake(r essentials.Conn) (int, essentials.Conn, error) {
frame := handshakeFrame{}
if _, err := io.ReadFull(r, frame.data[:]); err != nil {
return 0, nil, fmt.Errorf("cannot read frame: %w", err)
}
hasher := sha256.New()
recvCipher := o.getCipher(&frame, hasher)
frame.revert()
hasher.Reset()
sendCipher := o.getCipher(&frame, hasher)
recvCipher.XORKeyStream(frame.data[:], frame.data[:])
if val := frame.connectionType(); subtle.ConstantTimeCompare(val, hfConnectionType[:]) != 1 {
return 0, nil, fmt.Errorf("unsupported connection type: %s", hex.EncodeToString(val))
}
cn := conn{
Conn: r,
recvCipher: recvCipher,
sendCipher: sendCipher,
}
return frame.dc(), cn, nil
}
func (o Obfuscator) SendHandshake(w essentials.Conn, dc int) (essentials.Conn, error) {
frame := generateHandshake(dc)
copyFrame := frame
hasher := sha256.New()
sendCipher := o.getCipher(&frame, hasher)
frame.revert()
hasher.Reset()
recvCipher := o.getCipher(&frame, hasher)
sendCipher.XORKeyStream(frame.data[:], frame.data[:])
copy(frame.key(), copyFrame.key())
copy(frame.iv(), copyFrame.iv())
if _, err := w.Write(frame.data[:]); err != nil {
return nil, fmt.Errorf("cannot send a handshake: %w", err)
}
return conn{
Conn: w,
recvCipher: recvCipher,
sendCipher: sendCipher,
}, nil
}
func (o Obfuscator) getCipher(f *handshakeFrame, hasher hash.Hash) cipher.Stream {
blockKey := f.key()
if o.Secret != nil {
hasher.Write(blockKey)
hasher.Write(o.Secret)
blockKey = hasher.Sum(nil)
}
block, _ := aes.NewCipher(blockKey)
return cipher.NewCTR(block, f.iv())
}
@@ -0,0 +1,63 @@
package obfuscation_test
import (
"bytes"
"testing"
"github.com/9seconds/mtg/v2/internal/testlib"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscation"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func FuzzClientServerHandshakes(f *testing.F) {
f.Add(int16(1), make([]byte, mtglib.SecretKeyLength))
f.Fuzz(func(t *testing.T, dc int16, data []byte) {
if dc <= 0 {
dc = 1
}
client := obfuscation.Obfuscator{
Secret: data,
}
server := client
clientToServerBuf := &bytes.Buffer{}
writeConnMock := &testlib.EssentialsConnMock{}
writeConnMock.
On("Write", mock.AnythingOfType("[]uint8")).
Once().
Return(64, nil).
Run(func(args mock.Arguments) {
arg := args.Get(0).([]byte)
n, err := clientToServerBuf.Write(arg)
assert.Equal(t, 64, n)
assert.NoError(t, err)
})
readConnMock := &testlib.EssentialsConnMock{}
readConnMock.
On("Read", mock.AnythingOfType("[]uint8")).
Once().
Return(64, nil).
Run(func(args mock.Arguments) {
arg := args.Get(0).([]byte)
n, err := clientToServerBuf.Read(arg)
assert.Equal(t, 64, n)
assert.NoError(t, err)
})
_, err := client.SendHandshake(writeConnMock, int(dc))
assert.NoError(t, err)
readDc, _, err := server.ReadHandshake(readConnMock)
assert.NoError(t, err)
assert.EqualValues(t, dc, readDc)
writeConnMock.AssertExpectations(t)
readConnMock.AssertExpectations(t)
})
}
@@ -0,0 +1,94 @@
package obfuscation_test
import (
"bytes"
"testing"
"github.com/9seconds/mtg/v2/internal/testlib"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscation"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type ObfuscatorTestSuite struct {
SnapshotTestSuite
secret *mtglib.Secret
}
func (s *ObfuscatorTestSuite) SetupSuite() {
s.Setup("", "client-handshake")
secret := mtglib.GenerateSecret("hostname.com")
s.secret = &secret
}
func (s *ObfuscatorTestSuite) TestSnapshot() {
for name, snapshot := range s.snapshots {
s.T().Run(name, func(t *testing.T) {
obfs := obfuscation.Obfuscator{
Secret: snapshot.Secret.data,
}
connMock := &testlib.EssentialsConnMock{}
connMockReadBuffer := &bytes.Buffer{}
connMockReadBuffer.Write(snapshot.Frame.data)
connMockReadBuffer.Write(snapshot.Decrypted.Cipher.data)
connMockWriteBuffer := &bytes.Buffer{}
connMock.
On("Read", mock.AnythingOfType("[]uint8")).
Return(64, nil).
Run(func(args mock.Arguments) {
arr := args.Get(0).([]byte)
_, err := connMockReadBuffer.Read(arr)
require.NoError(t, err)
})
dc, cn, err := obfs.ReadHandshake(connMock)
assert.EqualValues(t, 2, dc)
assert.NoError(t, err)
connMock.Calls = []mock.Call{}
connMock.ExpectedCalls = []*mock.Call{}
connMock.
On("Read", mock.AnythingOfType("[]uint8")).
Return(len(snapshot.Decrypted.Cipher.data), nil).
Run(func(args mock.Arguments) {
arr := args.Get(0).([]byte)
_, err := connMockReadBuffer.Read(arr)
require.NoError(t, err)
})
connMock.
On("Write", mock.AnythingOfType("[]uint8")).
Return(len(snapshot.Encrypted.Cipher.data), nil).
Run(func(args mock.Arguments) {
arr := args.Get(0).([]byte)
_, err := connMockWriteBuffer.Write(arr)
require.NoError(t, err)
})
readBuf := make([]byte, len(snapshot.Decrypted.Text.data))
_, err = cn.Read(readBuf)
assert.NoError(t, err)
assert.Equal(t, readBuf, snapshot.Decrypted.Text.data)
_, err = cn.Write(snapshot.Encrypted.Text.data)
assert.NoError(t, err)
assert.Equal(t, connMockWriteBuffer.Bytes(), snapshot.Encrypted.Cipher.data)
connMock.AssertExpectations(t)
})
}
}
func TestObfuscator(t *testing.T) {
t.Parallel()
suite.Run(t, &ObfuscatorTestSuite{})
}
+1 -1
View File
@@ -5,5 +5,5 @@ const (
)
type Logger interface {
Printf(msg string, args ...interface{})
Printf(msg string, args ...any)
}
+1 -1
View File
@@ -2,4 +2,4 @@ package relay_test
type loggerMock struct{}
func (l loggerMock) Printf(format string, args ...interface{}) {}
func (l loggerMock) Printf(format string, args ...any) {}
-19
View File
@@ -1,19 +0,0 @@
package relay
import "sync"
var copyBufferPool = sync.Pool{
New: func() interface{} {
rv := make([]byte, copyBufferSize)
return &rv
},
}
func acquireCopyBuffer() *[]byte {
return copyBufferPool.Get().(*[]byte) //nolint: forcetypeassert
}
func releaseCopyBuffer(buf *[]byte) {
copyBufferPool.Put(buf)
}
+7 -8
View File
@@ -9,16 +9,16 @@ import (
)
func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials.Conn) {
defer telegramConn.Close()
defer clientConn.Close()
defer telegramConn.Close() //nolint: errcheck
defer clientConn.Close() //nolint: errcheck
ctx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
<-ctx.Done()
telegramConn.Close()
clientConn.Close()
telegramConn.Close() //nolint: errcheck
clientConn.Close() //nolint: errcheck
}()
closeChan := make(chan struct{})
@@ -35,13 +35,12 @@ func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials.
}
func pump(log Logger, src, dst essentials.Conn, direction string) {
var buf [copyBufferSize]byte
defer src.CloseRead() //nolint: errcheck
defer dst.CloseWrite() //nolint: errcheck
copyBuffer := acquireCopyBuffer()
defer releaseCopyBuffer(copyBuffer)
n, err := io.CopyBuffer(src, dst, *copyBuffer)
n, err := io.CopyBuffer(src, dst, buf[:])
switch {
case err == nil:
-41
View File
@@ -1,41 +0,0 @@
package telegram
import "math/rand"
type addressPool struct {
v4 [][]tgAddr
v6 [][]tgAddr
}
func (a addressPool) isValidDC(dc int) bool {
return dc > 0 && dc <= len(a.v4) && dc <= len(a.v6)
}
func (a addressPool) getRandomDC() int {
return 1 + rand.Intn(len(a.v4))
}
func (a addressPool) getV4(dc int) []tgAddr {
return a.get(a.v4, dc-1)
}
func (a addressPool) getV6(dc int) []tgAddr {
return a.get(a.v6, dc-1)
}
func (a addressPool) get(addresses [][]tgAddr, dc int) []tgAddr {
if dc < 0 || dc >= len(addresses) {
return nil
}
rv := make([]tgAddr, len(addresses[dc]))
copy(rv, addresses[dc])
if len(rv) > 1 {
rand.Shuffle(len(rv), func(i, j int) {
rv[i], rv[j] = rv[j], rv[i]
})
}
return rv
}
-90
View File
@@ -1,90 +0,0 @@
package telegram
import (
"context"
"errors"
"github.com/9seconds/mtg/v2/essentials"
)
var errNoAddresses = errors.New("no addresses")
type preferIP uint8
const (
preferIPOnlyIPv4 preferIP = iota
preferIPOnlyIPv6
preferIPPreferIPv4
preferIPPreferIPv6
)
type tgAddr struct {
network string
address string
}
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp#L30
var (
productionV4Addresses = [][]tgAddr{
{ // dc1
{network: "tcp4", address: "149.154.175.50:443"},
},
{ // dc2
{network: "tcp4", address: "149.154.167.51:443"},
{network: "tcp4", address: "95.161.76.100:443"},
},
{ // dc3
{network: "tcp4", address: "149.154.175.100:443"},
},
{ // dc4
{network: "tcp4", address: "149.154.167.91:443"},
},
{ // dc5
{network: "tcp4", address: "149.154.171.5:443"},
},
}
productionV6Addresses = [][]tgAddr{
{ // dc1
{network: "tcp6", address: "[2001:b28:f23d:f001::a]:443"},
},
{ // dc2
{network: "tcp6", address: "[2001:67c:04e8:f002::a]:443"},
},
{ // dc3
{network: "tcp6", address: "[2001:b28:f23d:f003::a]:443"},
},
{ // dc4
{network: "tcp6", address: "[2001:67c:04e8:f004::a]:443"},
},
{ // dc5
{network: "tcp6", address: "[2001:b28:f23f:f005::a]:443"},
},
}
testV4Addresses = [][]tgAddr{
{ // dc1
{network: "tcp4", address: "149.154.175.10:443"},
},
{ // dc2
{network: "tcp4", address: "149.154.167.40:443"},
},
{ // dc3
{network: "tcp4", address: "149.154.175.117:443"},
},
}
testV6Addresses = [][]tgAddr{
{ // dc1
{network: "tcp6", address: "[2001:b28:f23d:f001::e]:443"},
},
{ // dc2
{network: "tcp6", address: "[2001:67c:04e8:f002::e]:443"},
},
{ // dc3
{network: "tcp6", address: "[2001:b28:f23d:f003::e]:443"},
},
}
)
type Dialer interface {
DialContext(ctx context.Context, network, address string) (essentials.Conn, error)
}
-83
View File
@@ -1,83 +0,0 @@
package telegram
import (
"context"
"fmt"
"strings"
"github.com/9seconds/mtg/v2/essentials"
)
type Telegram struct {
dialer Dialer
preferIP preferIP
pool addressPool
}
func (t Telegram) Dial(ctx context.Context, dc int) (essentials.Conn, error) {
var addresses []tgAddr
switch t.preferIP {
case preferIPOnlyIPv4:
addresses = t.pool.getV4(dc)
case preferIPOnlyIPv6:
addresses = t.pool.getV6(dc)
case preferIPPreferIPv4:
addresses = append(t.pool.getV4(dc), t.pool.getV6(dc)...)
case preferIPPreferIPv6:
addresses = append(t.pool.getV6(dc), t.pool.getV4(dc)...)
}
var conn essentials.Conn
err := errNoAddresses
for _, v := range addresses {
conn, err = t.dialer.DialContext(ctx, v.network, v.address)
if err == nil {
return conn, nil
}
}
return nil, fmt.Errorf("cannot dial to %d dc: %w", dc, err)
}
func (t Telegram) IsKnownDC(dc int) bool {
return t.pool.isValidDC(dc)
}
func (t Telegram) GetFallbackDC() int {
return t.pool.getRandomDC()
}
func New(dialer Dialer, ipPreference string, useTestDCs bool) (*Telegram, error) {
var pref preferIP
switch strings.ToLower(ipPreference) {
case "prefer-ipv4":
pref = preferIPPreferIPv4
case "prefer-ipv6":
pref = preferIPPreferIPv6
case "only-ipv4":
pref = preferIPOnlyIPv4
case "only-ipv6":
pref = preferIPOnlyIPv6
default:
return nil, fmt.Errorf("unknown ip preference %s", ipPreference)
}
pool := addressPool{
v4: productionV4Addresses,
v6: productionV6Addresses,
}
if useTestDCs {
pool.v4 = testV4Addresses
pool.v6 = testV6Addresses
}
return &Telegram{
dialer: dialer,
preferIP: pref,
pool: pool,
}, nil
}
@@ -1,159 +0,0 @@
package telegram
import (
"context"
"errors"
"io"
"net"
"strconv"
"testing"
"github.com/9seconds/mtg/v2/internal/testlib"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)
type TelegramTestSuite struct {
suite.Suite
dialerMock *testlib.MtglibNetworkMock
t *Telegram
}
func (suite *TelegramTestSuite) SetupTest() {
suite.dialerMock = &testlib.MtglibNetworkMock{}
suite.t, _ = New(suite.dialerMock, "prefer-ipv4", false)
}
func (suite *TelegramTestSuite) TearDownTest() {
suite.dialerMock.AssertExpectations(suite.T())
}
func (suite *TelegramTestSuite) TestUnknownDC() {
testData := []int{
-1,
0,
6,
100,
}
for _, v := range testData {
value := v
suite.T().Run(strconv.Itoa(value), func(t *testing.T) {
_, err := suite.t.Dial(context.Background(), value)
assert.Error(t, err)
assert.False(t, suite.t.IsKnownDC(value))
})
}
}
func (suite *TelegramTestSuite) TestDialToCorrectIPs() {
testData := map[int][]tgAddr{}
for i := 1; i <= 5; i++ {
testData[i] = []tgAddr{}
testData[i] = append(testData[i], productionV4Addresses[i-1]...)
testData[i] = append(testData[i], productionV6Addresses[i-1]...)
}
for i, v := range testData {
idx := i
addresses := v
suite.T().Run(strconv.Itoa(idx), func(t *testing.T) {
for _, addr := range addresses {
suite.dialerMock.
On("DialContext", mock.Anything, addr.network, addr.address).
Once().
Return((*net.TCPConn)(nil), io.EOF)
}
_, err := suite.t.Dial(context.Background(), idx)
assert.True(t, errors.Is(err, io.EOF))
assert.True(t, suite.t.IsKnownDC(idx))
})
}
}
func (suite *TelegramTestSuite) TestDialPreferIPRange() {
testData := map[string][]tgAddr{
"prefer-ipv4": {testV4Addresses[0][0], testV6Addresses[0][0]},
"prefer-ipv6": {testV6Addresses[0][0], testV4Addresses[0][0]},
"only-ipv4": {testV4Addresses[0][0]},
"only-ipv6": {testV6Addresses[0][0]},
}
for k, v := range testData {
name := k
addresses := v
suite.T().Run(name, func(t *testing.T) {
for _, addr := range addresses {
suite.dialerMock.
On("DialContext", mock.Anything, addr.network, addr.address).
Once().
Return((*net.TCPConn)(nil), io.EOF)
}
tg, _ := New(suite.dialerMock, name, true)
_, err := tg.Dial(context.Background(), 1)
assert.True(t, errors.Is(err, io.EOF))
})
}
}
func (suite *TelegramTestSuite) TestDialPreferIPPriority() {
testData := map[string]tgAddr{
"prefer-ipv4": productionV4Addresses[0][0],
"prefer-ipv6": productionV6Addresses[0][0],
}
for k, v := range testData {
name := k
addr := v
suite.T().Run(name, func(t *testing.T) {
conn := &net.TCPConn{}
suite.dialerMock.
On("DialContext", mock.Anything, addr.network, addr.address).
Once().
Return(conn, nil)
tg, _ := New(suite.dialerMock, name, false)
res, err := tg.Dial(context.Background(), 1)
assert.NoError(t, err)
assert.Equal(t, conn, res)
})
}
}
func (suite *TelegramTestSuite) TestUnknownPreferIP() {
_, err := New(suite.dialerMock, "xxx", false)
suite.Error(err)
}
func (suite *TelegramTestSuite) TestFallbackDC() {
dcs := make([]int, 10)
for i := 0; i < len(dcs); i++ {
dcs[i] = suite.t.GetFallbackDC()
}
for _, v := range dcs {
value := v
suite.T().Run(strconv.Itoa(value), func(t *testing.T) {
assert.True(t, suite.t.IsKnownDC(value))
})
}
}
func TestTelegram(t *testing.T) {
t.Parallel()
suite.Run(t, &TelegramTestSuite{})
}
+83 -43
View File
@@ -10,11 +10,11 @@ import (
"time"
"github.com/9seconds/mtg/v2/essentials"
"github.com/9seconds/mtg/v2/mtglib/internal/dc"
"github.com/9seconds/mtg/v2/mtglib/internal/faketls"
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscation"
"github.com/9seconds/mtg/v2/mtglib/internal/relay"
"github.com/9seconds/mtg/v2/mtglib/internal/telegram"
"github.com/panjf2000/ants/v2"
)
@@ -24,11 +24,15 @@ type Proxy struct {
ctxCancel context.CancelFunc
streamWaitGroup sync.WaitGroup
allowFallbackOnUnknownDC bool
tolerateTimeSkewness time.Duration
domainFrontingPort int
workerPool *ants.PoolWithFunc
telegram *telegram.Telegram
allowFallbackOnUnknownDC bool
tolerateTimeSkewness time.Duration
domainFrontingPort int
domainFrontingIP string
domainFrontingProxyProtocol bool
workerPool *ants.PoolWithFunc
telegram *dc.Telegram
configUpdater *dc.PublicConfigUpdater
clientObfuscatror obfuscation.Obfuscator
secret Secret
network Network
@@ -40,8 +44,14 @@ type Proxy struct {
}
// DomainFrontingAddress returns a host:port pair for a fronting domain.
// If DomainFrontingIP is set, it is used instead of resolving the hostname.
func (p *Proxy) DomainFrontingAddress() string {
return net.JoinHostPort(p.secret.Host, strconv.Itoa(p.domainFrontingPort))
host := p.secret.Host
if p.domainFrontingIP != "" {
host = p.domainFrontingIP
}
return net.JoinHostPort(host, strconv.Itoa(p.domainFrontingPort))
}
// ServeConn serves a connection. We do not check IP blocklist and concurrency
@@ -70,8 +80,8 @@ func (p *Proxy) ServeConn(conn essentials.Conn) {
return
}
if err := p.doObfuscated2Handshake(ctx); err != nil {
p.logger.InfoError("obfuscated2 handshake is failed", err)
if err := p.doObfuscatedHandshake(ctx); err != nil {
p.logger.InfoError("obfuscated handshake is failed", err)
return
}
@@ -110,7 +120,7 @@ func (p *Proxy) Serve(listener net.Listener) error {
logger := p.logger.BindStr("ip", ipAddr.String())
if !p.allowlist.Contains(ipAddr) {
conn.Close()
conn.Close() //nolint: errcheck
logger.Info("ip was rejected by allowlist")
p.eventStream.Send(p.ctx, NewEventIPAllowlisted(ipAddr))
@@ -118,7 +128,7 @@ func (p *Proxy) Serve(listener net.Listener) error {
}
if p.blocklist.Contains(ipAddr) {
conn.Close()
conn.Close() //nolint: errcheck
logger.Info("ip was blacklisted")
p.eventStream.Send(p.ctx, NewEventIPBlocklisted(ipAddr))
@@ -144,6 +154,7 @@ func (p *Proxy) Shutdown() {
p.ctxCancel()
p.streamWaitGroup.Wait()
p.workerPool.Release()
p.configUpdater.Wait()
p.allowlist.Shutdown()
p.blocklist.Shutdown()
@@ -201,54 +212,61 @@ func (p *Proxy) doFakeTLSHandshake(ctx *streamContext) bool {
return true
}
func (p *Proxy) doObfuscated2Handshake(ctx *streamContext) error {
dc, encryptor, decryptor, err := obfuscated2.ClientHandshake(p.secret.Key[:], ctx.clientConn)
func (p *Proxy) doObfuscatedHandshake(ctx *streamContext) error {
dc, conn, err := p.clientObfuscatror.ReadHandshake(ctx.clientConn)
if err != nil {
return fmt.Errorf("cannot process client handshake: %w", err)
}
ctx.dc = dc
ctx.clientConn = conn
ctx.logger = ctx.logger.BindInt("dc", dc)
ctx.clientConn = obfuscated2.Conn{
Conn: ctx.clientConn,
Encryptor: encryptor,
Decryptor: decryptor,
}
return nil
}
func (p *Proxy) doTelegramCall(ctx *streamContext) error {
dc := ctx.dc
if p.allowFallbackOnUnknownDC && !p.telegram.IsKnownDC(dc) {
dc = p.telegram.GetFallbackDC()
ctx.logger = ctx.logger.BindInt("fallback_dc", dc)
dcid := ctx.dc
addresses := p.telegram.GetAddresses(dcid)
if len(addresses) == 0 && p.allowFallbackOnUnknownDC {
ctx.logger = ctx.logger.BindInt("original_dc", dcid)
ctx.logger.Warning("unknown DC, fallbacks")
ctx.dc = dc.DefaultDC
addresses = p.telegram.GetAddresses(dc.DefaultDC)
}
conn, err := p.telegram.Dial(ctx, dc)
var (
conn essentials.Conn
err error
foundAddr dc.Addr
)
for _, addr := range addresses {
conn, err = p.network.Dial(addr.Network, addr.Address)
if err == nil {
foundAddr = addr
break
}
}
if err != nil {
return fmt.Errorf("cannot dial to Telegram: %w", err)
return fmt.Errorf("no addresses to call: %w", err)
}
if conn == nil {
return fmt.Errorf("no available addresses for DC %d", ctx.dc)
}
encryptor, decryptor, err := obfuscated2.ServerHandshake(conn)
tgConn, err := foundAddr.Obfuscator.SendHandshake(conn, ctx.dc)
if err != nil {
conn.Close()
return fmt.Errorf("cannot perform obfuscated2 handshake: %w", err)
conn.Close() // nolint: errcheck
return fmt.Errorf("cannot perform server handshake: %w", err)
}
ctx.telegramConn = obfuscated2.Conn{
Conn: connTraffic{
Conn: conn,
streamID: ctx.streamID,
stream: p.eventStream,
ctx: ctx,
},
Encryptor: encryptor,
Decryptor: decryptor,
ctx.telegramConn = connTraffic{
Conn: tgConn,
streamID: ctx.streamID,
stream: p.eventStream,
ctx: ctx,
}
p.eventStream.Send(ctx,
@@ -271,6 +289,10 @@ func (p *Proxy) doDomainFronting(ctx *streamContext, conn *connRewind) {
return
}
if p.domainFrontingProxyProtocol {
frontConn = newConnProxyProtocol(ctx.clientConn, frontConn)
}
frontConn = connTraffic{
Conn: frontConn,
ctx: ctx,
@@ -292,12 +314,15 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) {
return nil, fmt.Errorf("invalid settings: %w", err)
}
tg, err := telegram.New(opts.Network, opts.getPreferIP(), opts.UseTestDCs)
tg, err := dc.New(opts.getPreferIP())
if err != nil {
return nil, fmt.Errorf("cannot build telegram dialer: %w", err)
return nil, fmt.Errorf("cannot build telegram dc fetcher: %w", err)
}
ctx, cancel := context.WithCancel(context.Background())
logger := opts.getLogger("proxy")
updatersLogger := logger.Named("telegram-updaters")
proxy := &Proxy{
ctx: ctx,
ctxCancel: cancel,
@@ -307,15 +332,30 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) {
blocklist: opts.IPBlocklist,
allowlist: opts.IPAllowlist,
eventStream: opts.EventStream,
logger: opts.getLogger("proxy"),
logger: logger,
domainFrontingPort: opts.getDomainFrontingPort(),
domainFrontingIP: opts.DomainFrontingIP,
tolerateTimeSkewness: opts.getTolerateTimeSkewness(),
allowFallbackOnUnknownDC: opts.AllowFallbackOnUnknownDC,
telegram: tg,
configUpdater: dc.NewPublicConfigUpdater(
tg,
updatersLogger.Named("public-config"),
opts.Network.MakeHTTPClient(nil),
),
clientObfuscatror: obfuscation.Obfuscator{
Secret: opts.Secret.Key[:],
},
domainFrontingProxyProtocol: opts.DomainFrontingProxyProtocol,
}
if opts.AutoUpdate {
proxy.configUpdater.Run(ctx, dc.PublicConfigUpdateURLv4, "tcp4")
proxy.configUpdater.Run(ctx, dc.PublicConfigUpdateURLv6, "tcp6")
}
pool, err := ants.NewPoolWithFunc(opts.getConcurrency(),
func(arg interface{}) {
func(arg any) {
proxy.ServeConn(arg.(essentials.Conn)) //nolint: forcetypeassert
},
ants.WithLogger(opts.getLogger("ants")),
+31
View File
@@ -85,6 +85,12 @@ type ProxyOpts struct {
// This is an optional setting.
PreferIP string
// AutoUpdate defines if it is required to auto update proxy list from
// Telegram instead of relying on a hardcoded list.
//
// This is an optional setting.
AutoUpdate bool
// DomainFrontingPort is a port we use to connect to a fronting domain.
//
// This is required because secret does not specify a port. It specifies a
@@ -93,6 +99,23 @@ type ProxyOpts struct {
// This is an optional setting.
DomainFrontingPort uint
// DomainFrontingIP is an IP address to use when connecting to the fronting
// domain instead of resolving the hostname from the secret via DNS.
//
// This is useful when DNS resolution of the fronting host is blocked.
// The hostname from the secret is still used for SNI in the TLS handshake.
//
// This is an optional setting.
DomainFrontingIP string
// DomainFrontingProxyProtocol is used if communication between upstream
// endpoint and mtg supports proxy protocol. This is useful in case
// if mtg is also placed behind load balancer, and this will make
// fronting webserver to know about real IP addresses
//
// This is an optional setting.
DomainFrontingProxyProtocol bool
// AllowFallbackOnUnknownDC defines how proxy behaves if unknown DC was
// requested. If this setting is set to false, then such connection will be
// rejected. Otherwise, proxy will chose any DC.
@@ -110,7 +133,15 @@ type ProxyOpts struct {
// Telegram-related projects.
//
// This is an optional setting.
//
// OBSOLETE and DEPRECATED. Ignored.
UseTestDCs bool
// DCOverrides defines a set of IP addresses that should be used
// with a higher priority to those that are calculated somehow by mtg.
//
// OBSOLETE and DEPRECATED. Ignored.
DCOverrides map[int][]string
}
func (p ProxyOpts) valid() error {
+2 -33
View File
@@ -1,9 +1,7 @@
package mtglib_test
import (
"context"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"io"
@@ -19,9 +17,6 @@ import (
"github.com/9seconds/mtg/v2/logger"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/network"
"github.com/gotd/td/telegram"
"github.com/gotd/td/telegram/dcs"
"github.com/gotd/td/tg"
"github.com/stretchr/testify/suite"
"github.com/yl2chen/cidranger"
)
@@ -91,7 +86,7 @@ func (suite *ProxyTestSuite) SetupSuite() {
func (suite *ProxyTestSuite) TearDownSuite() {
if suite.listener != nil {
suite.listener.Close()
suite.listener.Close() //nolint: errcheck
}
if suite.p != nil {
@@ -182,7 +177,7 @@ func (suite *ProxyTestSuite) TestHTTPSRequest() {
resp, err := client.Get(addr) //nolint: noctx
suite.NoError(err)
defer resp.Body.Close()
defer resp.Body.Close() //nolint: errcheck
suite.Equal(http.StatusOK, resp.StatusCode)
@@ -199,32 +194,6 @@ func (suite *ProxyTestSuite) TestHTTPSRequest() {
suite.NotEmpty(jsonStruct.Headers.TraceID)
}
func (suite *ProxyTestSuite) TestMakeRealRequest() {
secret, _ := hex.DecodeString(suite.opts.Secret.Hex())
resolver, err := dcs.MTProxyResolver(
suite.ProxyAddress(),
secret,
dcs.MTProxyOptions{},
)
suite.NoError(err)
tgClient := telegram.NewClient(telegram.TestAppID,
telegram.TestAppHash,
telegram.Options{
Resolver: resolver,
})
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
suite.NoError(tgClient.Run(ctx, func(ctx context.Context) error {
_, err := tg.NewClient(tgClient).HelpGetConfig(ctx)
suite.NoError(err)
return err //nolint: wrapcheck
}))
}
func TestProxy(t *testing.T) {
t.Parallel()
suite.Run(t, &ProxyTestSuite{})
+1 -1
View File
@@ -74,7 +74,7 @@ func (s *Secret) Set(text string) error {
return fmt.Errorf("incorrect secret format: %w", err)
}
if len(decoded) < 2 { //nolint: gomnd // we need at least 1 byte here
if len(decoded) < 2 { // we need at least 1 byte here
return fmt.Errorf("secret is truncated, length=%d", len(decoded))
}
+3 -3
View File
@@ -32,7 +32,7 @@ func (s *streamContext) Err() error {
return s.ctx.Err() //nolint: wrapcheck
}
func (s *streamContext) Value(key interface{}) interface{} {
func (s *streamContext) Value(key any) any {
return s.ctx.Value(key)
}
@@ -40,11 +40,11 @@ func (s *streamContext) Close() {
s.ctxCancel()
if s.clientConn != nil {
s.clientConn.Close()
s.clientConn.Close() //nolint: errcheck
}
if s.telegramConn != nil {
s.telegramConn.Close()
s.telegramConn.Close() //nolint: errcheck
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ func (suite *StreamContextTestSuite) SetupSuite() {
func (suite *StreamContextTestSuite) SetupTest() {
ctx, cancel := context.WithCancel(context.Background())
ctx = context.WithValue(ctx, "key", "value") //nolint: golint, staticcheck
ctx = context.WithValue(ctx, "key", "value") //nolint: staticcheck
suite.ctxCancel = cancel
suite.connMock = &testlib.EssentialsConnMock{}
+2 -2
View File
@@ -56,7 +56,7 @@ func (c *circuitBreakerDialer) doClosed(ctx context.Context,
select {
case <-ctx.Done():
if conn != nil {
conn.Close()
conn.Close() //nolint: errcheck
}
return nil, ctx.Err() //nolint: wrapcheck
@@ -93,7 +93,7 @@ func (c *circuitBreakerDialer) doHalfOpened(ctx context.Context,
select {
case <-ctx.Done():
if conn != nil {
conn.Close()
conn.Close() //nolint: errcheck
}
return nil, ctx.Err() //nolint: wrapcheck

Some files were not shown because too many files have changed in this diff Show More