REPOSITORY / ScuroNeko/mtg

Compare commits

DIFF REPOSITORY

Compare commits

...
116 Commits
Author SHA1 Message Date
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
9seconds 269852a4f1 Merge remote-tracking branch 'origin/master' into stable 2022-08-09 17:26:42 +03:00
Sergey ArkhipovandGitHub bd8a7ed8c9 Merge pull request #278 from 9seconds/socks5-proxies-for-simple-run
Add parameter for a simple run that sets socks5 proxies up
2022-08-09 16:24:37 +02:00
9seconds 7e3e4a611d Add parameter for a simple run that sets socks5 proxies up 2022-08-09 17:02:49 +03:00
Sergey ArkhipovandGitHub db2be4001b Merge pull request #277 from 9seconds/go1.19 2022-08-08 22:12:13 +02:00
9seconds 05c99aee4a Update github workflow 2022-08-08 16:52:58 +03:00
9seconds 53dde2aafa Update dependencies 2022-08-08 16:51:47 +03:00
9seconds 36dad5a2f6 Update golangci-lint 2022-08-08 15:54:38 +03:00
9seconds 6a19ded78e Update docs 2022-08-04 18:39:00 +03:00
9seconds 008e17cdff Update golangci-lint to 1.47.3 2022-08-04 11:25:10 +03:00
9seconds 5ad64390d6 There is no need to set rlimit in go 1.19 2022-08-04 10:58:31 +03:00
9seconds f9ad93dd06 More comprehensive build info collection 2022-08-04 10:57:28 +03:00
9seconds 77edea0e18 Update Go in Dockerfile to 1.19 2022-08-04 10:18:32 +03:00
Sergey ArkhipovandGitHub 79f921fde9 Merge pull request #271 from Dank-del/patch-1 2022-07-02 21:41:17 +02:00
Sayan BiswasandGitHub 1cdaaa16b0 change usage of $PWD in docker command 2022-07-02 17:36:03 +05:30
Sayan BiswasandGitHub 5852f48ca5 Fix instructions in readme 2022-06-30 14:59:36 +05:30
9seconds 0e80222990 Merge remote-tracking branch 'origin/stable' 2022-03-21 16:14:15 +03:00
9seconds c07e3d573d Merge remote-tracking branch 'origin/master' into stable 2022-03-21 16:08:55 +03:00
9seconds f192a718f4 Fix test 2022-03-21 15:58:15 +03:00
Sergey ArkhipovandGitHub 0c4523d2c8 Merge pull request #263 from 9seconds/tags-for-ipblocklisted
Tags for ipblocklisted
2022-03-21 14:21:31 +03:00
9seconds b0063a4a25 Increase test time in CI 2022-03-21 14:21:07 +03:00
9seconds 6dc494e7d0 Update README 2022-03-21 13:42:13 +03:00
9seconds 534d5b755e Add tags for ip blocklisted metric 2022-03-21 13:42:13 +03:00
9seconds aa7e488a3a Update dependencies 2022-03-21 12:42:23 +03:00
Sergey ArkhipovandGitHub eba3673e27 Merge pull request #262 from 9seconds/better-whitelist
More elegant management of ip allowlists
2022-03-21 12:13:25 +03:00
9seconds a27facaa16 More elegant management of ip allowlists 2022-03-21 12:04:31 +03:00
Sergey ArkhipovandGitHub 78a9ff18a5 Merge pull request #261 from arch-btw/patch-1
Add AmbientCapabilities
2022-03-21 11:11:12 +03:00
9seconds c0899d0743 Add in-memory file 2022-03-21 10:18:50 +03:00
arch-btwandGitHub 12563219e6 Add AmbientCapabilities
AmbientCapabilities=CAP_NET_BIND_SERVICE
2022-03-20 08:35:20 -07:00
Sergey ArkhipovandGitHub 59b5ff4080 Merge pull request #259 from 9seconds/go118
Support of Go 1.18
2022-03-19 15:18:21 +03:00
9seconds d7e554540d Use recommended way of running golangci-lint install 2022-03-19 14:45:58 +03:00
9seconds 1575b82688 Update golangci-lint 2022-03-19 14:45:58 +03:00
9seconds 5df1f594cc Use embedded buildinfo 2022-03-18 18:06:56 +03:00
9seconds 02ad052c49 Update update-deps make command 2022-03-18 18:06:56 +03:00
9seconds 2b8c7825ca Run fuzzing in GitHub actions 2022-03-18 18:06:56 +03:00
9seconds 75357b3e3c Add fuzz to makefile 2022-03-18 17:26:57 +03:00
9seconds d8b14dc765 Add fuzzing for client hello 2022-03-18 17:24:36 +03:00
9seconds e0850869ba Linting 2022-03-18 14:58:08 +03:00
9seconds 9375552180 Add fuzz tests for obfuscated2 2022-03-18 14:58:03 +03:00
9seconds cbe5b8c94e Update go.mod to 1.18 2022-03-16 09:39:58 +03:00
9seconds 63b425f245 Use Go 1.18 only for CI
This is required due to fuzzing
2022-03-16 09:37:52 +03:00
9seconds b53ead4372 Build image on go 1.18 2022-03-16 09:37:30 +03:00
Sergey ArkhipovandGitHub 2cdd66c722 Merge pull request #258 from AHOHNMYC/readme-service-edit
More detail and secure service example
2022-03-14 09:37:45 +03:00
AHOHNMYCandGitHub 2ae0101d18 More detail and secure service example 2022-03-13 04:52:56 +03:00
9seconds 6da9c2e58d Merge remote-tracking branch 'origin/stable' 2022-03-11 18:17:18 +03:00
129 changed files with 2173 additions and 2000 deletions
+55 -39
View File
@@ -38,36 +38,40 @@ jobs:
test: test:
name: Test name: Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 5 timeout-minutes: 10
strategy:
matrix:
go_version:
- ^1.17
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Setup Go - uses: jdx/mise-action@v3
uses: actions/setup-go@v2 name: Install mise
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-
- name: Run tests - name: Run tests
run: go test -coverprofile=./coverage.txt -covermode=atomic -v ./... run: mise tasks run covtest
- name: Collect coverage - name: Collect coverage
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v5
with: with:
file: ./coverage.txt files: ./coverage.txt
fuzz:
name: Fuzzing
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- uses: jdx/mise-action@v3
name: Install mise
- name: Run fuzzing
run: mise tasks run 'test:fuzz:*'
lint: lint:
name: Lint name: Lint
@@ -75,40 +79,52 @@ jobs:
timeout-minutes: 5 timeout-minutes: 5
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- uses: jdx/mise-action@v3
name: Install mise
- name: Run linter - name: Run linter
uses: golangci/golangci-lint-action@v2 run: mise tasks run lint
with:
version: v1.44.2
docker: docker:
name: Docker name: Docker
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 20 timeout-minutes: 20
permissions:
contents: read
packages: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Get Docker meta - name: Get Docker meta
id: docker_meta id: meta
uses: crazy-max/ghaction-docker-meta@v1 uses: docker/metadata-action@v5
with: with:
images: nineseconds/mtg,ghcr.io/9seconds/mtg images: |
tag-semver: "{{version}},{{major}},{{major}}.{{minor}}" 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 - name: Setup QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v3
- name: Setup BuildX - name: Setup BuildX
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v3
- name: Setup cache - name: Setup cache
uses: actions/cache@v2 uses: actions/cache@v5
with: with:
path: /tmp/buildx-cache path: /tmp/buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }} key: ${{ runner.os }}-buildx-${{ github.sha }}
@@ -117,18 +133,18 @@ jobs:
- name: Login to DockerHub - name: Login to DockerHub
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@v1 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }} password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to GHCR.io - name: Login to GitHub Container Registry
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@v1 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.actor }}
password: ${{ secrets.GH_PAT }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
@@ -137,7 +153,7 @@ jobs:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7,linux/arm/v6 platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7,linux/arm/v6
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=local,src=/tmp/buildx-cache cache-from: type=local,src=/tmp/buildx-cache
cache-to: type=local,dest=/tmp/buildx-cache cache-to: type=local,dest=/tmp/buildx-cache
-3
View File
@@ -6,8 +6,5 @@
*.test *.test
*.out *.out
mtg mtg
vendor/
ccbuilds/
.bin/
coverage.txt coverage.txt
dist/ dist/
+17 -8
View File
@@ -1,12 +1,21 @@
# https://golangci-lint.run/docs/configuration/file/
version = '2'
[run] [run]
concurrency = 4 concurrency = 4
deadline = "2m"
tests = true tests = true
skip-dirs = ["vendor"]
[output] # [linters]
format = "colored-line-number" # enable-all = true
# disable = [
[linters] # "containedctx",
enable-all = true # "exhaustivestruct",
disable = ["ireturn", "varnamelen", "gochecknoglobals", "gas", "goerr113", "exhaustivestruct", "containedctx"] # "exhaustruct",
# "gas",
# "gochecknoglobals",
# "goerr113",
# "ireturn",
# "thelper",
# "varnamelen",
# ]
+7 -3
View File
@@ -1,5 +1,7 @@
--- ---
version: 2
project_name: mtg project_name: mtg
before: before:
@@ -41,11 +43,13 @@ builds:
archives: archives:
- name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' - name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
format: tar.gz formats:
- tar.gz
wrap_in_directory: true wrap_in_directory: true
format_overrides: format_overrides:
- goos: windows - goos: windows
format: zip formats:
- zip
files: files:
- LICENSE - LICENSE
- README.md - README.md
@@ -55,7 +59,7 @@ gomod:
proxy: true proxy: true
snapshot: snapshot:
name_template: '{{ .Version }}' version_template: '{{ .Version }}'
checksum: checksum:
name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt' name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt'
+101
View File
@@ -0,0 +1,101 @@
[tools]
"go:golang.org/x/pkgsite/cmd/pkgsite" = "latest"
"go:golang.org/x/tools/gopls" = "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.25"
]
[tasks.lint]
description = "Run linter"
run = "golangci-lint run"
[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=FuzzClientHandshake ./mtglib/internal/obfuscated2"
[tasks."test:fuzz:server-generate-handshake-frame"]
description = "Run fuzzy test for ServerGenerateHandshakeFrame"
run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzServerGenerateHandshakeFrame ./mtglib/internal/obfuscated2"
[tasks."test:fuzz:server-receive"]
description = "Run fuzzy test for ServerReceive"
run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzServerReceive ./mtglib/internal/obfuscated2"
[tasks."test:fuzz:server-send"]
description = "Run fuzzy test for ServerSend"
run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzServerSend ./mtglib/internal/obfuscated2"
[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 # BUILD STAGE
FROM golang:1.17-alpine AS build FROM golang:1.26-alpine AS build
ENV CGO_ENABLED=0
RUN set -x \ RUN set -x \
&& apk --no-cache --update add \ && apk --no-cache --update add \
bash \ bash \
ca-certificates \ ca-certificates \
curl \ git
git \
make
COPY . /app COPY . /app
WORKDIR /app WORKDIR /app
RUN set -x \ 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
############################################################################### ###############################################################################
-98
View File
@@ -1,98 +0,0 @@
ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
IMAGE_NAME := mtg
APP_NAME := $(IMAGE_NAME)
GOLANGCI_LINT_VERSION := v1.44.2
VERSION_GO := $(shell go version)
VERSION_DATE := $(shell date -Ru)
VERSION_TAG := $(shell git describe --tags --always)
COMMON_BUILD_FLAGS := -trimpath -mod=readonly -ldflags="-extldflags '-static' -s -w -X 'main.version=$(VERSION_TAG) ($(VERSION_GO)) [$(VERSION_DATE)]'"
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://install.goreleaser.com/github.com/golangci/golangci-lint.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.17
+36 -7
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 **If you use v1.0 or upgrade broke you proxy, please read the chapter
[Version 2](#version-2)** [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 ## Rationale
There are several available proxies for Telegram MTPROTO available. Here There are several available proxies for Telegram MTPROTO available. Here
@@ -18,6 +29,7 @@ are the most notable:
* [Official](https://github.com/TelegramMessenger/MTProxy) * [Official](https://github.com/TelegramMessenger/MTProxy)
* [Python](https://github.com/alexbers/mtprotoproxy) * [Python](https://github.com/alexbers/mtprotoproxy)
* [Erlang](https://github.com/seriyps/mtproto_proxy) * [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 You can use any of these. They work great and all implementations have
feature parity now. This includes support of adtag, replay attack 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 way of doing business I suppose. I think the only viable way is to
have a proxy that can be restored anywhere easily. 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** * **A single secret**
I think that multiple secrets solve no problems and just complex I think that multiple secrets solve no problems and just complex
@@ -162,6 +180,12 @@ This project has several main branches
## Getting started ## 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 a tool
#### Download binaries #### Download binaries
@@ -190,7 +214,7 @@ surprises. Always choose some version tag.
Also, if you have `go` installed, you can always download this tool with `go get`: Also, if you have `go` installed, you can always download this tool with `go get`:
```console ```console
go get github.com/9seconds/mtg/v2 go install github.com/9seconds/mtg/v2@latest
``` ```
#### Build from sources #### Build from sources
@@ -198,13 +222,14 @@ go get github.com/9seconds/mtg/v2
```console ```console
git clone https://github.com/9seconds/mtg.git git clone https://github.com/9seconds/mtg.git
cd mtg cd mtg
make static mise install
mise tasks run build
``` ```
or for the docker image: or for the docker image:
```console ```console
make docker mise tasks run image
``` ```
### Generate secret ### Generate secret
@@ -275,7 +300,7 @@ Flags:
-b, --tcp-buffer="4KB" Size of TCP buffer to use. -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. -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. -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 -t, --timeout=10s Network timeout to use
-a, --antireplay-cache-size="1MB" A size of anti-replay cache to use. -a, --antireplay-cache-size="1MB" A size of anti-replay cache to use.
``` ```
@@ -317,12 +342,16 @@ Now you can create a systemd unit:
```console ```console
$ cat /etc/systemd/system/mtg.service $ cat /etc/systemd/system/mtg.service
[Unit] [Unit]
Description=mtg Description=mtg - MTProto proxy server
Documentation=https://github.com/9seconds/mtg
After=network.target
[Service] [Service]
ExecStart=/usr/local/bin/mtg run /etc/mtg.toml ExecStart=/usr/local/bin/mtg run /etc/mtg.toml
Restart=always Restart=always
RestartSec=3 RestartSec=3
DynamicUser=true
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@@ -334,7 +363,7 @@ $ sudo systemctl start mtg
or you can run a docker image or you can run a docker image
```console ```console
docker run -d -v /etc/mtg.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 where _443_ is a host port (a port you want to connect to from a
@@ -388,7 +417,7 @@ Here goes a list of metrics with their types but without a prefix.
| domain_fronting_traffic | counter | `direction` | Count of bytes, transmitted to/from fronting domain. | | domain_fronting_traffic | counter | `direction` | Count of bytes, transmitted to/from fronting domain. |
| domain_fronting | counter | | Count of domain fronting events. | | domain_fronting | counter | | Count of domain fronting events. |
| concurrency_limited | counter | | Count of events, when client connection was rejected due to concurrency limit. | | concurrency_limited | counter | | Count of events, when client connection was rejected due to concurrency limit. |
| ip_blocklisted | counter | | Count of events when client connection was rejected because IP was found in the blacklist. | | ip_blocklisted | counter | `ip_list` | Count of events when client connection was rejected because IP was found in the blocklist. |
| replay_attacks | counter | | Count of detected replay attacks. | | replay_attacks | counter | | Count of detected replay attacks. |
Tag meaning: Tag meaning:
+8 -8
View File
@@ -1,17 +1,17 @@
// Antireplay package has cache implementations that are effective // Antireplay package has cache implementations that are effective against
// against replay attacks. // replay attacks.
// //
// To understand more about replay attacks, please read documentation // To understand more about replay attacks, please read documentation for
// for mtglib.AntiReplayCache interface. This package has a list of some // [mtglib.AntiReplayCache] interface. This package has a list of some
// implementations of this interface. // implementations of this interface.
package antireplay package antireplay
const ( const (
// DefaultStableBloomFilterMaxSize is a recommended byte size for a // DefaultStableBloomFilterMaxSize is a recommended byte size for a stable
// stable bloom filter. // bloom filter.
DefaultStableBloomFilterMaxSize = 1024 * 1024 // 1MiB DefaultStableBloomFilterMaxSize = 1024 * 1024 // 1MiB
// DefaultStableBloomFilterErrorRate is a recommended default error // DefaultStableBloomFilterErrorRate is a recommended default error rate for a
// rate for a stable bloom filter. // stable bloom filter.
DefaultStableBloomFilterErrorRate = 0.001 DefaultStableBloomFilterErrorRate = 0.001
) )
+2 -3
View File
@@ -6,9 +6,8 @@ type noop struct{}
func (n noop) SeenBefore(_ []byte) bool { return false } func (n noop) SeenBefore(_ []byte) bool { return false }
// NewNoop returns an implementation that does nothing. A corresponding // NewNoop returns an implementation that does nothing. A corresponding method
// method always returns false, so this cache accepts everything you // always returns false, so this cache accepts everything you pass to it.
// pass to it.
func NewNoop() mtglib.AntiReplayCache { func NewNoop() mtglib.AntiReplayCache {
return noop{} return noop{}
} }
+9 -9
View File
@@ -20,19 +20,19 @@ func (s *stableBloomFilter) SeenBefore(digest []byte) bool {
return s.filter.TestAndAdd(digest) return s.filter.TestAndAdd(digest)
} }
// NewStableBloomFilter returns an implementation of AntiReplayCache // NewStableBloomFilter returns an implementation of AntiReplayCache based on
// based on stable bloom filter. // stable bloom filter.
// //
// http://webdocs.cs.ualberta.ca/~drafiei/papers/DupDet06Sigmod.pdf // http://webdocs.cs.ualberta.ca/~drafiei/papers/DupDet06Sigmod.pdf
// //
// The basic idea of a stable bloom filter is quite simple: each time // The basic idea of a stable bloom filter is quite simple: each time when you
// when you set a new element, you randomly reset P elements. There is a // set a new element, you randomly reset P elements. There is a hardcore math
// hardcore math which proves that if you choose this P correctly, you // which proves that if you choose this P correctly, you can maintain the same
// can maintain the same error rate for a stream of elements. // error rate for a stream of elements.
// //
// byteSize is the number of bytes you want to give to a bloom filter. // byteSize is the number of bytes you want to give to a bloom filter.
// errorRate is desired false-positive error rate. If you want to use // errorRate is desired false-positive error rate. If you want to use default
// default values, please pass 0 for byteSize and <0 for errorRate. // values, please pass 0 for byteSize and <0 for errorRate.
func NewStableBloomFilter(byteSize uint, errorRate float64) mtglib.AntiReplayCache { func NewStableBloomFilter(byteSize uint, errorRate float64) mtglib.AntiReplayCache {
if byteSize == 0 { if byteSize == 0 {
byteSize = DefaultStableBloomFilterMaxSize byteSize = DefaultStableBloomFilterMaxSize
@@ -42,7 +42,7 @@ func NewStableBloomFilter(byteSize uint, errorRate float64) mtglib.AntiReplayCac
errorRate = DefaultStableBloomFilterErrorRate errorRate = DefaultStableBloomFilterErrorRate
} }
sf := boom.NewDefaultStableBloomFilter(byteSize*8, errorRate) // nolint: gomnd sf := boom.NewDefaultStableBloomFilter(byteSize*8, errorRate)
sf.SetHash(xxhash.New64()) sf.SetHash(xxhash.New64())
return &stableBloomFilter{ return &stableBloomFilter{
+84
View File
@@ -0,0 +1,84 @@
package main
import (
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"fmt"
"io"
"runtime/debug"
"sort"
"strconv"
"time"
)
var version = "dev" // has to be set by ldflags
const (
buildInfoModuleStart byte = iota
buildInfoModuleFinish
buildInfoModuleDelimeter
)
func getVersion() string {
buildInfo, ok := debug.ReadBuildInfo()
if !ok {
return version
}
date := time.Now()
commit := ""
goVersion := buildInfo.GoVersion
dirtySuffix := ""
for _, setting := range buildInfo.Settings {
switch setting.Key {
case "vcs.time":
date, _ = time.Parse(time.RFC3339, setting.Value)
case "vcs.revision":
commit = setting.Value
case "vcs.modified":
if dirty, _ := strconv.ParseBool(setting.Value); dirty {
dirtySuffix = " [dirty]"
}
}
}
hasher := sha256.New()
checksumModule := func(mod *debug.Module) {
hasher.Write([]byte{buildInfoModuleStart})
io.WriteString(hasher, mod.Path) //nolint: errcheck
hasher.Write([]byte{buildInfoModuleDelimeter})
io.WriteString(hasher, mod.Version) //nolint: errcheck
hasher.Write([]byte{buildInfoModuleDelimeter})
io.WriteString(hasher, mod.Sum) //nolint: errcheck
hasher.Write([]byte{buildInfoModuleFinish})
}
io.WriteString(hasher, buildInfo.Path) //nolint: errcheck
binary.Write(hasher, binary.LittleEndian, uint64(1+len(buildInfo.Deps))) //nolint: errcheck
sort.Slice(buildInfo.Deps, func(i, j int) bool {
return buildInfo.Deps[i].Path > buildInfo.Deps[j].Path
})
checksumModule(&buildInfo.Main)
for _, module := range buildInfo.Deps {
checksumModule(module)
}
return fmt.Sprintf("%s (%s: %s on %s%s, modules checksum %s)",
version,
goVersion,
date.Format(time.RFC3339),
commit,
dirtySuffix,
base64.StdEncoding.EncodeToString(hasher.Sum(nil)))
}
+3 -3
View File
@@ -5,19 +5,19 @@ import (
"net" "net"
) )
// CloseableReader is a reader interface that can close its reading end. // CloseableReader is an [io.Reader] interface that can close its reading end.
type CloseableReader interface { type CloseableReader interface {
io.Reader io.Reader
CloseRead() error CloseRead() error
} }
// CloseableWriter is a writer that can close its writing end. // CloseableWriter is an [io.Writer] that can close its writing end.
type CloseableWriter interface { type CloseableWriter interface {
io.Writer io.Writer
CloseWrite() error CloseWrite() error
} }
// Conn is an extension of net.Conn that can close its ends. This mostly // Conn is an extension of [net.Conn] that can close its ends. This mostly
// implies TCP connections. // implies TCP connections.
type Conn interface { type Conn interface {
net.Conn net.Conn
+3 -3
View File
@@ -2,14 +2,14 @@ package events
import ( import (
"context" "context"
"math/rand" "math/rand/v2"
"runtime" "runtime"
"github.com/9seconds/mtg/v2/mtglib" "github.com/9seconds/mtg/v2/mtglib"
"github.com/OneOfOne/xxhash" "github.com/OneOfOne/xxhash"
) )
// EventStream is a default implementation of the mtglib.EventStream // EventStream is a default implementation of the [mtglib.EventStream]
// interface. // interface.
// //
// EventStream manages a set of goroutines, observers. Main // EventStream manages a set of goroutines, observers. Main
@@ -64,7 +64,7 @@ func NewEventStream(observerFactories []ObserverFactory) EventStream {
chans: make([]chan mtglib.Event, runtime.NumCPU()), 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) rv.chans[i] = make(chan mtglib.Event, 1)
if len(observerFactories) == 1 { if len(observerFactories) == 1 {
+20 -20
View File
@@ -1,19 +1,19 @@
// Events has a default implementations of EventStream for mtglib. // Events has a default implementations of EventStream for mtglib.
// //
// Please see documentation for mtglib.EventStream interface to get an // Please see documentation for [mtglib.EventStream] interface to get an idea
// idea of such an abstraction. This package has implementations for the // of such an abstraction. This package has implementations for the default
// default event stream. // event stream.
// //
// Default event stream has a list of its own concepts. First, all it // Default event stream has a list of its own concepts. First, all it does is a
// does is a routing of messages to known observers. It takes an event, // routing of messages to known observers. It takes an event, defines its type
// defines its type and pass this message to a method of the observer. // and pass this message to a method of the observer.
// //
// There might be many observers, but default event stream has a // There might be many observers, but default event stream has a guarantee
// guarantee though. It uses StreamID as a sharding key and guarantees // though. It uses StreamID as a sharding key and guarantees that a message
// that a message with the same StreamID will be devlivered to the same // with the same StreamID will be devlivered to the same observer instance. So,
// observer instance. So, each producer is guarateed to get all relevant // each producer is guarateed to get all relevant messages related to the same
// messages related to the same session. It is not possible that it will // session. It is not possible that it will get EventFinish if it has not seen
// get EventFinish if it has not seen EventStart for that session yet. // EventStart for that session yet.
package events package events
import "github.com/9seconds/mtg/v2/mtglib" import "github.com/9seconds/mtg/v2/mtglib"
@@ -21,10 +21,10 @@ import "github.com/9seconds/mtg/v2/mtglib"
// Observer is an instance that listens for the incoming events. // Observer is an instance that listens for the incoming events.
// //
// As it is said in the package description, the default event stream // As it is said in the package description, the default event stream
// guarantees that all events with the same StreamID are going to be // guarantees that all events with the same StreamID are going to be routed to
// routed to the same instance of the observer. So, there is no need // the same instance of the observer. So, there is no need to synchronize
// to synchronize information about streams between many observers // information about streams between many observers instances, they can have
// instances, they can have their local storage. // their local storage.
type Observer interface { type Observer interface {
// EventStart reacts on incoming mtglib.EventStart event. // EventStart reacts on incoming mtglib.EventStart event.
EventStart(mtglib.EventStart) EventStart(mtglib.EventStart)
@@ -65,8 +65,8 @@ type Observer interface {
// ObserverFactory creates a new instance of the observer. // ObserverFactory creates a new instance of the observer.
// //
// Default event stream creates a small set of goroutines to manage // Default event stream creates a small set of goroutines to manage incoming
// incoming messages. Each message is routed to an appropriate observer // messages. Each message is routed to an appropriate observer based on a
// based on a sharding key, stream id. So, it is possible that an // sharding key, stream id. So, it is possible that an instance of mtg will
// instance of mtg will have many observer instances, not a single one. // have many observer instances, not a single one.
type ObserverFactory func() Observer type ObserverFactory func() Observer
+27 -54
View File
@@ -12,14 +12,11 @@ type multiObserver struct {
func (m multiObserver) EventStart(evt mtglib.EventStart) { func (m multiObserver) EventStart(evt mtglib.EventStart) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventStart(evt)
})
obs.EventStart(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -27,14 +24,11 @@ func (m multiObserver) EventStart(evt mtglib.EventStart) {
func (m multiObserver) EventConnectedToDC(evt mtglib.EventConnectedToDC) { func (m multiObserver) EventConnectedToDC(evt mtglib.EventConnectedToDC) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventConnectedToDC(evt)
})
obs.EventConnectedToDC(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -42,14 +36,11 @@ func (m multiObserver) EventConnectedToDC(evt mtglib.EventConnectedToDC) {
func (m multiObserver) EventDomainFronting(evt mtglib.EventDomainFronting) { func (m multiObserver) EventDomainFronting(evt mtglib.EventDomainFronting) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventDomainFronting(evt)
})
obs.EventDomainFronting(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -57,14 +48,11 @@ func (m multiObserver) EventDomainFronting(evt mtglib.EventDomainFronting) {
func (m multiObserver) EventTraffic(evt mtglib.EventTraffic) { func (m multiObserver) EventTraffic(evt mtglib.EventTraffic) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventTraffic(evt)
})
obs.EventTraffic(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -72,14 +60,11 @@ func (m multiObserver) EventTraffic(evt mtglib.EventTraffic) {
func (m multiObserver) EventFinish(evt mtglib.EventFinish) { func (m multiObserver) EventFinish(evt mtglib.EventFinish) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventFinish(evt)
})
obs.EventFinish(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -87,14 +72,11 @@ func (m multiObserver) EventFinish(evt mtglib.EventFinish) {
func (m multiObserver) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimited) { func (m multiObserver) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimited) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventConcurrencyLimited(evt)
})
obs.EventConcurrencyLimited(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -102,14 +84,11 @@ func (m multiObserver) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimite
func (m multiObserver) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) { func (m multiObserver) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventIPBlocklisted(evt)
})
obs.EventIPBlocklisted(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -117,14 +96,11 @@ func (m multiObserver) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
func (m multiObserver) EventReplayAttack(evt mtglib.EventReplayAttack) { func (m multiObserver) EventReplayAttack(evt mtglib.EventReplayAttack) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventReplayAttack(evt)
})
obs.EventReplayAttack(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -132,14 +108,11 @@ func (m multiObserver) EventReplayAttack(evt mtglib.EventReplayAttack) {
func (m multiObserver) EventIPListSize(evt mtglib.EventIPListSize) { func (m multiObserver) EventIPListSize(evt mtglib.EventIPListSize) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventIPListSize(evt)
})
obs.EventIPListSize(evt)
}(v)
} }
wg.Wait() wg.Wait()
+20 -2
View File
@@ -23,6 +23,15 @@ secret = "ee367a189aee18fa31c190054efd4a8e9573746f726167652e676f6f676c6561706973
# Host:port pair to run proxy on. # Host:port pair to run proxy on.
bind-to = "0.0.0.0:3128" 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. # Defines how many concurrent connections are allowed to this proxy.
# All other incoming connections are going to be dropped. # All other incoming connections are going to be dropped.
concurrency = 8192 concurrency = 8192
@@ -71,6 +80,15 @@ tolerate-time-skewness = "5s"
# Otherwise, chose a new DC. # Otherwise, chose a new DC.
allow-fallback-on-unknown-dc = false allow-fallback-on-unknown-dc = false
# Telegram uses different DCs for different purposes. Unfortunately, most of
# DCs are not public, and dependent on a location of the current user, so
# mtg cannot know upfront about all of them, and how to access them. It has
# a default list of DCs, including some CDN IPs, but it is possible that some
# of them are not working for you. In this case, you can override them here.
[[dc-overrides]]
dc = 101
ips = ["127.0.0.1:443"]
# network defines different network-related settings # network defines different network-related settings
[network] [network]
# please be aware that mtg needs to do some external requests. For # please be aware that mtg needs to do some external requests. For
@@ -84,8 +102,8 @@ allow-fallback-on-unknown-dc = false
# resolver of the operating system and uses DOH instead. This is a host # resolver of the operating system and uses DOH instead. This is a host
# it has to access. # it has to access.
# #
# By default we use Quad9. # By default we use Cloudflare.
doh-ip = "9.9.9.9" doh-ip = "1.1.1.1"
# mtg can work via proxies (for now, we support only socks5). Proxy # mtg can work via proxies (for now, we support only socks5). Proxy
# configuration is done via list. So, you can specify many proxies # configuration is done via list. So, you can specify many proxies
+32 -35
View File
@@ -1,57 +1,54 @@
module github.com/9seconds/mtg/v2 module github.com/9seconds/mtg/v2
go 1.17 go 1.25
require ( require (
github.com/OneOfOne/xxhash v1.2.8 github.com/OneOfOne/xxhash v1.2.8
github.com/alecthomas/kong v0.5.0 github.com/alecthomas/kong v1.14.0
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6
github.com/d4l3k/messagediff v1.2.1 // indirect 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/jarcoal/httpmock v1.0.8
github.com/mccutchen/go-httpbin v1.1.1 github.com/mccutchen/go-httpbin v1.1.1
github.com/panjf2000/ants/v2 v2.4.8 github.com/panjf2000/ants/v2 v2.11.5
github.com/pelletier/go-toml v1.9.4 github.com/prometheus/client_golang v1.23.2
github.com/prometheus/client_golang v1.12.1 github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.19.2 // indirect
github.com/prometheus/procfs v0.7.3 // indirect github.com/rs/zerolog v1.34.0
github.com/rs/zerolog v1.26.1 github.com/smira/go-statsd v1.3.4
github.com/smira/go-statsd v1.3.2 github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/objx v0.3.0 // indirect github.com/stretchr/testify v1.11.1
github.com/stretchr/testify v1.7.0 github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43 golang.org/x/crypto v0.48.0
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 golang.org/x/net v0.49.0 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect golang.org/x/sys v0.41.0
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 google.golang.org/protobuf v1.36.11 // indirect
google.golang.org/protobuf v1.27.1 // indirect
) )
require ( require (
github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a 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 github.com/yl2chen/cidranger v1.0.2
) )
require ( require (
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.1.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gotd/ige v0.1.5 // indirect github.com/klauspost/compress v1.18.3 // indirect
github.com/gotd/xor v0.1.1 // indirect github.com/kr/text v0.2.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // 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/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/txthinking/runnergroup v0.0.0-20220212043759-8da8edb7dae8 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe // indirect github.com/txthinking/runnergroup v0.0.0-20250224021307-5864ffeb65ae // indirect
go.uber.org/atomic v1.7.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect
go.uber.org/multierr v1.6.0 // indirect golang.org/x/sync v0.19.0 // indirect
go.uber.org/zap v1.16.0 // indirect golang.org/x/tools v0.41.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
) )
+114 -544
View File
@@ -1,588 +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 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= 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/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/kong v0.5.0 h1:u8Kdw+eeml93qtMZ04iei0CFYve/WPcA5IFh+9wSskE= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v0.5.0/go.mod h1:uzxf/HUh0tj43x1AyJROl3JT7SgsZ5m+icOv1csRhc0= github.com/alecthomas/kong v1.14.0 h1:gFgEUZWu2ZmZ+UhyZ1bDhuutbKN1nTtJTwh19Wsn21s=
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48= github.com/alecthomas/kong v1.14.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 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/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 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg= 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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= 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/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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-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-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/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/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
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 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k= 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/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/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/k0kubun/pp v2.4.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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-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/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/mccutchen/go-httpbin v1.1.1 h1:aEws49HEJEyXHLDnshQVswfUlCVoS8g6h9YaDyaW7RE= 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/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/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/panjf2000/ants/v2 v2.11.5 h1:a7LMnMEeux/ebqTux140tRiaqcFTV0q2bEHF03nl6Rg=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/panjf2000/ants/v2 v2.11.5/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/panjf2000/ants/v2 v2.4.8 h1:JgTbolX6K6RreZ4+bfctI0Ifs+3mrE5BIHudQxUDQ9k=
github.com/panjf2000/ants/v2 v2.4.8/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 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/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/smira/go-statsd v1.3.4 h1:kBYWcLSGT+qC6JVbvfz48kX7mQys32fjDOPrfmsSx2c=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/smira/go-statsd v1.3.4/go.mod h1:RjdsESPgDODtg1VpVVf9MJrEW2Hw0wtRNbmB1CAhu6A=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
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 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
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.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.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.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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-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-20250224021307-5864ffeb65ae h1:ArVM1jICfm7g4E4dBet+KHUFMLuxmj1Nxdp/tr3ByCU=
github.com/txthinking/runnergroup v0.0.0-20220212043759-8da8edb7dae8/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM= github.com/txthinking/runnergroup v0.0.0-20250224021307-5864ffeb65ae/go.mod h1:cldYm15/XHcGt7ndItnEWHwFZo7dinU+2QoyjfErhsI=
github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a h1:BOqgJ4jku0LHPDoR51RD8Mxmo0LHxCzJT/M9MemYdHo= github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e h1:xA7GVlbz6teIF4FdvuqwbX6C4tiqNk2PH7FRPIDerao=
github.com/txthinking/socks5 v0.0.0-20220212043548-414499347d4a/go.mod h1:7NloQcrxaZYKURWph5HLxVDlIwMHJXCPkeWPtpftsIg= github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e/go.mod h1:ntmMHL/xPq1WLeKiw8p/eRATaae6PiVRNipHFJxI8PM=
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe h1:gMWxZxBFRAXqoGkwkYlPX2zvyyKNWJpxOxCrjqJkm5A= github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b h1:p+bJ3v5uUdEVMCoeFUs+BNJPsqt+Y6BLbDaPfTcbcH8=
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4= github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b/go.mod h1:OYRfF6eb5wY9VRFkXJH8FFBi3plw2v+giaIu7P054pM=
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/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= 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.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
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=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
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/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/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/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-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-20220722155255-886fb9371eb4/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.1.0/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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-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-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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/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-20220114195835-da31bd327af9/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-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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.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.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/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-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-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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
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/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
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.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.2.7/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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 := &sync.WaitGroup{}
wg.Add(2) // nolint: gomnd
go func() {
defer wg.Done()
wg.Go(func() {
ip := a.PublicIPv4 ip := a.PublicIPv4
if ip == nil { if ip == nil {
ip = a.getIP(ntw, "tcp4") ip = a.getIP(ntw, "tcp4")
@@ -76,11 +73,8 @@ func (a *Access) Run(cli *CLI, version string) error {
} }
resp.IPv4 = a.makeURLs(conf, ip) resp.IPv4 = a.makeURLs(conf, ip)
}() })
wg.Go(func() {
go func() {
defer wg.Done()
ip := a.PublicIPv6 ip := a.PublicIPv6
if ip == nil { if ip == nil {
ip = a.getIP(ntw, "tcp6") ip = a.getIP(ntw, "tcp6")
@@ -91,7 +85,7 @@ func (a *Access) Run(cli *CLI, version string) error {
} }
resp.IPv6 = a.makeURLs(conf, ip) resp.IPv6 = a.makeURLs(conf, ip)
}() })
wg.Wait() wg.Wait()
@@ -129,7 +123,7 @@ func (a *Access) getIP(ntw mtglib.Network, protocol string) net.IP {
defer func() { defer func() {
io.Copy(io.Discard, resp.Body) //nolint: errcheck io.Copy(io.Discard, resp.Body) //nolint: errcheck
resp.Body.Close() resp.Body.Close() //nolint: errcheck
}() }()
data, err := io.ReadAll(resp.Body) data, err := io.ReadAll(resp.Body)
+70 -13
View File
@@ -10,13 +10,17 @@ import (
"github.com/9seconds/mtg/v2/antireplay" "github.com/9seconds/mtg/v2/antireplay"
"github.com/9seconds/mtg/v2/events" "github.com/9seconds/mtg/v2/events"
"github.com/9seconds/mtg/v2/internal/config" "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/internal/utils"
"github.com/9seconds/mtg/v2/ipblocklist" "github.com/9seconds/mtg/v2/ipblocklist"
"github.com/9seconds/mtg/v2/ipblocklist/files"
"github.com/9seconds/mtg/v2/logger" "github.com/9seconds/mtg/v2/logger"
"github.com/9seconds/mtg/v2/mtglib" "github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/network" "github.com/9seconds/mtg/v2/network"
"github.com/9seconds/mtg/v2/stats" "github.com/9seconds/mtg/v2/stats"
"github.com/pires/go-proxyproto"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/yl2chen/cidranger"
) )
func makeLogger(conf *config.Config) mtglib.Logger { func makeLogger(conf *config.Config) mtglib.Logger {
@@ -89,7 +93,8 @@ func makeAntiReplayCache(conf *config.Config) mtglib.AntiReplayCache {
func makeIPBlocklist(conf config.ListConfig, func makeIPBlocklist(conf config.ListConfig,
logger mtglib.Logger, logger mtglib.Logger,
ntw mtglib.Network, ntw mtglib.Network,
updateCallback ipblocklist.FireholUpdateCallback) (mtglib.IPBlocklist, error) { updateCallback ipblocklist.FireholUpdateCallback,
) (mtglib.IPBlocklist, error) {
if !conf.Enabled.Get(false) { if !conf.Enabled.Get(false) {
return ipblocklist.NewNoop(), nil return ipblocklist.NewNoop(), nil
} }
@@ -105,7 +110,7 @@ func makeIPBlocklist(conf config.ListConfig,
} }
} }
firehol, err := ipblocklist.NewFirehol(logger.Named("ipblockist"), blocklist, err := ipblocklist.NewFirehol(logger.Named("ipblockist"),
ntw, ntw,
conf.DownloadConcurrency.Get(1), conf.DownloadConcurrency.Get(1),
remoteURLs, remoteURLs,
@@ -115,13 +120,53 @@ func makeIPBlocklist(conf config.ListConfig,
return nil, fmt.Errorf("incorrect parameters for firehol: %w", err) return nil, fmt.Errorf("incorrect parameters for firehol: %w", err)
} }
go firehol.Run(conf.UpdateEach.Get(ipblocklist.DefaultFireholUpdateEach)) go blocklist.Run(conf.UpdateEach.Get(ipblocklist.DefaultFireholUpdateEach))
return firehol, nil return blocklist, nil
}
func makeIPAllowlist(conf config.ListConfig,
logger mtglib.Logger,
ntw mtglib.Network,
updateCallback ipblocklist.FireholUpdateCallback,
) (mtglib.IPBlocklist, error) {
var (
allowlist mtglib.IPBlocklist
err error
)
if !conf.Enabled.Get(false) {
allowlist, err = ipblocklist.NewFireholFromFiles(
logger.Named("ipblocklist"),
1,
[]files.File{
files.NewMem([]*net.IPNet{
cidranger.AllIPv4,
cidranger.AllIPv6,
}),
},
updateCallback,
)
go allowlist.Run(conf.UpdateEach.Get(ipblocklist.DefaultFireholUpdateEach))
} else {
allowlist, err = makeIPBlocklist(
conf,
logger,
ntw,
updateCallback,
)
}
if err != nil {
return nil, fmt.Errorf("cannot build allowlist: %w", err)
}
return allowlist, nil
} }
func makeEventStream(conf *config.Config, logger mtglib.Logger) (mtglib.EventStream, error) { 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) { if conf.Stats.StatsD.Enabled.Get(false) {
statsdFactory, err := stats.NewStatsd( statsdFactory, err := stats.NewStatsd(
@@ -185,21 +230,24 @@ func runProxy(conf *config.Config, version string) error { // nolint: funlen
return fmt.Errorf("cannot build ip blocklist: %w", err) return fmt.Errorf("cannot build ip blocklist: %w", err)
} }
var whitelist mtglib.IPBlocklist allowlist, err := makeIPAllowlist(
if conf.Defense.Allowlist.Enabled.Get(false) {
whlist, err := makeIPBlocklist(
conf.Defense.Allowlist, conf.Defense.Allowlist,
logger.Named("allowlist"), logger.Named("allowlist"),
ntw, ntw,
func(ctx context.Context, size int) { func(ctx context.Context, size int) {
eventStream.Send(ctx, mtglib.NewEventIPListSize(size, false)) eventStream.Send(ctx, mtglib.NewEventIPListSize(size, false))
}) },
)
if err != nil { if err != nil {
return fmt.Errorf("cannot build ip allowlist: %w", err) return fmt.Errorf("cannot build ip allowlist: %w", err)
} }
whitelist = whlist dcOverrides := map[int][]string{}
for _, override := range conf.DCOverrides {
dcid := override.DC.Get()
for _, addr := range override.IPs {
dcOverrides[dcid] = append(dcOverrides[dcid], addr.Get(""))
}
} }
opts := mtglib.ProxyOpts{ opts := mtglib.ProxyOpts{
@@ -207,7 +255,7 @@ func runProxy(conf *config.Config, version string) error { // nolint: funlen
Network: ntw, Network: ntw,
AntiReplayCache: makeAntiReplayCache(conf), AntiReplayCache: makeAntiReplayCache(conf),
IPBlocklist: blocklist, IPBlocklist: blocklist,
IPWhitelist: whitelist, IPAllowlist: allowlist,
EventStream: eventStream, EventStream: eventStream,
Secret: conf.Secret, Secret: conf.Secret,
@@ -216,6 +264,7 @@ func runProxy(conf *config.Config, version string) error { // nolint: funlen
AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false), AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false),
TolerateTimeSkewness: conf.TolerateTimeSkewness.Value, TolerateTimeSkewness: conf.TolerateTimeSkewness.Value,
DCOverrides: dcOverrides,
} }
proxy, err := mtglib.NewProxy(opts) proxy, err := mtglib.NewProxy(opts)
@@ -228,12 +277,20 @@ func runProxy(conf *config.Config, version string) error { // nolint: funlen
return fmt.Errorf("cannot start proxy: %w", err) return fmt.Errorf("cannot start proxy: %w", err)
} }
if conf.ProxyProtocolListener.Get(false) {
listener = &proxyprotocol.ListenerAdapter{
Listener: proxyproto.Listener{
Listener: listener,
},
}
}
ctx := utils.RootContext() ctx := utils.RootContext()
go proxy.Serve(listener) //nolint: errcheck go proxy.Serve(listener) //nolint: errcheck
<-ctx.Done() <-ctx.Done()
listener.Close() listener.Close() //nolint: errcheck
proxy.Shutdown() proxy.Shutdown()
return nil return nil
+15 -4
View File
@@ -18,12 +18,13 @@ type SimpleRun struct {
TCPBuffer string `kong:"name='tcp-buffer',short='b',default='4KB',help='Deprecated and ignored'"` //nolint: lll 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 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 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 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 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 AntiReplayCacheSize string `kong:"name='antireplay-cache-size',short='a',default='1MB',help='A size of anti-replay cache to use.'"` //nolint: lll
} }
func (s *SimpleRun) Run(cli *CLI, version string) error { // nolint: cyclop func (s *SimpleRun) Run(cli *CLI, version string) error { //nolint: cyclop,funlen
conf := &config.Config{} conf := &config.Config{}
if err := conf.BindTo.Set(s.BindTo); err != nil { if err := conf.BindTo.Set(s.BindTo); err != nil {
@@ -34,7 +35,7 @@ func (s *SimpleRun) Run(cli *CLI, version string) error { // nolint: cyclop
return fmt.Errorf("incorrect secret: %w", err) 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) return fmt.Errorf("incorrect concurrency: %w", err)
} }
@@ -42,7 +43,7 @@ func (s *SimpleRun) Run(cli *CLI, version string) error { // nolint: cyclop
return fmt.Errorf("incorrect prefer-ip: %w", err) 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) return fmt.Errorf("incorrect domain-fronting-port: %w", err)
} }
@@ -66,6 +67,16 @@ func (s *SimpleRun) Run(cli *CLI, version string) error { // nolint: cyclop
return fmt.Errorf("incorrect antireplay-cache-size: %w", err) return fmt.Errorf("incorrect antireplay-cache-size: %w", err)
} }
for _, v := range s.Socks5Proxies {
proxyURL := config.TypeProxyURL{}
if err := proxyURL.Set(v); err != nil {
return fmt.Errorf("incorrect socks5 proxy URL: %w", err)
}
conf.Network.Proxies = append(conf.Network.Proxies, proxyURL)
}
conf.Debug.Value = s.Debug conf.Debug.Value = s.Debug
conf.AllowFallbackOnUnknownDC.Value = true conf.AllowFallbackOnUnknownDC.Value = true
conf.Defense.AntiReplay.Enabled.Value = true conf.Defense.AntiReplay.Enabled.Value = true
+5
View File
@@ -25,6 +25,7 @@ type Config struct {
AllowFallbackOnUnknownDC TypeBool `json:"allowFallbackOnUnknownDc"` AllowFallbackOnUnknownDC TypeBool `json:"allowFallbackOnUnknownDc"`
Secret mtglib.Secret `json:"secret"` Secret mtglib.Secret `json:"secret"`
BindTo TypeHostPort `json:"bindTo"` BindTo TypeHostPort `json:"bindTo"`
ProxyProtocolListener TypeBool `json:"proxyProtocolListener"`
PreferIP TypePreferIP `json:"preferIp"` PreferIP TypePreferIP `json:"preferIp"`
DomainFrontingPort TypePort `json:"domainFrontingPort"` DomainFrontingPort TypePort `json:"domainFrontingPort"`
TolerateTimeSkewness TypeDuration `json:"tolerateTimeSkewness"` TolerateTimeSkewness TypeDuration `json:"tolerateTimeSkewness"`
@@ -64,6 +65,10 @@ type Config struct {
MetricPrefix TypeMetricPrefix `json:"metricPrefix"` MetricPrefix TypeMetricPrefix `json:"metricPrefix"`
} `json:"prometheus"` } `json:"prometheus"`
} `json:"stats"` } `json:"stats"`
DCOverrides []struct {
DC TypeDC `json:"dc"`
IPs []TypeHostPort `json:"ips"`
} `json:"dcOverrides"`
} }
func (c *Config) Validate() error { func (c *Config) Validate() error {
+6 -1
View File
@@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/pelletier/go-toml" "github.com/pelletier/go-toml/v2"
) )
type tomlConfig struct { type tomlConfig struct {
@@ -13,6 +13,7 @@ type tomlConfig struct {
AllowFallbackOnUnknownDC bool `toml:"allow-fallback-on-unknown-dc" json:"allowFallbackOnUnknownDc,omitempty"` AllowFallbackOnUnknownDC bool `toml:"allow-fallback-on-unknown-dc" json:"allowFallbackOnUnknownDc,omitempty"`
Secret string `toml:"secret" json:"secret"` Secret string `toml:"secret" json:"secret"`
BindTo string `toml:"bind-to" json:"bindTo"` BindTo string `toml:"bind-to" json:"bindTo"`
ProxyProtocolListener bool `toml:"proxy-protocol-listener" json:"proxyProtocolListener"`
PreferIP string `toml:"prefer-ip" json:"preferIp,omitempty"` PreferIP string `toml:"prefer-ip" json:"preferIp,omitempty"`
DomainFrontingPort uint `toml:"domain-fronting-port" json:"domainFrontingPort,omitempty"` DomainFrontingPort uint `toml:"domain-fronting-port" json:"domainFrontingPort,omitempty"`
TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"` TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"`
@@ -59,6 +60,10 @@ type tomlConfig struct {
MetricPrefix string `toml:"metric-prefix" json:"metricPrefix,omitempty"` MetricPrefix string `toml:"metric-prefix" json:"metricPrefix,omitempty"`
} `toml:"prometheus" json:"prometheus,omitempty"` } `toml:"prometheus" json:"prometheus,omitempty"`
} `toml:"stats" json:"stats,omitempty"` } `toml:"stats" json:"stats,omitempty"`
DCOverrides []struct {
DC uint `toml:"dc" json:"dc"`
IPs []string `toml:"ips" json:"ips"`
} `toml:"dc-overrides" json:"dcOverrides,omitempty"`
} }
func Parse(rawData []byte) (*Config, error) { func Parse(rawData []byte) (*Config, error) {
+2 -2
View File
@@ -20,7 +20,7 @@ type TypeBoolTestSuite struct {
} }
func (suite *TypeBoolTestSuite) TestUnmarshalFail() { func (suite *TypeBoolTestSuite) TestUnmarshalFail() {
testData := []interface{}{ testData := []any{
"", "",
"np", "np",
"нет", "нет",
@@ -29,7 +29,7 @@ func (suite *TypeBoolTestSuite) TestUnmarshalFail() {
} }
for _, v := range testData { for _, v := range testData {
data, err := json.Marshal(map[string]interface{}{ data, err := json.Marshal(map[string]any{
"value": v, "value": v,
}) })
suite.NoError(err) suite.NoError(err)
+2 -2
View File
@@ -10,7 +10,7 @@ type TypeConcurrency struct {
} }
func (t *TypeConcurrency) Set(value string) error { 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 { if err != nil {
return fmt.Errorf("value is not uint (%s): %w", value, err) 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 { 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 { func (t *TypeErrorRate) Set(value string) error {
parsedValue, err := strconv.ParseFloat(value, 64) // nolint: gomnd parsedValue, err := strconv.ParseFloat(value, 64)
if err != nil { if err != nil {
return fmt.Errorf("value is not a float (%s): %w", value, err) 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 { 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) 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 { if err != nil {
return fmt.Errorf("incorrect port number (%v): %w", value, err) 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 { 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 { if err != nil {
return fmt.Errorf("incorrect port number (%v): %w", value, err) 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() callback()
writer.Close() writer.Close() //nolint: errcheck
<-closeChan <-closeChan
return strings.TrimSpace(buf.String()) return strings.TrimSpace(buf.String())
+2 -1
View File
@@ -25,6 +25,7 @@ func (m *MtglibNetworkMock) DialContext(ctx context.Context, network, address st
} }
func (m *MtglibNetworkMock) MakeHTTPClient(dialFunc func(ctx context.Context, func (m *MtglibNetworkMock) MakeHTTPClient(dialFunc func(ctx context.Context,
network, address string) (essentials.Conn, error)) *http.Client { network, address string) (essentials.Conn, error),
) *http.Client {
return m.Called(dialFunc).Get(0).(*http.Client) //nolint: forcetypeassert return m.Called(dialFunc).Get(0).(*http.Client) //nolint: forcetypeassert
} }
+1 -1
View File
@@ -18,7 +18,7 @@ func (l Listener) Accept() (net.Conn, error) {
} }
if err := network.SetClientSocketOptions(conn, 0); err != nil { if err := network.SetClientSocketOptions(conn, 0); err != nil {
conn.Close() conn.Close() //nolint: errcheck
return nil, fmt.Errorf("cannot set TCP options: %w", err) return nil, fmt.Errorf("cannot set TCP options: %w", err)
} }
-25
View File
@@ -1,25 +0,0 @@
//go:build !windows
// +build !windows
package utils
import (
"fmt"
"golang.org/x/sys/unix"
)
func SetLimits() error {
rLimit := unix.Rlimit{}
if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil {
return fmt.Errorf("cannot get rlimit: %w", err)
}
rLimit.Cur = rLimit.Max
if err := unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil {
return fmt.Errorf("cannot set rlimit: %w", err)
}
return nil
}
-8
View File
@@ -1,8 +0,0 @@
//go:build windows
// +build windows
package utils
func SetLimits() error {
return nil
}
+8
View File
@@ -0,0 +1,8 @@
// files defines a set of abstraction for 'files': an openable entities that
// could be read after.
//
// This is not a file on a filesystem of your local machine, it also can
// include "in memory" files or even remote ones, like HTTP endpoints. If you
// make a GET request to HTTP endpoint, then a body is readable and you can
// consider it as an openable file.
package files
+3 -1
View File
@@ -23,7 +23,7 @@ func (h httpFile) Open(ctx context.Context) (io.ReadCloser, error) {
if err != nil { if err != nil {
if response != nil { if response != nil {
io.Copy(io.Discard, response.Body) //nolint: errcheck 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) return nil, fmt.Errorf("cannot get url %s: %w", h.url, err)
@@ -40,6 +40,8 @@ func (h httpFile) String() string {
return h.url return h.url
} }
// NewHTTP returns a file abstraction for HTTP/HTTPS endpoint. You also need to
// provide a valid instance of [http.Client] to access it.
func NewHTTP(client *http.Client, endpoint string) (File, error) { func NewHTTP(client *http.Client, endpoint string) (File, error) {
if client == nil { if client == nil {
return nil, ErrBadHTTPClient return nil, ErrBadHTTPClient
+1 -1
View File
@@ -77,7 +77,7 @@ func (suite *HTTPTestSuite) TestOk() {
readCloser, err := file.Open(suite.ctx) readCloser, err := file.Open(suite.ctx)
suite.NoError(err) suite.NoError(err)
defer readCloser.Close() defer readCloser.Close() //nolint: errcheck
data, err := io.ReadAll(readCloser) data, err := io.ReadAll(readCloser)
suite.NoError(err) suite.NoError(err)
+7
View File
@@ -6,9 +6,16 @@ import (
"io" "io"
) )
// ErrBadHTTPClient is returned if given HTTP client is initialized
// incorrectly.
var ErrBadHTTPClient = errors.New("incorrect http client") var ErrBadHTTPClient = errors.New("incorrect http client")
// File is an abstraction for a entity that can be opened in some context.
type File interface { type File interface {
// Open returns an readable entity for a file. It is important to not forget
// to close it after the usage.
Open(context.Context) (io.ReadCloser, error) Open(context.Context) (io.ReadCloser, error)
// String returns a short text description for the file
String() string String() string
} }
+1
View File
@@ -19,6 +19,7 @@ func (l localFile) String() string {
return l.path return l.path
} }
// NewLocal returns an openable File for a path on a local file system.
func NewLocal(path string) (File, error) { func NewLocal(path string) (File, error) {
if stat, err := os.Stat(path); os.IsNotExist(err) || stat.IsDir() || stat.Mode().Perm()&0o400 == 0 { if stat, err := os.Stat(path); os.IsNotExist(err) || stat.IsDir() || stat.Mode().Perm()&0o400 == 0 {
return nil, fmt.Errorf("%s is not a readable file", path) return nil, fmt.Errorf("%s is not a readable file", path)
+38
View File
@@ -0,0 +1,38 @@
package files
import (
"context"
"io"
"net"
"strings"
)
type memFile struct {
data string
}
func (m memFile) Open(ctx context.Context) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader(m.data)), nil
}
func (m memFile) String() string {
return "mem"
}
// NewMem returns an openable file that is kept in RAM.
func NewMem(networks []*net.IPNet) File {
builder := strings.Builder{}
if len(networks) > 0 {
builder.WriteString(networks[0].String())
}
for i := 1; i < len(networks); i++ {
builder.WriteString("\n")
builder.WriteString(networks[i].String())
}
return memFile{
data: builder.String(),
}
}
+42
View File
@@ -0,0 +1,42 @@
package files_test
import (
"context"
"io"
"net"
"strings"
"testing"
"github.com/9seconds/mtg/v2/ipblocklist/files"
"github.com/stretchr/testify/suite"
)
type MemTestSuite struct {
suite.Suite
}
func (suite *MemTestSuite) TestOk() {
_, network1, _ := net.ParseCIDR("192.168.0.1/24")
_, network2, _ := net.ParseCIDR("2001:0db8:85a3:0000:0000:8a2e:0370:7334/36")
file := files.NewMem([]*net.IPNet{
network1,
network2,
})
reader, err := file.Open(context.Background())
suite.NoError(err)
data, err := io.ReadAll(reader)
suite.NoError(err)
strData := strings.TrimSpace(string(data))
suite.Contains(strData, "192.168.0.0/24")
suite.Contains(strData, "2001:db8:8000::/36")
}
func TestMem(t *testing.T) {
t.Parallel()
suite.Run(t, &MemTestSuite{})
}
+23 -21
View File
@@ -19,20 +19,20 @@ import (
var ( var (
fireholRegexpComment = regexp.MustCompile(`\s*#.*?$`) fireholRegexpComment = regexp.MustCompile(`\s*#.*?$`)
fireholIPv4DefaultCIDR = net.CIDRMask(32, 32) // nolint: gomnd fireholIPv4DefaultCIDR = net.CIDRMask(32, 32)
fireholIPv6DefaultCIDR = net.CIDRMask(128, 128) // nolint: gomnd fireholIPv6DefaultCIDR = net.CIDRMask(128, 128)
) )
// FireholUpdateCallback defines a signature of the callback that has to be // FireholUpdateCallback defines a signature of the callback that has to be
// execute when ip list is updated. // execute when ip list is updated.
type FireholUpdateCallback func(context.Context, int) type FireholUpdateCallback func(context.Context, int)
// Firehol is IPBlocklist which uses lists from FireHOL: // Firehol is [mtglib.IPBlocklist] which uses lists from FireHOL:
// https://iplists.firehol.org/ // https://iplists.firehol.org/
// //
// It can use both local files and remote URLs. This is not necessary // It can use both local files and remote URLs. This is not necessary that
// that blocklists should be taken from this website, we expect only // blocklists should be taken from this website, we expect only compatible
// compatible formats here. // formats here.
// //
// Example of the format: // Example of the format:
// //
@@ -78,8 +78,7 @@ func (f *Firehol) Contains(ip net.IP) bool {
// Run starts a background update process. // Run starts a background update process.
// //
// This is a blocking method so you probably want to run it in a // This is a blocking method so you probably want to run it in a goroutine.
// goroutine.
func (f *Firehol) Run(updateEach time.Duration) { func (f *Firehol) Run(updateEach time.Duration) {
if updateEach == 0 { if updateEach == 0 {
updateEach = DefaultFireholUpdateEach updateEach = DefaultFireholUpdateEach
@@ -113,30 +112,27 @@ func (f *Firehol) update() {
defer cancel() defer cancel()
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(f.blocklists))
mutex := &sync.Mutex{} mutex := &sync.Mutex{}
ranger := cidranger.NewPCTrieRanger() ranger := cidranger.NewPCTrieRanger()
for _, v := range f.blocklists { for _, v := range f.blocklists {
go func(file files.File) { wg.Go(func() {
defer wg.Done() logger := f.logger.BindStr("filename", v.String())
logger := f.logger.BindStr("filename", file.String()) fileContent, err := v.Open(ctx)
fileContent, err := file.Open(ctx)
if err != nil { if err != nil {
logger.WarningError("update has failed", err) logger.WarningError("update has failed", err)
return return
} }
defer fileContent.Close() defer fileContent.Close() //nolint: errcheck
if err := f.updateFromFile(mutex, ranger, bufio.NewScanner(fileContent)); err != nil { if err := f.updateFromFile(mutex, ranger, bufio.NewScanner(fileContent)); err != nil {
logger.WarningError("update has failed", err) logger.WarningError("update has failed", err)
} }
}(v) })
} }
wg.Wait() wg.Wait()
@@ -155,7 +151,8 @@ func (f *Firehol) update() {
func (f *Firehol) updateFromFile(mutex sync.Locker, func (f *Firehol) updateFromFile(mutex sync.Locker,
ranger cidranger.Ranger, ranger cidranger.Ranger,
scanner *bufio.Scanner) error { scanner *bufio.Scanner,
) error {
for scanner.Scan() { for scanner.Scan() {
text := scanner.Text() text := scanner.Text()
text = fireholRegexpComment.ReplaceAllLiteralString(text, "") text = fireholRegexpComment.ReplaceAllLiteralString(text, "")
@@ -210,13 +207,14 @@ func (f *Firehol) updateParseLine(text string) (*net.IPNet, error) {
// NewFirehol creates a new instance of FireHOL IP blocklist. // NewFirehol creates a new instance of FireHOL IP blocklist.
// //
// This method does not start an update process so please execute Run // This method does not start an update process so please execute Run when it
// when it is necessary. // is necessary.
func NewFirehol(logger mtglib.Logger, network mtglib.Network, func NewFirehol(logger mtglib.Logger, network mtglib.Network,
downloadConcurrency uint, downloadConcurrency uint,
urls []string, urls []string,
localFiles []string, localFiles []string,
updateCallback FireholUpdateCallback) (*Firehol, error) { updateCallback FireholUpdateCallback,
) (*Firehol, error) {
blocklists := []files.File{} blocklists := []files.File{}
for _, v := range localFiles { for _, v := range localFiles {
@@ -242,10 +240,14 @@ func NewFirehol(logger mtglib.Logger, network mtglib.Network,
return NewFireholFromFiles(logger, downloadConcurrency, blocklists, updateCallback) return NewFireholFromFiles(logger, downloadConcurrency, blocklists, updateCallback)
} }
// NewFirehol creates a new instance of FireHOL IP blocklist.
//
// This method creates this instances from a given list of files.
func NewFireholFromFiles(logger mtglib.Logger, func NewFireholFromFiles(logger mtglib.Logger,
downloadConcurrency uint, downloadConcurrency uint,
blocklists []files.File, blocklists []files.File,
updateCallback FireholUpdateCallback) (*Firehol, error) { updateCallback FireholUpdateCallback,
) (*Firehol, error) {
if downloadConcurrency == 0 { if downloadConcurrency == 0 {
downloadConcurrency = DefaultFireholDownloadConcurrency downloadConcurrency = DefaultFireholDownloadConcurrency
} }
+1 -1
View File
@@ -35,7 +35,7 @@ func (suite *FireholTestSuite) SetupSuite() {
panic(err) panic(err)
} }
defer filefp.Close() defer filefp.Close() //nolint: errcheck
io.Copy(w, filefp) //nolint: errcheck io.Copy(w, filefp) //nolint: errcheck
}) })
+5 -5
View File
@@ -1,8 +1,8 @@
// Package ipblocklist contains default implementation of the // Package ipblocklist contains default implementation of the
// IPBlocklist for mtg. // [mtglib.IPBlocklist] for mtg.
// //
// Please check documentation for mtglib.IPBlocklist interface to get an // Please check documentation for [mtglib.IPBlocklist] interface to get an idea
// idea of this abstraction. // of this abstraction.
package ipblocklist package ipblocklist
import "time" import "time"
@@ -12,7 +12,7 @@ const (
// concurrent downloads of ip blocklists for Firehol. // concurrent downloads of ip blocklists for Firehol.
DefaultFireholDownloadConcurrency = 1 DefaultFireholDownloadConcurrency = 1
// DefaultFireholUpdateEach defines a default time period when // DefaultFireholUpdateEach defines a default time period when Firehol
// Firehol requests updates of the blocklists. // requests updates of the blocklists.
DefaultFireholUpdateEach = 6 * time.Hour DefaultFireholUpdateEach = 6 * time.Hour
) )
+1 -2
View File
@@ -13,8 +13,7 @@ func (n noop) Contains(ip net.IP) bool { return false }
func (n noop) Run(updateEach time.Duration) {} func (n noop) Run(updateEach time.Duration) {}
func (n noop) Shutdown() {} func (n noop) Shutdown() {}
// NewNoop returns a dummy ipblocklist which allows all incoming // NewNoop returns a dummy ipblocklist which allows all incoming connections.
// connections.
func NewNoop() mtglib.IPBlocklist { func NewNoop() mtglib.IPBlocklist {
return noop{} return noop{}
} }
+7 -9
View File
@@ -1,14 +1,12 @@
// Package logger has implementation of loggers for mtglib.Logger // Package logger has implementation of loggers for [mtglib.Logger] interface.
// interface.
// //
// Please see a description of that interface to get some agreements // Please see a description of that interface to get some agreements which are
// which are used by mtglib. // used by mtglib.
package logger package logger
// StdLikeLogger is an interface which is close to log.Logger. This is // StdLikeLogger is an interface which is close to [log.Logger]. This is
// commonly used by many 3pp tools. While mtglib itself does not need // commonly used by many 3pp tools. While mtglib itself does not need it, it is
// it, it is always a good idea to support it and have a transient end // always a good idea to support it and have a transient end to end logging.
// to end logging.
type StdLikeLogger interface { 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) BindInt(_ string, _ int) mtglib.Logger { return n }
func (n noopLogger) BindStr(_, _ string) 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) 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) Info(_ string) {}
func (n noopLogger) Warning(_ string) {} func (n noopLogger) Warning(_ string) {}
func (n noopLogger) Debug(_ 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...)) z.Debug(fmt.Sprintf(format, args...))
} }
+1 -13
View File
@@ -9,26 +9,14 @@
package main package main
import ( import (
"math/rand"
"time"
"github.com/9seconds/mtg/v2/internal/cli" "github.com/9seconds/mtg/v2/internal/cli"
"github.com/9seconds/mtg/v2/internal/utils"
"github.com/alecthomas/kong" "github.com/alecthomas/kong"
) )
var version = "dev" // has to be set by ldflags
func main() { func main() {
rand.Seed(time.Now().UTC().UnixNano())
if err := utils.SetLimits(); err != nil {
panic(err)
}
cli := &cli.CLI{} cli := &cli.CLI{}
ctx := kong.Parse(cli, kong.Vars{ ctx := kong.Parse(cli, kong.Vars{
"version": version, "version": getVersion(),
}) })
ctx.FatalIfErrorf(ctx.Run(cli, version)) ctx.FatalIfErrorf(ctx.Run(cli, version))
+38
View File
@@ -0,0 +1,38 @@
[[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: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.13.3"
backend = "aqua:goreleaser/goreleaser"
"platforms.linux-arm64" = { checksum = "sha256:156656d0f874542d618568bd50afd3d33ced2e8aab2c60cc7c21e1b9fa52031e", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.13.3/goreleaser_Linux_arm64.tar.gz"}
"platforms.linux-x64" = { checksum = "sha256:4b66f2f78f78561330350651ade557b70328664718490f37834749073af21d20", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.13.3/goreleaser_Linux_x86_64.tar.gz"}
"platforms.macos-arm64" = { checksum = "sha256:5516c37779efb3935d5b213cda3b0b9025ae94ddbcb51df6919acbcdef4194b0", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.13.3/goreleaser_Darwin_all.tar.gz"}
"platforms.macos-x64" = { checksum = "sha256:5516c37779efb3935d5b213cda3b0b9025ae94ddbcb51df6919acbcdef4194b0", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.13.3/goreleaser_Darwin_all.tar.gz"}
"platforms.windows-x64" = { checksum = "sha256:c5586c4ed749ca358ad61ed73ee4b8039cfa68daae8c23e69fb086d549dfb31d", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.13.3/goreleaser_Windows_x86_64.zip"}
+31 -17
View File
@@ -29,13 +29,13 @@ type EventStart struct {
RemoteIP net.IP RemoteIP net.IP
} }
// EventConnectedToDC is emitted when mtg proxy has connected to a // EventConnectedToDC is emitted when mtg proxy has connected to a Telegram
// Telegram server. // server.
type EventConnectedToDC struct { type EventConnectedToDC struct {
eventBase eventBase
// RemoteIP is an IP address of the Telegram server proxy has been // RemoteIP is an IP address of the Telegram server proxy has been connected
// connected to. // to.
RemoteIP net.IP RemoteIP net.IP
// DC is an index of the datacenter proxy has been connected to. // DC is an index of the datacenter proxy has been connected to.
@@ -49,15 +49,15 @@ type EventTraffic struct {
// Traffic is a count of bytes which were transmitted. // Traffic is a count of bytes which were transmitted.
Traffic uint Traffic uint
// IsRead defines if we _read_ or _write_ to connection. A rule of // IsRead defines if we _read_ or _write_ to connection. A rule of thumb is
// thumb is simple: EventTraffic is bound to a remote connection. Not // simple: EventTraffic is bound to a remote connection. Not to a client one,
// to a client one, but either to Telegram or front domain one. // but either to Telegram or front domain one.
// //
// In the case of Telegram, isRead means that we've fetched some bytes // In the case of Telegram, isRead means that we've fetched some bytes from
// from Telegram to send it to a client. // Telegram to send it to a client.
// //
// In the case of the front domain, it means that we've fetched some // In the case of the front domain, it means that we've fetched some bytes
// bytes from this domain to send it to a client. // from this domain to send it to a client.
IsRead bool IsRead bool
} }
@@ -66,24 +66,25 @@ type EventFinish struct {
eventBase eventBase
} }
// EventDomainFronting is emitted when we connect to a front domain // EventDomainFronting is emitted when we connect to a front domain instead of
// instead of Telegram server. // Telegram server.
type EventDomainFronting struct { type EventDomainFronting struct {
eventBase eventBase
} }
// EventConcurrencyLimited is emitted when connection was declined // EventConcurrencyLimited is emitted when connection was declined because of
// because of the concurrency limit of the worker pool. // the concurrency limit of the worker pool.
type EventConcurrencyLimited struct { type EventConcurrencyLimited struct {
eventBase eventBase
} }
// EventIPBlocklisted is emitted when connection was declined because // EventIPBlocklisted is emitted when connection was declined because IP
// IP address was found in IP blocklist. // address was found in IP blocklist.
type EventIPBlocklisted struct { type EventIPBlocklisted struct {
eventBase eventBase
RemoteIP net.IP RemoteIP net.IP
IsBlockList bool
} }
// EventReplayAttack is emitted when mtg detects a replay attack on a // EventReplayAttack is emitted when mtg detects a replay attack on a
@@ -173,6 +174,19 @@ func NewEventIPBlocklisted(remoteIP net.IP) EventIPBlocklisted {
timestamp: time.Now(), timestamp: time.Now(),
}, },
RemoteIP: remoteIP, RemoteIP: remoteIP,
IsBlockList: true,
}
}
// NewEventIPAllowlisted creates a NewEventIPBlocklisted event with a mark that
// it is supposed to be for allow list.
func NewEventIPAllowlisted(remoteIP net.IP) EventIPBlocklisted {
return EventIPBlocklisted{
eventBase: eventBase{
timestamp: time.Now(),
},
RemoteIP: remoteIP,
IsBlockList: false,
} }
} }
+9
View File
@@ -60,6 +60,15 @@ func (suite *EventsTestSuite) TestEventIPBlocklisted() {
suite.Empty(evt.StreamID()) suite.Empty(evt.StreamID())
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond) suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
suite.True(evt.IsBlockList)
}
func (suite *EventsTestSuite) TestEventIPAllowlisted() {
evt := mtglib.NewEventIPAllowlisted(net.ParseIP("10.0.0.10"))
suite.Empty(evt.StreamID())
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
suite.False(evt.IsBlockList)
} }
func (suite *EventsTestSuite) TestEventReplayAttack() { func (suite *EventsTestSuite) TestEventReplayAttack() {
+120 -131
View File
@@ -1,20 +1,19 @@
// mtglib defines a package with MTPROTO proxy. // mtglib defines a package with MTPROTO proxy.
// //
// Since mtg itself is build as an example of how to work with mtglib, // Since mtg itself is build as an example of how to work with mtglib, it worth
// it worth to telling a couple of words about a project organization. // to telling a couple of words about a project organization.
// //
// A core object of the project is mtglib.Proxy. This is a proxy you // A core object of the project is [mtglib.Proxy]. This is a proxy you expect:
// expect: that one which you configure, set to serve on a listener // that one which you configure, set to serve on a listener and/or shutdown on
// and/or shutdown on application termination. // application termination.
// //
// But it also has a core logic unrelated to Telegram per se: anti // But it also has a core logic unrelated to Telegram per se: anti replay
// replay cache, network connectivity (who knows, maybe you want to have // cache, network connectivity (who knows, maybe you want to have a native
// a native VMESS integration) and so on. // VMESS integration) and so on.
// //
// You can supply such parts to a proxy with interfaces. The rest of // You can supply such parts to a proxy with interfaces. The rest of the
// the packages in mtg define some default implementations of these // packages in mtg define some default implementations of these interfaces. But
// interfaces. But if you want to integrate it with, let say, influxdb, // if you want to integrate it with, let say, influxdb, you can do it easily.
// you can do it easily.
package mtglib package mtglib
import ( import (
@@ -28,38 +27,42 @@ import (
) )
var ( var (
// ErrSecretEmpty is returned if you are trying to create a proxy // ErrSecretEmpty is returned if you are trying to create a proxy but do not
// but do not provide a secret. // provide a secret.
ErrSecretEmpty = errors.New("secret is empty") ErrSecretEmpty = errors.New("secret is empty")
// ErrSecretInvalid is returned if you are trying to create a proxy // ErrSecretInvalid is returned if you are trying to create a proxy but secret
// but secret value is invalid (no host or payload are zeroes). // value is invalid (no host or payload are zeroes).
ErrSecretInvalid = errors.New("secret is invalid") ErrSecretInvalid = errors.New("secret is invalid")
// ErrNetworkIsNotDefined is returned if you are trying to create a // ErrNetworkIsNotDefined is returned if you are trying to create a proxy but
// proxy but network value is undefined. // network value is undefined.
ErrNetworkIsNotDefined = errors.New("network is not defined") ErrNetworkIsNotDefined = errors.New("network is not defined")
// ErrAntiReplayCacheIsNotDefined is returned if you are trying to // ErrAntiReplayCacheIsNotDefined is returned if you are trying to create a
// create a proxy but anti replay cache value is undefined. // proxy but anti replay cache value is undefined.
ErrAntiReplayCacheIsNotDefined = errors.New("anti-replay cache is not defined") ErrAntiReplayCacheIsNotDefined = errors.New("anti-replay cache is not defined")
// ErrIPBlocklistIsNotDefined is returned if you are trying to // ErrIPBlocklistIsNotDefined is returned if you are trying to create a proxy
// create a proxy but ip blocklist instance is not defined. // but ip blocklist instance is not defined.
ErrIPBlocklistIsNotDefined = errors.New("ip blocklist is not defined") ErrIPBlocklistIsNotDefined = errors.New("ip blocklist is not defined")
// ErrEventStreamIsNotDefined is returned if you are trying to create a // ErrIPAllowlistIsNotDefined is returned if you are trying to create a proxy
// proxy but event stream instance is not defined. // but ip allowlist instance is not defined.
ErrIPAllowlistIsNotDefined = errors.New("ip allowlist is not defined")
// ErrEventStreamIsNotDefined is returned if you are trying to create a proxy
// but event stream instance is not defined.
ErrEventStreamIsNotDefined = errors.New("event stream is not defined") ErrEventStreamIsNotDefined = errors.New("event stream is not defined")
// ErrLoggerIsNotDefined is returned if you are trying to // ErrLoggerIsNotDefined is returned if you are trying to create a proxy but
// create a proxy but logger is not defined. // logger is not defined.
ErrLoggerIsNotDefined = errors.New("logger is not defined") ErrLoggerIsNotDefined = errors.New("logger is not defined")
) )
const ( const (
// DefaultConcurrency is a default max count of simultaneously // DefaultConcurrency is a default max count of simultaneously connected
// connected clients. // clients.
DefaultConcurrency = 4096 DefaultConcurrency = 4096
// DefaultBufferSize is a default size of a copy buffer. // DefaultBufferSize is a default size of a copy buffer.
@@ -67,31 +70,29 @@ const (
// Deprecated: this setting no longer makes any effect. // Deprecated: this setting no longer makes any effect.
DefaultBufferSize = 16 * 1024 // 16 kib DefaultBufferSize = 16 * 1024 // 16 kib
// DefaultDomainFrontingPort is a default port (HTTPS) to connect to in // DefaultDomainFrontingPort is a default port (HTTPS) to connect to in case
// case of probe-resistance activity. // of probe-resistance activity.
DefaultDomainFrontingPort = 443 DefaultDomainFrontingPort = 443
// DefaultIdleTimeout is a default timeout for closing a connection // DefaultIdleTimeout is a default timeout for closing a connection in case of
// in case of idling. // idling.
// //
// Deprecated: no longer in use because of changed TCP relay // Deprecated: no longer in use because of changed TCP relay algorithm.
// algorithm.
DefaultIdleTimeout = time.Minute DefaultIdleTimeout = time.Minute
// DefaultTolerateTimeSkewness is a default timeout for time // DefaultTolerateTimeSkewness is a default timeout for time skewness on a
// skewness on a faketls timeout verification. // faketls timeout verification.
DefaultTolerateTimeSkewness = 3 * time.Second DefaultTolerateTimeSkewness = 3 * time.Second
// DefaultPreferIP is a default value for Telegram IP connectivity // DefaultPreferIP is a default value for Telegram IP connectivity preference.
// preference.
DefaultPreferIP = "prefer-ipv6" DefaultPreferIP = "prefer-ipv6"
// SecretKeyLength defines a length of the secret bytes used // SecretKeyLength defines a length of the secret bytes used by Telegram and a
// by Telegram and a proxy. // proxy.
SecretKeyLength = 16 SecretKeyLength = 16
// ConnectionIDBytesLength defines a count of random bytes used to generate // ConnectionIDBytesLength defines a count of random bytes used to generate a
// a stream/connection ids. // stream/connection ids.
ConnectionIDBytesLength = 16 ConnectionIDBytesLength = 16
// TCPRelayReadTimeout defines a max time period between two consecuitive // TCPRelayReadTimeout defines a max time period between two consecuitive
@@ -100,81 +101,76 @@ const (
TCPRelayReadTimeout = 20 * time.Second TCPRelayReadTimeout = 20 * time.Second
) )
// Network defines a knowledge how to work with a network. It may sound // Network defines a knowledge how to work with a network. It may sound fun but
// fun but it encapsulates all the knowledge how to properly establish // it encapsulates all the knowledge how to properly establish connections to
// connections to remote hosts and configure HTTP clients. // remote hosts and configure HTTP clients.
// //
// For example, if you want to use SOCKS5 proxy, you probably want to // For example, if you want to use SOCKS5 proxy, you probably want to have all
// have all traffic routed to this proxy: telegram connections, http // traffic routed to this proxy: telegram connections, http requests and so on.
// requests and so on. This knowledge is encapsulated into instances of // This knowledge is encapsulated into instances of such interface.
// such interface.
// //
// mtglib uses Network for: // mtglib uses Network for:
//
// 1. Dialing to Telegram // 1. Dialing to Telegram
//
// 2. Dialing to front domain // 2. Dialing to front domain
//
// 3. Doing HTTP requests (for example, for FireHOL ipblocklist). // 3. Doing HTTP requests (for example, for FireHOL ipblocklist).
type Network interface { type Network interface {
// Dial establishes context-free TCP connections. // Dial establishes context-free TCP connections.
Dial(network, address string) (essentials.Conn, error) Dial(network, address string) (essentials.Conn, error)
// DialContext dials using a context. This is a preferrable // DialContext dials using a context. This is a preferrable way of
// way of establishing TCP connections. // establishing TCP connections.
DialContext(ctx context.Context, network, address string) (essentials.Conn, error) DialContext(ctx context.Context, network, address string) (essentials.Conn, error)
// MakeHTTPClient build an HTTP client with given dial function. If // MakeHTTPClient build an HTTP client with given dial function. If nothing is
// nothing is provided, then DialContext of this interface is going // provided, then DialContext of this interface is going to be used.
// to be used.
MakeHTTPClient(func(ctx context.Context, network, address string) (essentials.Conn, error)) *http.Client MakeHTTPClient(func(ctx context.Context, network, address string) (essentials.Conn, error)) *http.Client
} }
// AntiReplayCache is an interface that is used to detect replay attacks // AntiReplayCache is an interface that is used to detect replay attacks based
// based on some traffic fingerprints. // on some traffic fingerprints.
// //
// Replay attacks are probe attacks whose main goal is to identify if // Replay attacks are probe attacks whose main goal is to identify if server
// server software can be classified in some way. For example, if you // software can be classified in some way. For example, if you send some HTTP
// send some HTTP request to a web server, then you can expect that this // request to a web server, then you can expect that this server will respond
// server will respond with HTTP response back. // with HTTP response back.
// //
// There is a problem though. Let's imagine, that connection is // There is a problem though. Let's imagine, that connection is encrypted.
// encrypted. Let's imagine, that it is encrypted with some static key // Let's imagine, that it is encrypted with some static key like [ShadowSocks].
// like ShadowSocks (https://shadowsocks.org/assets/whitepaper.pdf). // In that case, in theory, if you repeat the same bytes, you can get the same
// In that case, in theory, if you repeat the same bytes, you can get // responses. Let's imagine, that you've cracked the key. then if you send the
// the same responses. Let's imagine, that you've cracked the key. then // same bytes, you can decrypt a response and see its structure. Based on its
// if you send the same bytes, you can decrypt a response and see its // structure you can identify if this server is SOCKS5, MTPROTO proxy etc.
// structure. Based on its structure you can identify if this server is
// SOCKS5, MTPROTO proxy etc.
// //
// This is just one example, maybe not the best or not the most // This is just one example, maybe not the best or not the most relevant. In
// relevant. In real life, different organizations use such replay // real life, different organizations use such replay attacks to perform some
// attacks to perform some reverse engineering of the proxy, do some // reverse engineering of the proxy, do some statical analysis to identify
// statical analysis to identify server software. // server software.
// //
// There are many ways how to protect your proxy against them. One // There are many ways how to protect your proxy against them. One is domain
// is domain fronting which is a core part of mtg. Another one is to // fronting which is a core part of mtg. Another one is to collect some
// collect some 'handshake fingerprints' and forbid duplication. // 'handshake fingerprints' and forbid duplication.
// //
// So, it one is sending the same byte flow right after you (or a couple // So, it one is sending the same byte flow right after you (or a couple of
// of hours after), mtg should detect that and reject this connection // hours after), mtg should detect that and reject this connection (or redirect
// (or redirect to fronting domain). // to fronting domain).
//
// [ShadowSocks]: https://shadowsocks.org/assets/whitepaper.pdf
type AntiReplayCache interface { type AntiReplayCache interface {
// Seen before checks if this set of bytes was observed before or // Seen before checks if this set of bytes was observed before or not. If it
// not. If it is required to store this information somewhere else, // is required to store this information somewhere else, then it has to do
// then it has to do that. // that.
SeenBefore(data []byte) bool SeenBefore(data []byte) bool
} }
// IPBlocklist filters requests based on IP address. // IPBlocklist filters requests based on IP address.
// //
// If this filter has an IP address, then mtg closes a request without // If this filter has an IP address, then mtg closes a request without reading
// reading anything from a socket. It also does not give such request to // anything from a socket. It also does not give such request to a worker pool,
// a worker pool, so in worst cases you can expect that you invoke this // so in worst cases you can expect that you invoke this object more frequent
// object more frequent than defined proxy concurrency. // than defined proxy concurrency.
type IPBlocklist interface { type IPBlocklist interface {
// Contains checks if given IP address belongs to this blocklist If. // Contains checks if given IP address belongs to this blocklist If. it is, a
// it is, a connection is terminated . // connection is terminated .
Contains(net.IP) bool Contains(net.IP) bool
// Run starts a background update procedure for a blocklist // Run starts a background update procedure for a blocklist
@@ -184,40 +180,35 @@ type IPBlocklist interface {
Shutdown() Shutdown()
} }
// Event is a data structure which is populated during mtg request // Event is a data structure which is populated during mtg request processing
// processing lifecycle. Each request popluates many events: // lifecycle. Each request popluates many events:
//
// 1. Client connected // 1. Client connected
//
// 2. Request is finished // 2. Request is finished
//
// 3. Connection to Telegram server is established // 3. Connection to Telegram server is established
// //
// and so on. All these events are data structures but all of them // and so on. All these events are data structures but all of them must conform
// must conform the same interface. // the same interface.
type Event interface { type Event interface {
// StreamID returns an identifier of the stream, connection, // StreamID returns an identifier of the stream, connection, request, you name
// request, you name it. All events within the same stream returns // it. All events within the same stream returns the same stream id.
// the same stream id.
StreamID() string StreamID() string
// Timestamp returns a timestamp when this event was generated. // Timestamp returns a timestamp when this event was generated.
Timestamp() time.Time Timestamp() time.Time
} }
// EventStream is an abstraction that accepts a set of events produced // EventStream is an abstraction that accepts a set of events produced by mtg.
// by mtg. Its main goal is to inject your logging or monitoring system. // Its main goal is to inject your logging or monitoring system.
// //
// The idea is simple. When mtg works, it emits a set of events during // The idea is simple. When mtg works, it emits a set of events during a
// a lifecycle of the requestor: EventStart, EventFinish etc. mtg is a // lifecycle of the requestor: EventStart, EventFinish etc. mtg is a producer
// producer which puts these events into a stream. Responsibility of // which puts these events into a stream. Responsibility of the stream is to
// the stream is to deliver this event to consumers/observers. There // deliver this event to consumers/observers. There might be many different
// might be many different observers (for example, you want to have both // observers (for example, you want to have both statsd and prometheus), mtg
// statsd and prometheus), mtg should know nothing about them. // should know nothing about them.
type EventStream interface { type EventStream interface {
// Send delivers an event to observers. Given context has to be // Send delivers an event to observers. Given context has to be respected. If
// respected. If the context is closed, all blocking operations should // the context is closed, all blocking operations should be released ASAP.
// be released ASAP.
// //
// It is possible that context is closed but the message is delivered. // It is possible that context is closed but the message is delivered.
// EventStream implementations should solve this issue somehow. // EventStream implementations should solve this issue somehow.
@@ -226,27 +217,26 @@ type EventStream interface {
// Logger defines an interface of the logger used by mtglib. // Logger defines an interface of the logger used by mtglib.
// //
// Each logger has a name. It is possible to stack names to organize // Each logger has a name. It is possible to stack names to organize poor-man
// poor-man namespaces. Also, each logger must be able to bind // namespaces. Also, each logger must be able to bind parameters to avoid
// parameters to avoid pushing them all the time. // pushing them all the time.
// //
// Example // Example
// //
// logger := SomeLogger{} // logger := SomeLogger{} logger = logger.BindStr("ip", net.IP{127, 0, 0, 1})
// logger = logger.BindStr("ip", net.IP{127, 0, 0, 1})
// logger.Info("Hello") // logger.Info("Hello")
// //
// In that case, ip is bound as a parameter. It is a great idea to // In that case, ip is bound as a parameter. It is a great idea to put this
// put this parameter somewhere in a log message. // parameter somewhere in a log message.
// //
// logger1 = logger.BindStr("param1", "11") // logger1 = logger.BindStr("param1", "11") logger2 = logger.BindInt("param2",
// logger2 = logger.BindInt("param2", 11) // 11)
// //
// logger1 should see no param2 and vice versa, logger2 should not see param1 // logger1 should see no param2 and vice versa, logger2 should not see param1
// If you attach a parameter to a logger, parents should not know about that. // If you attach a parameter to a logger, parents should not know about that.
type Logger interface { type Logger interface {
// Named returns a new logger with a bound name. Name chaining is // Named returns a new logger with a bound name. Name chaining is allowed and
// allowed and appreciated. // appreciated.
Named(name string) Logger Named(name string) Logger
// BindInt binds new integer parameter to a new logger instance. // BindInt binds new integer parameter to a new logger instance.
@@ -259,27 +249,26 @@ type Logger interface {
BindJSON(name, value string) Logger BindJSON(name, value string) Logger
// Printf is to support log.Logger behavior. // 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 puts a message about some normal situation.
Info(msg string) Info(msg string)
// InfoError puts a message about some normal situation but this // InfoError puts a message about some normal situation but this situation is
// situation is related to a given error. // related to a given error.
InfoError(msg string, err error) InfoError(msg string, err error)
// Warning puts a message about some extraordinary situation // Warning puts a message about some extraordinary situation worth to look at.
// worth to look at.
Warning(msg string) Warning(msg string)
// WarningError puts a message about some extraordinary situation // WarningError puts a message about some extraordinary situation worth to
// worth to look at. This situation is related to a given error. // look at. This situation is related to a given error.
WarningError(msg string, err error) WarningError(msg string, err error)
// Debug puts a message useful for debugging only. // Debug puts a message useful for debugging only.
Debug(msg string) Debug(msg string)
// Debug puts a message useful for debugging only. This message is // Debug puts a message useful for debugging only. This message is related to
// related to a given error. // a given error.
DebugError(msg string, err error) DebugError(msg string, err error)
} }
+1 -1
View File
@@ -12,7 +12,7 @@ func (n NoopLogger) Named(_ string) Logger { return n }
func (n NoopLogger) BindInt(_ string, _ int) Logger { return n } func (n NoopLogger) BindInt(_ string, _ int) Logger { return n }
func (n NoopLogger) BindStr(_, _ string) Logger { return n } func (n NoopLogger) BindStr(_, _ string) Logger { return n }
func (n NoopLogger) BindJSON(_, _ string) Logger { return n } func (n NoopLogger) BindJSON(_, _ string) Logger { return n }
func (n NoopLogger) Printf(_ string, _ ...interface{}) {} func (n NoopLogger) Printf(_ string, _ ...any) {}
func (n NoopLogger) Info(_ string) {} func (n NoopLogger) Info(_ string) {}
func (n NoopLogger) Warning(_ string) {} func (n NoopLogger) Warning(_ string) {}
func (n NoopLogger) Debug(_ string) {} func (n NoopLogger) Debug(_ string) {}
+10
View File
@@ -0,0 +1,10 @@
package dc
type Addr struct {
Network string
Address string
}
func (d Addr) String() string {
return d.Address
}
+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
}
+16
View File
@@ -0,0 +1,16 @@
package dc_test
import (
"testing"
"github.com/9seconds/mtg/v2/mtglib/internal/dc"
"github.com/stretchr/testify/assert"
)
func TestAddr(t *testing.T) {
t.Parallel()
addr := dc.Addr{Network: "tcp4", Address: "127.0.0.1:443"}
assert.Equal(t, "127.0.0.1:443", addr.String())
}
+73
View File
@@ -0,0 +1,73 @@
package dc
type preferIP uint8
const (
preferIPOnlyIPv4 preferIP = iota
preferIPOnlyIPv6
preferIPPreferIPv4
preferIPPreferIPv6
)
const (
DefaultDC = 2
)
type Logger interface {
Info(msg string)
WarningError(msg string, err error)
}
var (
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp#L30
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"},
},
},
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"},
},
},
}
defaultDCOverridesAddrSet = dcAddrSet{
v4: map[int][]Addr{
203: {
{Network: "tcp4", Address: "91.105.192.100:443"},
},
},
v6: map[int][]Addr{
203: {
{Network: "tcp6", Address: "[2a0a:f280:0203:000a:5000:0000:0000:0100]:443"},
},
},
}
)
+79
View File
@@ -0,0 +1,79 @@
package dc
import (
"fmt"
"net"
"strings"
)
type Telegram struct {
view dcView
preferIP preferIP
}
func (t *Telegram) GetAddresses(dc int) []Addr {
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, userOverrides map[int][]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)
}
overrides := dcAddrSet{
v4: map[int][]Addr{},
v6: map[int][]Addr{},
}
for dc, addrs := range userOverrides {
for _, addr := range addrs {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("incorrect host %s: %w", addr, err)
}
parsed := net.ParseIP(host)
if parsed == nil {
return nil, fmt.Errorf("incorrect host %s", addr)
}
if parsed.To4() != nil {
overrides.v4[dc] = append(overrides.v4[dc], Addr{
Network: "tcp4",
Address: addr,
})
} else {
overrides.v6[dc] = append(overrides.v6[dc], Addr{
Network: "tcp6",
Address: addr,
})
}
}
}
return &Telegram{
view: dcView{
overrides: overrides,
},
preferIP: pref,
}, nil
}
+21
View File
@@ -0,0 +1,21 @@
package dc
type dcView struct {
overrides dcAddrSet
}
func (d dcView) getV4(dc int) []Addr {
addrs := d.overrides.getV4(dc)
addrs = append(addrs, defaultDCOverridesAddrSet.getV4(dc)...)
addrs = append(addrs, defaultDCAddrSet.getV4(dc)...)
return addrs
}
func (d dcView) getV6(dc int) []Addr {
addrs := d.overrides.getV6(dc)
addrs = append(addrs, defaultDCOverridesAddrSet.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{
overrides: 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: {
{"tcp4", "127.0.0.1:443"},
},
203: {
{"tcp4", "127.0.0.2:443"},
{"tcp4", "91.105.192.100:443"},
},
2: {
{"tcp4", "149.154.167.51:443"},
{"tcp4", "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: {
{"tcp6", "xxx"},
{"tcp6", "[2a0a:f280:0203:000a:5000:0000:0000:0100]:443"},
},
1: {
{"tcp6", "[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) { if len(handshake)-4 != int(handshakeLength) {
return hello, return hello,
fmt.Errorf("incorrect handshake size. manifested=%d, real=%d", fmt.Errorf("incorrect handshake size. manifested=%d, real=%d",
handshakeLength, len(handshake)-4) // nolint: gomnd handshakeLength, len(handshake)-4)
} }
copy(hello.Random[:], handshake[ClientHelloRandomOffset:]) copy(hello.Random[:], handshake[ClientHelloRandomOffset:])
@@ -76,7 +76,7 @@ func ParseClientHello(secret, handshake []byte) (ClientHello, error) {
computedRandom := mac.Sum(nil) computedRandom := mac.Sum(nil)
for i := 0; i < RandomLen; i++ { for i := range RandomLen {
computedRandom[i] ^= hello.Random[i] computedRandom[i] ^= hello.Random[i]
} }
@@ -100,7 +100,7 @@ func parseSessionID(hello *ClientHello, handshake []byte) {
} }
func parseCipherSuite(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]) hello.CipherSuite = binary.BigEndian.Uint16(handshake[cipherSuiteOffset : cipherSuiteOffset+2])
} }
@@ -0,0 +1,21 @@
package faketls_test
import (
"testing"
"github.com/9seconds/mtg/v2/mtglib/internal/faketls"
"github.com/stretchr/testify/require"
)
var FuzzClientHelloSecret = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
func FuzzClientHello(f *testing.F) {
f.Add([]byte{1, 2, 3})
f.Fuzz(func(t *testing.T, frame []byte) {
_, err := faketls.ParseClientHello(FuzzClientHelloSecret, frame)
// a probability of having != err is almost negligible
require.Error(t, err)
})
}
+2 -2
View File
@@ -3,7 +3,7 @@ package faketls
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"math/rand" "math/rand/v2"
"github.com/9seconds/mtg/v2/essentials" "github.com/9seconds/mtg/v2/essentials"
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record" "github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
@@ -53,7 +53,7 @@ func (c *Conn) Write(p []byte) (int, error) {
lenP := len(p) lenP := len(p)
for len(p) > 0 { for len(p) > 0 {
chunkSize := rand.Intn(record.TLSMaxRecordSize) chunkSize := rand.IntN(record.TLSMaxRecordSize)
if chunkSize > len(p) || chunkSize == 0 { if chunkSize > len(p) || chunkSize == 0 {
chunkSize = len(p) chunkSize = len(p)
} }
+2 -2
View File
@@ -2,9 +2,9 @@ package faketls_test
import ( import (
"bytes" "bytes"
"crypto/rand"
"errors" "errors"
"io" "io"
"math/rand"
"testing" "testing"
"github.com/9seconds/mtg/v2/internal/testlib" "github.com/9seconds/mtg/v2/internal/testlib"
@@ -123,7 +123,7 @@ func (suite *ConnTestSuite) TestWrite() {
suite.connMock.On("Write", mock.Anything).Return(0, nil) suite.connMock.On("Write", mock.Anything).Return(0, nil)
dataToRec := make([]byte, record.TLSMaxRecordSize*2) dataToRec := make([]byte, record.TLSMaxRecordSize*2)
rand.Read(dataToRec) rand.Read(dataToRec) //nolint: staticcheck, errcheck
n, err := suite.c.Write(dataToRec) n, err := suite.c.Write(dataToRec)
suite.NoError(err) suite.NoError(err)
+1 -1
View File
@@ -19,7 +19,7 @@ const (
// ClientHelloMinLen is a minimal possible length of // ClientHelloMinLen is a minimal possible length of
// ClientHello record. // ClientHello record.
ClientHelloMinLen = 4 ClientHelloMinLen = 6
// WelcomePacketRandomOffset is an offset of random in ServerHello // WelcomePacketRandomOffset is an offset of random in ServerHello
// packet (including record envelope). // packet (including record envelope).
+1 -1
View File
@@ -6,7 +6,7 @@ import (
) )
var bytesBufferPool = sync.Pool{ var bytesBufferPool = sync.Pool{
New: func() interface{} { New: func() any {
return &bytes.Buffer{} return &bytes.Buffer{}
}, },
} }
+1 -1
View File
@@ -5,7 +5,7 @@ import (
) )
var recordPool = sync.Pool{ var recordPool = sync.Pool{
New: func() interface{} { New: func() any {
return &Record{} return &Record{}
}, },
} }
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"io" "io"
mrand "math/rand" mrand "math/rand/v2"
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record" "github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
"golang.org/x/crypto/curve25519" "golang.org/x/crypto/curve25519"
@@ -36,7 +36,7 @@ func SendWelcomePacket(writer io.Writer, secret []byte, clientHello ClientHello)
rec.Type = record.TypeApplicationData rec.Type = record.TypeApplicationData
rec.Version = record.Version12 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) panic(err)
} }
+3 -3
View File
@@ -3,8 +3,8 @@ package faketls_test
import ( import (
"bytes" "bytes"
"crypto/hmac" "crypto/hmac"
"crypto/rand"
"crypto/sha256" "crypto/sha256"
"math/rand"
"testing" "testing"
"time" "time"
@@ -30,10 +30,10 @@ func (suite *WelcomeTestSuite) SetupTest() {
SessionID: make([]byte, 32), SessionID: make([]byte, 32),
} }
_, err := rand.Read(suite.h.SessionID) _, err := rand.Read(suite.h.SessionID) //nolint: staticcheck
suite.NoError(err) suite.NoError(err)
_, err = rand.Read(suite.h.Random[:]) _, err = rand.Read(suite.h.Random[:]) //nolint: staticcheck
suite.NoError(err) suite.NoError(err)
suite.buf = &bytes.Buffer{} suite.buf = &bytes.Buffer{}
@@ -0,0 +1,32 @@
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())
})
}
@@ -36,7 +36,7 @@ type handshakeFrame struct {
} }
func (h *handshakeFrame) dc() int { 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 idx := int16(h.data[handshakeFrameOffsetDC]) | int16(h.data[handshakeFrameOffsetDC+1])<<8 //nolint: lll // little endian for int16 is here
switch { switch {
case idx > 0: case idx > 0:
@@ -63,7 +63,7 @@ func (h *handshakeFrame) connectionType() []byte {
func (h *handshakeFrame) invert() handshakeFrame { func (h *handshakeFrame) invert() handshakeFrame {
copyFrame := *h copyFrame := *h
for i := 0; i < handshakeFrameLenKey+handshakeFrameLenIV; i++ { for i := range handshakeFrameLenKey + handshakeFrameLenIV {
copyFrame.data[handshakeFrameOffsetKey+i] = h.data[handshakeFrameOffsetConnectionType-1-i] copyFrame.data[handshakeFrameOffsetKey+i] = h.data[handshakeFrameOffsetConnectionType-1-i]
} }
+54
View File
@@ -1,12 +1,20 @@
package obfuscated2_test package obfuscated2_test
import ( import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "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 { type snapshotBytes struct {
@@ -50,6 +58,14 @@ type SnapshotTestSuite struct {
snapshots map[string]*Obfuscated2Snapshot 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 { func (suite *SnapshotTestSuite) IngestSnapshots(dirname, namePrefix string) error {
suite.snapshots = map[string]*Obfuscated2Snapshot{} suite.snapshots = map[string]*Obfuscated2Snapshot{}
@@ -81,3 +97,41 @@ func (suite *SnapshotTestSuite) IngestSnapshots(dirname, namePrefix string) erro
return nil 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 := range 32 + 16 {
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,
}
}
+2 -2
View File
@@ -9,12 +9,12 @@ import (
var ( var (
sha256HasherPool = sync.Pool{ sha256HasherPool = sync.Pool{
New: func() interface{} { New: func() any {
return sha256.New() return sha256.New()
}, },
} }
bytesBufferPool = sync.Pool{ bytesBufferPool = sync.Pool{
New: func() interface{} { New: func() any {
return &bytes.Buffer{} return &bytes.Buffer{}
}, },
} }
@@ -47,12 +47,12 @@ func generateServerHanshakeFrame() serverHandshakeFrame {
panic(err) panic(err)
} }
if frame.data[0] == 0xef { // nolint: gomnd // taken from tg sources if frame.data[0] == 0xef { // taken from tg sources
continue continue
} }
switch binary.LittleEndian.Uint32(frame.data[:4]) { switch binary.LittleEndian.Uint32(frame.data[:4]) {
case 0x44414548, 0x54534f50, 0x20544547, 0x4954504f, 0xeeeeeeee: // nolint: gomnd // taken from tg sources case 0x44414548, 0x54534f50, 0x20544547, 0x4954504f, 0xeeeeeeee: // taken from tg sources
continue continue
} }
@@ -0,0 +1,30 @@
package obfuscated2
import (
"encoding/binary"
"testing"
"github.com/stretchr/testify/assert"
)
func FuzzServerGenerateHandshakeFrame(f *testing.F) {
f.Fuzz(func(t *testing.T, arg int) {
frame := generateServerHanshakeFrame()
assert.NotEqualValues(t, 0xef, frame.data[0])
firstBytes := binary.LittleEndian.Uint32(frame.data[:4])
assert.NotEqualValues(t, 0x44414548, firstBytes)
assert.NotEqualValues(t, 0x54534f50, firstBytes)
assert.NotEqualValues(t, 0x20544547, firstBytes)
assert.NotEqualValues(t, 0x4954504f, firstBytes)
assert.NotEqualValues(t, 0xeeeeeeee, firstBytes)
assert.NotEqualValues(
t,
0,
frame.data[4]|frame.data[5]|frame.data[6]|frame.data[7])
assert.Equal(t, handshakeConnectionType, frame.connectionType())
})
}
@@ -0,0 +1,58 @@
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,14 +1,8 @@
package obfuscated2_test package obfuscated2_test
import ( import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"testing" "testing"
"github.com/9seconds/mtg/v2/internal/testlib"
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
@@ -16,64 +10,31 @@ import (
type ServerHandshakeTestSuite struct { type ServerHandshakeTestSuite struct {
suite.Suite suite.Suite
connMock *testlib.EssentialsConnMock data ServerHandshakeTestData
proxyConn obfuscated2.Conn
encryptor cipher.Stream
decryptor cipher.Stream
} }
func (suite *ServerHandshakeTestSuite) SetupTest() { func (suite *ServerHandshakeTestSuite) SetupTest() {
buf := &bytes.Buffer{} suite.data = NewServerHandshakeTestData(suite.T())
suite.connMock = &testlib.EssentialsConnMock{}
encryptor, decryptor, err := obfuscated2.ServerHandshake(buf)
suite.NoError(err)
suite.proxyConn = obfuscated2.Conn{
Conn: suite.connMock,
Encryptor: encryptor,
Decryptor: decryptor,
}
serverEncrypted := buf.Bytes()
decBlock, _ := aes.NewCipher(serverEncrypted[8 : 8+32])
suite.decryptor = cipher.NewCTR(decBlock, serverEncrypted[8+32:8+32+16])
serverDecrypted := make([]byte, len(serverEncrypted))
suite.decryptor.XORKeyStream(serverDecrypted, serverEncrypted)
suite.Equal("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])
suite.encryptor = cipher.NewCTR(encBlock, serverEncryptedReverted[8+32:8+32+16])
} }
func (suite *ServerHandshakeTestSuite) TearDownTest() { func (suite *ServerHandshakeTestSuite) TearDownTest() {
suite.connMock.AssertExpectations(suite.T()) suite.data.connMock.AssertExpectations(suite.T())
} }
func (suite *ServerHandshakeTestSuite) TestSendToTelegram() { func (suite *ServerHandshakeTestSuite) TestSendToTelegram() {
messageToTelegram := []byte{10, 11, 12, 13, 14, 'a'} messageToTelegram := []byte{10, 11, 12, 13, 14, 'a'}
suite.connMock. suite.data.connMock.
On("Write", mock.Anything). On("Write", mock.Anything).
Return(len(messageToTelegram), nil). Return(len(messageToTelegram), nil).
Once(). Once().
Run(func(args mock.Arguments) { Run(func(args mock.Arguments) {
message := make([]byte, len(messageToTelegram)) message := make([]byte, len(messageToTelegram))
suite.decryptor.XORKeyStream(message, args.Get(0).([]byte)) // nolint: forcetypeassert suite.data.decryptor.XORKeyStream(message, args.Get(0).([]byte)) //nolint: forcetypeassert
suite.Equal(messageToTelegram, message) suite.Equal(messageToTelegram, message)
}) })
n, err := suite.proxyConn.Write(messageToTelegram) n, err := suite.data.proxyConn.Write(messageToTelegram)
suite.EqualValues(len(messageToTelegram), n) suite.EqualValues(len(messageToTelegram), n)
suite.NoError(err) suite.NoError(err)
} }
@@ -82,17 +43,17 @@ func (suite *ServerHandshakeTestSuite) TestRecieveFromTelegram() {
messageFromTelegram := []byte{10, 11, 12, 13, 14, 'a'} messageFromTelegram := []byte{10, 11, 12, 13, 14, 'a'}
buffer := make([]byte, len(messageFromTelegram)) buffer := make([]byte, len(messageFromTelegram))
suite.connMock. suite.data.connMock.
On("Read", mock.Anything). On("Read", mock.Anything).
Return(len(messageFromTelegram), nil). Return(len(messageFromTelegram), nil).
Once(). Once().
Run(func(args mock.Arguments) { Run(func(args mock.Arguments) {
message := make([]byte, len(messageFromTelegram)) message := make([]byte, len(messageFromTelegram))
suite.encryptor.XORKeyStream(message, messageFromTelegram) suite.data.encryptor.XORKeyStream(message, messageFromTelegram)
copy(args.Get(0).([]byte), message) //nolint: forcetypeassert copy(args.Get(0).([]byte), message) //nolint: forcetypeassert
}) })
n, err := suite.proxyConn.Read(buffer) n, err := suite.data.proxyConn.Read(buffer)
suite.EqualValues(len(messageFromTelegram), n) suite.EqualValues(len(messageFromTelegram), n)
suite.NoError(err) suite.NoError(err)
suite.Equal(messageFromTelegram, buffer) suite.Equal(messageFromTelegram, buffer)
+1 -1
View File
@@ -5,5 +5,5 @@ const (
) )
type Logger interface { 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{} type loggerMock struct{}
func (l loggerMock) Printf(format string, args ...interface{}) {} func (l loggerMock) Printf(format string, args ...any) {}
+1 -1
View File
@@ -3,7 +3,7 @@ package relay
import "sync" import "sync"
var copyBufferPool = sync.Pool{ var copyBufferPool = sync.Pool{
New: func() interface{} { New: func() any {
rv := make([]byte, copyBufferSize) rv := make([]byte, copyBufferSize)
return &rv return &rv
+4 -4
View File
@@ -9,16 +9,16 @@ import (
) )
func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials.Conn) { func Relay(ctx context.Context, log Logger, telegramConn, clientConn essentials.Conn) {
defer telegramConn.Close() defer telegramConn.Close() //nolint: errcheck
defer clientConn.Close() defer clientConn.Close() //nolint: errcheck
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
go func() { go func() {
<-ctx.Done() <-ctx.Done()
telegramConn.Close() telegramConn.Close() //nolint: errcheck
clientConn.Close() clientConn.Close() //nolint: errcheck
}() }()
closeChan := make(chan struct{}) closeChan := make(chan struct{})
-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{})
}
+34 -29
View File
@@ -10,11 +10,11 @@ import (
"time" "time"
"github.com/9seconds/mtg/v2/essentials" "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"
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record" "github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2" "github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
"github.com/9seconds/mtg/v2/mtglib/internal/relay" "github.com/9seconds/mtg/v2/mtglib/internal/relay"
"github.com/9seconds/mtg/v2/mtglib/internal/telegram"
"github.com/panjf2000/ants/v2" "github.com/panjf2000/ants/v2"
) )
@@ -28,13 +28,13 @@ type Proxy struct {
tolerateTimeSkewness time.Duration tolerateTimeSkewness time.Duration
domainFrontingPort int domainFrontingPort int
workerPool *ants.PoolWithFunc workerPool *ants.PoolWithFunc
telegram *telegram.Telegram telegram *dc.Telegram
secret Secret secret Secret
network Network network Network
antiReplayCache AntiReplayCache antiReplayCache AntiReplayCache
blocklist IPBlocklist blocklist IPBlocklist
whitelist IPBlocklist allowlist IPBlocklist
eventStream EventStream eventStream EventStream
logger Logger logger Logger
} }
@@ -44,8 +44,8 @@ func (p *Proxy) DomainFrontingAddress() string {
return net.JoinHostPort(p.secret.Host, strconv.Itoa(p.domainFrontingPort)) return net.JoinHostPort(p.secret.Host, strconv.Itoa(p.domainFrontingPort))
} }
// ServeConn serves a connection. We do not check IP blocklist and // ServeConn serves a connection. We do not check IP blocklist and concurrency
// concurrency limit here. // limit here.
func (p *Proxy) ServeConn(conn essentials.Conn) { func (p *Proxy) ServeConn(conn essentials.Conn) {
p.streamWaitGroup.Add(1) p.streamWaitGroup.Add(1)
defer p.streamWaitGroup.Done() defer p.streamWaitGroup.Done()
@@ -91,7 +91,7 @@ func (p *Proxy) ServeConn(conn essentials.Conn) {
} }
// Serve starts a proxy on a given listener. // Serve starts a proxy on a given listener.
func (p *Proxy) Serve(listener net.Listener) error { // nolint: cyclop func (p *Proxy) Serve(listener net.Listener) error {
p.streamWaitGroup.Add(1) p.streamWaitGroup.Add(1)
defer p.streamWaitGroup.Done() defer p.streamWaitGroup.Done()
@@ -109,16 +109,16 @@ func (p *Proxy) Serve(listener net.Listener) error { // nolint: cyclop
ipAddr := conn.RemoteAddr().(*net.TCPAddr).IP //nolint: forcetypeassert ipAddr := conn.RemoteAddr().(*net.TCPAddr).IP //nolint: forcetypeassert
logger := p.logger.BindStr("ip", ipAddr.String()) logger := p.logger.BindStr("ip", ipAddr.String())
if p.whitelist != nil && !p.whitelist.Contains(ipAddr) { if !p.allowlist.Contains(ipAddr) {
conn.Close() conn.Close() //nolint: errcheck
logger.Info("ip was rejected by whitelist") logger.Info("ip was rejected by allowlist")
p.eventStream.Send(p.ctx, NewEventIPBlocklisted(ipAddr)) p.eventStream.Send(p.ctx, NewEventIPAllowlisted(ipAddr))
continue continue
} }
if p.blocklist.Contains(ipAddr) { if p.blocklist.Contains(ipAddr) {
conn.Close() conn.Close() //nolint: errcheck
logger.Info("ip was blacklisted") logger.Info("ip was blacklisted")
p.eventStream.Send(p.ctx, NewEventIPBlocklisted(ipAddr)) p.eventStream.Send(p.ctx, NewEventIPBlocklisted(ipAddr))
@@ -138,17 +138,14 @@ func (p *Proxy) Serve(listener net.Listener) error { // nolint: cyclop
} }
} }
// Shutdown 'gracefully' shutdowns all connections. Please remember that // Shutdown 'gracefully' shutdowns all connections. Please remember that it
// it does not close an underlying listener. // does not close an underlying listener.
func (p *Proxy) Shutdown() { func (p *Proxy) Shutdown() {
p.ctxCancel() p.ctxCancel()
p.streamWaitGroup.Wait() p.streamWaitGroup.Wait()
p.workerPool.Release() p.workerPool.Release()
if p.whitelist != nil { p.allowlist.Shutdown()
p.whitelist.Shutdown()
}
p.blocklist.Shutdown() p.blocklist.Shutdown()
} }
@@ -222,23 +219,31 @@ func (p *Proxy) doObfuscated2Handshake(ctx *streamContext) error {
} }
func (p *Proxy) doTelegramCall(ctx *streamContext) error { func (p *Proxy) doTelegramCall(ctx *streamContext) error {
dc := ctx.dc dcid := ctx.dc
if p.allowFallbackOnUnknownDC && !p.telegram.IsKnownDC(dc) {
dc = p.telegram.GetFallbackDC()
ctx.logger = ctx.logger.BindInt("fallback_dc", dc)
addresses := p.telegram.GetAddresses(dcid)
if len(addresses) == 0 && p.allowFallbackOnUnknownDC {
ctx.logger = ctx.logger.BindInt("fallback_dc", dc.DefaultDC)
ctx.logger.Warning("unknown DC, fallbacks") ctx.logger.Warning("unknown DC, fallbacks")
addresses = p.telegram.GetAddresses(dc.DefaultDC)
} }
conn, err := p.telegram.Dial(ctx, dc) var conn essentials.Conn
var err error
for _, addr := range addresses {
conn, err = p.network.Dial(addr.Network, addr.Address)
if err == nil {
break
}
}
if err != nil { if err != nil {
return fmt.Errorf("cannot dial to Telegram: %w", err) return fmt.Errorf("no addresses to call: %w", err)
} }
encryptor, decryptor, err := obfuscated2.ServerHandshake(conn) encryptor, decryptor, err := obfuscated2.ServerHandshake(conn)
if err != nil { if err != nil {
conn.Close() conn.Close() //nolint: errcheck
return fmt.Errorf("cannot perform obfuscated2 handshake: %w", err) return fmt.Errorf("cannot perform obfuscated2 handshake: %w", err)
} }
@@ -295,9 +300,9 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) {
return nil, fmt.Errorf("invalid settings: %w", err) return nil, fmt.Errorf("invalid settings: %w", err)
} }
tg, err := telegram.New(opts.Network, opts.getPreferIP(), opts.UseTestDCs) tg, err := dc.New(opts.getPreferIP(), opts.DCOverrides)
if err != nil { 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()) ctx, cancel := context.WithCancel(context.Background())
@@ -308,7 +313,7 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) {
network: opts.Network, network: opts.Network,
antiReplayCache: opts.AntiReplayCache, antiReplayCache: opts.AntiReplayCache,
blocklist: opts.IPBlocklist, blocklist: opts.IPBlocklist,
whitelist: opts.IPWhitelist, allowlist: opts.IPAllowlist,
eventStream: opts.EventStream, eventStream: opts.EventStream,
logger: opts.getLogger("proxy"), logger: opts.getLogger("proxy"),
domainFrontingPort: opts.getDomainFrontingPort(), domainFrontingPort: opts.getDomainFrontingPort(),
@@ -318,7 +323,7 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) {
} }
pool, err := ants.NewPoolWithFunc(opts.getConcurrency(), pool, err := ants.NewPoolWithFunc(opts.getConcurrency(),
func(arg interface{}) { func(arg any) {
proxy.ServeConn(arg.(essentials.Conn)) //nolint: forcetypeassert proxy.ServeConn(arg.(essentials.Conn)) //nolint: forcetypeassert
}, },
ants.WithLogger(opts.getLogger("ants")), ants.WithLogger(opts.getLogger("ants")),
+37 -31
View File
@@ -4,16 +4,16 @@ import "time"
// ProxyOpts is a structure with settings to mtg proxy. // ProxyOpts is a structure with settings to mtg proxy.
// //
// This is not required per se, but this is to shorten function // This is not required per se, but this is to shorten function signature and
// signature and give an ability to conveniently provide default values. // give an ability to conveniently provide default values.
type ProxyOpts struct { type ProxyOpts struct {
// Secret defines a secret which should be used by a proxy. // Secret defines a secret which should be used by a proxy.
// //
// This is a mandatory setting. // This is a mandatory setting.
Secret Secret Secret Secret
// Network defines a network instance which should be used for all // Network defines a network instance which should be used for all network
// network communications made by proxies. // communications made by proxies.
// //
// This is a mandatory setting. // This is a mandatory setting.
Network Network Network Network
@@ -28,10 +28,10 @@ type ProxyOpts struct {
// This is a mandatory setting. // This is a mandatory setting.
IPBlocklist IPBlocklist IPBlocklist IPBlocklist
// IPWhitelist defines a whitelist of IPs to allow to use proxy. // IPAllowlist defines a whitelist of IPs to allow to use proxy.
// //
// This is an optional setting, ignored by default (no restrictions). // This is an optional setting, ignored by default (no restrictions).
IPWhitelist IPBlocklist IPAllowlist IPBlocklist
// EventStream defines an instance of event stream. // EventStream defines an instance of event stream.
// //
@@ -45,9 +45,8 @@ type ProxyOpts struct {
// BufferSize is a size of the copy buffer in bytes. // BufferSize is a size of the copy buffer in bytes.
// //
// Please remember that we multiply this number in 2, because when // Please remember that we multiply this number in 2, because when we relay
// we relay between proxies, we have to create 2 intermediate // between proxies, we have to create 2 intermediate buffers: to and from.
// buffers: to and from.
// //
// This is an optional setting. // This is an optional setting.
// //
@@ -62,22 +61,20 @@ type ProxyOpts struct {
// This is an optional setting. // This is an optional setting.
Concurrency uint Concurrency uint
// IdleTimeout is a timeout for relay when we have to break a // IdleTimeout is a timeout for relay when we have to break a stream.
// stream.
// //
// This is a timeout for any activity. So, if we have any message // This is a timeout for any activity. So, if we have any message which will
// which will pass to either direction, a timer is reset. If we have // pass to either direction, a timer is reset. If we have no any reads or
// no any reads or writes for this timeout, a connection will be // writes for this timeout, a connection will be aborted.
// aborted.
// //
// This is an optional setting. // This is an optional setting.
IdleTimeout time.Duration IdleTimeout time.Duration
// TolerateTimeSkewness is a time boundary that defines a time // TolerateTimeSkewness is a time boundary that defines a time range where
// range where faketls timestamp is acceptable. // faketls timestamp is acceptable.
// //
// This means that if if you got a timestamp X, now is Y, then // This means that if if you got a timestamp X, now is Y, then if |X-Y| <
// if |X-Y| < TolerateTimeSkewness, then you accept a packet. // TolerateTimeSkewness, then you accept a packet.
// //
// This is an optional setting. // This is an optional setting.
TolerateTimeSkewness time.Duration TolerateTimeSkewness time.Duration
@@ -88,33 +85,40 @@ type ProxyOpts struct {
// This is an optional setting. // This is an optional setting.
PreferIP string PreferIP string
// DomainFrontingPort is a port we use to connect to a fronting // DomainFrontingPort is a port we use to connect to a fronting domain.
// domain.
// //
// This is required because secret does not specify a port. It // This is required because secret does not specify a port. It specifies a
// specifies a hostname only. // hostname only.
// //
// This is an optional setting. // This is an optional setting.
DomainFrontingPort uint DomainFrontingPort uint
// AllowFallbackOnUnknownDC defines how proxy behaves if unknown DC was // AllowFallbackOnUnknownDC defines how proxy behaves if unknown DC was
// requested. If this setting is set to false, then such connection // requested. If this setting is set to false, then such connection will be
// will be rejected. Otherwise, proxy will chose any DC. // rejected. Otherwise, proxy will chose any DC.
// //
// Telegram is designed in a way that any DC can serve any request, // Telegram is designed in a way that any DC can serve any request, the
// the problem is a latency. // problem is a latency.
// //
// This is an optional setting. // This is an optional setting.
AllowFallbackOnUnknownDC bool AllowFallbackOnUnknownDC bool
// UseTestDCs defines if we have to connect to production or to staging // UseTestDCs defines if we have to connect to production or to staging DCs of
// DCs of Telegram. // Telegram.
// //
// This is required if you use mtglib as an integration library for // This is required if you use mtglib as an integration library for your
// your Telegram-related projects. // Telegram-related projects.
// //
// This is an optional setting. // This is an optional setting.
//
// OBSOLETE and DEPRECATED. Ignored.
UseTestDCs bool 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.
//
// This is an optional setting
DCOverrides map[int][]string
} }
func (p ProxyOpts) valid() error { func (p ProxyOpts) valid() error {
@@ -125,6 +129,8 @@ func (p ProxyOpts) valid() error {
return ErrAntiReplayCacheIsNotDefined return ErrAntiReplayCacheIsNotDefined
case p.IPBlocklist == nil: case p.IPBlocklist == nil:
return ErrIPBlocklistIsNotDefined return ErrIPBlocklistIsNotDefined
case p.IPAllowlist == nil:
return ErrIPAllowlistIsNotDefined
case p.EventStream == nil: case p.EventStream == nil:
return ErrEventStreamIsNotDefined return ErrEventStreamIsNotDefined
case p.Logger == nil: case p.Logger == nil:
+27 -33
View File
@@ -1,9 +1,7 @@
package mtglib_test package mtglib_test
import ( import (
"context"
"crypto/tls" "crypto/tls"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@@ -15,13 +13,12 @@ import (
"github.com/9seconds/mtg/v2/antireplay" "github.com/9seconds/mtg/v2/antireplay"
"github.com/9seconds/mtg/v2/events" "github.com/9seconds/mtg/v2/events"
"github.com/9seconds/mtg/v2/ipblocklist" "github.com/9seconds/mtg/v2/ipblocklist"
"github.com/9seconds/mtg/v2/ipblocklist/files"
"github.com/9seconds/mtg/v2/logger" "github.com/9seconds/mtg/v2/logger"
"github.com/9seconds/mtg/v2/mtglib" "github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/network" "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/stretchr/testify/suite"
"github.com/yl2chen/cidranger"
) )
type ProxyTestSuite struct { type ProxyTestSuite struct {
@@ -49,11 +46,26 @@ func (suite *ProxyTestSuite) SetupSuite() {
ntw, err := network.NewNetwork(dialer, "mtgtest", "1.1.1.1", 0) ntw, err := network.NewNetwork(dialer, "mtgtest", "1.1.1.1", 0)
suite.NoError(err) suite.NoError(err)
allowlist, _ := ipblocklist.NewFireholFromFiles(
logger.NewNoopLogger(),
1,
[]files.File{
files.NewMem([]*net.IPNet{
cidranger.AllIPv4,
cidranger.AllIPv6,
}),
},
nil,
)
go allowlist.Run(time.Second)
suite.opts = &mtglib.ProxyOpts{ suite.opts = &mtglib.ProxyOpts{
Secret: mtglib.GenerateSecret("httpbin.org"), Secret: mtglib.GenerateSecret("httpbin.org"),
Network: ntw, Network: ntw,
AntiReplayCache: antireplay.NewNoop(), AntiReplayCache: antireplay.NewNoop(),
IPBlocklist: ipblocklist.NewNoop(), IPBlocklist: ipblocklist.NewNoop(),
IPAllowlist: allowlist,
EventStream: events.NewNoopStream(), EventStream: events.NewNoopStream(),
Logger: logger.NewNoopLogger(), Logger: logger.NewNoopLogger(),
UseTestDCs: true, UseTestDCs: true,
@@ -74,7 +86,7 @@ func (suite *ProxyTestSuite) SetupSuite() {
func (suite *ProxyTestSuite) TearDownSuite() { func (suite *ProxyTestSuite) TearDownSuite() {
if suite.listener != nil { if suite.listener != nil {
suite.listener.Close() suite.listener.Close() //nolint: errcheck
} }
if suite.p != nil { if suite.p != nil {
@@ -114,6 +126,14 @@ func (suite *ProxyTestSuite) TestCannotInitNoIPBlocklist() {
suite.Error(err) suite.Error(err)
} }
func (suite *ProxyTestSuite) TestCannotInitNoIPAllowlist() {
opts := *suite.opts
opts.IPAllowlist = nil
_, err := mtglib.NewProxy(opts)
suite.Error(err)
}
func (suite *ProxyTestSuite) TestCannotInitNoEventStream() { func (suite *ProxyTestSuite) TestCannotInitNoEventStream() {
opts := *suite.opts opts := *suite.opts
opts.EventStream = nil opts.EventStream = nil
@@ -157,7 +177,7 @@ func (suite *ProxyTestSuite) TestHTTPSRequest() {
resp, err := client.Get(addr) //nolint: noctx resp, err := client.Get(addr) //nolint: noctx
suite.NoError(err) suite.NoError(err)
defer resp.Body.Close() defer resp.Body.Close() //nolint: errcheck
suite.Equal(http.StatusOK, resp.StatusCode) suite.Equal(http.StatusOK, resp.StatusCode)
@@ -174,32 +194,6 @@ func (suite *ProxyTestSuite) TestHTTPSRequest() {
suite.NotEmpty(jsonStruct.Headers.TraceID) 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) { func TestProxy(t *testing.T) {
t.Parallel() t.Parallel()
suite.Run(t, &ProxyTestSuite{}) suite.Run(t, &ProxyTestSuite{})
+15 -16
View File
@@ -21,24 +21,23 @@ var secretEmptyKey [SecretKeyLength]byte
// |-|-------------------------------|------------------------------------------- // |-|-------------------------------|-------------------------------------------
// p key hostname // p key hostname
// //
// Serialized secret starts with 'ee'. Actually, in the past we also had // Serialized secret starts with 'ee'. Actually, in the past we also had 'dd'
// 'dd' secrets and prefixless ones. But this is history. Currently, // secrets and prefixless ones. But this is history. Currently, we do have only
// we do have only 'ee' secrets which mean faketls + protection from // 'ee' secrets which mean faketls + protection from statistical attacks on a
// statistical attacks on a length. 'ee' is a byte 238 (0xee). // length. 'ee' is a byte 238 (0xee).
// //
// After that, we have 16 bytes of the key. This is a random generated // After that, we have 16 bytes of the key. This is a random generated secret
// secret data of the proxy and this data is used to derive // data of the proxy and this data is used to derive authentication schemas.
// authentication schemas. These secrets are mixed into hmacs and sha256 // These secrets are mixed into hmacs and sha256 checksums which are used to
// checksums which are used to build AEAD ciphers for obfuscated2 // build AEAD ciphers for obfuscated2 protocol and ensure faketls handshake.
// protocol and ensure faketls handshake.
// //
// Host is a domain fronting hostname in latin1 (ASCII) encoding. This // Host is a domain fronting hostname in latin1 (ASCII) encoding. This hostname
// hostname should be used for SNI in faketls and MTG verifies it. Also, // should be used for SNI in faketls and MTG verifies it. Also, this is when
// this is when mtg gets about a domain fronting hostname. // mtg gets about a domain fronting hostname.
// //
// Secrets can be serialized into 2 forms: hex and base64. If // Secrets can be serialized into 2 forms: hex and base64. If you decode both
// you decode both forms into bytes, you'll get the same byte array. // forms into bytes, you'll get the same byte array. Telegram clients nowadays
// Telegram clients nowadays accept all forms. // accept all forms.
type Secret struct { type Secret struct {
// Key is a set of bytes used for traffic authentication. // Key is a set of bytes used for traffic authentication.
Key [SecretKeyLength]byte Key [SecretKeyLength]byte
@@ -75,7 +74,7 @@ func (s *Secret) Set(text string) error {
return fmt.Errorf("incorrect secret format: %w", err) 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)) 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 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) return s.ctx.Value(key)
} }
@@ -40,11 +40,11 @@ func (s *streamContext) Close() {
s.ctxCancel() s.ctxCancel()
if s.clientConn != nil { if s.clientConn != nil {
s.clientConn.Close() s.clientConn.Close() //nolint: errcheck
} }
if s.telegramConn != nil { 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() { func (suite *StreamContextTestSuite) SetupTest() {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
ctx = context.WithValue(ctx, "key", "value") // nolint: golint, revive, staticcheck ctx = context.WithValue(ctx, "key", "value") //nolint: staticcheck
suite.ctxCancel = cancel suite.ctxCancel = cancel
suite.connMock = &testlib.EssentialsConnMock{} suite.connMock = &testlib.EssentialsConnMock{}
+12 -7
View File
@@ -36,7 +36,8 @@ func (c *circuitBreakerDialer) Dial(network, address string) (essentials.Conn, e
} }
func (c *circuitBreakerDialer) DialContext(ctx context.Context, func (c *circuitBreakerDialer) DialContext(ctx context.Context,
network, address string) (essentials.Conn, error) { network, address string,
) (essentials.Conn, error) {
switch atomic.LoadUint32(&c.state) { switch atomic.LoadUint32(&c.state) {
case circuitBreakerStateClosed: case circuitBreakerStateClosed:
return c.doClosed(ctx, network, address) return c.doClosed(ctx, network, address)
@@ -48,13 +49,14 @@ func (c *circuitBreakerDialer) DialContext(ctx context.Context,
} }
func (c *circuitBreakerDialer) doClosed(ctx context.Context, func (c *circuitBreakerDialer) doClosed(ctx context.Context,
network, address string) (essentials.Conn, error) { network, address string,
) (essentials.Conn, error) {
conn, err := c.Dialer.DialContext(ctx, network, address) conn, err := c.Dialer.DialContext(ctx, network, address)
select { select {
case <-ctx.Done(): case <-ctx.Done():
if conn != nil { if conn != nil {
conn.Close() conn.Close() //nolint: errcheck
} }
return nil, ctx.Err() //nolint: wrapcheck return nil, ctx.Err() //nolint: wrapcheck
@@ -80,7 +82,8 @@ func (c *circuitBreakerDialer) doClosed(ctx context.Context,
} }
func (c *circuitBreakerDialer) doHalfOpened(ctx context.Context, func (c *circuitBreakerDialer) doHalfOpened(ctx context.Context,
network, address string) (essentials.Conn, error) { network, address string,
) (essentials.Conn, error) {
if !atomic.CompareAndSwapUint32(&c.halfOpenAttempts, 0, 1) { if !atomic.CompareAndSwapUint32(&c.halfOpenAttempts, 0, 1) {
return nil, ErrCircuitBreakerOpened return nil, ErrCircuitBreakerOpened
} }
@@ -90,7 +93,7 @@ func (c *circuitBreakerDialer) doHalfOpened(ctx context.Context,
select { select {
case <-ctx.Done(): case <-ctx.Done():
if conn != nil { if conn != nil {
conn.Close() conn.Close() //nolint: errcheck
} }
return nil, ctx.Err() //nolint: wrapcheck return nil, ctx.Err() //nolint: wrapcheck
@@ -174,14 +177,16 @@ func (c *circuitBreakerDialer) stopTimer(timerRef **time.Timer) {
} }
func (c *circuitBreakerDialer) ensureTimer(timerRef **time.Timer, func (c *circuitBreakerDialer) ensureTimer(timerRef **time.Timer,
timeout time.Duration, callback func()) { timeout time.Duration, callback func(),
) {
if *timerRef == nil { if *timerRef == nil {
*timerRef = time.AfterFunc(timeout, callback) *timerRef = time.AfterFunc(timeout, callback)
} }
} }
func newCircuitBreakerDialer(baseDialer Dialer, func newCircuitBreakerDialer(baseDialer Dialer,
openThreshold uint32, halfOpenTimeout, resetFailuresTimeout time.Duration) Dialer { openThreshold uint32, halfOpenTimeout, resetFailuresTimeout time.Duration,
) Dialer {
cb := &circuitBreakerDialer{ cb := &circuitBreakerDialer{
Dialer: baseDialer, Dialer: baseDialer,
stateMutexChan: make(chan bool, 1), stateMutexChan: make(chan bool, 1),
+8 -11
View File
@@ -52,17 +52,9 @@ func (suite *CircuitBreakerTestSuite) TestMultipleRunsOk() {
Return(suite.connMock, nil) Return(suite.connMock, nil)
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(5)
go func() {
wg.Wait()
suite.ctxCancel()
}()
for i := 0; i < 5; i++ {
go func() {
defer wg.Done()
for range 5 {
wg.Go(func() {
conn, err := suite.d.DialContext(suite.ctx, "tcp", "127.0.0.1") conn, err := suite.d.DialContext(suite.ctx, "tcp", "127.0.0.1")
suite.mutex.Lock() suite.mutex.Lock()
@@ -70,9 +62,14 @@ func (suite *CircuitBreakerTestSuite) TestMultipleRunsOk() {
suite.NoError(err) suite.NoError(err)
suite.Equal("127.0.0.1:3128", conn.RemoteAddr().String()) suite.Equal("127.0.0.1:3128", conn.RemoteAddr().String())
}() })
} }
go func() {
wg.Wait()
suite.ctxCancel()
}()
suite.Eventually(func() bool { suite.Eventually(func() bool {
_, ok := <-suite.ctx.Done() _, ok := <-suite.ctx.Done()
+1 -1
View File
@@ -31,7 +31,7 @@ func (d *defaultDialer) DialContext(ctx context.Context, network, address string
// we do not need to call to end user. End users call us. // we do not need to call to end user. End users call us.
if err := SetServerSocketOptions(conn, 0); err != nil { if err := SetServerSocketOptions(conn, 0); err != nil {
conn.Close() conn.Close() //nolint: errcheck
return nil, fmt.Errorf("cannot set socket options: %w", err) return nil, fmt.Errorf("cannot set socket options: %w", err)
} }
+2 -2
View File
@@ -51,7 +51,7 @@ func (suite *DefaultDialerTestSuite) TestConnectOk() {
suite.NoError(err) suite.NoError(err)
suite.NotNil(conn) suite.NotNil(conn)
conn.Close() conn.Close() //nolint: errcheck
} }
func (suite *DefaultDialerTestSuite) TestHTTPRequest() { func (suite *DefaultDialerTestSuite) TestHTTPRequest() {
@@ -59,7 +59,7 @@ func (suite *DefaultDialerTestSuite) TestHTTPRequest() {
resp, err := httpClient.Get(suite.MakeURL("/get")) //nolint: noctx resp, err := httpClient.Get(suite.MakeURL("/get")) //nolint: noctx
if err == nil { if err == nil {
defer resp.Body.Close() defer resp.Body.Close() //nolint: errcheck
} }
suite.NoError(err) suite.NoError(err)

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