mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 18:44:02 +03:00
REPOSITORY / ScuroNeko/mtg
Compare commits
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64859069ed | ||
|
|
c1935b4cef | ||
|
|
f48156814b | ||
|
|
9d67414db6 | ||
|
|
a587f85dd2 | ||
|
|
18a52340fa | ||
|
|
981ffb0dbe | ||
|
|
b976e74fc0 | ||
|
|
b2de98c8f9 | ||
|
|
6f3abf0990 | ||
|
|
e74726af70 | ||
|
|
8da55410de | ||
|
|
4b1f823777 | ||
|
|
511090b806 | ||
|
|
eba378817e | ||
|
|
c178b4b9cc | ||
|
|
3e0880f6b4 | ||
|
|
7718f62477 | ||
|
|
df0994dad3 | ||
|
|
dca43853fb | ||
|
|
ea8e31346c | ||
|
|
ed495b3800 | ||
|
|
4025f223c4 | ||
|
|
90e0ce4c48 | ||
|
|
ac0b442b43 | ||
|
|
af7021fac8 | ||
|
|
5468625212 | ||
|
|
345c1bf715 | ||
|
|
52d47a9786 | ||
|
|
2ba79a8508 | ||
|
|
ce7769472d | ||
|
|
eb5eea2625 | ||
|
|
d965dc3c07 | ||
|
|
040b3c371c | ||
|
|
c7ba8e54be | ||
|
|
33b1a5426c | ||
|
|
91197c0334 | ||
|
|
e075169dd4 | ||
|
|
225444b1b3 | ||
|
|
586ac9dcb9 | ||
|
|
c3be5311e8 | ||
|
|
bfeded67ee | ||
|
|
dde0d25b77 | ||
|
|
296a34cd6e | ||
|
|
44cea68e83 | ||
|
|
5d17e264e6 | ||
|
|
7ef1e99f66 | ||
|
|
3acbacbdb3 | ||
|
|
5fd7062972 | ||
|
|
7a6695f6c4 | ||
|
|
8ac786581b | ||
|
|
86a8f69273 | ||
|
|
95b16cd805 | ||
|
|
3996b9a6b8 | ||
|
|
e286dea650 | ||
|
|
5e27e48129 | ||
|
|
95abb54dc8 | ||
|
|
dbd3d3c266 | ||
|
|
ad7acee64d | ||
|
|
9ce6674070 | ||
|
|
25edb60f2c | ||
|
|
b5a8d8b804 | ||
|
|
35f0fca3c5 | ||
|
|
cd46a734ea | ||
|
|
cd92a343aa | ||
|
|
289e8f5945 | ||
|
|
850bed0dfc | ||
|
|
8977d77a13 | ||
|
|
91726d461f | ||
|
|
be1a47287a | ||
|
|
426483d27b | ||
|
|
bc72717582 | ||
|
|
391d901402 | ||
|
|
837d96dc43 | ||
|
|
9125a29e79 | ||
|
|
6449109b2b | ||
|
|
829669eefd | ||
|
|
2d9f2345dc | ||
|
|
6a9069ca72 | ||
|
|
d72ed03a43 | ||
|
|
f0d2cd494a | ||
|
|
b5810c955d | ||
|
|
4ebae693e0 | ||
|
|
1a619e3090 | ||
|
|
8e29ff78ba | ||
|
|
efdac2c0d8 | ||
|
|
e559388802 | ||
|
|
cb691453f0 | ||
|
|
020065f389 | ||
|
|
cf5197cee0 | ||
|
|
720158f15c | ||
|
|
07f00a363e | ||
|
|
d32ab34e60 | ||
|
|
5a52418394 | ||
|
|
cf43ae780b | ||
|
|
2407748ab0 | ||
|
|
a47edf08d0 | ||
|
|
566955b8b7 | ||
|
|
5cc07a54a2 | ||
|
|
ba2143a6ae | ||
|
|
acb02baa2e | ||
|
|
422ed5b071 | ||
|
|
19efcc93fc | ||
|
|
b4c47ff4e1 | ||
|
|
8a783d6b4e | ||
|
|
c5029c706c | ||
|
|
d7e565e0f5 | ||
|
|
93686377d5 | ||
|
|
87de28636a | ||
|
|
7788b169b6 | ||
|
|
6b9601fc2c | ||
|
|
a65cb05d20 | ||
|
|
31866d5f64 | ||
|
|
0db83a0cc8 | ||
|
|
40bb59faf5 | ||
|
|
307910fe4a | ||
|
|
4c2c468289 | ||
|
|
f2f7c2a4de | ||
|
|
8fdd4292b1 | ||
|
|
44d339d777 | ||
|
|
c6223e256d | ||
|
|
7928c2b323 | ||
|
|
b2e7e1a225 | ||
|
|
843fc4b70f | ||
|
|
ed9689d813 | ||
|
|
59ddffa911 | ||
|
|
3af9ede1c1 | ||
|
|
47364e7ba8 | ||
|
|
b6251608d9 | ||
|
|
6dce3944e4 | ||
|
|
112d3a916c | ||
|
|
5d3824704c | ||
|
|
0ff5ca6808 | ||
|
|
8e0436e89a | ||
|
|
646294b6ba | ||
|
|
196ef3d449 | ||
|
|
4f8755879d | ||
|
|
3451832d13 | ||
|
|
dfa53af567 | ||
|
|
27c5646680 | ||
|
|
596fafe30d | ||
|
|
e465817011 | ||
|
|
ef60877cb0 | ||
|
|
8b802819ee | ||
|
|
3c75fcca80 | ||
|
|
1258f07197 | ||
|
|
24137b6ea2 | ||
|
|
7d1d9007af | ||
|
|
ae479adeaa | ||
|
|
6732e80d06 | ||
|
|
2bf291bab5 | ||
|
|
273aa18367 | ||
|
|
b491e03504 | ||
|
|
a7a4da8ee5 | ||
|
|
34ad883559 | ||
|
|
22905b2a25 | ||
|
|
0ff9a58780 | ||
|
|
ed180ecb3d | ||
|
|
f005592872 | ||
|
|
d4cd779c42 | ||
|
|
9e3683ce08 | ||
|
|
d8216f776d | ||
|
|
038b2b200d | ||
|
|
fd8506c82a | ||
|
|
559a1a666b | ||
|
|
4c5f1a5636 | ||
|
|
fbbf9b1634 | ||
|
|
1e428e13fc | ||
|
|
f531d90fc4 | ||
|
|
5f168d1ee4 | ||
|
|
5009859a1e | ||
|
|
432e2970a0 | ||
|
|
6e154b3f44 | ||
|
|
56cf90b13d | ||
|
|
d10729490d | ||
|
|
d459efcf9c | ||
|
|
2eba78b0db | ||
|
|
61450ea3db | ||
|
|
c62afbdfd8 | ||
|
|
828627b0d8 | ||
|
|
c68f2cd50f | ||
|
|
e81c5970d4 | ||
|
|
1a7eee444e | ||
|
|
d9bd07b027 | ||
|
|
3ae69183aa | ||
|
|
6c7edfb7db | ||
|
|
d44474012a | ||
|
|
413cafeeb6 | ||
|
|
c9743b5675 | ||
|
|
072bce2922 | ||
|
|
386522ba22 | ||
|
|
029245cd16 | ||
|
|
b280f47731 | ||
|
|
1617866c24 | ||
|
|
691954bd16 | ||
|
|
9534cb19d0 | ||
|
|
7827d1255a | ||
|
|
d431feb0ba | ||
|
|
3816dbf5b1 | ||
|
|
2918ed11e5 | ||
|
|
701f62ed4c | ||
|
|
299a34252e | ||
|
|
2492a47d0a | ||
|
|
c2b8e88961 | ||
|
|
c9d3020ea6 | ||
|
|
018e5b96f4 | ||
|
|
07985cf418 | ||
|
|
389f6fd4bf | ||
|
|
09c7ce45d2 | ||
|
|
199eed0f18 | ||
|
|
82ec3871a4 | ||
|
|
2748fbd1e5 | ||
|
|
f14f104bea | ||
|
|
b1074193a8 | ||
|
|
78dea9ae3f | ||
|
|
da8dba1585 | ||
|
|
33852ca481 | ||
|
|
eff53694a0 | ||
|
|
74a7c505eb | ||
|
|
9983f7fcba | ||
|
|
a90072260e | ||
|
|
b153240bc3 | ||
|
|
6dfbd26524 | ||
|
|
39e7663e0c | ||
|
|
71f7bf6cad | ||
|
|
fba0e235c1 | ||
|
|
70be70de3c | ||
|
|
b565d83d40 | ||
|
|
ebc459440c | ||
|
|
6e6049a73a | ||
|
|
916714a909 | ||
|
|
f68b195306 | ||
|
|
5f1d1ce883 | ||
|
|
16558a7c55 | ||
|
|
e7958aaf33 | ||
|
|
7d6b661d97 | ||
|
|
28b3cbe91a | ||
|
|
6818364231 | ||
|
|
ea97bf51c8 | ||
|
|
1678ddbd53 | ||
|
|
f81f29cbb4 | ||
|
|
2d9259db48 | ||
|
|
c721c636d9 | ||
|
|
60472072aa | ||
|
|
6721e6fd9f | ||
|
|
47fb5c23cb | ||
|
|
fce6118c71 | ||
|
|
d277a2975a | ||
|
|
c3f21a7b0d | ||
|
|
61a1024264 | ||
|
|
4695a0c433 | ||
|
|
a9d0ca1e90 | ||
|
|
9346c11f37 | ||
|
|
5f00363da5 | ||
|
|
3aec9657d7 | ||
|
|
358d42ec61 | ||
|
|
ac2625209b | ||
|
|
c74e0ee359 | ||
|
|
7c463851b2 | ||
|
|
c9f6b922c3 | ||
|
|
7128e70e50 | ||
|
|
cf9bf56d8e | ||
|
|
76cfbb009a | ||
|
|
cb78169d6a | ||
|
|
d1703873c1 | ||
|
|
ac33abbbb1 | ||
|
|
a6893c8df7 | ||
|
|
7182c7bf65 |
+7
-1
@@ -1,3 +1,9 @@
|
||||
version.go
|
||||
vendor/
|
||||
tags
|
||||
.github/
|
||||
.bin/
|
||||
*.md
|
||||
mtg
|
||||
.golangci.toml
|
||||
.gitignore
|
||||
run.sh
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
branches:
|
||||
- master
|
||||
- stable
|
||||
- v1
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
- released
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
strategy:
|
||||
matrix:
|
||||
go_version:
|
||||
- ^1.19
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go_version }}
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-go-
|
||||
|
||||
- name: Run tests
|
||||
run: go test -coverprofile=./coverage.txt -covermode=atomic -v ./...
|
||||
|
||||
- name: Collect coverage
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
file: ./coverage.txt
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Run linter
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
version: v1.48.0
|
||||
|
||||
docker:
|
||||
name: Docker
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Get Docker meta
|
||||
id: docker_meta
|
||||
uses: crazy-max/ghaction-docker-meta@v1
|
||||
with:
|
||||
images: nineseconds/mtg,ghcr.io/9seconds/mtg
|
||||
tag-semver: "{{version}},{{major}},{{major}}.{{minor}}"
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Setup BuildX
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Setup cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Login to GHCR.io
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GH_PAT }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
pull: true
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7,linux/arm/v6
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
cache-from: type=local,src=/tmp/buildx-cache
|
||||
cache-to: type=local,dest=/tmp/buildx-cache
|
||||
@@ -0,0 +1,69 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- stable
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '24 20 * * 5'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
+2
-2
@@ -7,5 +7,5 @@
|
||||
*.out
|
||||
mtg
|
||||
vendor/
|
||||
version.go
|
||||
ccbuilds/
|
||||
.bin/
|
||||
dist/
|
||||
|
||||
+11
-1
@@ -3,10 +3,20 @@ concurrency = 4
|
||||
deadline = "2m"
|
||||
tests = true
|
||||
skip-dirs = ["vendor"]
|
||||
skip-files = ["version.go"]
|
||||
|
||||
[output]
|
||||
format = "colored-line-number"
|
||||
|
||||
[linters]
|
||||
enable-all = true
|
||||
disable = [
|
||||
"containedctx",
|
||||
"exhaustivestruct",
|
||||
"exhaustruct",
|
||||
"gas",
|
||||
"gochecknoglobals",
|
||||
"goerr113",
|
||||
"ireturn",
|
||||
"thelper",
|
||||
"varnamelen",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
|
||||
project_name: mtg
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- go generate ./...
|
||||
|
||||
builds:
|
||||
- binary: '{{ .ProjectName }}'
|
||||
goos:
|
||||
- darwin
|
||||
- freebsd
|
||||
- linux
|
||||
- netbsd
|
||||
- openbsd
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
goarm:
|
||||
- 6
|
||||
- 7
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
flags:
|
||||
- -trimpath
|
||||
- -mod=readonly
|
||||
ldflags: -s -w -X main.version={{ .Version }}
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
- goos: freebsd
|
||||
goarch: arm64
|
||||
- goos: netbsd
|
||||
goarch: arm64
|
||||
- goos: openbsd
|
||||
goarch: arm64
|
||||
|
||||
archives:
|
||||
- name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
||||
format: tar.gz
|
||||
wrap_in_directory: true
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
files:
|
||||
- LICENSE
|
||||
- README.md
|
||||
- SECURITY.md
|
||||
|
||||
gomod:
|
||||
proxy: true
|
||||
|
||||
snapshot:
|
||||
name_template: '{{ .Version }}'
|
||||
|
||||
checksum:
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt'
|
||||
|
||||
source:
|
||||
enabled: true
|
||||
name_template: '{{ .ProjectName }}-sources'
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
---
|
||||
|
||||
language: go
|
||||
sudo: false
|
||||
dist: trusty
|
||||
|
||||
go:
|
||||
- "1.11.x"
|
||||
- master
|
||||
|
||||
before_script: make prepare
|
||||
|
||||
script:
|
||||
- make all
|
||||
- make lint
|
||||
- make critic
|
||||
- make test
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
+10
-15
@@ -1,7 +1,7 @@
|
||||
###############################################################################
|
||||
# BUILD STAGE
|
||||
|
||||
FROM golang:1.11-alpine
|
||||
FROM golang:1.19-alpine AS build
|
||||
|
||||
RUN set -x \
|
||||
&& apk --no-cache --update add \
|
||||
@@ -9,16 +9,13 @@ RUN set -x \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
make \
|
||||
upx \
|
||||
&& update-ca-certificates
|
||||
make
|
||||
|
||||
COPY . /go/src/github.com/9seconds/mtg/
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
|
||||
RUN set -x \
|
||||
&& cd /go/src/github.com/9seconds/mtg \
|
||||
&& make -j 4 static \
|
||||
&& upx --ultra-brute -qq ./mtg
|
||||
&& make -j 4 static
|
||||
|
||||
|
||||
###############################################################################
|
||||
@@ -26,12 +23,10 @@ RUN set -x \
|
||||
|
||||
FROM scratch
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/mtg"]
|
||||
ENV MTG_IP=0.0.0.0 \
|
||||
MTG_PORT=3128 \
|
||||
MTG_STATS_IP=0.0.0.0 \
|
||||
MTG_STATS_PORT=3129
|
||||
ENTRYPOINT ["/mtg"]
|
||||
ENV MTG_BIND=0.0.0.0:3128 \
|
||||
MTG_STATS_BIND=0.0.0.0:3129
|
||||
EXPOSE 3128 3129
|
||||
|
||||
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=0 /go/src/github.com/9seconds/mtg/mtg /usr/local/bin/mtg
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=build /app/mtg /mtg
|
||||
|
||||
@@ -2,67 +2,41 @@ ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
IMAGE_NAME := mtg
|
||||
APP_NAME := $(IMAGE_NAME)
|
||||
|
||||
CC_BINARIES := $(shell bash -c "echo -n $(APP_NAME)-{linux,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}")
|
||||
APP_DEPS := version.go
|
||||
GOLANGCI_LINT_VERSION := v1.48.0
|
||||
|
||||
GOLANGCI_LINT_VERSION := v1.10.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="-s -w -X 'main.version=$(VERSION_TAG) ($(VERSION_GO)) [$(VERSION_DATE)]'"
|
||||
|
||||
COMMON_BUILD_FLAGS := -ldflags="-s -w"
|
||||
|
||||
MOD_ON := env GO111MODULE=on
|
||||
MOD_OFF := env GO111MODULE=auto
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
$(APP_NAME): $(APP_DEPS)
|
||||
@$(MOD_ON) go build $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)"
|
||||
|
||||
static-$(APP_NAME): $(APP_DEPS)
|
||||
@$(MOD_ON) env CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)"
|
||||
|
||||
$(APP_NAME)-%: GOOS=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f1 -d-)
|
||||
$(APP_NAME)-%: GOARCH=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f2 -d-)
|
||||
$(APP_NAME)-%: $(APP_DEPS) ccbuilds
|
||||
@$(MOD_ON) env "GOOS=$(GOOS)" "GOARCH=$(GOARCH)" \
|
||||
go build \
|
||||
$(COMMON_BUILD_FLAGS) \
|
||||
-o "./ccbuilds/$(APP_NAME)-$(GOOS)-$(GOARCH)"
|
||||
|
||||
ccbuilds:
|
||||
@rm -rf ./ccbuilds && mkdir -p ./ccbuilds
|
||||
|
||||
version.go:
|
||||
@$(MOD_ON) go generate main.go
|
||||
|
||||
vendor: go.mod go.sum
|
||||
@$(MOD_ON) go mod vendor
|
||||
GOBIN := $(ROOT_DIR)/.bin
|
||||
GOTOOL := env "GOBIN=$(GOBIN)" "PATH=$(ROOT_DIR)/.bin:$(PATH)"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
.PHONY: all
|
||||
all: $(APP_NAME)
|
||||
all: build
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
@go build $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)"
|
||||
|
||||
$(APP_NAME): build
|
||||
|
||||
.PHONY: static
|
||||
static: static-$(APP_NAME)
|
||||
static:
|
||||
@env CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)"
|
||||
|
||||
.PHONY: crosscompile
|
||||
crosscompile: $(CC_BINARIES)
|
||||
|
||||
.PHONY: crosscompile-dir
|
||||
crosscompile-dir:
|
||||
@rm -rf "$(CC_DIR)" && mkdir -p "$(CC_DIR)"
|
||||
vendor: go.mod go.sum
|
||||
@$(MOD_ON) go mod vendor
|
||||
|
||||
.PHONY: test
|
||||
test: vendor $(APP_DEPS)
|
||||
@$(MOD_ON) go test -v ./...
|
||||
test:
|
||||
@go test -v ./...
|
||||
|
||||
.PHONY: lint
|
||||
lint: vendor $(APP_DEPS)
|
||||
@$(MOD_OFF) golangci-lint run
|
||||
|
||||
.PHONY: critic
|
||||
critic: vendor $(APP_DEPS)
|
||||
@$(MOD_OFF) gocritic check-project "$(ROOT_DIR)"
|
||||
.PHONY: citest
|
||||
citest:
|
||||
@go test -coverprofile=coverage.txt -covermode=atomic -race -v ./...
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@@ -70,18 +44,50 @@ clean:
|
||||
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: docker
|
||||
docker:
|
||||
@docker build --pull -t "$(IMAGE_NAME)" "$(ROOT_DIR)"
|
||||
|
||||
.PHONY: prepare
|
||||
prepare: install-lint install-critic
|
||||
.PHONY: doc
|
||||
doc:
|
||||
@$(GOTOOL) godoc -http 0.0.0.0:10000
|
||||
|
||||
.PHONY: install-lint
|
||||
install-lint:
|
||||
@curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh \
|
||||
| $(MOD_OFF) bash -s -- -b $(GOPATH)/bin $(GOLANGCI_LINT_VERSION)
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
@$(GOTOOL) gofumpt -w -extra "$(ROOT_DIR)"
|
||||
|
||||
.PHONY: install-critic
|
||||
install-critic:
|
||||
@$(MOD_OFF) go get -u github.com/go-critic/go-critic/...
|
||||
.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: install-tools
|
||||
install-tools: install-tools-lint install-tools-godoc install-tools-gofumpt install-tools-goreleaser
|
||||
|
||||
.PHONY: install-tools-lint
|
||||
install-tools-lint:
|
||||
@mkdir -p "$(GOBIN)" || true && \
|
||||
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:
|
||||
@mkdir -p "$(GOBIN)" || true && \
|
||||
$(GOTOOL) go get -u golang.org/x/tools/cmd/godoc
|
||||
|
||||
.PHONY: install-tools-gofumpt
|
||||
install-tools-gofumpt: .bin
|
||||
@$(GOTOOL) go install mvdan.cc/gofumpt@latest
|
||||
|
||||
.PHONY: goreleaser
|
||||
install-tools-goreleaser: .bin
|
||||
@$(GOTOOL) go install github.com/goreleaser/goreleaser@latest
|
||||
|
||||
.PHONY: update-deps
|
||||
update-deps:
|
||||
@go get -u && go mod tidy -go=1.18
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
Bullshit-free MTPROTO proxy for Telegram
|
||||
|
||||
[](https://travis-ci.org/9seconds/mtg)
|
||||
[](https://goreportcard.com/report/github.com/9seconds/mtg)
|
||||
[](https://hub.docker.com/r/nineseconds/mtg/)
|
||||
|
||||
**Please see a guide on upgrading to 1.0 at the end of this README.**
|
||||
|
||||
# Rationale
|
||||
|
||||
There are several available proxies for Telegram MTPROTO available. Here
|
||||
@@ -14,33 +17,33 @@ are the most notable:
|
||||
* [Python](https://github.com/alexbers/mtprotoproxy)
|
||||
* [Erlang](https://github.com/seriyps/mtproto_proxy)
|
||||
|
||||
Almost all of them follow the way how official proxy was build. This
|
||||
includes support of multiple secrets, support of promoted channels etc.
|
||||
Almost all of them follow the way how official proxy was built. This
|
||||
includes support of multiple secrets, support of promoted channels, etc.
|
||||
|
||||
mtg is an implementation in golang which is intended to be:
|
||||
|
||||
* **Lightweight**
|
||||
It has to consume as less resources as possible but not by losing
|
||||
It has to consume as few resources as possible but not by losing
|
||||
maintainability.
|
||||
* **Easily deployable**
|
||||
I strongly believe that Telegram proxies should follow the way of
|
||||
ShadowSocks: promoted channels is a strange way of doing business
|
||||
I suppose. I think the only viable way is to have a proxy with
|
||||
minimum configuration which should work everywhere.
|
||||
* **Single secret**
|
||||
I think that multiple secrets solves no problems and just complexify
|
||||
software. I also believe that in case of throwout proxies, this feature
|
||||
is useless luxury.
|
||||
* **A single secret**
|
||||
I think that multiple secrets solve no problems and just complexify
|
||||
software. I also believe that in the case of throwout proxies, this
|
||||
feature is a useless luxury.
|
||||
* **Minimum docker image size**
|
||||
Official image is less than 2.5 megabytes. Literally.
|
||||
Official image is less than 3.5 megabytes. Literally.
|
||||
* **No management WebUI**
|
||||
This is an implementation of simple lightweight proxy. I won't do that.
|
||||
This is an implementation of a simple lightweight proxy. I won't do that.
|
||||
|
||||
This proxy supports 2 modes of work: direct connection to Telegram and
|
||||
promoted channel mode. If you do not need promoted channels, I would
|
||||
recommend you to go with direct mode: this is way more robust.
|
||||
recommend you to go with direct mode: this way is more robust.
|
||||
|
||||
To run proxy in direct mode, all you need to do is just provide a
|
||||
To run a proxy in direct mode, all you need to do is just provide a
|
||||
secret. If you do not provide ADTag as a second parameter, promoted
|
||||
channels mode won't be activated.
|
||||
|
||||
@@ -94,68 +97,110 @@ docker pull nineseconds/mtg:stable
|
||||
docker pull nineseconds/mtg:0.10
|
||||
```
|
||||
|
||||
# Ansible role
|
||||
|
||||
You can find unofficial Ansible role for mtg here: https://github.com/rlex/ansible-role-mtg
|
||||
Also, there is another project on Ansible Galaxy: https://galaxy.ansible.com/ivansible/lin_mtproxy
|
||||
|
||||
# Configuration
|
||||
|
||||
Basically, to run this tool you need to configure as less as possible.
|
||||
To run this tool you need to configure as less as possible. Telegram
|
||||
clients support 3 different secret types:
|
||||
|
||||
* Simple - basically, it is just a flow of frames ciphered by AES-CTR stream
|
||||
cipher.
|
||||
* Secured - the same stream as simple but with some random noise to prevent
|
||||
statistical analysis of traffic flow.
|
||||
* FakeTLS - this mode envelops telegram stream in TLS so it looks (in theory)
|
||||
the same as any TLS1.3 traffic from DPI point of view.
|
||||
|
||||
If you do not have preferences, go with FakeTLS or at least secured.
|
||||
Simple mode is a little bit naive and traffic flow can be easily
|
||||
identified as Telegram one.
|
||||
|
||||
Unlike the rest of implementation, mtg is quite strict about the
|
||||
execution mode: if you run a proxy instance with FakeTLS secret, you
|
||||
can't connect to it with simple or secured clients. You can't connect
|
||||
to the proxy with secured secret with FakeTLS key. It forces one mode
|
||||
of working. So, unfortunately, there is no way how to connect to the
|
||||
deployed proxy with another secret (if you know how to construct and
|
||||
convert them). But at the same time, old clients can't connect so they
|
||||
won't expose the type of the service.
|
||||
|
||||
First, you need to generate a secret:
|
||||
|
||||
```console
|
||||
openssl rand -hex 16
|
||||
$ mtg generate-secret simple
|
||||
52a493bdfb90eea55739eabff2d92a14
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```console
|
||||
head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' '
|
||||
$ mtg generate-secret secured
|
||||
ddf05fb7acb549be047a7c585116581418
|
||||
```
|
||||
|
||||
## Secure mode
|
||||
|
||||
If you want to support new secure mode, please prepend `dd` to the
|
||||
secret. For example, secret `cf18fa8ea0267057e2c61a5f7322a8e7` should
|
||||
be `ddcf18fa8ea0267057e2c61a5f7322a8e7`. But pay attention that some
|
||||
old clients won't support this mode. If this is not your case, I would
|
||||
suggest to go with this mode.
|
||||
|
||||
Oneliners to generate such secrets:
|
||||
|
||||
```console
|
||||
echo dd$(openssl rand -hex 16)
|
||||
$ mtg generate-secret -c google.com tls
|
||||
ee852380f362a09343efb4690c4e17862e676f6f676c652e636f6d
|
||||
```
|
||||
|
||||
or
|
||||
Or, if you prefer docker:
|
||||
|
||||
```console
|
||||
echo dd$(head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' ')
|
||||
$ docker run --rm nineseconds/mtg generate-secret tls -c bing.com
|
||||
eedf71035a8ed48a623d8e83e66aec4d0562696e672e636f6d
|
||||
```
|
||||
|
||||
## Antireplay cache
|
||||
|
||||
To prevent replay attacks, we have internal storage of first frames
|
||||
messages for connected clients. These frames are generated randomly
|
||||
by design and we have the negligible possibility of duplication
|
||||
(probability is 1/(2^64)) but it could be quite effective to prevent
|
||||
replays.
|
||||
|
||||
It is possible to disable this cache. To do that, please explicitly set
|
||||
its size to 0.
|
||||
|
||||
|
||||
## FakeTLS
|
||||
|
||||
If you run this a proxy in faketls mode, this proxy will try to hide
|
||||
itself cloaking a host provided as a part of the generated secret. It
|
||||
means that if you cloak google.com then you can curl this proxy and
|
||||
you'll get a google.com response back.
|
||||
|
||||
mtg proxies L3 traffic. In other words, only TCP, without interfering in
|
||||
TLS, HTTP or any other high-level protocol.
|
||||
|
||||
|
||||
## Environment variables
|
||||
|
||||
It is possible to configure this tool using environment variables. You
|
||||
can configure any flag but not secret or adtag. Here is the list of
|
||||
supported environment variables:
|
||||
|
||||
| Environment variable | Corresponding flags | Default value | Description |
|
||||
|--------------------------|------------------------|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `MTG_DEBUG` | `-d`, `--debug` | `false` | Run in debug mode. Usually, you need to run in this mode only if you develop this tool or its maintainer is asking you to provide logs with such verbosity. |
|
||||
| `MTG_VERBOSE` | `-v`, `--verbose` | `false` | Run in verbose mode. This is way less chatty than debug mode. |
|
||||
| `MTG_IP` | `-b`, `--bind-ip` | `127.0.0.1` | Which IP should we bind to. As usual, `0.0.0.0` means that we want to listen on all interfaces. Also, 4 zeroes will bind to both IPv4 and IPv6. |
|
||||
| `MTG_PORT` | `-p`, `--bind-port` | `3128` | Which port should we bind to (listen on). |
|
||||
| `MTG_IPV4` | `-4`, `--public-ipv4` | [Autodetect](https://ifconfig.co) | IPv4 address of this proxy. This is required if you NAT your proxy or run it in a docker container. In that case, you absolutely need to specify public IPv4 address of the proxy, otherwise either URLs will be broken or proxy could not access Telegram middle proxies. |
|
||||
| `MTG_IPV4_PORT` | `--public-ipv4-port` | Value of `--bind-port` | Which port should be public of IPv4 interface. This affects only generated links and should be changed only if you NAT your proxy or run it in a docker container. |
|
||||
| `MTG_IPV6` | `-6`, `--public-ipv6` | [Autodetect](https://ifconfig.co) | IPv6 address of this proxy. This is required if you NAT your proxy or run it in a docker container. In that case, you absolutely need to specify public IPv6 address of the proxy, otherwise either URLs will be broken or proxy could not access Telegram middle proxies. |
|
||||
| `MTG_IPV6_PORT` | `--public-ipv6-port` | Value of `--bind-port` | Which port should be public of IPv6 interface. This affects only generated links and should be changed only if you NAT your proxy or run it in a docker container. |
|
||||
| `MTG_STATS_IP` | `-t`, `--stats-ip` | `127.0.0.1` | Which IP should we bind the internal statistics HTTP server. |
|
||||
| `MTG_STATS_PORT` | `-q`, `--stats-port` | `3129` | Which port should we bind the internal statistics HTTP server. |
|
||||
| `MTG_STATSD_IP` | `--statsd-ip` | | IP/host addresses of statsd service. No defaults, by defaults we do not send anything there. |
|
||||
| `MTG_STATSD_PORT` | `--statsd-port` | `8125` | Which port should we use to work with statsd. |
|
||||
| `MTG_STATSD_NETWORK` | `--statsd-network` | `udp` | Which protocol should we use to work with statsd. Possible options are `udp` and `tcp`. |
|
||||
| `MTG_STATSD_PREFIX` | `--statsd-prefix` | `mtg` | Which bucket prefix we should use. For example, if you set `mtg`, then metric `traffic.ingress` would be send as `mtg.traffic.ingress`. |
|
||||
| `MTG_STATSD_TAGS_FORMAT` | `--statsd-tags-format` | | Which tags format we should use. By default, we are using default vanilla statsd tags format but if you want to send directly to InfluxDB or Datadog, please specify it there. Possible options are `influxdb` and `datadog`. |
|
||||
| `MTG_STATSD_TAGS` | `--statsd-tags` | | Which tags should we send to statsd with our metrics. Please specify them as `key=value` pairs. |
|
||||
| `MTG_BUFFER_WRITE` | `-w`, `--write-buffer` | `65536` | The size of TCP write buffer in bytes. Write buffer is the buffer for messages which are going from client to Telegram. |
|
||||
| `MTG_BUFFER_READ` | `-r`, `--read-buffer` | `131072` | The size of TCP read buffer in bytes. Read buffer is the buffer for messages from Telegram to client. |
|
||||
| Environment variable | Corresponding flags | Default value | Description |
|
||||
|-------------------------------|------------------------------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `MTG_DEBUG` | `-d`, `--debug` | `false` | Run in debug mode. Usually, you need to run in this mode only if you develop this tool or its maintainer is asking you to provide logs with such verbosity. |
|
||||
| `MTG_VERBOSE` | `-v`, `--verbose` | `false` | Run in verbose mode. This is way less chatty than debug mode. |
|
||||
| `MTG_BIND` | `-b`, `--bind` | `0.0.0.0:3128` | Which host/port pair should we bind to (listen on). |
|
||||
| `MTG_IPV4` | `-4`, `--public-ipv4` | [Autodetect](https://ifconfig.co) | IPv4 address:port of this proxy. This is required if you NAT your proxy or run it in a docker container. In that case, you absolutely need to specify public IPv4 address of the proxy, otherwise either URLs will be broken or proxy could not access Telegram middle proxies. |
|
||||
| `MTG_IPV6` | `-6`, `--public-ipv6` | [Autodetect](https://ifconfig.co) | IPv6 address:port of this proxy. This is required if you NAT your proxy or run it in a docker container. In that case, you absolutely need to specify public IPv6 address of the proxy, otherwise either URLs will be broken or proxy could not access Telegram middle proxies. |
|
||||
| `MTG_STATS_BIND` | `-t`, `--stats-bind` | `127.0.0.1:3129` | Which hist:port should we bind the internal statistics HTTP server (Prometheus). |
|
||||
| `MTG_STATS_NAMESPACE` | `--stats-namespace` | `mtg` | Which namespace should be used for prometheus metrics. |
|
||||
| `MTG_STATSD_ADDR` | `--statsd-addr` | | host:port of statsd service. No defaults, by default we do not send anything there. |
|
||||
| `MTG_STATSD_PREFIX` | `--statsd-prefix` | `mtg` | Which bucket prefix we should use. For example, if you set `mtg`, then metric `traffic.ingress` would be send as `mtg.traffic.ingress`. |
|
||||
| `MTG_STATSD_TAGS_FORMAT` | `--statsd-tags-format` | | Which tags format we should use. By default, we are using default vanilla statsd tags format but if you want to send directly to InfluxDB or Datadog, please specify it there. Possible options are `influxdb` and `datadog`. |
|
||||
| `MTG_STATSD_TAGS` | `--statsd-tags` | | Which tags should we send to statsd with our metrics. Please specify them as `key=value` pairs. |
|
||||
| `MTG_BUFFER_WRITE` | `-w`, `--write-buffer` | `32KB` | The size of TCP write buffer in bytes. Write buffer is the buffer for messages which are going from client to Telegram. |
|
||||
| `MTG_BUFFER_READ` | `-r`, `--read-buffer` | `32KB` | The size of TCP read buffer in bytes. Read buffer is the buffer for messages from Telegram to client. |
|
||||
| `MTG_ANTIREPLAY_MAXSIZE` | `--anti-replay-max-size` | `128MB` | Max size of antireplay cache. |
|
||||
| `MTG_CLOAK_PORT` | `--cloak-port` | `443` | Which port we should use to connect to cloaked host in FakeTLS mode. |
|
||||
| `MTG_MULTIPLEX_PERCONNECTION` | `--multiplex-per-connection` | `50` | How many client connections can share a single Telegram connection in adtag mode |
|
||||
| `MTG_NTP_SERVERS` | `--ntp-server` | default pool | A list of NTP servers to use. |
|
||||
| `MTG_PREFER_DIRECT_IP` | `--prefer-ip` | `ipv6` | Which IP protocol to prefer if possible. Works mostly in direct mode. |
|
||||
|
||||
|
||||
Usually you want to modify only read/write buffer sizes. If you feel
|
||||
that proxy is slow, try to increase both sizes giving more priority to
|
||||
@@ -170,35 +215,26 @@ userspace.
|
||||
Now run the tool:
|
||||
|
||||
```console
|
||||
mtg <secret>
|
||||
$ mtg run <secret>
|
||||
```
|
||||
|
||||
How to run the tool with ADTag:
|
||||
|
||||
```console
|
||||
mtg <secret> <adtag>
|
||||
$ mtg run <secret> <adtag>
|
||||
```
|
||||
|
||||
This tool will listen on port 3128 by default with the given secret.
|
||||
|
||||
# One-line runner
|
||||
|
||||
# oneliner to run this proxy
|
||||
|
||||
Please ensure that docker is installed. After that just execute
|
||||
|
||||
```console
|
||||
docker run --name mtg --restart=unless-stopped -p 3128:3128 -p 3129:3129 -d nineseconds/mtg:stable $(openssl rand -hex 16)
|
||||
curl -sfL --compressed https://raw.githubusercontent.com/9seconds/mtg/master/run.sh | bash
|
||||
```
|
||||
|
||||
or in secret mode:
|
||||
|
||||
```console
|
||||
docker run --name mtg --restart=unless-stopped -p 3128:3128 -p 3129:3129 -d nineseconds/mtg:stable dd$(openssl rand -hex 16)
|
||||
```
|
||||
|
||||
You will have this tool up and running on port 3128. Now curl
|
||||
`localhost:3129` to get `tg://` links or do `docker logs mtg`. Also,
|
||||
port 3129 will show you some statistics if you are interested in.
|
||||
|
||||
Also, you can use [run-mtg.sh](https://github.com/9seconds/mtg/blob/master/run-mtg.sh) script
|
||||
|
||||
|
||||
# statsd integration
|
||||
|
||||
@@ -211,20 +247,37 @@ and [Datadog](https://docs.datadoghq.com/developers/dogstatsd/).
|
||||
|
||||
All metrics are gauges. Here is the list of metrics and their meaning:
|
||||
|
||||
| Metric name | Unit | Description |
|
||||
|---------------------------------|---------|-----------------------------------------------------------|
|
||||
| `connections.abridged.ipv4` | number | The number of active abridged IPv4 connections |
|
||||
| `connections.abridged.ipv6` | number | The number of active abridged IPv6 connections |
|
||||
| `connections.intermediate.ipv4` | number | The number of active intermediate IPv4 connections |
|
||||
| `connections.intermediate.ipv6` | number | The number of active intermediate IPv6 connections |
|
||||
| `connections.secure.ipv4` | number | The number of active secure intermediate IPv4 connections |
|
||||
| `connections.secure.ipv6` | number | The number of active secure intermediate IPv6 connections |
|
||||
| `crashes` | number | An amount of crashes in client handlers |
|
||||
| `traffic.ingress` | bytes | Ingress traffic from the start of application (incoming) |
|
||||
| `traffic.egress` | bytes | Egress traffic from the start of application (outgoing) |
|
||||
| `speed.ingress` | bytes/s | Ingress bandwidth of the latest second (incoming traffic) |
|
||||
| `speed.egress` | bytes/s | Egress bandwidth of the latest second (outgoing traffic) |
|
||||
| Metric name | Unit | Description |
|
||||
|------------------------|---------|--------------------------------------------|
|
||||
| `connections` | number | The number of active connections. |
|
||||
| `telegram_connections` | number | The number of active telegram connections. |
|
||||
| `crashes` | number | An amount of crashes in client handlers. |
|
||||
| `traffic.egress` | bytes | Traffic from the start of application. |
|
||||
| `replay_attacks` | number | The number of prevented replay attacks. |
|
||||
|
||||
All metrics are prefixed with given prefix. Default prefix is `mtg`.
|
||||
With such prefix metric name `traffic.ingress`, for example, would be
|
||||
`mtg.traffic.ingress`.
|
||||
Also, metrics provide tags (ipv4/ipv6, dc indexes etc).
|
||||
|
||||
|
||||
# Prometheus integration
|
||||
|
||||
[Prometheus](https://prometheus.io) integration comes out of
|
||||
the box, you do not need to setup anything special.
|
||||
|
||||
|
||||
# Upgrade to 1.0
|
||||
|
||||
Version 1.0 breaks compatibility with previous versions so please read
|
||||
this chapter carefully:
|
||||
|
||||
1. mtg now uses subcommands. Please use `mtg run` instead of just
|
||||
`mtg` to run a proxy.
|
||||
2. Options which set host and port separately were removed in a
|
||||
favor of fused `host:port` options.
|
||||
3. Own stats server was removed. Prometheus endpoint is moved to
|
||||
default stats endpoint.
|
||||
4. It is possible to connect to this proxy only with a secret which
|
||||
was used to run it. So, no backward compatibility of clients.
|
||||
5. Multiplexing involves connectivity with middle proxies and involves
|
||||
the most complex code path of this proxy. To avoid potential bugs,
|
||||
we still recommend using direct mode.
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We support 2 tracks: 2.x and 1.x
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.x | :white_check_mark: |
|
||||
| 1.x | :interrobang: |
|
||||
| < 1.0 | :x: |
|
||||
|
||||
1.x has adtag support. We do not plan any active development there but we guarantee:
|
||||
|
||||
1. Regular dependency updates
|
||||
2. Updates for Golang versions
|
||||
3. Security vulnerability fixes
|
||||
4. Merging small PRs
|
||||
|
||||
2.x is in active development and have a full support.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you have found a vulnerability, please report about it to nineseconds@yandex.ru, telegram @9seconds or here in issues
|
||||
@@ -0,0 +1,36 @@
|
||||
package antireplay
|
||||
|
||||
import "github.com/VictoriaMetrics/fastcache"
|
||||
|
||||
var (
|
||||
prefixObfuscated2 = []byte{0x00}
|
||||
prefixTLS = []byte{0x01}
|
||||
)
|
||||
|
||||
type cache struct {
|
||||
data *fastcache.Cache
|
||||
}
|
||||
|
||||
func (c cache) AddObfuscated2(data []byte) {
|
||||
c.data.Set(keyObfuscated2(data), nil)
|
||||
}
|
||||
|
||||
func (c cache) AddTLS(data []byte) {
|
||||
c.data.Set(keyTLS(data), nil)
|
||||
}
|
||||
|
||||
func (c cache) HasObfuscated2(data []byte) bool {
|
||||
return c.data.Has(keyObfuscated2(data))
|
||||
}
|
||||
|
||||
func (c cache) HasTLS(data []byte) bool {
|
||||
return c.data.Has(keyTLS(data))
|
||||
}
|
||||
|
||||
func keyObfuscated2(data []byte) []byte {
|
||||
return append(prefixObfuscated2, data...)
|
||||
}
|
||||
|
||||
func keyTLS(data []byte) []byte {
|
||||
return append(prefixTLS, data...)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package antireplay
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/VictoriaMetrics/fastcache"
|
||||
)
|
||||
|
||||
type CacheInterface interface {
|
||||
AddObfuscated2([]byte)
|
||||
AddTLS([]byte)
|
||||
HasObfuscated2([]byte) bool
|
||||
HasTLS([]byte) bool
|
||||
}
|
||||
|
||||
var (
|
||||
Cache CacheInterface
|
||||
initOnce sync.Once
|
||||
)
|
||||
|
||||
func Init() {
|
||||
initOnce.Do(func() {
|
||||
if config.C.AntiReplayMaxSize == 0 {
|
||||
Cache = nilCache{}
|
||||
} else {
|
||||
Cache = cache{fastcache.New(config.C.AntiReplayMaxSize)}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package antireplay
|
||||
|
||||
type nilCache struct{}
|
||||
|
||||
func (n nilCache) AddObfuscated2(_ []byte) {}
|
||||
func (n nilCache) AddTLS(_ []byte) {}
|
||||
func (n nilCache) HasObfuscated2(_ []byte) bool { return false }
|
||||
func (n nilCache) HasTLS(_ []byte) bool { return false }
|
||||
@@ -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)))
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
)
|
||||
|
||||
func Generate(secretType, hostname string) {
|
||||
data := make([]byte, config.SimpleSecretLength)
|
||||
if _, err := rand.Read(data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
secret := hex.EncodeToString(data)
|
||||
|
||||
switch secretType {
|
||||
case "simple":
|
||||
PrintStdout(secret)
|
||||
case "secured":
|
||||
PrintStdout("dd" + secret)
|
||||
default:
|
||||
PrintStdout("ee" + secret + hex.EncodeToString([]byte(hostname)))
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/antireplay"
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/faketls"
|
||||
"github.com/9seconds/mtg/hub"
|
||||
"github.com/9seconds/mtg/ntp"
|
||||
"github.com/9seconds/mtg/obfuscated2"
|
||||
"github.com/9seconds/mtg/proxy"
|
||||
"github.com/9seconds/mtg/stats"
|
||||
"github.com/9seconds/mtg/telegram"
|
||||
"github.com/9seconds/mtg/utils"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func Proxy() error { //nolint: funlen,cyclop
|
||||
ctx := utils.GetSignalContext()
|
||||
|
||||
atom := zap.NewAtomicLevel()
|
||||
|
||||
switch {
|
||||
case config.C.Debug:
|
||||
atom.SetLevel(zapcore.DebugLevel)
|
||||
case config.C.Verbose:
|
||||
atom.SetLevel(zapcore.InfoLevel)
|
||||
default:
|
||||
atom.SetLevel(zapcore.ErrorLevel)
|
||||
}
|
||||
|
||||
encoderCfg := zap.NewProductionEncoderConfig()
|
||||
logger := zap.New(zapcore.NewCore(
|
||||
zapcore.NewJSONEncoder(encoderCfg),
|
||||
zapcore.Lock(os.Stderr),
|
||||
atom,
|
||||
))
|
||||
|
||||
zap.ReplaceGlobals(logger)
|
||||
defer logger.Sync() //nolint: errcheck
|
||||
|
||||
if err := config.InitPublicAddress(ctx); err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
|
||||
zap.S().Debugw("Configuration", "config", config.Printable())
|
||||
|
||||
if config.C.MiddleProxyMode() {
|
||||
zap.S().Infow("Use middle proxy connection to Telegram")
|
||||
|
||||
diff, err := ntp.Fetch()
|
||||
if err != nil {
|
||||
Fatal("Cannot fetch time data from NTP")
|
||||
}
|
||||
|
||||
if diff > time.Second {
|
||||
Fatal("Your local time is skewed and drift is bigger than a second. Please sync your time.")
|
||||
}
|
||||
|
||||
go ntp.AutoUpdate()
|
||||
} else {
|
||||
zap.S().Infow("Use direct connection to Telegram")
|
||||
}
|
||||
|
||||
PrintJSONStdout(config.GetURLs())
|
||||
|
||||
if err := stats.Init(ctx); err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
|
||||
antireplay.Init()
|
||||
telegram.Init()
|
||||
hub.Init(ctx)
|
||||
|
||||
proxyListener, err := net.Listen("tcp", config.C.Bind.String())
|
||||
if err != nil {
|
||||
Fatal(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
proxyListener.Close()
|
||||
}()
|
||||
|
||||
app := &proxy.Proxy{
|
||||
Logger: zap.S().Named("proxy"),
|
||||
Context: ctx,
|
||||
ClientProtocolMaker: obfuscated2.MakeClientProtocol,
|
||||
}
|
||||
if config.C.SecretMode == config.SecretModeTLS {
|
||||
app.ClientProtocolMaker = faketls.MakeClientProtocol
|
||||
}
|
||||
|
||||
app.Serve(proxyListener)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Fatal(arg interface{}) {
|
||||
if value, ok := arg.(error); ok {
|
||||
arg = fmt.Errorf("fatal error: %+v", value) //nolint: errorlint
|
||||
}
|
||||
|
||||
PrintStderr(arg)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func PrintStderr(args ...interface{}) {
|
||||
fmt.Fprintln(os.Stderr, args...)
|
||||
}
|
||||
|
||||
func PrintStdout(args ...interface{}) {
|
||||
fmt.Println(args...) //nolint: forbidigo
|
||||
}
|
||||
|
||||
func PrintJSONStderr(data interface{}) {
|
||||
printJSON(os.Stderr, data)
|
||||
}
|
||||
|
||||
func PrintJSONStdout(data interface{}) {
|
||||
printJSON(os.Stdout, data)
|
||||
}
|
||||
|
||||
func printJSON(writer io.Writer, data interface{}) {
|
||||
encoder := json.NewEncoder(writer)
|
||||
encoder.SetEscapeHTML(false)
|
||||
encoder.SetIndent("", " ")
|
||||
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
"github.com/9seconds/mtg/wrappers"
|
||||
)
|
||||
|
||||
// Init defines common method for initializing client connections.
|
||||
type Init func(context.Context, context.CancelFunc, net.Conn, string,
|
||||
*config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error)
|
||||
@@ -1,53 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
"github.com/9seconds/mtg/obfuscated2"
|
||||
"github.com/9seconds/mtg/wrappers"
|
||||
)
|
||||
|
||||
const handshakeTimeout = 10 * time.Second
|
||||
|
||||
// DirectInit initializes client connection for proxy which connects to
|
||||
// Telegram directly.
|
||||
func DirectInit(ctx context.Context, cancel context.CancelFunc, socket net.Conn,
|
||||
connID string, conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) {
|
||||
tcpSocket := socket.(*net.TCPConn)
|
||||
if err := tcpSocket.SetNoDelay(false); err != nil {
|
||||
return nil, nil, errors.Annotate(err, "Cannot disable NO_DELAY to client socket")
|
||||
}
|
||||
if err := tcpSocket.SetReadBuffer(conf.ReadBufferSize); err != nil {
|
||||
return nil, nil, errors.Annotate(err, "Cannot set read buffer size of client socket")
|
||||
}
|
||||
if err := tcpSocket.SetWriteBuffer(conf.WriteBufferSize); err != nil {
|
||||
return nil, nil, errors.Annotate(err, "Cannot set write buffer size of client socket")
|
||||
}
|
||||
|
||||
socket.SetReadDeadline(time.Now().Add(handshakeTimeout)) // nolint: errcheck, gosec
|
||||
frame, err := obfuscated2.ExtractFrame(socket)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Annotate(err, "Cannot extract frame")
|
||||
}
|
||||
socket.SetReadDeadline(time.Time{}) // nolint: errcheck, gosec
|
||||
|
||||
conn := wrappers.NewConn(ctx, cancel, socket, connID, wrappers.ConnPurposeClient, conf.PublicIPv4, conf.PublicIPv6)
|
||||
obfs2, connOpts, err := obfuscated2.ParseObfuscated2ClientFrame(conf.Secret, frame)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Annotate(err, "Cannot parse obfuscated frame")
|
||||
}
|
||||
connOpts.ConnectionProto = mtproto.ConnectionProtocolAny
|
||||
connOpts.ClientAddr = conn.RemoteAddr()
|
||||
|
||||
conn = wrappers.NewStreamCipher(conn, obfs2.Encryptor, obfs2.Decryptor)
|
||||
|
||||
conn.Logger().Infow("Client connection initialized")
|
||||
|
||||
return conn, connOpts, nil
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
"github.com/9seconds/mtg/wrappers"
|
||||
)
|
||||
|
||||
// MiddleInit initializes client connection for proxy which has to
|
||||
// support promoted channels, connect to Telegram middle proxies etc.
|
||||
func MiddleInit(ctx context.Context, cancel context.CancelFunc, socket net.Conn,
|
||||
connID string, conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) {
|
||||
conn, opts, err := DirectInit(ctx, cancel, socket, connID, conf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
connStream := conn.(wrappers.StreamReadWriteCloser)
|
||||
|
||||
var newConn wrappers.PacketReadWriteCloser
|
||||
switch opts.ConnectionType {
|
||||
case mtproto.ConnectionTypeAbridged:
|
||||
newConn = wrappers.NewMTProtoAbridged(connStream, opts)
|
||||
case mtproto.ConnectionTypeIntermediate:
|
||||
newConn = wrappers.NewMTProtoIntermediate(connStream, opts)
|
||||
case mtproto.ConnectionTypeSecure:
|
||||
newConn = wrappers.NewMTProtoIntermediateSecure(connStream, opts)
|
||||
default:
|
||||
panic("Unknown connection type")
|
||||
}
|
||||
|
||||
opts.ConnectionProto = mtproto.ConnectionProtocolIPv4
|
||||
if socket.LocalAddr().(*net.TCPAddr).IP.To4() == nil {
|
||||
opts.ConnectionProto = mtproto.ConnectionProtocolIPv6
|
||||
}
|
||||
|
||||
return newConn, opts, err
|
||||
}
|
||||
+272
-170
@@ -2,209 +2,311 @@ package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/juju/errors"
|
||||
statsd "gopkg.in/alexcesaro/statsd.v2"
|
||||
"github.com/alecthomas/units"
|
||||
statsd "github.com/smira/go-statsd"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Config represents common configuration of mtg.
|
||||
type Config struct {
|
||||
Debug bool
|
||||
Verbose bool
|
||||
SecureMode bool
|
||||
type SecretMode uint8
|
||||
|
||||
ReadBufferSize int
|
||||
WriteBufferSize int
|
||||
|
||||
BindPort uint16
|
||||
PublicIPv4Port uint16
|
||||
PublicIPv6Port uint16
|
||||
StatsPort uint16
|
||||
|
||||
BindIP net.IP
|
||||
PublicIPv4 net.IP
|
||||
PublicIPv6 net.IP
|
||||
StatsIP net.IP
|
||||
|
||||
StatsD struct {
|
||||
Addr net.Addr
|
||||
Prefix string
|
||||
Tags map[string]string
|
||||
TagsFormat statsd.TagFormat
|
||||
Enabled bool
|
||||
func (s SecretMode) String() string {
|
||||
switch s {
|
||||
case SecretModeSimple:
|
||||
return "simple"
|
||||
case SecretModeSecured:
|
||||
return "secured"
|
||||
case SecretModeTLS:
|
||||
return "tls"
|
||||
}
|
||||
|
||||
Secret []byte
|
||||
AdTag []byte
|
||||
return "tls"
|
||||
}
|
||||
|
||||
// URLs contains links to the proxy (tg://, t.me) and their QR codes.
|
||||
type URLs struct {
|
||||
TG string `json:"tg_url"`
|
||||
TMe string `json:"tme_url"`
|
||||
TGQRCode string `json:"tg_qrcode"`
|
||||
TMeQRCode string `json:"tme_qrcode"`
|
||||
const (
|
||||
SecretModeSimple SecretMode = iota
|
||||
SecretModeSecured
|
||||
SecretModeTLS
|
||||
)
|
||||
|
||||
type PreferIP uint8
|
||||
|
||||
const (
|
||||
PreferIPv4 PreferIP = iota
|
||||
PreferIPv6
|
||||
)
|
||||
|
||||
const SimpleSecretLength = 16
|
||||
|
||||
type OptionType uint8
|
||||
|
||||
const (
|
||||
OptionTypeDebug OptionType = iota
|
||||
OptionTypeVerbose
|
||||
|
||||
OptionTypePreferIP
|
||||
|
||||
OptionTypeBind
|
||||
OptionTypePublicIPv4
|
||||
OptionTypePublicIPv6
|
||||
|
||||
OptionTypeStatsBind
|
||||
OptionTypeStatsNamespace
|
||||
OptionTypeStatsdAddress
|
||||
OptionTypeStatsdTagsFormat
|
||||
OptionTypeStatsdTags
|
||||
|
||||
OptionTypeWriteBufferSize
|
||||
OptionTypeReadBufferSize
|
||||
|
||||
OptionTypeCloakPort
|
||||
|
||||
OptionTypeAntiReplayMaxSize
|
||||
|
||||
OptionTypeMultiplexPerConnection
|
||||
|
||||
OptionTypeNTPServers
|
||||
|
||||
OptionTypeSecret
|
||||
OptionTypeAdtag
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Bind *net.TCPAddr `json:"bind"`
|
||||
PublicIPv4 *net.TCPAddr `json:"public_ipv4"` //nolint: tagliatelle
|
||||
PublicIPv6 *net.TCPAddr `json:"public_ipv6"` //nolint: tagliatelle
|
||||
StatsBind *net.TCPAddr `json:"stats_bind"` //nolint: tagliatelle
|
||||
StatsdAddr *net.TCPAddr `json:"stats_addr"` //nolint: tagliatelle
|
||||
StatsdTagsFormat *statsd.TagFormat `json:"statsd_tags_format"` //nolint: tagliatelle
|
||||
|
||||
StatsNamespace string `json:"stats_namespace"` //nolint: tagliatelle
|
||||
CloakHost string `json:"cloak_host"` //nolint: tagliatelle
|
||||
StatsdTags map[string]string `json:"statsd_tags"` //nolint: tagliatelle
|
||||
|
||||
WriteBuffer int `json:"write_buffer"` //nolint: tagliatelle
|
||||
ReadBuffer int `json:"read_buffer"` //nolint: tagliatelle
|
||||
CloakPort int `json:"cloak_port"` //nolint: tagliatelle
|
||||
|
||||
AntiReplayMaxSize int `json:"anti_replay_max_size"` //nolint: tagliatelle
|
||||
|
||||
MultiplexPerConnection int `json:"multiplex_per_connection"` //nolint: tagliatelle
|
||||
|
||||
Debug bool `json:"debug"`
|
||||
Verbose bool `json:"verbose"`
|
||||
SecretMode SecretMode `json:"secret_mode"` //nolint: tagliatelle
|
||||
PreferIP PreferIP `json:"prefer_ip"` //nolint: tagliatelle
|
||||
NTPServers []string `json:"ntp_servers"` //nolint: tagliatelle
|
||||
|
||||
Secret []byte `json:"secret"`
|
||||
AdTag []byte `json:"adtag"`
|
||||
}
|
||||
|
||||
// IPURLs contains links to both ipv4 and ipv6 of the proxy.
|
||||
type IPURLs struct {
|
||||
IPv4 URLs `json:"ipv4"`
|
||||
IPv6 URLs `json:"ipv6"`
|
||||
BotSecret string `json:"secret_for_mtproxybot"`
|
||||
func (c *Config) ClientReadBuffer() int {
|
||||
return c.ReadBuffer
|
||||
}
|
||||
|
||||
// BindAddr returns connection for this server to bind to.
|
||||
func (c *Config) BindAddr() string {
|
||||
return getAddr(c.BindIP, c.BindPort)
|
||||
func (c *Config) ClientWriteBuffer() int {
|
||||
return c.WriteBuffer
|
||||
}
|
||||
|
||||
// StatAddr returns connection string to the stats API.
|
||||
func (c *Config) StatAddr() string {
|
||||
return getAddr(c.StatsIP, c.StatsPort)
|
||||
}
|
||||
|
||||
// UseMiddleProxy defines if this proxy has to connect middle proxies
|
||||
// which supports promoted channels or directly access Telegram.
|
||||
func (c *Config) UseMiddleProxy() bool {
|
||||
func (c *Config) MiddleProxyMode() bool {
|
||||
return len(c.AdTag) > 0
|
||||
}
|
||||
|
||||
// BotSecretString returns secret string which should work with MTProxybot.
|
||||
func (c *Config) BotSecretString() string {
|
||||
return hex.EncodeToString(c.Secret)
|
||||
func (c *Config) ProxyReadBuffer() int {
|
||||
value := c.ReadBuffer
|
||||
|
||||
if c.MiddleProxyMode() {
|
||||
value = c.adjustProxyValue(value)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// SecretString returns a secret in a form entered on the start of the
|
||||
// application.
|
||||
func (c *Config) SecretString() string {
|
||||
secret := c.BotSecretString()
|
||||
if c.SecureMode {
|
||||
return "dd" + secret
|
||||
func (c *Config) ProxyWriteBuffer() int {
|
||||
value := c.WriteBuffer
|
||||
|
||||
if c.MiddleProxyMode() {
|
||||
value = c.adjustProxyValue(value)
|
||||
}
|
||||
return secret
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// GetURLs returns configured IPURLs instance with links to this server.
|
||||
func (c *Config) GetURLs() IPURLs {
|
||||
urls := IPURLs{}
|
||||
secret := c.SecretString()
|
||||
if c.PublicIPv4 != nil {
|
||||
urls.IPv4 = getURLs(c.PublicIPv4, c.PublicIPv4Port, secret)
|
||||
func (c *Config) adjustProxyValue(value int) int {
|
||||
if c.MultiplexPerConnection == 0 {
|
||||
return value
|
||||
}
|
||||
if c.PublicIPv6 != nil {
|
||||
urls.IPv6 = getURLs(c.PublicIPv6, c.PublicIPv6Port, secret)
|
||||
}
|
||||
urls.BotSecret = c.BotSecretString()
|
||||
|
||||
return urls
|
||||
fvalue := float64(value)
|
||||
|
||||
newValue := fvalue * 2 * math.Log(float64(c.MultiplexPerConnection)) //nolint: gomnd
|
||||
newValue = math.Ceil(newValue)
|
||||
newValue = math.Max(fvalue, newValue)
|
||||
|
||||
return int(newValue)
|
||||
}
|
||||
|
||||
func getAddr(host fmt.Stringer, port uint16) string {
|
||||
return net.JoinHostPort(host.String(), strconv.Itoa(int(port)))
|
||||
type Opt struct {
|
||||
Option OptionType
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// NewConfig returns new configuration. If required, it manages and
|
||||
// fetches data from external sources. Parameters passed to this
|
||||
// function, should come from command line arguments.
|
||||
func NewConfig(debug, verbose bool, // nolint: gocyclo
|
||||
writeBufferSize, readBufferSize uint32,
|
||||
bindIP, publicIPv4, publicIPv6, statsIP net.IP,
|
||||
bindPort, publicIPv4Port, publicIPv6Port, statsPort, statsdPort uint16,
|
||||
statsdIP, statsdNetwork, statsdPrefix, statsdTagsFormat string,
|
||||
statsdTags map[string]string,
|
||||
secret, adtag []byte) (*Config, error) {
|
||||
secureMode := false
|
||||
if bytes.HasPrefix(secret, []byte{0xdd}) && len(secret) == 17 {
|
||||
secureMode = true
|
||||
secret = bytes.TrimPrefix(secret, []byte{0xdd})
|
||||
} else if len(secret) != 16 {
|
||||
return nil, errors.New("Telegram demands secret of length 32")
|
||||
}
|
||||
var C = Config{}
|
||||
|
||||
var err error
|
||||
if publicIPv4 == nil {
|
||||
publicIPv4, err = getGlobalIPv4()
|
||||
if err != nil {
|
||||
publicIPv4 = nil
|
||||
} else if publicIPv4.To4() == nil {
|
||||
return nil, errors.Errorf("IP %s is not IPv4", publicIPv4.String())
|
||||
}
|
||||
}
|
||||
if publicIPv4Port == 0 {
|
||||
publicIPv4Port = bindPort
|
||||
}
|
||||
|
||||
if publicIPv6 == nil {
|
||||
publicIPv6, err = getGlobalIPv6()
|
||||
if err != nil {
|
||||
publicIPv6 = nil
|
||||
} else if publicIPv6.To4() != nil {
|
||||
return nil, errors.Errorf("IP %s is not IPv6", publicIPv6.String())
|
||||
}
|
||||
}
|
||||
if publicIPv6Port == 0 {
|
||||
publicIPv6Port = bindPort
|
||||
}
|
||||
|
||||
if statsIP == nil {
|
||||
statsIP = publicIPv4
|
||||
}
|
||||
|
||||
conf := &Config{
|
||||
Debug: debug,
|
||||
Verbose: verbose,
|
||||
BindIP: bindIP,
|
||||
BindPort: bindPort,
|
||||
PublicIPv4: publicIPv4,
|
||||
PublicIPv4Port: publicIPv4Port,
|
||||
PublicIPv6: publicIPv6,
|
||||
PublicIPv6Port: publicIPv6Port,
|
||||
StatsIP: statsIP,
|
||||
StatsPort: statsPort,
|
||||
Secret: secret,
|
||||
AdTag: adtag,
|
||||
SecureMode: secureMode,
|
||||
ReadBufferSize: int(readBufferSize),
|
||||
WriteBufferSize: int(writeBufferSize),
|
||||
}
|
||||
|
||||
if statsdIP != "" {
|
||||
conf.StatsD.Enabled = true
|
||||
conf.StatsD.Prefix = statsdPrefix
|
||||
conf.StatsD.Tags = statsdTags
|
||||
|
||||
var (
|
||||
addr net.Addr
|
||||
err error
|
||||
)
|
||||
hostPort := net.JoinHostPort(statsdIP, strconv.Itoa(int(statsdPort)))
|
||||
switch statsdNetwork {
|
||||
case "tcp":
|
||||
addr, err = net.ResolveTCPAddr("tcp", hostPort)
|
||||
case "udp":
|
||||
addr, err = net.ResolveUDPAddr("udp", hostPort)
|
||||
func Init(options ...Opt) error { //nolint: gocyclo, funlen, cyclop
|
||||
for _, opt := range options {
|
||||
switch opt.Option {
|
||||
case OptionTypeDebug:
|
||||
C.Debug = opt.Value.(bool) //nolint: forcetypeassert
|
||||
case OptionTypeVerbose:
|
||||
C.Verbose = opt.Value.(bool) //nolint: forcetypeassert
|
||||
case OptionTypePreferIP:
|
||||
value := opt.Value.(string) //nolint: forcetypeassert
|
||||
switch value {
|
||||
case "ipv4":
|
||||
C.PreferIP = PreferIPv4
|
||||
case "ipv6":
|
||||
C.PreferIP = PreferIPv6
|
||||
default:
|
||||
return fmt.Errorf("incorrect direct IP mode %s", value)
|
||||
}
|
||||
case OptionTypeBind:
|
||||
C.Bind = opt.Value.(*net.TCPAddr) //nolint: forcetypeassert
|
||||
case OptionTypePublicIPv4:
|
||||
C.PublicIPv4 = opt.Value.(*net.TCPAddr) //nolint: forcetypeassert
|
||||
if C.PublicIPv4 == nil {
|
||||
C.PublicIPv4 = &net.TCPAddr{}
|
||||
}
|
||||
case OptionTypePublicIPv6:
|
||||
C.PublicIPv6 = opt.Value.(*net.TCPAddr) //nolint: forcetypeassert
|
||||
if C.PublicIPv6 == nil {
|
||||
C.PublicIPv6 = &net.TCPAddr{}
|
||||
}
|
||||
case OptionTypeStatsBind:
|
||||
C.StatsBind = opt.Value.(*net.TCPAddr) //nolint: forcetypeassert
|
||||
case OptionTypeStatsNamespace:
|
||||
C.StatsNamespace = opt.Value.(string) //nolint: forcetypeassert
|
||||
case OptionTypeStatsdAddress:
|
||||
C.StatsdAddr = opt.Value.(*net.TCPAddr) //nolint: forcetypeassert
|
||||
case OptionTypeStatsdTagsFormat:
|
||||
value := opt.Value.(string) //nolint: forcetypeassert
|
||||
switch value {
|
||||
case "datadog":
|
||||
C.StatsdTagsFormat = statsd.TagFormatDatadog
|
||||
case "influxdb":
|
||||
C.StatsdTagsFormat = statsd.TagFormatInfluxDB
|
||||
default:
|
||||
return fmt.Errorf("incorrect statsd tag %s", value)
|
||||
}
|
||||
case OptionTypeStatsdTags:
|
||||
C.StatsdTags = opt.Value.(map[string]string) //nolint: forcetypeassert
|
||||
case OptionTypeWriteBufferSize:
|
||||
C.WriteBuffer = int(opt.Value.(units.Base2Bytes)) //nolint: forcetypeassert
|
||||
case OptionTypeReadBufferSize:
|
||||
C.ReadBuffer = int(opt.Value.(units.Base2Bytes)) //nolint: forcetypeassert
|
||||
case OptionTypeCloakPort:
|
||||
C.CloakPort = int(opt.Value.(uint16)) //nolint: forcetypeassert
|
||||
case OptionTypeAntiReplayMaxSize:
|
||||
C.AntiReplayMaxSize = int(opt.Value.(units.Base2Bytes)) //nolint: forcetypeassert
|
||||
case OptionTypeMultiplexPerConnection:
|
||||
C.MultiplexPerConnection = int(opt.Value.(uint)) //nolint: forcetypeassert
|
||||
case OptionTypeNTPServers:
|
||||
C.NTPServers = opt.Value.([]string) //nolint: forcetypeassert
|
||||
if len(C.NTPServers) == 0 {
|
||||
return errors.New("ntp server list is empty")
|
||||
}
|
||||
case OptionTypeSecret:
|
||||
C.Secret = opt.Value.([]byte) //nolint: forcetypeassert
|
||||
case OptionTypeAdtag:
|
||||
C.AdTag = opt.Value.([]byte) //nolint: forcetypeassert
|
||||
default:
|
||||
err = errors.Errorf("Unknown network %s", statsdNetwork)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot resolve statsd address")
|
||||
}
|
||||
conf.StatsD.Addr = addr
|
||||
|
||||
switch statsdTagsFormat {
|
||||
case "datadog":
|
||||
conf.StatsD.TagsFormat = statsd.Datadog
|
||||
case "influxdb":
|
||||
conf.StatsD.TagsFormat = statsd.InfluxDB
|
||||
case "":
|
||||
default:
|
||||
return nil, errors.Errorf("Unknown tags format %s", statsdTagsFormat)
|
||||
return fmt.Errorf("unknown tag %v", opt.Option)
|
||||
}
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
switch {
|
||||
case len(C.Secret) == 1+SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xdd}):
|
||||
C.SecretMode = SecretModeSecured
|
||||
C.Secret = bytes.TrimPrefix(C.Secret, []byte{0xdd})
|
||||
case len(C.Secret) > SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xee}):
|
||||
C.SecretMode = SecretModeTLS
|
||||
secret := bytes.TrimPrefix(C.Secret, []byte{0xee})
|
||||
C.Secret = secret[:SimpleSecretLength]
|
||||
C.CloakHost = string(secret[SimpleSecretLength:])
|
||||
case len(C.Secret) == SimpleSecretLength:
|
||||
C.SecretMode = SecretModeSimple
|
||||
default:
|
||||
return errors.New("incorrect secret")
|
||||
}
|
||||
|
||||
if C.MultiplexPerConnection == 0 {
|
||||
return errors.New("cannot use 0 clients per connection for multiplexing")
|
||||
}
|
||||
|
||||
if C.CloakHost != "" {
|
||||
if _, err := net.LookupHost(C.CloakHost); err != nil {
|
||||
zap.S().Warnw("Cannot resolve address of host", "hostname", C.CloakHost, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitPublicAddress(ctx context.Context) error {
|
||||
if C.PublicIPv4.Port == 0 {
|
||||
C.PublicIPv4.Port = C.Bind.Port
|
||||
}
|
||||
|
||||
if C.PublicIPv6.Port == 0 {
|
||||
C.PublicIPv6.Port = C.Bind.Port
|
||||
}
|
||||
|
||||
foundAddress := C.PublicIPv4.IP != nil || C.PublicIPv6.IP != nil
|
||||
|
||||
if C.PublicIPv4.IP == nil {
|
||||
ip, err := getGlobalIPv4(ctx)
|
||||
if err != nil {
|
||||
zap.S().Warnw("Cannot resolve public address", "error", err)
|
||||
} else {
|
||||
C.PublicIPv4.IP = ip
|
||||
foundAddress = true
|
||||
}
|
||||
}
|
||||
|
||||
if C.PublicIPv6.IP == nil {
|
||||
ip, err := getGlobalIPv6(ctx)
|
||||
if err != nil {
|
||||
zap.S().Warnw("Cannot resolve public address", "error", err)
|
||||
} else {
|
||||
C.PublicIPv6.IP = ip
|
||||
foundAddress = true
|
||||
}
|
||||
}
|
||||
|
||||
if !foundAddress {
|
||||
return errors.New("cannot resolve any public address")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Printable() interface{} {
|
||||
data, err := json.Marshal(C)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rv := map[string]interface{}{}
|
||||
if err := json.Unmarshal(data, &rv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
+43
-18
@@ -2,50 +2,75 @@ package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/juju/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
const ifconfigAddress = "https://ifconfig.co/ip"
|
||||
const (
|
||||
ifconfigAddress = "https://ifconfig.co/ip"
|
||||
ifconfigTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
func getGlobalIPv4() (net.IP, error) {
|
||||
return fetchIP("tcp4")
|
||||
func getGlobalIPv4(ctx context.Context) (net.IP, error) {
|
||||
ip, err := fetchIP(ctx, "tcp4")
|
||||
if err != nil || ip.To4() == nil {
|
||||
return nil, fmt.Errorf("cannot find public ipv4 address: %w", err)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func getGlobalIPv6() (net.IP, error) {
|
||||
return fetchIP("tcp6")
|
||||
func getGlobalIPv6(ctx context.Context) (net.IP, error) {
|
||||
ip, err := fetchIP(ctx, "tcp6")
|
||||
if err != nil || ip.To4() != nil {
|
||||
return nil, fmt.Errorf("cannot find public ipv6 address: %w", err)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func fetchIP(network string) (net.IP, error) {
|
||||
dialer := &net.Dialer{DualStack: false}
|
||||
func fetchIP(ctx context.Context, network string) (net.IP, error) {
|
||||
dialer := &net.Dialer{FallbackDelay: -1}
|
||||
client := &http.Client{
|
||||
Jar: nil,
|
||||
Jar: nil,
|
||||
Timeout: ifconfigTimeout,
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, addr)
|
||||
return dialer.DialContext(ctx, network, addr) //nolint: wrapcheck
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, ifconfigAddress, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create a request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := client.Get(ifconfigAddress)
|
||||
resp, err := client.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if resp != nil {
|
||||
io.Copy(io.Discard, resp.Body) //nolint: errcheck
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("cannot perform a request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close() // nolint: errcheck
|
||||
|
||||
respDataBytes, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
|
||||
respDataBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("cannot read response body: %w", err)
|
||||
}
|
||||
|
||||
respData := strings.TrimSpace(string(respDataBytes))
|
||||
|
||||
ip := net.ParseIP(respData)
|
||||
if ip == nil {
|
||||
return nil, errors.Errorf("ifconfig.co returns incorrect IP %s", respData)
|
||||
return nil, fmt.Errorf("ifconfig.co returns incorrect IP %s", respData)
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
|
||||
+51
-9
@@ -1,23 +1,65 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func getURLs(addr net.IP, port uint16, secret string) (urls URLs) {
|
||||
type URLs struct {
|
||||
TG string `json:"tg_url"` //nolint: tagliatelle
|
||||
TMe string `json:"tme_url"` //nolint: tagliatelle
|
||||
TGQRCode string `json:"tg_qrcode"` //nolint: tagliatelle
|
||||
TMeQRCode string `json:"tme_qrcode"` //nolint: tagliatelle
|
||||
}
|
||||
|
||||
type IPURLs struct {
|
||||
IPv4 *URLs `json:"ipv4,omitempty"`
|
||||
IPv6 *URLs `json:"ipv6,omitempty"`
|
||||
BotSecret string `json:"secret_for_mtproxybot"` //nolint: tagliatelle
|
||||
}
|
||||
|
||||
func GetURLs() IPURLs {
|
||||
secret := ""
|
||||
urls := IPURLs{}
|
||||
|
||||
switch C.SecretMode {
|
||||
case SecretModeSimple:
|
||||
secret = hex.EncodeToString(C.Secret)
|
||||
case SecretModeSecured:
|
||||
secret = "dd" + hex.EncodeToString(C.Secret)
|
||||
case SecretModeTLS:
|
||||
secret = "ee" + hex.EncodeToString(C.Secret) + hex.EncodeToString([]byte(C.CloakHost))
|
||||
}
|
||||
|
||||
if C.PublicIPv4.IP != nil {
|
||||
urls.IPv4 = makeURLs(C.PublicIPv4, secret)
|
||||
}
|
||||
|
||||
if C.PublicIPv6.IP != nil {
|
||||
urls.IPv6 = makeURLs(C.PublicIPv6, secret)
|
||||
}
|
||||
|
||||
urls.BotSecret = hex.EncodeToString(C.Secret)
|
||||
|
||||
return urls
|
||||
}
|
||||
|
||||
func makeURLs(addr *net.TCPAddr, secret string) *URLs {
|
||||
urls := &URLs{}
|
||||
|
||||
values := url.Values{}
|
||||
values.Set("server", addr.String())
|
||||
values.Set("port", strconv.Itoa(int(port)))
|
||||
values.Set("server", addr.IP.String())
|
||||
values.Set("port", strconv.Itoa(addr.Port))
|
||||
values.Set("secret", secret)
|
||||
|
||||
urls.TG = makeTGURL(values)
|
||||
urls.TMe = makeTMeURL(values)
|
||||
urls.TGQRCode = makeQRCodeURL(urls.TG)
|
||||
urls.TMeQRCode = makeQRCodeURL(urls.TG)
|
||||
|
||||
return
|
||||
return &URLs{
|
||||
TG: makeTGURL(values),
|
||||
TMe: makeTMeURL(values),
|
||||
TGQRCode: makeQRCodeURL(urls.TG),
|
||||
TMeQRCode: makeQRCodeURL(urls.TMe),
|
||||
}
|
||||
}
|
||||
|
||||
func makeTGURL(values url.Values) string {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package conntypes
|
||||
|
||||
type ConnectionAcks struct {
|
||||
Simple bool
|
||||
Quick bool
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package conntypes
|
||||
|
||||
type DC int16
|
||||
|
||||
const DCDefaultIdx DC = 1
|
||||
@@ -0,0 +1,24 @@
|
||||
package conntypes
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
const ConnIDLength = 8
|
||||
|
||||
type ConnID [ConnIDLength]byte
|
||||
|
||||
func (c ConnID) String() string {
|
||||
return hex.EncodeToString(c[:])
|
||||
}
|
||||
|
||||
func NewConnID() ConnID {
|
||||
var id ConnID
|
||||
|
||||
if _, err := rand.Read(id[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package conntypes
|
||||
|
||||
type Packet []byte
|
||||
@@ -0,0 +1,22 @@
|
||||
package conntypes
|
||||
|
||||
type ConnectionProtocol uint8
|
||||
|
||||
func (c ConnectionProtocol) String() string {
|
||||
switch c {
|
||||
case ConnectionProtocolAny:
|
||||
return "any"
|
||||
case ConnectionProtocolIPv4:
|
||||
return "ipv4"
|
||||
case ConnectionProtocolIPv6:
|
||||
return "ipv6"
|
||||
}
|
||||
|
||||
return "ipv6"
|
||||
}
|
||||
|
||||
const (
|
||||
ConnectionProtocolIPv4 ConnectionProtocol = 1
|
||||
ConnectionProtocolIPv6 = ConnectionProtocolIPv4 << 1
|
||||
ConnectionProtocolAny = ConnectionProtocolIPv4 | ConnectionProtocolIPv6
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
package conntypes
|
||||
|
||||
type ConnectionType uint8
|
||||
|
||||
const (
|
||||
ConnectionTypeUnknown ConnectionType = iota
|
||||
ConnectionTypeAbridged
|
||||
ConnectionTypeIntermediate
|
||||
ConnectionTypeSecure
|
||||
)
|
||||
|
||||
var (
|
||||
ConnectionTagAbridged = []byte{0xef, 0xef, 0xef, 0xef}
|
||||
ConnectionTagIntermediate = []byte{0xee, 0xee, 0xee, 0xee}
|
||||
ConnectionTagSecure = []byte{0xdd, 0xdd, 0xdd, 0xdd}
|
||||
)
|
||||
|
||||
func (t ConnectionType) Tag() []byte {
|
||||
switch t {
|
||||
case ConnectionTypeAbridged:
|
||||
return ConnectionTagAbridged
|
||||
case ConnectionTypeIntermediate:
|
||||
return ConnectionTagIntermediate
|
||||
case ConnectionTypeSecure, ConnectionTypeUnknown:
|
||||
return ConnectionTagSecure
|
||||
}
|
||||
|
||||
return ConnectionTagSecure
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package conntypes
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Wrap interface {
|
||||
Conn() net.Conn
|
||||
Logger() *zap.SugaredLogger
|
||||
LocalAddr() *net.TCPAddr
|
||||
RemoteAddr() *net.TCPAddr
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package conntypes
|
||||
|
||||
import "io"
|
||||
|
||||
type PacketAckReader interface {
|
||||
Read(*ConnectionAcks) (Packet, error)
|
||||
}
|
||||
|
||||
type PacketAckWriter interface {
|
||||
Write(Packet, *ConnectionAcks) error
|
||||
}
|
||||
|
||||
type PacketAckCloser interface {
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type PacketAckReadCloser interface {
|
||||
PacketAckReader
|
||||
PacketAckCloser
|
||||
}
|
||||
|
||||
type PacketAckWriteCloser interface {
|
||||
PacketAckWriter
|
||||
PacketAckCloser
|
||||
}
|
||||
|
||||
type PacketAckReadWriter interface {
|
||||
PacketAckReader
|
||||
PacketAckWriter
|
||||
}
|
||||
|
||||
type PacketAckReadWriteCloser interface {
|
||||
PacketAckReader
|
||||
PacketAckWriter
|
||||
PacketAckCloser
|
||||
}
|
||||
|
||||
type PacketAckFullReadWriteCloser interface {
|
||||
Wrap
|
||||
PacketAckReadWriteCloser
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package conntypes
|
||||
|
||||
import "io"
|
||||
|
||||
type BasePacketReader interface {
|
||||
Read() (Packet, error)
|
||||
}
|
||||
|
||||
type BasePacketWriter interface {
|
||||
Write(Packet) error
|
||||
}
|
||||
|
||||
type PacketReader interface {
|
||||
Wrap
|
||||
BasePacketReader
|
||||
}
|
||||
|
||||
type PacketWriter interface {
|
||||
Wrap
|
||||
BasePacketWriter
|
||||
}
|
||||
|
||||
type PacketCloser interface {
|
||||
Wrap
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type PacketReadCloser interface {
|
||||
Wrap
|
||||
BasePacketReader
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type PacketWriteCloser interface {
|
||||
Wrap
|
||||
BasePacketWriter
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type PacketReadWriter interface {
|
||||
Wrap
|
||||
BasePacketWriter
|
||||
BasePacketReader
|
||||
}
|
||||
|
||||
type PacketReadWriteCloser interface {
|
||||
Wrap
|
||||
BasePacketWriter
|
||||
BasePacketReader
|
||||
io.Closer
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package conntypes
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BaseStreamReaderWithTimeout interface {
|
||||
ReadTimeout([]byte, time.Duration) (int, error)
|
||||
}
|
||||
|
||||
type BaseStreamWriterWithTimeout interface {
|
||||
WriteTimeout([]byte, time.Duration) (int, error)
|
||||
}
|
||||
|
||||
type StreamReader interface {
|
||||
Wrap
|
||||
io.Reader
|
||||
BaseStreamReaderWithTimeout
|
||||
}
|
||||
|
||||
type StreamWriter interface {
|
||||
Wrap
|
||||
io.Writer
|
||||
BaseStreamWriterWithTimeout
|
||||
}
|
||||
|
||||
type StreamCloser interface {
|
||||
Wrap
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type StreamReadCloser interface {
|
||||
Wrap
|
||||
io.ReadCloser
|
||||
BaseStreamReaderWithTimeout
|
||||
}
|
||||
|
||||
type StreamWriteCloser interface {
|
||||
Wrap
|
||||
io.WriteCloser
|
||||
BaseStreamWriterWithTimeout
|
||||
}
|
||||
|
||||
type StreamReadWriter interface {
|
||||
Wrap
|
||||
io.ReadWriter
|
||||
BaseStreamReaderWithTimeout
|
||||
}
|
||||
|
||||
type StreamReadWriteCloser interface {
|
||||
Wrap
|
||||
io.ReadWriteCloser
|
||||
BaseStreamReaderWithTimeout
|
||||
BaseStreamWriterWithTimeout
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/antireplay"
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
"github.com/9seconds/mtg/obfuscated2"
|
||||
"github.com/9seconds/mtg/protocol"
|
||||
"github.com/9seconds/mtg/stats"
|
||||
"github.com/9seconds/mtg/tlstypes"
|
||||
"github.com/9seconds/mtg/wrappers/stream"
|
||||
)
|
||||
|
||||
type ClientProtocol struct {
|
||||
obfuscated2.ClientProtocol
|
||||
}
|
||||
|
||||
func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error) {
|
||||
rewinded := stream.NewRewind(socket)
|
||||
bufferedReader := bufio.NewReader(rewinded)
|
||||
|
||||
for _, expected := range faketlsStartBytes {
|
||||
if actual, err := bufferedReader.ReadByte(); err != nil || actual != expected {
|
||||
rewinded.Rewind()
|
||||
c.cloakHost(rewinded)
|
||||
|
||||
return nil, errors.New("failed first bytes of tls handshake")
|
||||
}
|
||||
}
|
||||
|
||||
rewinded.Rewind()
|
||||
rewinded = stream.NewRewind(rewinded)
|
||||
|
||||
if err := c.tlsHandshake(rewinded); err != nil {
|
||||
rewinded.Rewind()
|
||||
c.cloakHost(rewinded)
|
||||
|
||||
return nil, fmt.Errorf("failed tls handshake: %w", err)
|
||||
}
|
||||
|
||||
conn := stream.NewFakeTLS(socket)
|
||||
|
||||
conn, err := c.ClientProtocol.Handshake(conn)
|
||||
if err != nil {
|
||||
return nil, err //nolint: wrapcheck
|
||||
}
|
||||
|
||||
return conn, err //nolint: wrapcheck
|
||||
}
|
||||
|
||||
func (c *ClientProtocol) tlsHandshake(conn io.ReadWriter) error {
|
||||
helloRecord, err := tlstypes.ReadRecord(conn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot read initial record: %w", err)
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
helloRecord.Data.WriteBytes(buf)
|
||||
|
||||
clientHello, err := tlstypes.ParseClientHello(buf.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse client hello: %w", err)
|
||||
}
|
||||
|
||||
digest := clientHello.Digest()
|
||||
for i := 0; i < len(digest)-4; i++ {
|
||||
if digest[i] != 0 {
|
||||
return errBadDigest
|
||||
}
|
||||
}
|
||||
|
||||
timestamp := int64(binary.LittleEndian.Uint32(digest[len(digest)-4:]))
|
||||
createdAt := time.Unix(timestamp, 0)
|
||||
timeDiff := time.Since(createdAt)
|
||||
|
||||
if (timeDiff > TimeSkew || timeDiff < -TimeSkew) && timestamp > TimeFromBoot {
|
||||
return errBadTime
|
||||
}
|
||||
|
||||
if antireplay.Cache.HasTLS(clientHello.Random[:]) {
|
||||
stats.Stats.ReplayDetected()
|
||||
|
||||
return errors.New("replay attack is detected")
|
||||
}
|
||||
|
||||
antireplay.Cache.AddTLS(clientHello.Random[:])
|
||||
serverHello := tlstypes.NewServerHello(clientHello)
|
||||
serverHelloPacket := serverHello.WelcomePacket()
|
||||
|
||||
if _, err := conn.Write(serverHelloPacket); err != nil {
|
||||
return fmt.Errorf("cannot send welcome packet: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClientProtocol) cloakHost(clientConn io.ReadWriteCloser) {
|
||||
stats.Stats.CloakedRequest()
|
||||
|
||||
addr := net.JoinHostPort(config.C.CloakHost, strconv.Itoa(config.C.CloakPort))
|
||||
|
||||
hostConn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cloak(clientConn, hostConn)
|
||||
}
|
||||
|
||||
func MakeClientProtocol() protocol.ClientProtocol {
|
||||
return &ClientProtocol{}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/wrappers/rwc"
|
||||
)
|
||||
|
||||
const (
|
||||
cloakLastActivityTimeout = 5 * time.Second
|
||||
cloakMaxTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
func cloak(one, another io.ReadWriteCloser) {
|
||||
defer func() {
|
||||
one.Close()
|
||||
another.Close()
|
||||
}()
|
||||
|
||||
channelPing := make(chan struct{}, 1)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
one = rwc.NewPing(ctx, one, channelPing)
|
||||
another = rwc.NewPing(ctx, another, channelPing)
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
wg.Add(2) //nolint: gomnd
|
||||
|
||||
go cloakPipe(one, another, wg)
|
||||
|
||||
go cloakPipe(another, one, wg)
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
cancel()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
lastActivityTimer := time.NewTimer(cloakLastActivityTimeout)
|
||||
defer lastActivityTimer.Stop()
|
||||
|
||||
maxTimer := time.NewTimer(cloakMaxTimeout)
|
||||
defer maxTimer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-channelPing:
|
||||
lastActivityTimer.Stop()
|
||||
lastActivityTimer = time.NewTimer(cloakLastActivityTimeout)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-lastActivityTimer.C:
|
||||
cancel()
|
||||
|
||||
return
|
||||
case <-maxTimer.C:
|
||||
cancel()
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
}
|
||||
|
||||
func cloakPipe(one io.Writer, another io.Reader, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
io.Copy(one, another) //nolint: errcheck
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
TimeSkew = 5 * time.Second
|
||||
TimeFromBoot = 24 * 60 * 60
|
||||
)
|
||||
|
||||
var (
|
||||
errBadDigest = errors.New("bad digest")
|
||||
errBadTime = errors.New("bad time")
|
||||
|
||||
faketlsStartBytes = [...]byte{
|
||||
0x16,
|
||||
0x03,
|
||||
0x01,
|
||||
0x02,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x01,
|
||||
0xfc,
|
||||
0x03,
|
||||
0x03,
|
||||
}
|
||||
)
|
||||
@@ -1,26 +1,31 @@
|
||||
module github.com/9seconds/mtg
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
|
||||
github.com/beevik/ntp v0.2.0
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d
|
||||
github.com/gofrs/uuid v3.1.0+incompatible
|
||||
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1
|
||||
github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect
|
||||
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/pkg/errors v0.8.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.2.2
|
||||
go.uber.org/atomic v1.3.2 // indirect
|
||||
go.uber.org/multierr v1.1.0 // indirect
|
||||
go.uber.org/zap v1.9.1
|
||||
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f // indirect
|
||||
github.com/VictoriaMetrics/fastcache v1.10.0
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137
|
||||
github.com/beevik/ntp v0.3.0
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/smira/go-statsd v1.3.2
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.22.0
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
||||
golang.org/x/net v0.0.0-20220809012201-f428fae20770 // indirect
|
||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||
gopkg.in/alexcesaro/statsd.v2 v2.0.0
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect
|
||||
gopkg.in/yaml.v2 v2.2.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,48 +1,512 @@
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
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/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/VictoriaMetrics/fastcache v1.10.0 h1:5hDJnLsKLpnUEToub7ETuRu8RCkb40woBZAUiKonXzY=
|
||||
github.com/VictoriaMetrics/fastcache v1.10.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw=
|
||||
github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
|
||||
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/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||
github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
|
||||
github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d h1:lDrio3iIdNb0Gw9CgH7cQF+iuB5mOOjdJ9ERNJCBgb4=
|
||||
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA=
|
||||
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1 h1:wnhMXidtb70kDZCeLt/EfsVtkXS5c8zLnE9y/6DIRAU=
|
||||
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
||||
github.com/juju/loggo v0.0.0-20180524022052-584905176618 h1:MK144iBQF9hTSwBW/9eJm034bVoG30IshVm688T2hi8=
|
||||
github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
||||
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 h1:WQM1NildKThwdP7qWrNAFGzp4ijNLw8RlgENkaI4MJs=
|
||||
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/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/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
|
||||
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
||||
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
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.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f h1:QM2QVxvDoW9PFSPp/zy9FgxJLfaWTZlS61KEPtBwacM=
|
||||
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/zap v1.22.0 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0=
|
||||
go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U=
|
||||
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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/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/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-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-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220809012201-f428fae20770 h1:dIi4qVdvjZEjiMDv7vhokAZNGnz3kepwuXqFKYDdDMs=
|
||||
golang.org/x/net v0.0.0-20220809012201-f428fae20770/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/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-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-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-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-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 h1:v1W7bwXHsnLLloWYTVEdvGvA7BHMeBYsPcF0GLDxIRs=
|
||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/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-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/statsd.v2 v2.0.0 h1:FXkZSCZIH17vLCO5sO2UucTHsH9pc+17F6pl3JVCwMc=
|
||||
gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7SLonnE4drU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/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-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
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/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=
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
"github.com/9seconds/mtg/mtproto/rpc"
|
||||
"github.com/9seconds/mtg/protocol"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const connectionTTL = time.Hour
|
||||
|
||||
type connection struct {
|
||||
conn conntypes.PacketReadWriteCloser
|
||||
proxyConns map[string]*ProxyConn
|
||||
closeOnce sync.Once
|
||||
proxyConnsMutex sync.RWMutex
|
||||
id int
|
||||
logger *zap.SugaredLogger
|
||||
|
||||
channelDone chan struct{}
|
||||
channelWrite chan conntypes.Packet
|
||||
channelRead chan *rpc.ProxyResponse
|
||||
channelConnAttach chan *ProxyConn
|
||||
channelConnDetach chan conntypes.ConnID
|
||||
}
|
||||
|
||||
func (c *connection) run() { //nolint: cyclop
|
||||
defer c.Close()
|
||||
|
||||
ttl := time.NewTimer(connectionTTL)
|
||||
defer ttl.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.channelDone:
|
||||
for _, v := range c.proxyConns {
|
||||
v.Close()
|
||||
}
|
||||
|
||||
return
|
||||
case <-ttl.C:
|
||||
c.logger.Debugw("Closing connection by TTL")
|
||||
c.Close()
|
||||
case resp := <-c.channelRead:
|
||||
if channel, ok := c.proxyConns[string(resp.ConnID[:])]; ok {
|
||||
if resp.Type == rpc.ProxyResponseTypeCloseExt {
|
||||
channel.Close()
|
||||
} else {
|
||||
channel.put(resp)
|
||||
}
|
||||
}
|
||||
case packet := <-c.channelWrite:
|
||||
if err := c.conn.Write(packet); err != nil {
|
||||
c.logger.Debugw("Cannot write packet", "error", err)
|
||||
c.Close()
|
||||
}
|
||||
case conn := <-c.channelConnAttach:
|
||||
c.proxyConnsMutex.Lock()
|
||||
c.proxyConns[string(conn.req.ConnID[:])] = conn
|
||||
c.proxyConnsMutex.Unlock()
|
||||
conn.channelWrite = c.channelWrite
|
||||
case connID := <-c.channelConnDetach:
|
||||
if conn, ok := c.proxyConns[string(connID[:])]; ok {
|
||||
c.proxyConnsMutex.Lock()
|
||||
delete(c.proxyConns, string(connID[:]))
|
||||
c.proxyConnsMutex.Unlock()
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connection) readLoop() {
|
||||
for {
|
||||
packet, err := c.conn.Read()
|
||||
if err != nil {
|
||||
c.logger.Debugw("Cannot read packet", "error", err)
|
||||
c.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
response, err := rpc.ParseProxyResponse(packet)
|
||||
if err != nil {
|
||||
c.logger.Debugw("Failed response", "error", err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case <-c.channelDone:
|
||||
return
|
||||
case c.channelRead <- response:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connection) Close() {
|
||||
c.closeOnce.Do(func() {
|
||||
c.logger.Debugw("Closing connection")
|
||||
|
||||
close(c.channelDone)
|
||||
c.conn.Close()
|
||||
})
|
||||
}
|
||||
|
||||
func (c *connection) Done() bool {
|
||||
select {
|
||||
case <-c.channelDone:
|
||||
return true
|
||||
default:
|
||||
return c.Len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connection) Len() int {
|
||||
c.proxyConnsMutex.RLock()
|
||||
defer c.proxyConnsMutex.RUnlock()
|
||||
|
||||
return len(c.proxyConns)
|
||||
}
|
||||
|
||||
func (c *connection) Attach(conn *ProxyConn) error {
|
||||
select {
|
||||
case <-c.channelDone:
|
||||
return ErrClosed
|
||||
case c.channelConnAttach <- conn:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connection) Detach(connID conntypes.ConnID) {
|
||||
select {
|
||||
case <-c.channelDone:
|
||||
case c.channelConnDetach <- connID:
|
||||
}
|
||||
}
|
||||
|
||||
func newConnection(req *protocol.TelegramRequest) (*connection, error) {
|
||||
conn, err := mtproto.TelegramProtocol(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create a new connection: %w", err)
|
||||
}
|
||||
|
||||
id := rand.Int() //nolint: gosec
|
||||
rv := &connection{
|
||||
conn: conn,
|
||||
id: id,
|
||||
logger: zap.S().Named("hub-connection").With("id", id,
|
||||
"dc", req.ClientProtocol.DC(),
|
||||
"protocol", req.ClientProtocol.ConnectionProtocol()),
|
||||
proxyConns: make(map[string]*ProxyConn),
|
||||
|
||||
channelRead: make(chan *rpc.ProxyResponse, 1),
|
||||
channelDone: make(chan struct{}),
|
||||
channelWrite: make(chan conntypes.Packet),
|
||||
channelConnAttach: make(chan *ProxyConn),
|
||||
channelConnDetach: make(chan conntypes.ConnID),
|
||||
}
|
||||
|
||||
go rv.readLoop()
|
||||
|
||||
go rv.run()
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
)
|
||||
|
||||
type connectionList struct {
|
||||
connections []*connection
|
||||
}
|
||||
|
||||
func (c *connectionList) get(conn *ProxyConn) (*connection, error) {
|
||||
if len(c.connections) > 0 && c.connections[0].Len() < config.C.MultiplexPerConnection {
|
||||
if err := c.connections[0].Attach(conn); err == nil {
|
||||
return c.connections[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
newConn, err := newConnection(conn.req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot allocate a new connection: %w", err)
|
||||
}
|
||||
|
||||
if err = newConn.Attach(conn); err != nil {
|
||||
newConn.Close()
|
||||
|
||||
return nil, fmt.Errorf("cannot attach to the newly created connection: %w", err)
|
||||
}
|
||||
|
||||
c.connections = append(c.connections, newConn)
|
||||
lastIndex := len(c.connections) - 1
|
||||
c.connections[0], c.connections[lastIndex] = c.connections[lastIndex], c.connections[0]
|
||||
|
||||
return newConn, nil
|
||||
}
|
||||
|
||||
func (c *connectionList) gc() {
|
||||
prevLen := len(c.connections)
|
||||
if prevLen == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := len(c.connections) - 1; i >= 0; i-- {
|
||||
lastIndex := len(c.connections) - 1
|
||||
|
||||
if c.connections[i].Done() {
|
||||
c.connections[i].Close()
|
||||
|
||||
if len(c.connections)-1 == i {
|
||||
c.connections = c.connections[:lastIndex]
|
||||
} else {
|
||||
c.connections[i], c.connections[lastIndex] = c.connections[lastIndex], c.connections[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if prevLen != len(c.connections) {
|
||||
c.sort()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connectionList) sort() {
|
||||
if len(c.connections) > 1 {
|
||||
sort.Slice(c.connections, func(i, j int) bool {
|
||||
return c.connections[i].Len() < c.connections[j].Len()
|
||||
})
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/9seconds/mtg/protocol"
|
||||
)
|
||||
|
||||
type hub struct {
|
||||
muxes map[int32]*mux
|
||||
mutex sync.RWMutex
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (h *hub) Register(req *protocol.TelegramRequest) (*ProxyConn, error) {
|
||||
return h.getMux(req).Get(req)
|
||||
}
|
||||
|
||||
func (h *hub) getMux(req *protocol.TelegramRequest) *mux {
|
||||
key := 32767 + int32(req.ClientProtocol.DC()) + 100000*int32(req.ClientProtocol.ConnectionProtocol()) //nolint: gomnd
|
||||
|
||||
h.mutex.RLock()
|
||||
m, ok := h.muxes[key]
|
||||
h.mutex.RUnlock()
|
||||
|
||||
if !ok {
|
||||
h.mutex.Lock()
|
||||
m, ok = h.muxes[key]
|
||||
|
||||
if !ok {
|
||||
m = newMux(h.ctx)
|
||||
h.muxes[key] = m
|
||||
}
|
||||
|
||||
h.mutex.Unlock()
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTimeout = errors.New("timeout")
|
||||
ErrClosed = errors.New("context is closed")
|
||||
|
||||
Hub Interface
|
||||
initOnce sync.Once
|
||||
)
|
||||
|
||||
func Init(ctx context.Context) {
|
||||
initOnce.Do(func() {
|
||||
Hub = &hub{
|
||||
muxes: make(map[int32]*mux),
|
||||
ctx: ctx,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package hub
|
||||
|
||||
import "github.com/9seconds/mtg/protocol"
|
||||
|
||||
type Interface interface {
|
||||
Register(*protocol.TelegramRequest) (*ProxyConn, error)
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
"github.com/9seconds/mtg/protocol"
|
||||
)
|
||||
|
||||
const muxGCEvery = time.Minute
|
||||
|
||||
type muxNewRequest struct {
|
||||
req *protocol.TelegramRequest
|
||||
resp chan<- muxNewResponse
|
||||
}
|
||||
|
||||
type muxNewResponse struct {
|
||||
conn *ProxyConn
|
||||
err error
|
||||
}
|
||||
|
||||
type mux struct {
|
||||
connections connectionList
|
||||
clients map[string]*connection
|
||||
ctx context.Context
|
||||
channelClosed chan conntypes.ConnID
|
||||
channelNew chan muxNewRequest
|
||||
}
|
||||
|
||||
func (m *mux) run() {
|
||||
gcTicker := time.NewTicker(muxGCEvery)
|
||||
defer gcTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-m.ctx.Done():
|
||||
for _, v := range m.clients {
|
||||
v.Close()
|
||||
}
|
||||
|
||||
return
|
||||
case <-gcTicker.C:
|
||||
m.connections.gc()
|
||||
case req := <-m.channelNew:
|
||||
m.connections.gc()
|
||||
proxyConn := newProxyConn(req.req, m.channelClosed)
|
||||
conn, err := m.connections.get(proxyConn)
|
||||
|
||||
if err == nil {
|
||||
m.clients[string(req.req.ConnID[:])] = conn
|
||||
}
|
||||
|
||||
req.resp <- muxNewResponse{
|
||||
conn: proxyConn,
|
||||
err: err,
|
||||
}
|
||||
close(req.resp)
|
||||
case connID := <-m.channelClosed:
|
||||
if conn, ok := m.clients[string(connID[:])]; ok {
|
||||
conn.Detach(connID)
|
||||
delete(m.clients, string(connID[:]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mux) Get(req *protocol.TelegramRequest) (*ProxyConn, error) {
|
||||
resp := make(chan muxNewResponse)
|
||||
m.channelNew <- muxNewRequest{
|
||||
req: req,
|
||||
resp: resp,
|
||||
}
|
||||
|
||||
rv := <-resp
|
||||
|
||||
return rv.conn, rv.err
|
||||
}
|
||||
|
||||
func newMux(ctx context.Context) *mux {
|
||||
m := &mux{
|
||||
ctx: ctx,
|
||||
clients: make(map[string]*connection),
|
||||
channelClosed: make(chan conntypes.ConnID, 1),
|
||||
channelNew: make(chan muxNewRequest),
|
||||
}
|
||||
go m.run()
|
||||
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
"github.com/9seconds/mtg/mtproto/rpc"
|
||||
"github.com/9seconds/mtg/protocol"
|
||||
)
|
||||
|
||||
const (
|
||||
proxyConnWriteTimeout = 2 * time.Minute
|
||||
proxyConnReadTimeout = 2 * time.Minute
|
||||
|
||||
proxyConnBackpressureAfter = 10
|
||||
)
|
||||
|
||||
type ProxyConn struct {
|
||||
closeOnce sync.Once
|
||||
req *protocol.TelegramRequest
|
||||
channelResponse chan *rpc.ProxyResponse
|
||||
channelClosed chan<- conntypes.ConnID
|
||||
channelWrite chan<- conntypes.Packet
|
||||
channelDone chan struct{}
|
||||
}
|
||||
|
||||
func (p *ProxyConn) Read() (*rpc.ProxyResponse, error) {
|
||||
timer := time.NewTimer(proxyConnReadTimeout)
|
||||
defer timer.Stop()
|
||||
|
||||
select {
|
||||
case <-timer.C:
|
||||
return nil, ErrTimeout
|
||||
case <-p.channelDone:
|
||||
return nil, ErrClosed
|
||||
case packet := <-p.channelResponse:
|
||||
return packet, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProxyConn) Write(packet conntypes.Packet) error {
|
||||
timer := time.NewTimer(proxyConnWriteTimeout)
|
||||
defer timer.Stop()
|
||||
|
||||
select {
|
||||
case <-timer.C:
|
||||
return ErrTimeout
|
||||
case <-p.channelDone:
|
||||
return ErrClosed
|
||||
case p.channelWrite <- packet:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProxyConn) put(response *rpc.ProxyResponse) {
|
||||
select {
|
||||
case <-p.channelDone:
|
||||
case p.channelResponse <- response:
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProxyConn) Close() {
|
||||
p.closeOnce.Do(func() {
|
||||
close(p.channelDone)
|
||||
go func() {
|
||||
p.channelClosed <- p.req.ConnID
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
func newProxyConn(req *protocol.TelegramRequest, channelClosed chan<- conntypes.ConnID) *ProxyConn {
|
||||
return &ProxyConn{
|
||||
channelResponse: make(chan *rpc.ProxyResponse, proxyConnBackpressureAfter),
|
||||
channelDone: make(chan struct{}),
|
||||
channelClosed: channelClosed,
|
||||
req: req,
|
||||
}
|
||||
}
|
||||
@@ -1,232 +1,159 @@
|
||||
package main
|
||||
|
||||
//go:generate scripts/generate_version.sh
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/juju/errors"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||
|
||||
"github.com/9seconds/mtg/cli"
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/ntp"
|
||||
"github.com/9seconds/mtg/proxy"
|
||||
"github.com/9seconds/mtg/stats"
|
||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
app = kingpin.New("mtg", "Simple MTPROTO proxy.")
|
||||
|
||||
debug = app.Flag("debug",
|
||||
generateSecretCommand = app.Command("generate-secret",
|
||||
"Generate new secret")
|
||||
generateCloakHost = generateSecretCommand.Flag("cloak-host",
|
||||
"A host to use for TLS cloaking.").
|
||||
Short('c').
|
||||
Default("storage.googleapis.com").
|
||||
String()
|
||||
generateSecretType = generateSecretCommand.Arg("type",
|
||||
"A type of secret to generate. Valid options are 'simple', 'secured' and 'tls'").
|
||||
Required().
|
||||
Enum("simple", "secured", "tls")
|
||||
|
||||
runCommand = app.Command("run",
|
||||
"Run new proxy instance")
|
||||
runDebug = runCommand.Flag("debug",
|
||||
"Run in debug mode.").
|
||||
Short('d').
|
||||
Envar("MTG_DEBUG").
|
||||
Bool()
|
||||
verbose = app.Flag("verbose",
|
||||
runVerbose = runCommand.Flag("verbose",
|
||||
"Run in verbose mode.").
|
||||
Short('v').
|
||||
Envar("MTG_VERBOSE").
|
||||
Bool()
|
||||
|
||||
bindIP = app.Flag("bind-ip",
|
||||
"Which IP to bind to.").
|
||||
runPreferIP = runCommand.Flag("prefer-ip",
|
||||
"Prefer this IP protocol if possible. Valid options are 'ipv4' and 'ipv6'").
|
||||
Envar("MTG_PREFER_DIRECT_IP").
|
||||
Default("ipv6").
|
||||
Enum("ipv4", "ipv6")
|
||||
runBind = runCommand.Flag("bind",
|
||||
"Host:Port to bind proxy to.").
|
||||
Short('b').
|
||||
Envar("MTG_IP").
|
||||
Default("127.0.0.1").
|
||||
IP()
|
||||
bindPort = app.Flag("bind-port",
|
||||
"Which port to bind to.").
|
||||
Short('p').
|
||||
Envar("MTG_PORT").
|
||||
Default("3128").
|
||||
Uint16()
|
||||
|
||||
publicIPv4 = app.Flag("public-ipv4",
|
||||
"Which IPv4 address is public.").
|
||||
Envar("MTG_BIND").
|
||||
Default("0.0.0.0:3128").
|
||||
TCP()
|
||||
runPublicIPv4 = runCommand.Flag("public-ipv4",
|
||||
"Which IPv4 host:port to use.").
|
||||
Short('4').
|
||||
Envar("MTG_IPV4").
|
||||
IP()
|
||||
publicIPv4Port = app.Flag("public-ipv4-port",
|
||||
"Which IPv4 port is public. Default is 'bind-port' value.").
|
||||
Envar("MTG_IPV4_PORT").
|
||||
Uint16()
|
||||
|
||||
publicIPv6 = app.Flag("public-ipv6",
|
||||
"Which IPv6 address is public.").
|
||||
TCP()
|
||||
runPublicIPv6 = runCommand.Flag("public-ipv6",
|
||||
"Which IPv6 host:port to use.").
|
||||
Short('6').
|
||||
Envar("MTG_IPV6").
|
||||
IP()
|
||||
publicIPv6Port = app.Flag("public-ipv6-port",
|
||||
"Which IPv6 port is public. Default is 'bind-port' value.").
|
||||
Envar("MTG_IPV6_PORT").
|
||||
Uint16()
|
||||
|
||||
statsIP = app.Flag("stats-ip",
|
||||
"Which IP bind stats server to.").
|
||||
TCP()
|
||||
runStatsBind = runCommand.Flag("stats-bind",
|
||||
"Which Host:Port to bind stats server to.").
|
||||
Short('t').
|
||||
Envar("MTG_STATS_IP").
|
||||
Default("127.0.0.1").
|
||||
IP()
|
||||
statsPort = app.Flag("stats-port",
|
||||
"Which port bind stats to.").
|
||||
Short('q').
|
||||
Envar("MTG_STATS_PORT").
|
||||
Default("3129").
|
||||
Uint16()
|
||||
|
||||
statsdIP = app.Flag("statsd-ip",
|
||||
"Which IP should we use for working with statsd.").
|
||||
Envar("MTG_STATSD_IP").
|
||||
String()
|
||||
statsdPort = app.Flag("statsd-port",
|
||||
"Which port should we use for working with statsd.").
|
||||
Envar("MTG_STATSD_PORT").
|
||||
Default("8125").
|
||||
Uint16()
|
||||
statsdNetwork = app.Flag("statsd-network",
|
||||
"Which network is used to work with statsd. Only 'tcp' and 'udp' are supported.").
|
||||
Envar("MTG_STATSD_NETWORK").
|
||||
Default("udp").
|
||||
String()
|
||||
statsdPrefix = app.Flag("statsd-prefix",
|
||||
"Which bucket prefix should we use for sending stats to statsd.").
|
||||
Envar("MTG_STATSD_PREFIX").
|
||||
Envar("MTG_STATS_BIND").
|
||||
Default("127.0.0.1:3129").
|
||||
TCP()
|
||||
runStatsNamespace = runCommand.Flag("stats-namespace",
|
||||
"Which namespace to use for Prometheus.").
|
||||
Envar("MTG_STATS_NAMESPACE").
|
||||
Default("mtg").
|
||||
String()
|
||||
statsdTagsFormat = app.Flag("statsd-tags-format",
|
||||
runStatsdAddress = runCommand.Flag("statsd-addr",
|
||||
"Host:port of statsd server").
|
||||
Envar("MTG_STATSD_ADDR").
|
||||
TCP()
|
||||
runStatsdTagsFormat = runCommand.Flag("statsd-tags-format",
|
||||
"Which tag format should we use to send stats metrics. Valid options are 'datadog' and 'influxdb'.").
|
||||
Envar("MTG_STATSD_TAGS_FORMAT").
|
||||
String()
|
||||
statsdTags = app.Flag("statsd-tags",
|
||||
Default("influxdb").
|
||||
Enum("datadog", "influxdb")
|
||||
runStatsdTags = runCommand.Flag("statsd-tags",
|
||||
"Tags to use for working with statsd (specified as 'key=value').").
|
||||
Envar("MTG_STATSD_TAGS").
|
||||
StringMap()
|
||||
|
||||
writeBufferSize = app.Flag("write-buffer",
|
||||
"Write buffer size in bytes. You can think about it as a buffer from client to Telegram.").
|
||||
runWriteBufferSize = runCommand.Flag("write-buffer",
|
||||
"Write buffer size. You can think about it as a buffer from client to Telegram.").
|
||||
Short('w').
|
||||
Envar("MTG_BUFFER_WRITE").
|
||||
Default("65536").
|
||||
Uint32()
|
||||
readBufferSize = app.Flag("read-buffer",
|
||||
"Read buffer size in bytes. You can think about it as a buffer from Telegram to client.").
|
||||
Default("32KB").
|
||||
Bytes()
|
||||
runReadBufferSize = runCommand.Flag("read-buffer",
|
||||
"Read buffer size. You can think about it as a buffer from Telegram to client.").
|
||||
Short('r').
|
||||
Envar("MTG_BUFFER_READ").
|
||||
Default("131072").
|
||||
Uint32()
|
||||
|
||||
secret = app.Arg("secret", "Secret of this proxy.").Required().HexBytes()
|
||||
adtag = app.Arg("adtag", "ADTag of the proxy.").HexBytes()
|
||||
Default("32KB").
|
||||
Bytes()
|
||||
runTLSCloakPort = runCommand.Flag("cloak-port",
|
||||
"Port which should be used for host cloaking.").
|
||||
Envar("MTG_CLOAK_PORT").
|
||||
Default("443").
|
||||
Uint16()
|
||||
runAntiReplayMaxSize = runCommand.Flag("anti-replay-max-size",
|
||||
"Max size of antireplay cache.").
|
||||
Envar("MTG_ANTIREPLAY_MAXSIZE").
|
||||
Default("128MB").
|
||||
Bytes()
|
||||
runMultiplexPerConnection = runCommand.Flag("multiplex-per-connection",
|
||||
"How many clients can share a single connection to Telegram.").
|
||||
Envar("MTG_MULTIPLEX_PERCONNECTION").
|
||||
Default("50").
|
||||
Uint()
|
||||
runNTPServers = runCommand.Flag("ntp-server",
|
||||
"A list of NTP servers to use.").
|
||||
Envar("MTG_NTP_SERVERS").
|
||||
Default("0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org", "3.pool.ntp.org").
|
||||
Strings()
|
||||
runSecret = runCommand.Arg("secret", "Secret of this proxy.").Required().HexBytes()
|
||||
runAdtag = runCommand.Arg("adtag", "ADTag of the proxy.").HexBytes()
|
||||
)
|
||||
|
||||
func init() {
|
||||
func main() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
app.Version(version)
|
||||
app.Version(getVersion())
|
||||
app.HelpFlag.Short('h')
|
||||
}
|
||||
|
||||
func main() { // nolint: gocyclo
|
||||
kingpin.MustParse(app.Parse(os.Args[1:]))
|
||||
|
||||
err := setRLimit()
|
||||
if err != nil {
|
||||
usage(err.Error())
|
||||
}
|
||||
|
||||
conf, err := config.NewConfig(*debug, *verbose,
|
||||
*writeBufferSize, *readBufferSize,
|
||||
*bindIP, *publicIPv4, *publicIPv6, *statsIP,
|
||||
*bindPort, *publicIPv4Port, *publicIPv6Port, *statsPort, *statsdPort,
|
||||
*statsdIP, *statsdNetwork, *statsdPrefix, *statsdTagsFormat,
|
||||
*statsdTags,
|
||||
*secret, *adtag,
|
||||
)
|
||||
if err != nil {
|
||||
usage(err.Error())
|
||||
}
|
||||
|
||||
atom := zap.NewAtomicLevel()
|
||||
switch {
|
||||
case conf.Debug:
|
||||
atom.SetLevel(zapcore.DebugLevel)
|
||||
case conf.Verbose:
|
||||
atom.SetLevel(zapcore.InfoLevel)
|
||||
default:
|
||||
atom.SetLevel(zapcore.ErrorLevel)
|
||||
}
|
||||
encoderCfg := zap.NewProductionEncoderConfig()
|
||||
logger := zap.New(zapcore.NewCore(
|
||||
zapcore.NewJSONEncoder(encoderCfg),
|
||||
zapcore.Lock(os.Stderr),
|
||||
atom,
|
||||
))
|
||||
zap.ReplaceGlobals(logger)
|
||||
defer logger.Sync() // nolint: errcheck
|
||||
|
||||
printURLs(conf.GetURLs())
|
||||
|
||||
if conf.UseMiddleProxy() {
|
||||
zap.S().Infow("Use middle proxy connection to Telegram")
|
||||
if diff, err := ntp.Fetch(); err != nil {
|
||||
zap.S().Warnw("Could not fetch time data from NTP")
|
||||
} else {
|
||||
if diff >= time.Second {
|
||||
usage(fmt.Sprintf("You choose to use middle proxy but your clock drift (%s) "+
|
||||
"is bigger than 1 second. Please, sync your time", diff))
|
||||
}
|
||||
go ntp.AutoUpdate()
|
||||
switch kingpin.MustParse(app.Parse(os.Args[1:])) {
|
||||
case generateSecretCommand.FullCommand():
|
||||
cli.Generate(*generateSecretType, *generateCloakHost)
|
||||
case runCommand.FullCommand():
|
||||
err := config.Init(
|
||||
config.Opt{Option: config.OptionTypeDebug, Value: *runDebug},
|
||||
config.Opt{Option: config.OptionTypeVerbose, Value: *runVerbose},
|
||||
config.Opt{Option: config.OptionTypePreferIP, Value: *runPreferIP},
|
||||
config.Opt{Option: config.OptionTypeBind, Value: *runBind},
|
||||
config.Opt{Option: config.OptionTypePublicIPv4, Value: *runPublicIPv4},
|
||||
config.Opt{Option: config.OptionTypePublicIPv6, Value: *runPublicIPv6},
|
||||
config.Opt{Option: config.OptionTypeStatsBind, Value: *runStatsBind},
|
||||
config.Opt{Option: config.OptionTypeStatsNamespace, Value: *runStatsNamespace},
|
||||
config.Opt{Option: config.OptionTypeStatsdAddress, Value: *runStatsdAddress},
|
||||
config.Opt{Option: config.OptionTypeStatsdTagsFormat, Value: *runStatsdTagsFormat},
|
||||
config.Opt{Option: config.OptionTypeStatsdTags, Value: *runStatsdTags},
|
||||
config.Opt{Option: config.OptionTypeWriteBufferSize, Value: *runWriteBufferSize},
|
||||
config.Opt{Option: config.OptionTypeReadBufferSize, Value: *runReadBufferSize},
|
||||
config.Opt{Option: config.OptionTypeCloakPort, Value: *runTLSCloakPort},
|
||||
config.Opt{Option: config.OptionTypeAntiReplayMaxSize, Value: *runAntiReplayMaxSize},
|
||||
config.Opt{Option: config.OptionTypeMultiplexPerConnection, Value: *runMultiplexPerConnection},
|
||||
config.Opt{Option: config.OptionTypeNTPServers, Value: *runNTPServers},
|
||||
config.Opt{Option: config.OptionTypeSecret, Value: *runSecret},
|
||||
config.Opt{Option: config.OptionTypeAdtag, Value: *runAdtag},
|
||||
)
|
||||
if err != nil {
|
||||
cli.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
zap.S().Infow("Use direct connection to Telegram")
|
||||
}
|
||||
|
||||
if err := stats.Start(conf); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
server := proxy.NewProxy(conf)
|
||||
if err := server.Serve(); err != nil {
|
||||
zap.S().Fatalw("Server stopped", "error", err)
|
||||
if err := cli.Proxy(); err != nil {
|
||||
cli.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setRLimit() (err error) {
|
||||
rLimit := syscall.Rlimit{}
|
||||
err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
|
||||
if err != nil {
|
||||
err = errors.Annotate(err, "Cannot get rlimit")
|
||||
return
|
||||
}
|
||||
rLimit.Cur = rLimit.Max
|
||||
|
||||
err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
|
||||
if err != nil {
|
||||
err = errors.Annotate(err, "Cannot set rlimit")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func printURLs(data interface{}) {
|
||||
encoder := json.NewEncoder(os.Stdout)
|
||||
encoder.SetEscapeHTML(false)
|
||||
encoder.SetIndent("", " ")
|
||||
|
||||
err := encoder.Encode(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func usage(msg string) {
|
||||
io.WriteString(os.Stderr, msg+"\n") // nolint: errcheck, gosec
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package mtproto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
// ConnectionType is a type of obfuscated2/mtproto connection requested
|
||||
// by the user.
|
||||
type ConnectionType uint8
|
||||
|
||||
// ConnectionProtocol is a type of IP protocol to use.
|
||||
type ConnectionProtocol uint8
|
||||
|
||||
// Hacks is a simple structure to store flags for packet transmission.
|
||||
type Hacks struct {
|
||||
SimpleAck bool
|
||||
QuickAck bool
|
||||
}
|
||||
|
||||
// ConnectionOpts presents an options, metadata on connection requested
|
||||
// by the user on handshake.
|
||||
type ConnectionOpts struct {
|
||||
DC int16
|
||||
ConnectionType ConnectionType
|
||||
ConnectionProto ConnectionProtocol
|
||||
// Read and Write means direction related to the client.
|
||||
// ReadHacks are meant to be flushed on client read
|
||||
// WriteHacks are meant to be flushed on client write.
|
||||
ReadHacks Hacks
|
||||
WriteHacks Hacks
|
||||
ClientAddr *net.TCPAddr
|
||||
}
|
||||
|
||||
// Different connection types which user requests from Telegram.
|
||||
const (
|
||||
ConnectionTypeUnknown ConnectionType = iota
|
||||
ConnectionTypeAbridged
|
||||
ConnectionTypeIntermediate
|
||||
ConnectionTypeSecure
|
||||
)
|
||||
|
||||
// ConnectionProtocol* define which connection protocols to use.
|
||||
// ConnectionProtocolAny means that any is suitable.
|
||||
const (
|
||||
ConnectionProtocolIPv4 ConnectionProtocol = 1
|
||||
ConnectionProtocolIPv6 = ConnectionProtocolIPv4 << 1
|
||||
ConnectionProtocolAny = ConnectionProtocolIPv4 | ConnectionProtocolIPv6
|
||||
)
|
||||
|
||||
// Connection tags for mtproto handshakes.
|
||||
var (
|
||||
ConnectionTagAbridged = []byte{0xef, 0xef, 0xef, 0xef}
|
||||
ConnectionTagIntermediate = []byte{0xee, 0xee, 0xee, 0xee}
|
||||
ConnectionTagSecure = []byte{0xdd, 0xdd, 0xdd, 0xdd}
|
||||
)
|
||||
|
||||
// Tag maps connection type to the corresponding handshake tag.
|
||||
func (t ConnectionType) Tag() ([]byte, error) {
|
||||
switch t {
|
||||
case ConnectionTypeAbridged:
|
||||
return ConnectionTagAbridged, nil
|
||||
case ConnectionTypeIntermediate:
|
||||
return ConnectionTagIntermediate, nil
|
||||
case ConnectionTypeSecure:
|
||||
return ConnectionTagSecure, nil
|
||||
default:
|
||||
return nil, errors.Errorf("Unknown connection type %d", t)
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionTagFromHandshake maps magic bytes to the connection type.
|
||||
func ConnectionTagFromHandshake(magic []byte) (ConnectionType, error) {
|
||||
if bytes.Equal(magic, ConnectionTagIntermediate) {
|
||||
return ConnectionTypeIntermediate, nil
|
||||
}
|
||||
if bytes.Equal(magic, ConnectionTagAbridged) {
|
||||
return ConnectionTypeAbridged, nil
|
||||
}
|
||||
if bytes.Equal(magic, ConnectionTagSecure) {
|
||||
return ConnectionTypeSecure, nil
|
||||
}
|
||||
|
||||
return ConnectionTypeUnknown, errors.New("Unknown handshake protocol")
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package mtproto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
"github.com/9seconds/mtg/mtproto/rpc"
|
||||
"github.com/9seconds/mtg/protocol"
|
||||
"github.com/9seconds/mtg/telegram"
|
||||
"github.com/9seconds/mtg/wrappers/packet"
|
||||
"github.com/9seconds/mtg/wrappers/stream"
|
||||
)
|
||||
|
||||
func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.PacketReadWriteCloser, error) {
|
||||
conn, err := telegram.Middle.Dial(req.ClientProtocol.DC(),
|
||||
req.ClientProtocol.ConnectionProtocol())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot connect to telegram: %w", err)
|
||||
}
|
||||
|
||||
rpcNonceConn := packet.NewMtprotoFrame(conn, rpc.SeqNoNonce)
|
||||
|
||||
rpcNonceReq, err := doRPCNonceRequest(rpcNonceConn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot do nonce request: %w", err)
|
||||
}
|
||||
|
||||
rpcNonceResp, err := getRPCNonceResponse(rpcNonceConn, rpcNonceReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get nonce response: %w", err)
|
||||
}
|
||||
|
||||
secureConn := stream.NewMiddleProxyCipher(conn, rpcNonceReq, rpcNonceResp, telegram.Middle.Secret())
|
||||
frameConn := packet.NewMtprotoFrame(secureConn, rpc.SeqNoHandshake)
|
||||
|
||||
if err := doRPCHandshakeRequest(frameConn); err != nil {
|
||||
return nil, fmt.Errorf("cannot do handshake request: %w", err)
|
||||
}
|
||||
|
||||
if err := getRPCHandshakeResponse(frameConn); err != nil {
|
||||
return nil, fmt.Errorf("cannot get handshake response: %w", err)
|
||||
}
|
||||
|
||||
return frameConn, nil
|
||||
}
|
||||
|
||||
func doRPCNonceRequest(conn conntypes.BasePacketWriter) (*rpc.NonceRequest, error) {
|
||||
rpcNonceReq, err := rpc.NewNonceRequest(telegram.Middle.Secret())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := conn.Write(rpcNonceReq.Bytes()); err != nil {
|
||||
return nil, err //nolint: wrapcheck
|
||||
}
|
||||
|
||||
return rpcNonceReq, nil
|
||||
}
|
||||
|
||||
func getRPCNonceResponse(conn conntypes.BasePacketReader, req *rpc.NonceRequest) (*rpc.NonceResponse, error) {
|
||||
packet, err := conn.Read()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read from connection: %w", err)
|
||||
}
|
||||
|
||||
resp, err := rpc.NewNonceResponse(packet)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot build rpc nonce response: %w", err)
|
||||
}
|
||||
|
||||
if err = resp.Valid(req); err != nil {
|
||||
return nil, fmt.Errorf("invalid nonce response: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func doRPCHandshakeRequest(conn conntypes.BasePacketWriter) error {
|
||||
if err := conn.Write(rpc.HandshakeRequest); err != nil {
|
||||
return fmt.Errorf("cannot make a request: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRPCHandshakeResponse(conn conntypes.BasePacketReader) error {
|
||||
packet, err := conn.Read()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot read a response: %w", err)
|
||||
}
|
||||
|
||||
resp, err := rpc.NewHandshakeResponse(packet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot build a handshake response: %w", err)
|
||||
}
|
||||
|
||||
if err := resp.Valid(); err != nil {
|
||||
return fmt.Errorf("invalid handshake response: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -7,7 +7,7 @@ const (
|
||||
SeqNoHandshake = -1
|
||||
)
|
||||
|
||||
// Different constants for RPC protocol
|
||||
// Different constants for RPC protocol.
|
||||
var (
|
||||
TagCloseExt = []byte{0xa2, 0x34, 0xb6, 0x5e}
|
||||
TagProxyAns = []byte{0x0d, 0xda, 0x03, 0x44}
|
||||
@@ -23,11 +23,6 @@ var (
|
||||
ProxyRequestExtraSize = []byte{0x18, 0x00, 0x00, 0x00}
|
||||
ProxyRequestProxyTag = []byte{0xae, 0x26, 0x1e, 0xdb}
|
||||
|
||||
HandshakeSenderPID []byte
|
||||
HandshakePeerPID []byte
|
||||
)
|
||||
|
||||
func init() {
|
||||
HandshakeSenderPID = []byte("IPIPPRPDTIME")
|
||||
HandshakePeerPID = []byte("IPIPPRPDTIME")
|
||||
}
|
||||
HandshakePeerPID = []byte("IPIPPRPDTIME")
|
||||
)
|
||||
@@ -1,26 +1,5 @@
|
||||
package rpc
|
||||
|
||||
import "bytes"
|
||||
|
||||
// HandshakeRequest is the data type which is responsible for
|
||||
// constructing of correct handshake request.
|
||||
type HandshakeRequest struct {
|
||||
}
|
||||
|
||||
// Bytes returns serialized handshake request.
|
||||
func (r *HandshakeRequest) Bytes() []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
buf.Grow(len(TagHandshake) + len(HandshakeFlags) + len(HandshakeSenderPID) + len(HandshakePeerPID))
|
||||
|
||||
buf.Write(TagHandshake) // nolint: gosec
|
||||
buf.Write(HandshakeFlags) // nolint: gosec
|
||||
buf.Write(HandshakeSenderPID) // nolint: gosec
|
||||
buf.Write(HandshakePeerPID) // nolint: gosec
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// NewHandshakeRequest creates new HandshakeRequest instance.
|
||||
func NewHandshakeRequest() *HandshakeRequest {
|
||||
return &HandshakeRequest{}
|
||||
}
|
||||
var HandshakeRequest = append(TagHandshake,
|
||||
append(HandshakeFlags,
|
||||
append(HandshakeSenderPID, HandshakePeerPID...)...)...)
|
||||
|
||||
@@ -2,12 +2,10 @@ package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/juju/errors"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// HandshakeResponse defines data structure which is used for storage of
|
||||
// handshake response.
|
||||
type HandshakeResponse struct {
|
||||
Type []byte
|
||||
Flags []byte
|
||||
@@ -19,21 +17,22 @@ type HandshakeResponse struct {
|
||||
func (r *HandshakeResponse) Bytes() []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
buf.Write(r.Type) // nolint: gosec
|
||||
buf.Write(r.Flags) // nolint: gosec
|
||||
buf.Write(r.SenderPID) // nolint: gosec
|
||||
buf.Write(r.PeerPID) // nolint: gosec
|
||||
buf.Write(r.Type)
|
||||
buf.Write(r.Flags)
|
||||
buf.Write(r.SenderPID)
|
||||
buf.Write(r.PeerPID)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Valid checks that handshake response compliments request.
|
||||
func (r *HandshakeResponse) Valid(req *HandshakeRequest) error {
|
||||
func (r *HandshakeResponse) Valid() error {
|
||||
if !bytes.Equal(r.Type, TagHandshake) {
|
||||
return errors.New("Unexpected handshake tag")
|
||||
return errors.New("unexpected handshake tag")
|
||||
}
|
||||
|
||||
if !bytes.Equal(r.PeerPID, HandshakeSenderPID) {
|
||||
return errors.New("Incorrect sender PID")
|
||||
return errors.New("incorrect sender PID")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -42,8 +41,8 @@ func (r *HandshakeResponse) Valid(req *HandshakeRequest) error {
|
||||
// NewHandshakeResponse constructs new handshake response from the given
|
||||
// data.
|
||||
func NewHandshakeResponse(data []byte) (*HandshakeResponse, error) {
|
||||
if len(data) != 32 {
|
||||
return nil, errors.New("Incorrect handshake response length")
|
||||
if len(data) != 32 { //nolint: gomnd
|
||||
return nil, fmt.Errorf("incorrect handshake response length %d", len(data))
|
||||
}
|
||||
|
||||
return &HandshakeResponse{
|
||||
|
||||
@@ -4,13 +4,10 @@ import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
// NonceRequest is the data type which contains all the data for correct
|
||||
// nonce request.
|
||||
type NonceRequest struct {
|
||||
KeySelector []byte
|
||||
CryptoTS []byte
|
||||
@@ -21,27 +18,28 @@ type NonceRequest struct {
|
||||
func (r *NonceRequest) Bytes() []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
buf.Write(TagNonce) // nolint: gosec
|
||||
buf.Write(r.KeySelector) // nolint: gosec
|
||||
buf.Write(NonceCryptoAES) // nolint: gosec
|
||||
buf.Write(r.CryptoTS) // nolint: gosec
|
||||
buf.Write(r.Nonce) // nolint: gosec
|
||||
buf.Write(TagNonce)
|
||||
buf.Write(r.KeySelector)
|
||||
buf.Write(NonceCryptoAES)
|
||||
buf.Write(r.CryptoTS)
|
||||
buf.Write(r.Nonce)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// NewNonceRequest builds new none request based on proxy secret.
|
||||
func NewNonceRequest(proxySecret []byte) (*NonceRequest, error) {
|
||||
nonce := make([]byte, 16)
|
||||
keySelector := make([]byte, 4)
|
||||
cryptoTS := make([]byte, 4)
|
||||
nonce := make([]byte, 16) //nolint: gomnd
|
||||
keySelector := make([]byte, 4) //nolint: gomnd
|
||||
cryptoTS := make([]byte, 4) //nolint: gomnd
|
||||
|
||||
if _, err := rand.Read(nonce); err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot generate nonce")
|
||||
return nil, fmt.Errorf("cannot generate nonce: %w", err)
|
||||
}
|
||||
copy(keySelector, proxySecret)
|
||||
|
||||
timestamp := time.Now().Truncate(time.Second).Unix() % 4294967296 // 256 ^ 4 - do not know how to name
|
||||
copy(keySelector, proxySecret)
|
||||
// 256 ^ 4 - do not know how to name
|
||||
timestamp := time.Now().Truncate(time.Second).Unix() % 4294967296 //nolint: gomnd
|
||||
binary.LittleEndian.PutUint32(cryptoTS, uint32(timestamp))
|
||||
|
||||
return &NonceRequest{
|
||||
|
||||
@@ -2,11 +2,10 @@ package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/juju/errors"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// NonceResponse is the data type which contains data of nonce response.
|
||||
type NonceResponse struct {
|
||||
NonceRequest
|
||||
|
||||
@@ -16,27 +15,28 @@ type NonceResponse struct {
|
||||
|
||||
// Bytes returns serialized form of the nonce response.
|
||||
func (r *NonceResponse) Bytes() []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
buf := bytes.Buffer{}
|
||||
|
||||
buf.Write(r.Type) // nolint: gosec
|
||||
buf.Write(r.KeySelector) // nolint: gosec
|
||||
buf.Write(r.Crypto) // nolint: gosec
|
||||
buf.Write(r.CryptoTS) // nolint: gosec
|
||||
buf.Write(r.Nonce) // nolint: gosec
|
||||
buf.Write(r.Type)
|
||||
buf.Write(r.KeySelector)
|
||||
buf.Write(r.Crypto)
|
||||
buf.Write(r.CryptoTS)
|
||||
buf.Write(r.Nonce)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Valid checks that nonce response compliments nonce request.
|
||||
func (r *NonceResponse) Valid(req *NonceRequest) error {
|
||||
if !bytes.Equal(r.Type, TagNonce) {
|
||||
return errors.New("Unexpected RPC type")
|
||||
return errors.New("unexpected RPC type")
|
||||
}
|
||||
|
||||
if !bytes.Equal(r.Crypto, NonceCryptoAES) {
|
||||
return errors.New("Unexpected crypto type")
|
||||
return errors.New("unexpected crypto type")
|
||||
}
|
||||
|
||||
if !bytes.Equal(r.KeySelector, req.KeySelector) {
|
||||
return errors.New("Unexpected key selector")
|
||||
return errors.New("unexpected key selector")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -44,8 +44,8 @@ func (r *NonceResponse) Valid(req *NonceRequest) error {
|
||||
|
||||
// NewNonceResponse build new nonce response based on the given data.
|
||||
func NewNonceResponse(data []byte) (*NonceResponse, error) {
|
||||
if len(data) != 32 {
|
||||
return nil, errors.New("Unexpected message length")
|
||||
if len(data) != 32 { //nolint: gomnd
|
||||
return nil, fmt.Errorf("unexpected message length %d", len(data))
|
||||
}
|
||||
|
||||
return &NonceResponse{
|
||||
|
||||
+29
-22
@@ -5,53 +5,60 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type proxyRequestFlags uint32
|
||||
type ProxyRequestFlags uint32
|
||||
|
||||
const (
|
||||
proxyRequestFlagsHasAdTag proxyRequestFlags = 0x8
|
||||
proxyRequestFlagsEncrypted proxyRequestFlags = 0x2
|
||||
proxyRequestFlagsMagic proxyRequestFlags = 0x1000
|
||||
proxyRequestFlagsExtMode2 proxyRequestFlags = 0x20000
|
||||
proxyRequestFlagsIntermediate proxyRequestFlags = 0x20000000
|
||||
proxyRequestFlagsAbdridged proxyRequestFlags = 0x40000000
|
||||
proxyRequestFlagsQuickAck proxyRequestFlags = 0x80000000
|
||||
proxyRequestFlagsPad proxyRequestFlags = 0x8000000
|
||||
ProxyRequestFlagsHasAdTag ProxyRequestFlags = 0x8
|
||||
ProxyRequestFlagsEncrypted ProxyRequestFlags = 0x2
|
||||
ProxyRequestFlagsMagic ProxyRequestFlags = 0x1000
|
||||
ProxyRequestFlagsExtMode2 ProxyRequestFlags = 0x20000
|
||||
ProxyRequestFlagsIntermediate ProxyRequestFlags = 0x20000000
|
||||
ProxyRequestFlagsAbdridged ProxyRequestFlags = 0x40000000
|
||||
ProxyRequestFlagsQuickAck ProxyRequestFlags = 0x80000000
|
||||
ProxyRequestFlagsPad ProxyRequestFlags = 0x8000000
|
||||
)
|
||||
|
||||
var proxyRequestFlagsEncryptedPrefix [8]byte
|
||||
var ProxyRequestFlagsEncryptedPrefix [8]byte
|
||||
|
||||
func (r proxyRequestFlags) Bytes() []byte {
|
||||
converted := make([]byte, 4)
|
||||
func (r ProxyRequestFlags) Bytes() []byte {
|
||||
converted := make([]byte, 4) //nolint: gomnd
|
||||
binary.LittleEndian.PutUint32(converted, uint32(r))
|
||||
|
||||
return converted
|
||||
}
|
||||
|
||||
func (r proxyRequestFlags) String() string {
|
||||
flags := make([]string, 0, 7)
|
||||
func (r ProxyRequestFlags) String() string {
|
||||
flags := make([]string, 0, 7) //nolint: gomnd
|
||||
|
||||
if r&proxyRequestFlagsHasAdTag != 0 {
|
||||
if r&ProxyRequestFlagsHasAdTag != 0 {
|
||||
flags = append(flags, "HAS_AD_TAG")
|
||||
}
|
||||
if r&proxyRequestFlagsEncrypted != 0 {
|
||||
|
||||
if r&ProxyRequestFlagsEncrypted != 0 {
|
||||
flags = append(flags, "ENCRYPTED")
|
||||
}
|
||||
if r&proxyRequestFlagsMagic != 0 {
|
||||
|
||||
if r&ProxyRequestFlagsMagic != 0 {
|
||||
flags = append(flags, "MAGIC")
|
||||
}
|
||||
if r&proxyRequestFlagsExtMode2 != 0 {
|
||||
|
||||
if r&ProxyRequestFlagsExtMode2 != 0 {
|
||||
flags = append(flags, "EXT_MODE_2")
|
||||
}
|
||||
if r&proxyRequestFlagsIntermediate != 0 {
|
||||
|
||||
if r&ProxyRequestFlagsIntermediate != 0 {
|
||||
flags = append(flags, "INTERMEDIATE")
|
||||
}
|
||||
if r&proxyRequestFlagsAbdridged != 0 {
|
||||
|
||||
if r&ProxyRequestFlagsAbdridged != 0 {
|
||||
flags = append(flags, "ABRIDGED")
|
||||
}
|
||||
if r&proxyRequestFlagsQuickAck != 0 {
|
||||
|
||||
if r&ProxyRequestFlagsQuickAck != 0 {
|
||||
flags = append(flags, "QUICK_ACK")
|
||||
}
|
||||
if r&proxyRequestFlagsPad != 0 {
|
||||
|
||||
if r&ProxyRequestFlagsPad != 0 {
|
||||
flags = append(flags, "PAD")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
)
|
||||
|
||||
// ProxyRequest is the data type for storing data required to compose
|
||||
// RPC_PROXY_REQ request.
|
||||
type ProxyRequest struct {
|
||||
Flags proxyRequestFlags
|
||||
ConnectionID []byte
|
||||
OurIPPort []byte
|
||||
ClientIPPort []byte
|
||||
ADTag []byte
|
||||
Options *mtproto.ConnectionOpts
|
||||
}
|
||||
|
||||
// MakeHeader makes RPC_PROXY_REQ header. We need only to append the
|
||||
// data for it.
|
||||
func (r *ProxyRequest) MakeHeader(message []byte) (*bytes.Buffer, fmt.Stringer) {
|
||||
bufferLength := len(TagProxyRequest) +
|
||||
4 + // len(flags)
|
||||
len(r.ConnectionID) +
|
||||
len(r.ClientIPPort) +
|
||||
len(r.OurIPPort) +
|
||||
len(ProxyRequestExtraSize) +
|
||||
len(ProxyRequestProxyTag) +
|
||||
1 + // len(AdTag)
|
||||
len(r.ADTag)
|
||||
bufferLength += bufferLength % 4
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
buf.Grow(bufferLength + len(message))
|
||||
|
||||
flags := r.Flags
|
||||
if r.Options.ReadHacks.QuickAck {
|
||||
flags |= proxyRequestFlagsQuickAck
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(message, proxyRequestFlagsEncryptedPrefix[:]) {
|
||||
flags |= proxyRequestFlagsEncrypted
|
||||
}
|
||||
|
||||
buf.Write(TagProxyRequest) // nolint: gosec
|
||||
buf.Write(flags.Bytes()) // nolint: gosec
|
||||
buf.Write(r.ConnectionID) // nolint: gosec
|
||||
buf.Write(r.ClientIPPort) // nolint: gosec
|
||||
buf.Write(r.OurIPPort) // nolint: gosec
|
||||
buf.Write(ProxyRequestExtraSize) // nolint: gosec
|
||||
buf.Write(ProxyRequestProxyTag) // nolint: gosec
|
||||
buf.WriteByte(byte(len(r.ADTag))) // nolint: gosec
|
||||
buf.Write(r.ADTag) // nolint: gosec
|
||||
buf.Write(make([]byte, (4-buf.Len()%4)%4)) // nolint: gosec
|
||||
|
||||
return buf, flags
|
||||
}
|
||||
|
||||
// NewProxyRequest build new ProxyRequest data structure.
|
||||
func NewProxyRequest(clientAddr, ownAddr *net.TCPAddr,
|
||||
opts *mtproto.ConnectionOpts, adTag []byte) (*ProxyRequest, error) {
|
||||
flags := proxyRequestFlagsHasAdTag | proxyRequestFlagsMagic | proxyRequestFlagsExtMode2
|
||||
|
||||
switch opts.ConnectionType {
|
||||
case mtproto.ConnectionTypeAbridged:
|
||||
flags |= proxyRequestFlagsAbdridged
|
||||
case mtproto.ConnectionTypeIntermediate:
|
||||
flags |= proxyRequestFlagsIntermediate
|
||||
case mtproto.ConnectionTypeSecure:
|
||||
flags |= proxyRequestFlagsIntermediate | proxyRequestFlagsPad
|
||||
default:
|
||||
panic("Unknown connection type")
|
||||
}
|
||||
|
||||
request := &ProxyRequest{
|
||||
Flags: flags,
|
||||
ADTag: adTag,
|
||||
Options: opts,
|
||||
ConnectionID: make([]byte, 8),
|
||||
ClientIPPort: make([]byte, 16+4),
|
||||
OurIPPort: make([]byte, 16+4),
|
||||
}
|
||||
|
||||
if _, err := rand.Read(request.ConnectionID); err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot generate connection ID")
|
||||
}
|
||||
|
||||
port := [4]byte{}
|
||||
copy(request.ClientIPPort[:16], clientAddr.IP.To16())
|
||||
binary.LittleEndian.PutUint32(port[:], uint32(clientAddr.Port))
|
||||
copy(request.ClientIPPort[16:], port[:])
|
||||
|
||||
copy(request.OurIPPort[:16], ownAddr.IP.To16())
|
||||
binary.LittleEndian.PutUint32(port[:], uint32(ownAddr.Port))
|
||||
copy(request.OurIPPort[16:], port[:])
|
||||
|
||||
return request, nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
)
|
||||
|
||||
type ProxyResponseType uint8
|
||||
|
||||
const (
|
||||
ProxyResponseTypeAns ProxyResponseType = iota
|
||||
ProxyResponseTypeSimpleAck
|
||||
ProxyResponseTypeCloseExt
|
||||
)
|
||||
|
||||
type ProxyResponse struct {
|
||||
Type ProxyResponseType
|
||||
ConnID conntypes.ConnID
|
||||
Payload conntypes.Packet
|
||||
}
|
||||
|
||||
func ParseProxyResponse(packet conntypes.Packet) (*ProxyResponse, error) {
|
||||
var response ProxyResponse
|
||||
|
||||
if len(packet) < 4 { //nolint: gomnd
|
||||
return nil, fmt.Errorf("incorrect packet length: %d", len(packet))
|
||||
}
|
||||
|
||||
tag := packet[:4]
|
||||
|
||||
switch {
|
||||
case bytes.Equal(tag, TagProxyAns):
|
||||
response.Type = ProxyResponseTypeAns
|
||||
copy(response.ConnID[:], packet[8:16])
|
||||
response.Payload = packet[16:]
|
||||
|
||||
return &response, nil
|
||||
case bytes.Equal(tag, TagSimpleAck):
|
||||
response.Type = ProxyResponseTypeSimpleAck
|
||||
copy(response.ConnID[:], packet[4:12])
|
||||
response.Payload = packet[12:]
|
||||
|
||||
return &response, nil
|
||||
case bytes.Equal(tag, TagCloseExt):
|
||||
response.Type = ProxyResponseTypeCloseExt
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown response type %x", tag)
|
||||
}
|
||||
+7
-10
@@ -1,35 +1,31 @@
|
||||
package ntp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/beevik/ntp"
|
||||
"github.com/juju/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const autoUpdatePeriod = time.Minute
|
||||
|
||||
var ntpEndpoints = []string{
|
||||
"0.pool.ntp.org",
|
||||
"1.pool.ntp.org",
|
||||
"2.pool.ntp.org",
|
||||
"3.pool.ntp.org",
|
||||
}
|
||||
|
||||
// Fetch fetches the data on time drift.
|
||||
func Fetch() (time.Duration, error) {
|
||||
url := ntpEndpoints[rand.Intn(len(ntpEndpoints))]
|
||||
url := config.C.NTPServers[rand.Intn(len(config.C.NTPServers))] //nolint: gosec
|
||||
|
||||
resp, err := ntp.Query(url)
|
||||
if err != nil {
|
||||
return 0, errors.Annotatef(err, "Cannot fetch NTP server %s", url)
|
||||
return 0, fmt.Errorf("cannot fetch NTP server %s: %w", url, err)
|
||||
}
|
||||
|
||||
offsetInt := int64(resp.ClockOffset)
|
||||
if offsetInt < 0 {
|
||||
offsetInt = -offsetInt
|
||||
}
|
||||
|
||||
offset := time.Duration(offsetInt)
|
||||
|
||||
return offset, nil
|
||||
@@ -43,6 +39,7 @@ func AutoUpdate() {
|
||||
diff, err := Fetch()
|
||||
if err != nil {
|
||||
logger.Debugw("Cannot fetch time from NTP", "error", err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package obfuscated2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/antireplay"
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
"github.com/9seconds/mtg/protocol"
|
||||
"github.com/9seconds/mtg/stats"
|
||||
"github.com/9seconds/mtg/utils"
|
||||
"github.com/9seconds/mtg/wrappers/stream"
|
||||
)
|
||||
|
||||
const clientProtocolHandshakeTimeout = 10 * time.Second
|
||||
|
||||
type ClientProtocol struct {
|
||||
connectionType conntypes.ConnectionType
|
||||
connectionProtocol conntypes.ConnectionProtocol
|
||||
dc conntypes.DC
|
||||
}
|
||||
|
||||
func (c *ClientProtocol) ConnectionType() conntypes.ConnectionType {
|
||||
return c.connectionType
|
||||
}
|
||||
|
||||
func (c *ClientProtocol) ConnectionProtocol() conntypes.ConnectionProtocol {
|
||||
return c.connectionProtocol
|
||||
}
|
||||
|
||||
func (c *ClientProtocol) DC() conntypes.DC {
|
||||
return c.dc
|
||||
}
|
||||
|
||||
func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error) {
|
||||
fm, err := c.ReadFrame(socket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot make a client handshake: %w", err)
|
||||
}
|
||||
|
||||
decHasher := sha256.New()
|
||||
decHasher.Write(fm.Key())
|
||||
decHasher.Write(config.C.Secret)
|
||||
decryptor := utils.MakeStreamCipher(decHasher.Sum(nil), fm.IV())
|
||||
|
||||
invertedFrame := fm.Invert()
|
||||
encHasher := sha256.New()
|
||||
encHasher.Write(invertedFrame.Key())
|
||||
encHasher.Write(config.C.Secret)
|
||||
encryptor := utils.MakeStreamCipher(encHasher.Sum(nil), invertedFrame.IV())
|
||||
|
||||
decryptedFrame := Frame{}
|
||||
decryptor.XORKeyStream(decryptedFrame.Bytes(), fm.Bytes())
|
||||
|
||||
magic := decryptedFrame.Magic()
|
||||
|
||||
switch {
|
||||
case bytes.Equal(magic, conntypes.ConnectionTagAbridged):
|
||||
c.connectionType = conntypes.ConnectionTypeAbridged
|
||||
case bytes.Equal(magic, conntypes.ConnectionTagIntermediate):
|
||||
c.connectionType = conntypes.ConnectionTypeIntermediate
|
||||
case bytes.Equal(magic, conntypes.ConnectionTagSecure):
|
||||
c.connectionType = conntypes.ConnectionTypeSecure
|
||||
default:
|
||||
return nil, errors.New("unknown connection type")
|
||||
}
|
||||
|
||||
c.connectionProtocol = conntypes.ConnectionProtocolIPv4
|
||||
if socket.LocalAddr().IP.To4() == nil {
|
||||
c.connectionProtocol = conntypes.ConnectionProtocolIPv6
|
||||
}
|
||||
|
||||
buf := bytes.NewReader(decryptedFrame.DC())
|
||||
if err := binary.Read(buf, binary.LittleEndian, &c.dc); err != nil {
|
||||
c.dc = conntypes.DCDefaultIdx
|
||||
}
|
||||
|
||||
replayKey := decryptedFrame.Unique()
|
||||
if antireplay.Cache.HasObfuscated2(replayKey) {
|
||||
stats.Stats.ReplayDetected()
|
||||
|
||||
return nil, errors.New("replay attack is detected")
|
||||
}
|
||||
|
||||
antireplay.Cache.AddObfuscated2(replayKey)
|
||||
|
||||
return stream.NewObfuscated2(socket, encryptor, decryptor), nil
|
||||
}
|
||||
|
||||
func (c *ClientProtocol) ReadFrame(socket conntypes.StreamReader) (Frame, error) {
|
||||
fm := Frame{}
|
||||
|
||||
if _, err := io.ReadFull(handshakeReader{socket}, fm.Bytes()); err != nil {
|
||||
return fm, fmt.Errorf("cannot extract obfuscated2 frame: %w", err)
|
||||
}
|
||||
|
||||
return fm, nil
|
||||
}
|
||||
|
||||
type handshakeReader struct {
|
||||
parent conntypes.StreamReader
|
||||
}
|
||||
|
||||
func (h handshakeReader) Read(p []byte) (int, error) {
|
||||
return h.parent.ReadTimeout(p, clientProtocolHandshakeTimeout) //nolint: wrapcheck
|
||||
}
|
||||
|
||||
func MakeClientProtocol() protocol.ClientProtocol {
|
||||
return &ClientProtocol{}
|
||||
}
|
||||
+23
-90
@@ -1,17 +1,5 @@
|
||||
package obfuscated2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
)
|
||||
|
||||
// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd]
|
||||
const (
|
||||
frameLenKey = 32
|
||||
frameLenIV = 16
|
||||
@@ -24,98 +12,43 @@ const (
|
||||
frameOffsetMagic = frameOffsetIV + frameLenMagic
|
||||
frameOffsetDC = frameOffsetMagic + frameLenDC
|
||||
|
||||
FrameLen = 64
|
||||
frameLen = 64
|
||||
)
|
||||
|
||||
// Frame represents handshake frame. Telegram sends 64 bytes of obfuscated2
|
||||
// initialization data first.
|
||||
// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
|
||||
type Frame []byte
|
||||
|
||||
// Key returns AES encryption key.
|
||||
func (f Frame) Key() []byte {
|
||||
return f[frameOffsetFirst:frameOffsetKey]
|
||||
// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd].
|
||||
type Frame struct {
|
||||
data [frameLen]byte
|
||||
}
|
||||
|
||||
// IV returns AES encryption initialization vector
|
||||
func (f Frame) IV() []byte {
|
||||
return f[frameOffsetKey:frameOffsetIV]
|
||||
func (f *Frame) Bytes() []byte {
|
||||
return f.data[:]
|
||||
}
|
||||
|
||||
// Magic returns magic bytes from last 8 bytes of frame. Telegram checks
|
||||
// for values there. If after decryption magic is not as expected,
|
||||
// connection considered as failed.
|
||||
func (f Frame) Magic() []byte {
|
||||
return f[frameOffsetIV:frameOffsetMagic]
|
||||
func (f *Frame) Key() []byte {
|
||||
return f.data[frameOffsetFirst:frameOffsetKey]
|
||||
}
|
||||
|
||||
// DC returns number of datacenter IP client wants to use.
|
||||
func (f Frame) DC() (n int16) {
|
||||
buf := bytes.NewReader(f[frameOffsetMagic:frameOffsetDC])
|
||||
if err := binary.Read(buf, binary.LittleEndian, &n); err != nil {
|
||||
n = 1
|
||||
}
|
||||
|
||||
return
|
||||
func (f *Frame) IV() []byte {
|
||||
return f.data[frameOffsetKey:frameOffsetIV]
|
||||
}
|
||||
|
||||
// ConnectionType identifies connection type of the handshake frame.
|
||||
func (f Frame) ConnectionType() (mtproto.ConnectionType, error) {
|
||||
return mtproto.ConnectionTagFromHandshake(f.Magic())
|
||||
func (f *Frame) Magic() []byte {
|
||||
return f.data[frameOffsetIV:frameOffsetMagic]
|
||||
}
|
||||
|
||||
// Invert inverts frame for extracting encryption keys. Pkease check that link:
|
||||
// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
|
||||
func (f Frame) Invert() Frame {
|
||||
reversed := make(Frame, FrameLen)
|
||||
copy(reversed, f)
|
||||
func (f *Frame) DC() []byte {
|
||||
return f.data[frameOffsetMagic:frameOffsetDC]
|
||||
}
|
||||
|
||||
func (f *Frame) Unique() []byte {
|
||||
return f.data[frameOffsetFirst:frameOffsetDC]
|
||||
}
|
||||
|
||||
func (f *Frame) Invert() Frame {
|
||||
nf := *f
|
||||
for i := 0; i < frameLenKey+frameLenIV; i++ {
|
||||
reversed[frameOffsetFirst+i] = f[frameOffsetIV-1-i]
|
||||
nf.data[frameOffsetFirst+i] = f.data[frameOffsetIV-1-i]
|
||||
}
|
||||
|
||||
return reversed
|
||||
}
|
||||
|
||||
// ExtractFrame extracts exact obfuscated2 handshake frame from given reader.
|
||||
func ExtractFrame(conn io.Reader) (Frame, error) {
|
||||
frame := make(Frame, FrameLen)
|
||||
buf := bytes.NewBuffer(frame)
|
||||
buf.Reset()
|
||||
|
||||
if _, err := io.CopyN(buf, conn, FrameLen); err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot extract obfuscated header")
|
||||
}
|
||||
copy(frame, buf.Bytes())
|
||||
|
||||
return frame, nil
|
||||
}
|
||||
|
||||
func generateFrame(connectionType mtproto.ConnectionType) Frame {
|
||||
frame := make(Frame, FrameLen)
|
||||
|
||||
for {
|
||||
if _, err := rand.Read(frame); err != nil {
|
||||
continue
|
||||
}
|
||||
if frame[0] == 0xef {
|
||||
continue
|
||||
}
|
||||
|
||||
val := (uint32(frame[3]) << 24) | (uint32(frame[2]) << 16) | (uint32(frame[1]) << 8) | uint32(frame[0])
|
||||
if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee {
|
||||
continue
|
||||
}
|
||||
|
||||
val = (uint32(frame[7]) << 24) | (uint32(frame[6]) << 16) | (uint32(frame[5]) << 8) | uint32(frame[4])
|
||||
if val == 0x00000000 {
|
||||
continue
|
||||
}
|
||||
|
||||
// error has to be checked before calling this function
|
||||
tag, _ := connectionType.Tag() // nolint: errcheck, gosec
|
||||
copy(frame.Magic(), tag)
|
||||
|
||||
return frame
|
||||
}
|
||||
return nf
|
||||
}
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
package obfuscated2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
)
|
||||
|
||||
func TestFrameKey(t *testing.T) {
|
||||
toCompare := make([]byte, 32)
|
||||
for i := 0; i < 32; i++ {
|
||||
toCompare[i] = byte(1)
|
||||
}
|
||||
|
||||
assert.Equal(t, toCompare, makeFrame().Key())
|
||||
}
|
||||
|
||||
func TestFrameIV(t *testing.T) {
|
||||
toCompare := make([]byte, 16)
|
||||
for i := 0; i < 16; i++ {
|
||||
toCompare[i] = byte(2)
|
||||
}
|
||||
|
||||
assert.Equal(t, toCompare, makeFrame().IV())
|
||||
}
|
||||
|
||||
func TestFrameMagic(t *testing.T) {
|
||||
toCompare := make([]byte, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
toCompare[i] = 0xee
|
||||
}
|
||||
|
||||
assert.Equal(t, toCompare, makeFrame().Magic())
|
||||
}
|
||||
|
||||
func TestFrameDC(t *testing.T) {
|
||||
assert.Equal(t, int16(771), makeFrame().DC())
|
||||
}
|
||||
|
||||
func TestFrameValid(t *testing.T) {
|
||||
frame := makeFrame()
|
||||
connType, err := frame.ConnectionType()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, connType, mtproto.ConnectionTypeIntermediate)
|
||||
|
||||
frame[8+32+16+2] = byte(3)
|
||||
_, err = frame.ConnectionType()
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestFrameDoubleInvert(t *testing.T) {
|
||||
frame := makeFrame()
|
||||
assert.True(t, bytes.Equal(frame, frame.Invert().Invert()))
|
||||
}
|
||||
|
||||
func TestFrameInvert(t *testing.T) {
|
||||
frame := makeFrame()
|
||||
reversed := frame.Invert()
|
||||
|
||||
assert.Exactly(t, frame[:8], reversed[:8])
|
||||
assert.Exactly(t, frame[56:], reversed[56:])
|
||||
|
||||
toCompare := make([]byte, 48)
|
||||
for i := 0; i < 48; i++ {
|
||||
toCompare[i] = frame[55-i]
|
||||
}
|
||||
assert.Equal(t, []byte(reversed[8:56]), toCompare)
|
||||
}
|
||||
|
||||
func TestFrameGenerateValid(t *testing.T) {
|
||||
validTests := []mtproto.ConnectionType{
|
||||
mtproto.ConnectionTypeIntermediate,
|
||||
mtproto.ConnectionTypeAbridged,
|
||||
}
|
||||
for _, test := range validTests {
|
||||
t.Run(strconv.Itoa(int(test)), func(tt *testing.T) {
|
||||
frame := generateFrame(test)
|
||||
conType, err := frame.ConnectionType()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, conType, test)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func makeFrame() Frame {
|
||||
f := make(Frame, FrameLen)
|
||||
|
||||
for i := 8; i < (8 + 32); i++ {
|
||||
f[i] = byte(1)
|
||||
}
|
||||
for i := (8 + 32); i < (8 + 32 + 16); i++ {
|
||||
f[i] = byte(2)
|
||||
}
|
||||
for i := (8 + 32 + 16); i < (8 + 32 + 16 + 4); i++ {
|
||||
f[i] = 0xee
|
||||
}
|
||||
for i := (8 + 32 + 16 + 4); i < (8 + 32 + 16 + 4 + 2); i++ {
|
||||
f[i] = byte(3)
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package obfuscated2
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
)
|
||||
|
||||
// Obfuscated2 contains AES CTR encryption and decryption streams
|
||||
// for telegram connection.
|
||||
type Obfuscated2 struct {
|
||||
Decryptor cipher.Stream
|
||||
Encryptor cipher.Stream
|
||||
}
|
||||
|
||||
// ParseObfuscated2ClientFrame parses client frame. Please check this link for
|
||||
// details: http://telegra.ph/telegram-blocks-wtf-05-26
|
||||
//
|
||||
// Beware, link above is in russian.
|
||||
func ParseObfuscated2ClientFrame(secret []byte, frame Frame) (*Obfuscated2, *mtproto.ConnectionOpts, error) {
|
||||
decHasher := sha256.New()
|
||||
decHasher.Write(frame.Key()) // nolint: errcheck, gosec
|
||||
decHasher.Write(secret) // nolint: errcheck, gosec
|
||||
decryptor := makeStreamCipher(decHasher.Sum(nil), frame.IV())
|
||||
|
||||
invertedFrame := frame.Invert()
|
||||
encHasher := sha256.New()
|
||||
encHasher.Write(invertedFrame.Key()) // nolint: errcheck, gosec
|
||||
encHasher.Write(secret) // nolint: errcheck, gosec
|
||||
encryptor := makeStreamCipher(encHasher.Sum(nil), invertedFrame.IV())
|
||||
|
||||
decryptedFrame := make(Frame, FrameLen)
|
||||
decryptor.XORKeyStream(decryptedFrame, frame)
|
||||
connType, err := decryptedFrame.ConnectionType()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Annotate(err, "Unknown protocol")
|
||||
}
|
||||
|
||||
obfs := &Obfuscated2{
|
||||
Decryptor: decryptor,
|
||||
Encryptor: encryptor,
|
||||
}
|
||||
connOpts := &mtproto.ConnectionOpts{
|
||||
DC: decryptedFrame.DC(),
|
||||
ConnectionType: connType,
|
||||
}
|
||||
|
||||
return obfs, connOpts, nil
|
||||
}
|
||||
|
||||
// MakeTelegramObfuscated2Frame creates new handshake frame to send to
|
||||
// Telegram.
|
||||
// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
|
||||
func MakeTelegramObfuscated2Frame(opts *mtproto.ConnectionOpts) (*Obfuscated2, Frame) {
|
||||
frame := generateFrame(opts.ConnectionType)
|
||||
|
||||
encryptor := makeStreamCipher(frame.Key(), frame.IV())
|
||||
decryptorFrame := frame.Invert()
|
||||
decryptor := makeStreamCipher(decryptorFrame.Key(), decryptorFrame.IV())
|
||||
|
||||
copyFrame := make(Frame, FrameLen)
|
||||
copy(copyFrame[:frameOffsetIV], frame[:frameOffsetIV])
|
||||
encryptor.XORKeyStream(frame, frame)
|
||||
copy(frame[:frameOffsetIV], copyFrame[:frameOffsetIV])
|
||||
|
||||
obfs := &Obfuscated2{
|
||||
Decryptor: decryptor,
|
||||
Encryptor: encryptor,
|
||||
}
|
||||
|
||||
return obfs, frame
|
||||
}
|
||||
|
||||
func makeStreamCipher(key, iv []byte) cipher.Stream {
|
||||
block, _ := aes.NewCipher(key) // nolint: gosec
|
||||
return cipher.NewCTR(block, iv)
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package obfuscated2
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
)
|
||||
|
||||
func TestObfs2TelegramFrameDecrypt(t *testing.T) {
|
||||
connOpts := &mtproto.ConnectionOpts{
|
||||
DC: 1,
|
||||
ConnectionType: mtproto.ConnectionTypeIntermediate,
|
||||
}
|
||||
_, frame := MakeTelegramObfuscated2Frame(connOpts)
|
||||
decryptor := makeStreamCipher(frame.Key(), frame.IV())
|
||||
|
||||
decrypted := make(Frame, FrameLen)
|
||||
decryptor.XORKeyStream(decrypted, frame)
|
||||
|
||||
_, err := decrypted.ConnectionType()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestObfs2TelegramDecryptEncryptDecrypt(t *testing.T) {
|
||||
connOpts := &mtproto.ConnectionOpts{
|
||||
DC: 1,
|
||||
ConnectionType: mtproto.ConnectionTypeIntermediate,
|
||||
}
|
||||
obfs2, frame := MakeTelegramObfuscated2Frame(connOpts)
|
||||
inverted := frame.Invert()
|
||||
encryptor := makeStreamCipher(inverted.Key(), inverted.IV())
|
||||
|
||||
data := []byte{1, 2, 3}
|
||||
encrypted := make([]byte, 3)
|
||||
encryptor.XORKeyStream(encrypted, data)
|
||||
decrypted := make([]byte, 3)
|
||||
obfs2.Decryptor.XORKeyStream(decrypted, encrypted)
|
||||
|
||||
assert.Equal(t, data, decrypted)
|
||||
}
|
||||
|
||||
func TestObfs2Full(t *testing.T) {
|
||||
secret := []byte{1, 2, 3, 4, 5}
|
||||
|
||||
clientFrame := generateFrame(mtproto.ConnectionTypeIntermediate)
|
||||
clientHasher := sha256.New()
|
||||
clientHasher.Write(clientFrame.Key()) // nolint: errcheck, gosec
|
||||
clientHasher.Write(secret) // nolint: errcheck, gosec
|
||||
clientKey := clientHasher.Sum(nil)
|
||||
|
||||
encryptor := makeStreamCipher(clientKey, clientFrame.IV())
|
||||
encrypted := make(Frame, FrameLen)
|
||||
encryptor.XORKeyStream(encrypted, clientFrame)
|
||||
copy(encrypted[:56], clientFrame[:56])
|
||||
|
||||
invertedClientFrame := clientFrame.Invert()
|
||||
clientHasher = sha256.New()
|
||||
clientHasher.Write(invertedClientFrame.Key()) // nolint: errcheck, gosec
|
||||
clientHasher.Write(secret) // nolint: errcheck, gosec
|
||||
invertedClientKey := clientHasher.Sum(nil)
|
||||
clientDecryptor := makeStreamCipher(invertedClientKey, invertedClientFrame.IV())
|
||||
|
||||
clientObfs, _, err := ParseObfuscated2ClientFrame(secret, encrypted)
|
||||
assert.Nil(t, err)
|
||||
|
||||
connOpts := &mtproto.ConnectionOpts{
|
||||
DC: 1,
|
||||
ConnectionType: mtproto.ConnectionTypeIntermediate,
|
||||
}
|
||||
tgObfs, tgFrame := MakeTelegramObfuscated2Frame(connOpts)
|
||||
tgDecryptor := makeStreamCipher(tgFrame.Key(), tgFrame.IV())
|
||||
decrypted := make(Frame, FrameLen)
|
||||
tgDecryptor.XORKeyStream(decrypted, tgFrame)
|
||||
_, err = decrypted.ConnectionType()
|
||||
assert.Nil(t, err)
|
||||
|
||||
tgInvertedFrame := tgFrame.Invert()
|
||||
tgEncryptor := makeStreamCipher(tgInvertedFrame.Key(), tgInvertedFrame.IV())
|
||||
|
||||
message := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
tgEncryptedMessage := make([]byte, len(message))
|
||||
tgEncryptor.XORKeyStream(tgEncryptedMessage, message)
|
||||
|
||||
tgEncDecryptedMessage := make([]byte, len(tgEncryptedMessage))
|
||||
tgObfs.Decryptor.XORKeyStream(tgEncDecryptedMessage, tgEncryptedMessage)
|
||||
assert.Equal(t, message, tgEncDecryptedMessage)
|
||||
|
||||
clientEncryptedMessage := make([]byte, len(tgEncDecryptedMessage))
|
||||
clientObfs.Encryptor.XORKeyStream(clientEncryptedMessage, tgEncDecryptedMessage)
|
||||
finalMessage := make([]byte, len(clientEncryptedMessage))
|
||||
clientDecryptor.XORKeyStream(finalMessage, clientEncryptedMessage)
|
||||
|
||||
assert.Equal(t, finalMessage, message)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package obfuscated2
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
"github.com/9seconds/mtg/protocol"
|
||||
"github.com/9seconds/mtg/telegram"
|
||||
"github.com/9seconds/mtg/utils"
|
||||
"github.com/9seconds/mtg/wrappers/stream"
|
||||
)
|
||||
|
||||
func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteCloser, error) {
|
||||
conn, err := telegram.Direct.Dial(req.ClientProtocol.DC(),
|
||||
req.ClientProtocol.ConnectionProtocol())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot dial to telegram: %w", err)
|
||||
}
|
||||
|
||||
conn = stream.NewTimeout(conn)
|
||||
conn = stream.NewCtx(req.Ctx, req.Cancel, conn)
|
||||
fm := generateFrame(req.ClientProtocol)
|
||||
data := fm.Bytes()
|
||||
|
||||
encryptor := utils.MakeStreamCipher(fm.Key(), fm.IV())
|
||||
decryptedFrame := fm.Invert()
|
||||
decryptor := utils.MakeStreamCipher(decryptedFrame.Key(), decryptedFrame.IV())
|
||||
|
||||
copyFrame := make([]byte, frameLen)
|
||||
copy(copyFrame[:frameOffsetIV], data[:frameOffsetIV])
|
||||
encryptor.XORKeyStream(data, data)
|
||||
copy(data[:frameOffsetIV], copyFrame[:frameOffsetIV])
|
||||
|
||||
if _, err := conn.Write(data); err != nil {
|
||||
return nil, fmt.Errorf("cannot write handshake frame to telegram: %w", err)
|
||||
}
|
||||
|
||||
return stream.NewObfuscated2(conn, encryptor, decryptor), nil
|
||||
}
|
||||
|
||||
func generateFrame(cp protocol.ClientProtocol) Frame {
|
||||
fm := Frame{}
|
||||
data := fm.Bytes()
|
||||
|
||||
for {
|
||||
if _, err := rand.Read(data); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if data[0] == 0xef { //nolint: gomnd
|
||||
continue
|
||||
}
|
||||
|
||||
val := (uint32(data[3]) << 24) | (uint32(data[2]) << 16) | (uint32(data[1]) << 8) | uint32(data[0]) //nolint: gomnd, lll
|
||||
if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee { //nolint: lll
|
||||
continue
|
||||
}
|
||||
|
||||
val = (uint32(data[7]) << 24) | (uint32(data[6]) << 16) | (uint32(data[5]) << 8) | uint32(data[4]) //nolint: gomnd
|
||||
if val == 0x00000000 { //nolint: gomnd
|
||||
continue
|
||||
}
|
||||
|
||||
copy(fm.Magic(), cp.ConnectionType().Tag())
|
||||
|
||||
return fm
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package protocol
|
||||
|
||||
import "github.com/9seconds/mtg/conntypes"
|
||||
|
||||
type ClientProtocol interface {
|
||||
Handshake(conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error)
|
||||
ConnectionType() conntypes.ConnectionType
|
||||
ConnectionProtocol() conntypes.ConnectionProtocol
|
||||
DC() conntypes.DC
|
||||
}
|
||||
|
||||
type ClientProtocolMaker func() ClientProtocol
|
||||
@@ -0,0 +1,17 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type TelegramRequest struct {
|
||||
Logger *zap.SugaredLogger
|
||||
ClientConn conntypes.StreamReadWriteCloser
|
||||
ConnID conntypes.ConnID
|
||||
Ctx context.Context
|
||||
Cancel context.CancelFunc
|
||||
ClientProtocol ClientProtocol
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/9seconds/mtg/obfuscated2"
|
||||
"github.com/9seconds/mtg/protocol"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const directPipeBufferSize = 1024
|
||||
|
||||
func directConnection(request *protocol.TelegramRequest) error {
|
||||
telegramConnRaw, err := obfuscated2.TelegramProtocol(request)
|
||||
if err != nil {
|
||||
return err //nolint: wrapcheck
|
||||
}
|
||||
|
||||
defer telegramConnRaw.Close()
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2) //nolint: gomnd
|
||||
|
||||
go directPipe(telegramConnRaw, request.ClientConn, wg, request.Logger)
|
||||
|
||||
go directPipe(request.ClientConn, telegramConnRaw, wg, request.Logger)
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func directPipe(dst io.WriteCloser, src io.ReadCloser, wg *sync.WaitGroup, logger *zap.SugaredLogger) {
|
||||
defer func() {
|
||||
dst.Close()
|
||||
src.Close()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
buf := [directPipeBufferSize]byte{}
|
||||
|
||||
if _, err := io.CopyBuffer(dst, src, buf[:]); err != nil {
|
||||
logger.Debugw("Cannot pump sockets", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
"github.com/9seconds/mtg/protocol"
|
||||
"github.com/9seconds/mtg/wrappers/packetack"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func middleConnection(request *protocol.TelegramRequest) {
|
||||
telegramConn, err := packetack.NewProxy(request)
|
||||
if err != nil {
|
||||
request.Logger.Debugw("Cannot dial to Telegram", "error", err)
|
||||
|
||||
return
|
||||
}
|
||||
defer telegramConn.Close()
|
||||
|
||||
var clientConn conntypes.PacketAckFullReadWriteCloser
|
||||
|
||||
switch request.ClientProtocol.ConnectionType() {
|
||||
case conntypes.ConnectionTypeAbridged:
|
||||
clientConn = packetack.NewClientAbridged(request.ClientConn)
|
||||
case conntypes.ConnectionTypeIntermediate:
|
||||
clientConn = packetack.NewClientIntermediate(request.ClientConn)
|
||||
case conntypes.ConnectionTypeSecure:
|
||||
clientConn = packetack.NewClientIntermediateSecure(request.ClientConn)
|
||||
case conntypes.ConnectionTypeUnknown:
|
||||
panic("unknown connection type")
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2) //nolint: gomnd
|
||||
|
||||
go middlePipe(telegramConn, clientConn, wg, request.Logger)
|
||||
|
||||
go middlePipe(clientConn, telegramConn, wg, request.Logger)
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func middlePipe(dst conntypes.PacketAckWriteCloser,
|
||||
src conntypes.PacketAckReadCloser,
|
||||
wg *sync.WaitGroup,
|
||||
logger *zap.SugaredLogger,
|
||||
) {
|
||||
defer func() {
|
||||
dst.Close()
|
||||
src.Close()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
acks := conntypes.ConnectionAcks{}
|
||||
|
||||
packet, err := src.Read(&acks)
|
||||
if err != nil {
|
||||
logger.Debugw("Cannot read packet", "error", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err = dst.Write(packet, &acks); err != nil {
|
||||
logger.Debugw("Cannot send packet", "error", err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
-134
@@ -2,174 +2,99 @@ package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/juju/errors"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/9seconds/mtg/client"
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
"github.com/9seconds/mtg/protocol"
|
||||
"github.com/9seconds/mtg/stats"
|
||||
"github.com/9seconds/mtg/telegram"
|
||||
"github.com/9seconds/mtg/wrappers"
|
||||
"github.com/9seconds/mtg/utils"
|
||||
"github.com/9seconds/mtg/wrappers/stream"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Proxy is a core of this program.
|
||||
type Proxy struct {
|
||||
clientInit client.Init
|
||||
tg telegram.Telegram
|
||||
conf *config.Config
|
||||
Logger *zap.SugaredLogger
|
||||
Context context.Context
|
||||
ClientProtocolMaker protocol.ClientProtocolMaker
|
||||
}
|
||||
|
||||
// Serve runs TCP proxy server.
|
||||
func (p *Proxy) Serve() error {
|
||||
lsock, err := net.Listen("tcp", p.conf.BindAddr())
|
||||
if err != nil {
|
||||
return errors.Annotate(err, "Cannot create listen socket")
|
||||
}
|
||||
func (p *Proxy) Serve(listener net.Listener) {
|
||||
doneChan := p.Context.Done()
|
||||
|
||||
for {
|
||||
if conn, err := lsock.Accept(); err != nil {
|
||||
zap.S().Errorw("Cannot allocate incoming connection", "error", err)
|
||||
} else {
|
||||
go p.accept(conn)
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
select {
|
||||
case <-doneChan:
|
||||
return
|
||||
default:
|
||||
p.Logger.Fatalw("Cannot allocate incoming connection", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
go p.accept(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Proxy) accept(conn net.Conn) {
|
||||
connID := uuid.Must(uuid.NewV4()).String()
|
||||
log := zap.S().With("connection_id", connID).Named("main")
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
defer func() {
|
||||
cancel()
|
||||
conn.Close() // nolint: errcheck, gosec
|
||||
conn.Close()
|
||||
|
||||
if err := recover(); err != nil {
|
||||
stats.NewCrash()
|
||||
log.Errorw("Crash of accept handler", "error", err)
|
||||
stats.Stats.Crash()
|
||||
p.Logger.Errorw("Crash of accept handler", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Infow("Client connected", "addr", conn.RemoteAddr())
|
||||
connID := conntypes.NewConnID()
|
||||
logger := p.Logger.With("connection_id", connID)
|
||||
|
||||
if err := utils.InitTCP(conn, config.C.ClientReadBuffer(), config.C.ClientWriteBuffer()); err != nil {
|
||||
logger.Errorw("Cannot initialize client TCP connection", "error", err)
|
||||
|
||||
clientConn, opts, err := p.clientInit(ctx, cancel, conn, connID, p.conf)
|
||||
if err != nil {
|
||||
log.Errorw("Cannot initialize client connection", "error", err)
|
||||
return
|
||||
}
|
||||
defer clientConn.(io.Closer).Close() // nolint: errcheck
|
||||
|
||||
stats.ClientConnected(opts.ConnectionType, clientConn.RemoteAddr())
|
||||
defer stats.ClientDisconnected(opts.ConnectionType, clientConn.RemoteAddr())
|
||||
ctx, cancel := context.WithCancel(p.Context)
|
||||
defer cancel()
|
||||
|
||||
serverConn, err := p.getTelegramConn(ctx, cancel, opts, connID)
|
||||
clientConn := stream.NewClientConn(conn, connID)
|
||||
clientConn = stream.NewCtx(ctx, cancel, clientConn)
|
||||
clientConn = stream.NewTimeout(clientConn)
|
||||
|
||||
defer clientConn.Close()
|
||||
|
||||
clientProtocol := p.ClientProtocolMaker()
|
||||
|
||||
clientConn, err := clientProtocol.Handshake(clientConn)
|
||||
if err != nil {
|
||||
log.Errorw("Cannot initialize server connection", "error", err)
|
||||
stats.Stats.AuthenticationFailed()
|
||||
logger.Warnw("Cannot perform client handshake", "error", err)
|
||||
|
||||
return
|
||||
}
|
||||
defer serverConn.(io.Closer).Close() // nolint: errcheck
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
serverConn.(io.Closer).Close() // nolint: gosec
|
||||
clientConn.(io.Closer).Close() // nolint: gosec
|
||||
}()
|
||||
stats.Stats.ClientConnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr())
|
||||
defer stats.Stats.ClientDisconnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr())
|
||||
logger.Infow("Client connected", "addr", conn.RemoteAddr())
|
||||
|
||||
wait := &sync.WaitGroup{}
|
||||
wait.Add(2)
|
||||
req := &protocol.TelegramRequest{
|
||||
Logger: logger,
|
||||
ClientConn: clientConn,
|
||||
ConnID: connID,
|
||||
Ctx: ctx,
|
||||
Cancel: cancel,
|
||||
ClientProtocol: clientProtocol,
|
||||
}
|
||||
|
||||
if p.conf.UseMiddleProxy() {
|
||||
clientPacket := clientConn.(wrappers.PacketReadWriteCloser)
|
||||
serverPacket := serverConn.(wrappers.PacketReadWriteCloser)
|
||||
go p.middlePipe(clientPacket, serverPacket, wait, &opts.ReadHacks)
|
||||
go p.middlePipe(serverPacket, clientPacket, wait, &opts.WriteHacks)
|
||||
err = nil
|
||||
|
||||
if config.C.MiddleProxyMode() {
|
||||
middleConnection(req)
|
||||
} else {
|
||||
clientStream := clientConn.(wrappers.StreamReadWriteCloser)
|
||||
serverStream := serverConn.(wrappers.StreamReadWriteCloser)
|
||||
go p.directPipe(clientStream, serverStream, wait, p.conf.ReadBufferSize)
|
||||
go p.directPipe(serverStream, clientStream, wait, p.conf.WriteBufferSize)
|
||||
err = directConnection(req)
|
||||
}
|
||||
|
||||
wait.Wait()
|
||||
|
||||
log.Infow("Client disconnected", "addr", conn.RemoteAddr())
|
||||
}
|
||||
|
||||
func (p *Proxy) getTelegramConn(ctx context.Context, cancel context.CancelFunc,
|
||||
opts *mtproto.ConnectionOpts, connID string) (wrappers.Wrap, error) {
|
||||
streamConn, err := p.tg.Dial(ctx, cancel, connID, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot dial to Telegram")
|
||||
}
|
||||
|
||||
packetConn, err := p.tg.Init(opts, streamConn)
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot handshake telegram")
|
||||
}
|
||||
|
||||
return packetConn, nil
|
||||
}
|
||||
|
||||
func (p *Proxy) middlePipe(src wrappers.PacketReadCloser, dst io.WriteCloser,
|
||||
wait *sync.WaitGroup, hacks *mtproto.Hacks) {
|
||||
defer func() {
|
||||
src.Close() // nolint: errcheck, gosec
|
||||
dst.Close() // nolint: errcheck, gosec
|
||||
wait.Done()
|
||||
}()
|
||||
|
||||
for {
|
||||
hacks.SimpleAck = false
|
||||
hacks.QuickAck = false
|
||||
|
||||
packet, err := src.Read()
|
||||
if err != nil {
|
||||
src.Logger().Warnw("Cannot read packet", "error", err)
|
||||
return
|
||||
}
|
||||
if _, err = dst.Write(packet); err != nil {
|
||||
src.Logger().Warnw("Cannot write packet", "error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Proxy) directPipe(src wrappers.StreamReadCloser, dst io.WriteCloser,
|
||||
wait *sync.WaitGroup, bufferSize int) {
|
||||
defer func() {
|
||||
src.Close() // nolint: errcheck, gosec
|
||||
dst.Close() // nolint: errcheck, gosec
|
||||
wait.Done()
|
||||
}()
|
||||
|
||||
buffer := make([]byte, bufferSize)
|
||||
if _, err := io.CopyBuffer(dst, src, buffer); err != nil {
|
||||
src.Logger().Warnw("Cannot pump sockets", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// NewProxy returns new proxy instance.
|
||||
func NewProxy(conf *config.Config) *Proxy {
|
||||
var clientInit client.Init
|
||||
var tg telegram.Telegram
|
||||
|
||||
if conf.UseMiddleProxy() {
|
||||
clientInit = client.MiddleInit
|
||||
tg = telegram.NewMiddleTelegram(conf)
|
||||
} else {
|
||||
clientInit = client.DirectInit
|
||||
tg = telegram.NewDirectTelegram(conf)
|
||||
}
|
||||
|
||||
return &Proxy{
|
||||
conf: conf,
|
||||
clientInit: clientInit,
|
||||
tg: tg,
|
||||
}
|
||||
logger.Infow("Client disconnected", "error", err, "addr", conn.RemoteAddr())
|
||||
}
|
||||
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -eu -o pipefail
|
||||
|
||||
IMAGE_NAME="nineseconds/mtg"
|
||||
CONTAINER_NAME="mtg"
|
||||
SECRET_PATH="$HOME/.mtg.secret"
|
||||
PROXY_PORT=444
|
||||
STAT_PORT=3129
|
||||
|
||||
[[ -e "$SECRET_PATH" ]] || (
|
||||
openssl rand -hex 16 > "$SECRET_PATH"
|
||||
chmod 0400 "$SECRET_PATH"
|
||||
)
|
||||
|
||||
docker pull "$IMAGE_NAME"
|
||||
docker ps --filter "Name=$CONTAINER_NAME" -aq | xargs -r docker rm -fv
|
||||
docker run \
|
||||
-d \
|
||||
--name "$CONTAINER_NAME" \
|
||||
--sysctl 'net.ipv4.ip_local_port_range=10000 65000' \
|
||||
--sysctl net.ipv4.tcp_congestion_control=bbr \
|
||||
--sysctl net.ipv4.tcp_fastopen=3 \
|
||||
--sysctl net.ipv4.tcp_fin_timeout=30 \
|
||||
--sysctl net.ipv4.tcp_max_syn_backlog=4096 \
|
||||
--sysctl net.ipv4.tcp_max_tw_buckets=5000 \
|
||||
--sysctl net.ipv4.tcp_mtu_probing=1 \
|
||||
--sysctl 'net.ipv4.tcp_rmem=4096 87380 67108864' \
|
||||
--sysctl net.ipv4.tcp_syncookies=1 \
|
||||
--sysctl net.ipv4.tcp_tw_reuse=1 \
|
||||
--sysctl 'net.ipv4.tcp_wmem=4096 65536 67108864' \
|
||||
--ulimit nofile=51200:51200 \
|
||||
--restart=unless-stopped \
|
||||
-p $PROXY_PORT:3128 \
|
||||
-p $STAT_PORT:3129 \
|
||||
"$IMAGE_NAME" "$(cat "$SECRET_PATH")"
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Configuration options (set by environment variables during script execution)
|
||||
# - MTG_CONFIG - directory where mtg stores its configuration
|
||||
# - MTG_IMAGENAME - a name of the docker image to use
|
||||
# - MTG_PORT - which port of the host system should be used
|
||||
# - MTG_CONTAINER - a name of the container to use
|
||||
#
|
||||
# Example:
|
||||
# export MTG_CONFIG="$HOME/mtg_config"
|
||||
# export MTG_IMAGENAME="nineseconds/mtg:latest"
|
||||
# curl -sfL --compressed https://raw.githubusercontent.com/9seconds/mtg/master/run.sh | bash
|
||||
|
||||
set -eu
|
||||
|
||||
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
export MTG_CONFIG="${MTG_CONFIG:-$XDG_CONFIG_HOME/mtg}"
|
||||
|
||||
if ! [ -x "$(command -v docker)" ]; then
|
||||
echo 'Error: docker is not installed.' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
id -Gn "$USER" | grep -qw 'docker' > /dev/null
|
||||
if [ $? -eq 0 ] || [ "$(id -u)" -eq '0' ]; then
|
||||
DOCKER_CMD="$(command -v docker)"
|
||||
else
|
||||
DOCKER_CMD="sudo $(command -v docker)"
|
||||
fi
|
||||
|
||||
mkdir -p "$MTG_CONFIG" || true
|
||||
|
||||
MTG_SECRET="$MTG_CONFIG/secret"
|
||||
MTG_ENV="$MTG_CONFIG/env"
|
||||
|
||||
if [ ! -f "$MTG_ENV" ]; then
|
||||
MTG_IMAGENAME="${MTG_IMAGENAME:-nineseconds/mtg:stable}"
|
||||
MTG_PORT="${MTG_PORT:-3128}"
|
||||
MTG_CONTAINER="${MTG_CONTAINER:-mtg}"
|
||||
|
||||
echo "MTG_IMAGENAME=$MTG_IMAGENAME" > "$MTG_ENV"
|
||||
echo "MTG_PORT=$MTG_PORT" >> "$MTG_ENV"
|
||||
echo "MTG_CONTAINER=$MTG_CONTAINER" >> "$MTG_ENV"
|
||||
fi
|
||||
|
||||
set -a
|
||||
source "$MTG_ENV"
|
||||
set +a
|
||||
|
||||
$DOCKER_CMD pull "$MTG_IMAGENAME" > /dev/null
|
||||
if [ ! -f "$MTG_SECRET" ]; then
|
||||
$DOCKER_CMD run \
|
||||
--rm \
|
||||
"$MTG_IMAGENAME" \
|
||||
generate-secret tls -c "$(openssl rand -hex 16).com" \
|
||||
> "$MTG_SECRET"
|
||||
fi
|
||||
|
||||
echo "Proxy secret is $(cat "$MTG_SECRET"). Port is $MTG_PORT."
|
||||
|
||||
$DOCKER_CMD ps --filter "Name=$MTG_CONTAINER" -aq | xargs -r $DOCKER_CMD rm -fv > /dev/null
|
||||
$DOCKER_CMD run \
|
||||
-d \
|
||||
--restart=unless-stopped \
|
||||
--name "$MTG_CONTAINER" \
|
||||
--ulimit nofile=51200:51200 \
|
||||
-p "$MTG_PORT:3128" \
|
||||
"$MTG_IMAGENAME" run "$(cat "$MTG_SECRET")" > /dev/null
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PROJECT_DIR="$(git rev-parse --show-toplevel)"
|
||||
OUTPUT_FILE="${PROJECT_DIR}/version.go"
|
||||
|
||||
cat > "$OUTPUT_FILE" <<EOF
|
||||
package main
|
||||
// autogenerated by $(basename "$0") on $(date -Ru)
|
||||
|
||||
const version = "$(git describe --long --always) ($(go version)) [$(date -Ru)]"
|
||||
EOF
|
||||
@@ -1,145 +0,0 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
)
|
||||
|
||||
const (
|
||||
crashesChanLength = 1
|
||||
connectionsChanLength = 20
|
||||
trafficChanLength = 5000
|
||||
)
|
||||
|
||||
var (
|
||||
crashesChan = make(chan struct{}, crashesChanLength)
|
||||
connectionsChan = make(chan *connectionData, connectionsChanLength)
|
||||
trafficChan = make(chan *trafficData, trafficChanLength)
|
||||
)
|
||||
|
||||
type connectionData struct {
|
||||
connectionType mtproto.ConnectionType
|
||||
connected bool
|
||||
addr *net.TCPAddr
|
||||
}
|
||||
|
||||
type trafficData struct {
|
||||
traffic int
|
||||
ingress bool
|
||||
}
|
||||
|
||||
func crashManager() {
|
||||
for range crashesChan {
|
||||
instance.mutex.RLock()
|
||||
|
||||
instance.Crashes++
|
||||
|
||||
instance.mutex.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
func connectionManager() {
|
||||
for event := range connectionsChan {
|
||||
instance.mutex.RLock()
|
||||
|
||||
isIPv4 := event.addr.IP.To4() != nil
|
||||
var inc uint32 = 1
|
||||
if !event.connected {
|
||||
inc = ^uint32(0)
|
||||
}
|
||||
|
||||
switch event.connectionType {
|
||||
case mtproto.ConnectionTypeAbridged:
|
||||
if isIPv4 {
|
||||
instance.Connections.Abridged.IPv4 += inc
|
||||
} else {
|
||||
instance.Connections.Abridged.IPv6 += inc
|
||||
}
|
||||
case mtproto.ConnectionTypeSecure:
|
||||
if isIPv4 {
|
||||
instance.Connections.Secure.IPv4 += inc
|
||||
} else {
|
||||
instance.Connections.Secure.IPv6 += inc
|
||||
}
|
||||
default:
|
||||
if isIPv4 {
|
||||
instance.Connections.Intermediate.IPv4 += inc
|
||||
} else {
|
||||
instance.Connections.Intermediate.IPv6 += inc
|
||||
}
|
||||
}
|
||||
|
||||
instance.mutex.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
func trafficManager() {
|
||||
speedChan := time.Tick(time.Second)
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-trafficChan:
|
||||
instance.mutex.RLock()
|
||||
|
||||
if event.ingress {
|
||||
instance.Traffic.Ingress += trafficValue(event.traffic)
|
||||
instance.speedCurrent.Ingress += trafficSpeedValue(event.traffic)
|
||||
} else {
|
||||
instance.Traffic.Egress += trafficValue(event.traffic)
|
||||
instance.speedCurrent.Egress += trafficSpeedValue(event.traffic)
|
||||
}
|
||||
|
||||
instance.mutex.RUnlock()
|
||||
case <-speedChan:
|
||||
instance.mutex.RLock()
|
||||
|
||||
instance.Speed.Ingress = instance.speedCurrent.Ingress
|
||||
instance.Speed.Egress = instance.speedCurrent.Egress
|
||||
instance.speedCurrent.Ingress = trafficSpeedValue(0)
|
||||
instance.speedCurrent.Egress = trafficSpeedValue(0)
|
||||
|
||||
instance.mutex.RUnlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewCrash indicates new crash.
|
||||
func NewCrash() {
|
||||
crashesChan <- struct{}{}
|
||||
}
|
||||
|
||||
// ClientConnected indicates that new client was connected.
|
||||
func ClientConnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr) {
|
||||
connectionsChan <- &connectionData{
|
||||
connectionType: connectionType,
|
||||
addr: addr,
|
||||
connected: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ClientDisconnected indicates that client was disconnected.
|
||||
func ClientDisconnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr) {
|
||||
connectionsChan <- &connectionData{
|
||||
connectionType: connectionType,
|
||||
addr: addr,
|
||||
connected: false,
|
||||
}
|
||||
}
|
||||
|
||||
// IngressTraffic accounts new ingress traffic.
|
||||
func IngressTraffic(traffic int) {
|
||||
trafficChan <- &trafficData{
|
||||
traffic: traffic,
|
||||
ingress: true,
|
||||
}
|
||||
}
|
||||
|
||||
// EgressTraffic accounts new ingress traffic.
|
||||
func EgressTraffic(traffic int) {
|
||||
trafficChan <- &trafficData{
|
||||
traffic: traffic,
|
||||
ingress: false,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
)
|
||||
|
||||
type IngressTrafficInterface interface {
|
||||
IngressTraffic(int)
|
||||
}
|
||||
|
||||
type EgressTrafficInterface interface {
|
||||
EgressTraffic(int)
|
||||
}
|
||||
|
||||
type ClientConnectedInterface interface {
|
||||
ClientConnected(conntypes.ConnectionType, *net.TCPAddr)
|
||||
}
|
||||
|
||||
type ClientDisconnectedInterface interface {
|
||||
ClientDisconnected(conntypes.ConnectionType, *net.TCPAddr)
|
||||
}
|
||||
|
||||
type TelegramConnectedInterface interface {
|
||||
TelegramConnected(conntypes.DC, *net.TCPAddr)
|
||||
}
|
||||
|
||||
type TelegramDisconnectedInterface interface {
|
||||
TelegramDisconnected(conntypes.DC, *net.TCPAddr)
|
||||
}
|
||||
|
||||
type CrashInterface interface {
|
||||
Crash()
|
||||
}
|
||||
|
||||
type ReplayDetectedInterface interface {
|
||||
ReplayDetected()
|
||||
}
|
||||
|
||||
type AuthenticationFailedInterface interface {
|
||||
AuthenticationFailed()
|
||||
}
|
||||
|
||||
type CloakedRequestInterface interface {
|
||||
CloakedRequest()
|
||||
}
|
||||
|
||||
type Interface interface {
|
||||
IngressTrafficInterface
|
||||
EgressTrafficInterface
|
||||
ClientConnectedInterface
|
||||
ClientDisconnectedInterface
|
||||
TelegramConnectedInterface
|
||||
TelegramDisconnectedInterface
|
||||
CrashInterface
|
||||
ReplayDetectedInterface
|
||||
AuthenticationFailedInterface
|
||||
CloakedRequestInterface
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
)
|
||||
|
||||
type multiStats []Interface
|
||||
|
||||
func (m multiStats) IngressTraffic(traffic int) {
|
||||
for i := range m {
|
||||
go m[i].IngressTraffic(traffic)
|
||||
}
|
||||
}
|
||||
|
||||
func (m multiStats) EgressTraffic(traffic int) {
|
||||
for i := range m {
|
||||
go m[i].EgressTraffic(traffic)
|
||||
}
|
||||
}
|
||||
|
||||
func (m multiStats) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
|
||||
for i := range m {
|
||||
go m[i].ClientConnected(connectionType, addr)
|
||||
}
|
||||
}
|
||||
|
||||
func (m multiStats) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
|
||||
for i := range m {
|
||||
go m[i].ClientDisconnected(connectionType, addr)
|
||||
}
|
||||
}
|
||||
|
||||
func (m multiStats) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) {
|
||||
for i := range m {
|
||||
go m[i].TelegramConnected(dc, addr)
|
||||
}
|
||||
}
|
||||
|
||||
func (m multiStats) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) {
|
||||
for i := range m {
|
||||
go m[i].TelegramDisconnected(dc, addr)
|
||||
}
|
||||
}
|
||||
|
||||
func (m multiStats) Crash() {
|
||||
for i := range m {
|
||||
go m[i].Crash()
|
||||
}
|
||||
}
|
||||
|
||||
func (m multiStats) ReplayDetected() {
|
||||
for i := range m {
|
||||
go m[i].ReplayDetected()
|
||||
}
|
||||
}
|
||||
|
||||
func (m multiStats) AuthenticationFailed() {
|
||||
for i := range m {
|
||||
go m[i].AuthenticationFailed()
|
||||
}
|
||||
}
|
||||
|
||||
func (m multiStats) CloakedRequest() {
|
||||
for i := range m {
|
||||
go m[i].CloakedRequest()
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
)
|
||||
|
||||
var instance *stats
|
||||
|
||||
// Start starts new statistics server.
|
||||
func Start(conf *config.Config) error {
|
||||
log := zap.S().Named("stats")
|
||||
|
||||
instance = &stats{
|
||||
URLs: conf.GetURLs(),
|
||||
Uptime: uptime(time.Now()),
|
||||
mutex: &sync.RWMutex{},
|
||||
}
|
||||
|
||||
if conf.StatsD.Enabled {
|
||||
client, err := newStatsd(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go client.run()
|
||||
}
|
||||
|
||||
go crashManager()
|
||||
go connectionManager()
|
||||
go trafficManager()
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
instance.mutex.Lock()
|
||||
first, err := json.Marshal(instance)
|
||||
instance.mutex.Unlock()
|
||||
|
||||
if err != nil {
|
||||
log.Errorw("Cannot encode json", "error", err)
|
||||
http.Error(w, "Internal server error", 500)
|
||||
return
|
||||
}
|
||||
|
||||
interm := map[string]interface{}{}
|
||||
json.Unmarshal(first, &interm) // nolint: errcheck, gosec
|
||||
|
||||
encoder := json.NewEncoder(w)
|
||||
encoder.SetEscapeHTML(false)
|
||||
encoder.SetIndent("", " ")
|
||||
if err = encoder.Encode(interm); err != nil {
|
||||
log.Errorw("Cannot encode json", "error", err)
|
||||
}
|
||||
})
|
||||
|
||||
go func() {
|
||||
if err := http.ListenAndServe(conf.StatAddr(), nil); err != nil {
|
||||
log.Fatalw("Stats server has been stopped", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
+22
-83
@@ -1,102 +1,41 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
)
|
||||
|
||||
type uptime time.Time
|
||||
var Stats Interface
|
||||
|
||||
func (u uptime) MarshalJSON() ([]byte, error) {
|
||||
duration := time.Since(time.Time(u))
|
||||
value := map[string]string{
|
||||
"seconds": strconv.Itoa(int(duration.Seconds())),
|
||||
"human": humanize.Time(time.Time(u)),
|
||||
func Init(ctx context.Context) error {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
stats := []Interface{newStatsPrometheus(mux)}
|
||||
if config.C.StatsdAddr != nil {
|
||||
stats = append(stats, newStatsStatsd())
|
||||
}
|
||||
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
type trafficValue uint64
|
||||
|
||||
func (t trafficValue) MarshalJSON() ([]byte, error) {
|
||||
tv := uint64(t)
|
||||
value := map[string]interface{}{
|
||||
"bytes": tv,
|
||||
"human": humanize.Bytes(tv),
|
||||
listener, err := net.Listen("tcp", config.C.StatsBind.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot initialize stats server: %w", err)
|
||||
}
|
||||
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
type trafficSpeedValue uint64
|
||||
|
||||
func (t trafficSpeedValue) MarshalJSON() ([]byte, error) {
|
||||
speed := uint64(t)
|
||||
value := map[string]interface{}{
|
||||
"bytes/s": speed,
|
||||
"human": fmt.Sprintf("%s/S", humanize.Bytes(speed)),
|
||||
srv := http.Server{
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
return json.Marshal(value)
|
||||
}
|
||||
go srv.Serve(listener) //nolint: errcheck
|
||||
|
||||
type connections struct {
|
||||
All connectionType `json:"all"`
|
||||
Abridged connectionType `json:"abridged"`
|
||||
Intermediate connectionType `json:"intermediate"`
|
||||
Secure connectionType `json:"secure"`
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
srv.Shutdown(context.Background()) //nolint: errcheck
|
||||
}()
|
||||
|
||||
func (c connections) MarshalJSON() ([]byte, error) {
|
||||
c.All.IPv4 = c.Abridged.IPv4 + c.Intermediate.IPv4 + c.Secure.IPv4
|
||||
c.All.IPv6 = c.Abridged.IPv6 + c.Intermediate.IPv6 + c.Secure.IPv6
|
||||
Stats = multiStats(stats)
|
||||
|
||||
value := struct {
|
||||
All connectionType `json:"all"`
|
||||
Abridged connectionType `json:"abridged"`
|
||||
Intermediate connectionType `json:"intermediate"`
|
||||
Secure connectionType `json:"secure"`
|
||||
}{
|
||||
All: c.All,
|
||||
Abridged: c.Abridged,
|
||||
Intermediate: c.Intermediate,
|
||||
Secure: c.Secure,
|
||||
}
|
||||
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
type connectionType struct {
|
||||
IPv6 uint32 `json:"ipv6"`
|
||||
IPv4 uint32 `json:"ipv4"`
|
||||
}
|
||||
|
||||
type traffic struct {
|
||||
Ingress trafficValue `json:"ingress"`
|
||||
Egress trafficValue `json:"egress"`
|
||||
}
|
||||
|
||||
type speed struct {
|
||||
Ingress trafficSpeedValue `json:"ingress"`
|
||||
Egress trafficSpeedValue `json:"egress"`
|
||||
}
|
||||
|
||||
type stats struct {
|
||||
URLs config.IPURLs `json:"urls"`
|
||||
Connections connections `json:"connections"`
|
||||
Traffic traffic `json:"traffic"`
|
||||
Speed speed `json:"speed"`
|
||||
Uptime uptime `json:"uptime"`
|
||||
Crashes uint32 `json:"crashes"`
|
||||
|
||||
speedCurrent speed
|
||||
mutex *sync.RWMutex
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
type statsPrometheus struct {
|
||||
connections *prometheus.GaugeVec
|
||||
telegramConnections *prometheus.GaugeVec
|
||||
traffic *prometheus.GaugeVec
|
||||
crashes prometheus.Counter
|
||||
replayAttacks prometheus.Counter
|
||||
authenticationFailed prometheus.Counter
|
||||
cloakedRequests prometheus.Counter
|
||||
}
|
||||
|
||||
func (s *statsPrometheus) IngressTraffic(traffic int) {
|
||||
s.traffic.WithLabelValues("ingress").Add(float64(traffic))
|
||||
}
|
||||
|
||||
func (s *statsPrometheus) EgressTraffic(traffic int) {
|
||||
s.traffic.WithLabelValues("egress").Add(float64(traffic))
|
||||
}
|
||||
|
||||
func (s *statsPrometheus) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
|
||||
s.changeConnections(connectionType, addr, 1.0)
|
||||
}
|
||||
|
||||
func (s *statsPrometheus) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
|
||||
s.changeConnections(connectionType, addr, -1.0)
|
||||
}
|
||||
|
||||
func (s *statsPrometheus) changeConnections(connectionType conntypes.ConnectionType,
|
||||
addr *net.TCPAddr,
|
||||
increment float64,
|
||||
) {
|
||||
labels := [...]string{
|
||||
"intermediate",
|
||||
"ipv4",
|
||||
}
|
||||
|
||||
switch connectionType {
|
||||
case conntypes.ConnectionTypeAbridged:
|
||||
labels[0] = "abridged"
|
||||
case conntypes.ConnectionTypeSecure:
|
||||
labels[0] = "secured"
|
||||
case conntypes.ConnectionTypeIntermediate:
|
||||
labels[0] = "intermediate"
|
||||
case conntypes.ConnectionTypeUnknown:
|
||||
panic("unknown connection type")
|
||||
}
|
||||
|
||||
if addr.IP.To4() == nil {
|
||||
labels[1] = "ipv6"
|
||||
}
|
||||
|
||||
s.connections.WithLabelValues(labels[:]...).Add(increment)
|
||||
}
|
||||
|
||||
func (s *statsPrometheus) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) {
|
||||
s.changeTelegramConnections(dc, addr, 1.0)
|
||||
}
|
||||
|
||||
func (s *statsPrometheus) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) {
|
||||
s.changeTelegramConnections(dc, addr, -1.0)
|
||||
}
|
||||
|
||||
func (s *statsPrometheus) changeTelegramConnections(dc conntypes.DC, addr *net.TCPAddr, increment float64) {
|
||||
labels := [...]string{
|
||||
strconv.Itoa(int(dc)),
|
||||
"ipv4",
|
||||
}
|
||||
|
||||
if addr.IP.To4() == nil {
|
||||
labels[1] = "ipv6"
|
||||
}
|
||||
|
||||
s.telegramConnections.WithLabelValues(labels[:]...).Add(increment)
|
||||
}
|
||||
|
||||
func (s *statsPrometheus) Crash() {
|
||||
s.crashes.Inc()
|
||||
}
|
||||
|
||||
func (s *statsPrometheus) ReplayDetected() {
|
||||
s.replayAttacks.Inc()
|
||||
}
|
||||
|
||||
func (s *statsPrometheus) AuthenticationFailed() {
|
||||
s.authenticationFailed.Inc()
|
||||
}
|
||||
|
||||
func (s *statsPrometheus) CloakedRequest() {
|
||||
s.cloakedRequests.Inc()
|
||||
}
|
||||
|
||||
func newStatsPrometheus(mux *http.ServeMux) Interface {
|
||||
registry := prometheus.NewPedanticRegistry()
|
||||
|
||||
instance := &statsPrometheus{
|
||||
connections: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: config.C.StatsNamespace,
|
||||
Name: "connections",
|
||||
Help: "Current number of client connections to the proxy.",
|
||||
}, []string{"type", "protocol"}),
|
||||
telegramConnections: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: config.C.StatsNamespace,
|
||||
Name: "telegram_connections",
|
||||
Help: "Current number of telegram connections established by this proxy.",
|
||||
}, []string{"dc", "protocol"}),
|
||||
traffic: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: config.C.StatsNamespace,
|
||||
Name: "traffic",
|
||||
Help: "Traffic passed through the proxy in bytes.",
|
||||
}, []string{"direction"}),
|
||||
crashes: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: config.C.StatsNamespace,
|
||||
Name: "crashes",
|
||||
Help: "How many crashes happened.",
|
||||
}),
|
||||
replayAttacks: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: config.C.StatsNamespace,
|
||||
Name: "replay_attacks",
|
||||
Help: "How many replay attacks were prevented.",
|
||||
}),
|
||||
authenticationFailed: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: config.C.StatsNamespace,
|
||||
Name: "authentication_failed",
|
||||
Help: "How many authentication failed events we've seen.",
|
||||
}),
|
||||
cloakedRequests: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: config.C.StatsNamespace,
|
||||
Name: "cloaked_requests",
|
||||
Help: "How many requests were proxified during cloaking.",
|
||||
}),
|
||||
}
|
||||
|
||||
registry.MustRegister(instance.connections)
|
||||
registry.MustRegister(instance.telegramConnections)
|
||||
registry.MustRegister(instance.traffic)
|
||||
registry.MustRegister(instance.crashes)
|
||||
registry.MustRegister(instance.replayAttacks)
|
||||
registry.MustRegister(instance.authenticationFailed)
|
||||
registry.MustRegister(instance.cloakedRequests)
|
||||
|
||||
handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
|
||||
mux.Handle("/", handler)
|
||||
|
||||
return instance
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
statsd "github.com/smira/go-statsd"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
tagTrafficIngress = &statsStatsdTag{
|
||||
name: "ingress",
|
||||
tag: statsd.StringTag("type", "ingress"),
|
||||
}
|
||||
tagTrafficEgress = &statsStatsdTag{
|
||||
name: "egress",
|
||||
tag: statsd.StringTag("type", "egress"),
|
||||
}
|
||||
|
||||
tagConnectionTypeAbridged = &statsStatsdTag{
|
||||
name: "abridged",
|
||||
tag: statsd.StringTag("type", "abridged"),
|
||||
}
|
||||
tagConnectionTypeIntermediate = &statsStatsdTag{
|
||||
name: "intermediate",
|
||||
tag: statsd.StringTag("type", "intermediate"),
|
||||
}
|
||||
tagConnectionTypeSecured = &statsStatsdTag{
|
||||
name: "secured",
|
||||
tag: statsd.StringTag("type", "secured"),
|
||||
}
|
||||
|
||||
tagConnectionProtocol4 = &statsStatsdTag{
|
||||
name: "ipv4",
|
||||
tag: statsd.StringTag("protocol", "ipv4"),
|
||||
}
|
||||
tagConnectionProtocol6 = &statsStatsdTag{
|
||||
name: "ipv6",
|
||||
tag: statsd.StringTag("protocol", "ipv6"),
|
||||
}
|
||||
)
|
||||
|
||||
type statsStatsdTag struct {
|
||||
tag statsd.Tag
|
||||
name string
|
||||
}
|
||||
|
||||
type statsStatsdLogger struct {
|
||||
log *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func (s statsStatsdLogger) Printf(msg string, args ...interface{}) {
|
||||
s.log.Debugw(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
type statsStatsd struct {
|
||||
seen map[string]struct{}
|
||||
seenMutex sync.RWMutex
|
||||
client *statsd.Client
|
||||
}
|
||||
|
||||
func (s *statsStatsd) IngressTraffic(traffic int) {
|
||||
s.gauge("traffic", int64(traffic), tagTrafficIngress)
|
||||
}
|
||||
|
||||
func (s *statsStatsd) EgressTraffic(traffic int) {
|
||||
s.gauge("traffic", int64(traffic), tagTrafficEgress)
|
||||
}
|
||||
|
||||
func (s *statsStatsd) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
|
||||
s.changeConnections(connectionType, addr, 1)
|
||||
}
|
||||
|
||||
func (s *statsStatsd) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
|
||||
s.changeConnections(connectionType, addr, -1)
|
||||
}
|
||||
|
||||
func (s *statsStatsd) changeConnections(connectionType conntypes.ConnectionType, addr *net.TCPAddr, increment int64) {
|
||||
tags := make([]*statsStatsdTag, 0, 2) //nolint: gomnd
|
||||
|
||||
switch connectionType {
|
||||
case conntypes.ConnectionTypeAbridged:
|
||||
tags = append(tags, tagConnectionTypeAbridged)
|
||||
case conntypes.ConnectionTypeIntermediate:
|
||||
tags = append(tags, tagConnectionTypeIntermediate)
|
||||
case conntypes.ConnectionTypeSecure:
|
||||
tags = append(tags, tagConnectionTypeSecured)
|
||||
case conntypes.ConnectionTypeUnknown:
|
||||
panic("Unknown connection type")
|
||||
}
|
||||
|
||||
if addr.IP.To4() == nil {
|
||||
tags = append(tags, tagConnectionProtocol6)
|
||||
} else {
|
||||
tags = append(tags, tagConnectionProtocol4)
|
||||
}
|
||||
|
||||
s.gauge("connections", increment, tags...)
|
||||
}
|
||||
|
||||
func (s *statsStatsd) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) {
|
||||
s.changeTelegramConnections(dc, addr, 1)
|
||||
}
|
||||
|
||||
func (s *statsStatsd) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) {
|
||||
s.changeTelegramConnections(dc, addr, -1)
|
||||
}
|
||||
|
||||
func (s *statsStatsd) changeTelegramConnections(dc conntypes.DC, addr *net.TCPAddr, increment int64) {
|
||||
tags := []*statsStatsdTag{
|
||||
{
|
||||
name: "dc" + strconv.Itoa(int(dc)),
|
||||
tag: statsd.IntTag("dc", int(dc)),
|
||||
},
|
||||
}
|
||||
|
||||
if addr.IP.To4() == nil {
|
||||
tags = append(tags, tagConnectionProtocol6)
|
||||
} else {
|
||||
tags = append(tags, tagConnectionProtocol4)
|
||||
}
|
||||
|
||||
s.gauge("telegram_connections", increment, tags...)
|
||||
}
|
||||
|
||||
func (s *statsStatsd) Crash() {
|
||||
s.gauge("crashes", 1)
|
||||
}
|
||||
|
||||
func (s *statsStatsd) ReplayDetected() {
|
||||
s.gauge("replay_attacks", 1)
|
||||
}
|
||||
|
||||
func (s *statsStatsd) AuthenticationFailed() {
|
||||
s.gauge("authentication_failed", 1)
|
||||
}
|
||||
|
||||
func (s *statsStatsd) CloakedRequest() {
|
||||
s.gauge("cloaked_requests", 1)
|
||||
}
|
||||
|
||||
func (s *statsStatsd) gauge(metric string, value int64, tags ...*statsStatsdTag) {
|
||||
key, tagList := s.prepareVals(metric, tags)
|
||||
s.initGauge(metric, key, tagList)
|
||||
s.client.GaugeDelta(metric, value, tagList...)
|
||||
}
|
||||
|
||||
func (s *statsStatsd) prepareVals(metric string, tags []*statsStatsdTag) (string, []statsd.Tag) {
|
||||
tagList := make([]statsd.Tag, len(tags))
|
||||
builder := strings.Builder{}
|
||||
builder.WriteString(metric)
|
||||
|
||||
for i, v := range tags {
|
||||
builder.WriteRune('.')
|
||||
builder.WriteString(v.name)
|
||||
tagList[i] = v.tag
|
||||
}
|
||||
|
||||
return builder.String(), tagList
|
||||
}
|
||||
|
||||
func (s *statsStatsd) initGauge(metric, key string, tags []statsd.Tag) {
|
||||
s.seenMutex.RLock()
|
||||
if _, ok := s.seen[key]; ok {
|
||||
s.seenMutex.RUnlock()
|
||||
|
||||
return
|
||||
} else { //nolint: golint,revive
|
||||
s.seenMutex.RUnlock()
|
||||
}
|
||||
|
||||
s.seenMutex.Lock()
|
||||
defer s.seenMutex.Unlock()
|
||||
|
||||
if _, ok := s.seen[key]; !ok {
|
||||
s.seen[key] = struct{}{}
|
||||
s.client.Gauge(metric, 0, tags...)
|
||||
}
|
||||
}
|
||||
|
||||
func newStatsStatsd() Interface {
|
||||
prefix := strings.TrimSuffix(config.C.StatsNamespace, ".") + "."
|
||||
logger := statsStatsdLogger{
|
||||
log: zap.S().Named("stats").Named("statsd"),
|
||||
}
|
||||
|
||||
return &statsStatsd{
|
||||
seen: make(map[string]struct{}),
|
||||
client: statsd.NewClient(config.C.StatsdAddr.String(),
|
||||
statsd.SendLoopCount(2), //nolint: gomnd
|
||||
statsd.ReconnectInterval(10*time.Second), //nolint: gomnd
|
||||
statsd.Logger(logger),
|
||||
statsd.MetricPrefix(prefix),
|
||||
statsd.TagStyle(config.C.StatsdTagsFormat),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/juju/errors"
|
||||
statsd "gopkg.in/alexcesaro/statsd.v2"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
)
|
||||
|
||||
const (
|
||||
statsdConnectionsAbridgedV4 = "connections.abridged.ipv4"
|
||||
statsdConnectionsAbridgedV6 = "connections.abridged.ipv6"
|
||||
|
||||
statsdConnectionsIntermediateV4 = "connections.intermediate.ipv4"
|
||||
statsdConnectionsIntermediateV6 = "connections.intermediate.ipv6"
|
||||
|
||||
statsdConnectionsSecureV4 = "connections.secure.ipv4"
|
||||
statsdConnectionsSecureV6 = "connections.secure.ipv6"
|
||||
|
||||
statsdTrafficIngress = "traffic.ingress"
|
||||
statsdTrafficEgress = "traffic.egress"
|
||||
|
||||
statsdSpeedIngress = "speed.ingress"
|
||||
statsdSpeedEgress = "speed.egress"
|
||||
|
||||
statsdCrashes = "crashes"
|
||||
)
|
||||
|
||||
const statsdPollTime = time.Second
|
||||
|
||||
type statsdExporter struct {
|
||||
client *statsd.Client
|
||||
}
|
||||
|
||||
func (s *statsdExporter) run() {
|
||||
for range time.Tick(statsdPollTime) {
|
||||
instance.mutex.Lock()
|
||||
|
||||
s.client.Gauge(statsdConnectionsAbridgedV4, instance.Connections.Abridged.IPv4)
|
||||
s.client.Gauge(statsdConnectionsAbridgedV6, instance.Connections.Abridged.IPv6)
|
||||
s.client.Gauge(statsdConnectionsIntermediateV4, instance.Connections.Intermediate.IPv4)
|
||||
s.client.Gauge(statsdConnectionsIntermediateV6, instance.Connections.Intermediate.IPv6)
|
||||
s.client.Gauge(statsdConnectionsSecureV4, instance.Connections.Secure.IPv4)
|
||||
s.client.Gauge(statsdConnectionsSecureV6, instance.Connections.Secure.IPv6)
|
||||
s.client.Gauge(statsdTrafficIngress, uint64(instance.Traffic.Ingress))
|
||||
s.client.Gauge(statsdTrafficEgress, uint64(instance.Traffic.Egress))
|
||||
s.client.Gauge(statsdSpeedIngress, uint64(instance.Speed.Ingress))
|
||||
s.client.Gauge(statsdSpeedEgress, uint64(instance.Speed.Egress))
|
||||
s.client.Gauge(statsdCrashes, instance.Crashes)
|
||||
|
||||
instance.mutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func newStatsd(conf *config.Config) (*statsdExporter, error) {
|
||||
options := []statsd.Option{
|
||||
statsd.Network(conf.StatsD.Addr.Network()),
|
||||
statsd.Address(conf.StatsD.Addr.String()),
|
||||
statsd.Prefix(conf.StatsD.Prefix),
|
||||
}
|
||||
|
||||
if conf.StatsD.TagsFormat > 0 {
|
||||
options = append(options, statsd.TagsFormat(conf.StatsD.TagsFormat))
|
||||
tags := make([]string, len(conf.StatsD.Tags)*2)
|
||||
for k, v := range conf.StatsD.Tags {
|
||||
tags = append(tags, k, v)
|
||||
}
|
||||
options = append(options, statsd.Tags(tags...))
|
||||
}
|
||||
|
||||
client, err := statsd.New(options...)
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot create statsd client")
|
||||
}
|
||||
|
||||
return &statsdExporter{client: client}, nil
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
)
|
||||
|
||||
const (
|
||||
addressesURLV4 = "https://core.telegram.org/getProxyConfig" //nolint: gas
|
||||
addressesURLV6 = "https://core.telegram.org/getProxyConfigV6" //nolint: gas
|
||||
)
|
||||
|
||||
var addressesProxyForSplitter = regexp.MustCompile(`\s+`)
|
||||
|
||||
func AddressesV4() (map[conntypes.DC][]string, conntypes.DC, error) {
|
||||
return getAddresses(addressesURLV4)
|
||||
}
|
||||
|
||||
func AddressesV6() (map[conntypes.DC][]string, conntypes.DC, error) {
|
||||
return getAddresses(addressesURLV6)
|
||||
}
|
||||
|
||||
func getAddresses(url string) (map[conntypes.DC][]string, conntypes.DC, error) {
|
||||
resp, err := request(url)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("cannot get http response: %w", err)
|
||||
}
|
||||
|
||||
defer resp.Close()
|
||||
|
||||
scanner := bufio.NewScanner(resp)
|
||||
data := map[conntypes.DC][]string{}
|
||||
defaultDC := conntypes.DCDefaultIdx
|
||||
|
||||
for scanner.Scan() {
|
||||
text := strings.TrimSpace(scanner.Text())
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(text, "#"):
|
||||
continue
|
||||
case strings.HasPrefix(text, "proxy_for"):
|
||||
addr, idx, err := addressesParseProxyFor(text)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("cannot parse 'proxy_for' section: %w", err)
|
||||
}
|
||||
|
||||
if addresses, ok := data[idx]; ok {
|
||||
data[idx] = append(addresses, addr)
|
||||
} else {
|
||||
data[idx] = []string{addr}
|
||||
}
|
||||
case strings.HasPrefix(text, "default"):
|
||||
idx, err := addressesParseDefault(text)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("cannot parse 'default' section: %w", err)
|
||||
}
|
||||
|
||||
defaultDC = idx
|
||||
}
|
||||
}
|
||||
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("cannot parse http response: %w", err)
|
||||
}
|
||||
|
||||
return data, defaultDC, nil
|
||||
}
|
||||
|
||||
func addressesParseProxyFor(text string) (string, conntypes.DC, error) {
|
||||
chunks := addressesProxyForSplitter.Split(text, 3) //nolint: gomnd
|
||||
if len(chunks) != 3 || chunks[0] != "proxy_for" {
|
||||
return "", 0, fmt.Errorf("incorrect config %s", text)
|
||||
}
|
||||
|
||||
dc, err := strconv.ParseInt(chunks[1], 10, 16) //nolint: gomnd
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("incorrect config '%s': %w", text, err)
|
||||
}
|
||||
|
||||
addr := strings.TrimRight(chunks[2], ";")
|
||||
if _, _, err = net.SplitHostPort(addr); err != nil {
|
||||
return "", 0, fmt.Errorf("incorrect config '%s': %w", text, err)
|
||||
}
|
||||
|
||||
return addr, conntypes.DC(dc), nil
|
||||
}
|
||||
|
||||
func addressesParseDefault(text string) (conntypes.DC, error) {
|
||||
chunks := addressesProxyForSplitter.Split(text, 2) //nolint: gomnd
|
||||
if len(chunks) != 2 || chunks[0] != "default" {
|
||||
return 0, fmt.Errorf("incorrect config '%s'", text)
|
||||
}
|
||||
|
||||
dcString := strings.TrimRight(chunks[1], ";")
|
||||
|
||||
dc, err := strconv.ParseInt(dcString, 10, 16) //nolint: gomnd
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("incorrect config '%s': %w", text, err)
|
||||
}
|
||||
|
||||
return conntypes.DC(dc), nil
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
apiUserAgent = "github.com/9seconds/mtg"
|
||||
apiHTTPTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
var httpClient = http.Client{
|
||||
Timeout: apiHTTPTimeout,
|
||||
}
|
||||
|
||||
func request(url string) (io.ReadCloser, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), apiHTTPTimeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "text/plan")
|
||||
req.Header.Set("User-Agent", apiUserAgent)
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
io.Copy(io.Discard, resp.Body) //nolint: errcheck
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("cannot perform a request: %w", err)
|
||||
}
|
||||
|
||||
return resp.Body, err //nolint: wrapcheck
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const secretURL = "https://core.telegram.org/getProxySecret" //nolint: gas
|
||||
|
||||
func Secret() ([]byte, error) {
|
||||
resp, err := request(secretURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot access telegram server: %w", err)
|
||||
}
|
||||
|
||||
defer resp.Close()
|
||||
|
||||
secret, err := io.ReadAll(resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read response: %w", err)
|
||||
}
|
||||
|
||||
return secret, nil
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
"github.com/9seconds/mtg/utils"
|
||||
"github.com/9seconds/mtg/wrappers/stream"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type baseTelegram struct {
|
||||
dialer net.Dialer
|
||||
logger *zap.SugaredLogger
|
||||
|
||||
secret []byte
|
||||
v4DefaultDC conntypes.DC
|
||||
v6DefaultDC conntypes.DC
|
||||
v4Addresses map[conntypes.DC][]string
|
||||
v6Addresses map[conntypes.DC][]string
|
||||
}
|
||||
|
||||
func (b *baseTelegram) Secret() []byte {
|
||||
return b.secret
|
||||
}
|
||||
|
||||
func (b *baseTelegram) dial(dc conntypes.DC,
|
||||
protocol conntypes.ConnectionProtocol,
|
||||
) (conntypes.StreamReadWriteCloser, error) {
|
||||
for _, addr := range b.getAddresses(dc, protocol) {
|
||||
conn, err := b.dialer.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
b.logger.Infow("Cannot dial to Telegram", "address", addr, "error", err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := utils.InitTCP(conn, config.C.ProxyReadBuffer(), config.C.ProxyWriteBuffer()); err != nil {
|
||||
b.logger.Infow("Cannot initialize TCP socket", "address", addr, "error", err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
return stream.NewTelegramConn(dc, conn), nil
|
||||
}
|
||||
|
||||
return nil, errors.New("cannot dial to the chosen DC")
|
||||
}
|
||||
|
||||
func (b *baseTelegram) getAddresses(dc conntypes.DC, protocol conntypes.ConnectionProtocol) []string {
|
||||
addresses := make([]string, 0, 2) //nolint: gomnd
|
||||
protos := []conntypes.ConnectionProtocol{
|
||||
conntypes.ConnectionProtocolIPv6,
|
||||
conntypes.ConnectionProtocolIPv4,
|
||||
}
|
||||
|
||||
if config.C.PreferIP == config.PreferIPv4 {
|
||||
protos[0], protos[1] = protos[1], protos[0]
|
||||
}
|
||||
|
||||
for _, proto := range protos {
|
||||
switch {
|
||||
case proto&protocol == 0:
|
||||
case proto&conntypes.ConnectionProtocolIPv6 != 0:
|
||||
addresses = append(addresses, b.chooseAddress(b.v6Addresses, dc, b.v6DefaultDC))
|
||||
case proto&conntypes.ConnectionProtocolIPv4 != 0:
|
||||
addresses = append(addresses, b.chooseAddress(b.v4Addresses, dc, b.v4DefaultDC))
|
||||
}
|
||||
}
|
||||
|
||||
return addresses
|
||||
}
|
||||
|
||||
func (b *baseTelegram) chooseAddress(addresses map[conntypes.DC][]string,
|
||||
dc, defaultDC conntypes.DC,
|
||||
) string {
|
||||
addrs, ok := addresses[dc]
|
||||
if !ok {
|
||||
addrs = addresses[defaultDC]
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(addrs) == 1:
|
||||
return addrs[0]
|
||||
case len(addrs) > 1:
|
||||
return addrs[rand.Intn(len(addrs))] //nolint: gosec
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/wrappers"
|
||||
)
|
||||
|
||||
const telegramDialTimeout = 10 * time.Second
|
||||
|
||||
type tgDialer struct {
|
||||
net.Dialer
|
||||
|
||||
conf *config.Config
|
||||
}
|
||||
|
||||
func (t *tgDialer) dial(addr string) (net.Conn, error) {
|
||||
conn, err := t.Dialer.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot connect to Telegram")
|
||||
}
|
||||
|
||||
tcpSocket := conn.(*net.TCPConn)
|
||||
if err = tcpSocket.SetNoDelay(true); err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot set NO_DELAY to Telegram")
|
||||
}
|
||||
if err = tcpSocket.SetReadBuffer(t.conf.WriteBufferSize); err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot set read buffer size on telegram socket")
|
||||
}
|
||||
if err = tcpSocket.SetWriteBuffer(t.conf.ReadBufferSize); err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot set write buffer size on telegram socket")
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (t *tgDialer) dialRWC(ctx context.Context, cancel context.CancelFunc,
|
||||
addr, connID string) (wrappers.StreamReadWriteCloser, error) {
|
||||
conn, err := t.dial(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tgConn := wrappers.NewConn(ctx, cancel, conn, connID,
|
||||
wrappers.ConnPurposeTelegram, t.conf.PublicIPv4, t.conf.PublicIPv6)
|
||||
|
||||
return tgConn, nil
|
||||
}
|
||||
+13
-50
@@ -1,31 +1,21 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
"github.com/9seconds/mtg/obfuscated2"
|
||||
"github.com/9seconds/mtg/wrappers"
|
||||
)
|
||||
import "github.com/9seconds/mtg/conntypes"
|
||||
|
||||
const (
|
||||
directV4DefaultIdx = 1
|
||||
directV6DefaultIdx = 1
|
||||
directV4DefaultIdx conntypes.DC = 1
|
||||
directV6DefaultIdx conntypes.DC = 1
|
||||
)
|
||||
|
||||
var (
|
||||
directV4Addresses = map[int16][]string{
|
||||
directV4Addresses = map[conntypes.DC][]string{
|
||||
0: {"149.154.175.50:443"},
|
||||
1: {"149.154.167.51:443"},
|
||||
2: {"149.154.175.100:443"},
|
||||
3: {"149.154.167.91:443"},
|
||||
4: {"149.154.171.5:443"},
|
||||
}
|
||||
directV6Addresses = map[int16][]string{
|
||||
directV6Addresses = map[conntypes.DC][]string{
|
||||
0: {"[2001:b28:f23d:f001::a]:443"},
|
||||
1: {"[2001:67c:04e8:f002::a]:443"},
|
||||
2: {"[2001:b28:f23d:f003::a]:443"},
|
||||
@@ -38,42 +28,15 @@ type directTelegram struct {
|
||||
baseTelegram
|
||||
}
|
||||
|
||||
func (t *directTelegram) Dial(ctx context.Context, cancel context.CancelFunc,
|
||||
connID string, connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) {
|
||||
dc := connOpts.DC
|
||||
if dc < 0 {
|
||||
func (d *directTelegram) Dial(dc conntypes.DC,
|
||||
protocol conntypes.ConnectionProtocol,
|
||||
) (conntypes.StreamReadWriteCloser, error) {
|
||||
switch {
|
||||
case dc < 0:
|
||||
dc = -dc
|
||||
} else if dc == 0 {
|
||||
dc = 1
|
||||
case dc == 0:
|
||||
dc = conntypes.DCDefaultIdx
|
||||
}
|
||||
|
||||
return t.baseTelegram.dial(ctx, cancel, dc-1, connID, connOpts.ConnectionProto)
|
||||
}
|
||||
|
||||
func (t *directTelegram) Init(connOpts *mtproto.ConnectionOpts,
|
||||
conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) {
|
||||
obfs2, frame := obfuscated2.MakeTelegramObfuscated2Frame(connOpts)
|
||||
|
||||
if _, err := conn.Write(frame); err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot write hadnshake frame")
|
||||
}
|
||||
|
||||
return wrappers.NewStreamCipher(conn, obfs2.Encryptor, obfs2.Decryptor), nil
|
||||
}
|
||||
|
||||
// NewDirectTelegram returns Telegram instance which connects directly
|
||||
// to Telegram bypassing middleproxies.
|
||||
func NewDirectTelegram(conf *config.Config) Telegram {
|
||||
return &directTelegram{
|
||||
baseTelegram: baseTelegram{
|
||||
dialer: tgDialer{
|
||||
Dialer: net.Dialer{Timeout: telegramDialTimeout},
|
||||
conf: conf,
|
||||
},
|
||||
v4DefaultIdx: directV4DefaultIdx,
|
||||
v6DefaultIdx: directV6DefaultIdx,
|
||||
v4Addresses: directV4Addresses,
|
||||
v6Addresses: directV6Addresses,
|
||||
},
|
||||
}
|
||||
return d.baseTelegram.dial(dc-1, conntypes.ConnectionProtocolAny)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const telegramDialTimeout = 10 * time.Second
|
||||
|
||||
var (
|
||||
Direct Telegram
|
||||
Middle Telegram
|
||||
|
||||
initOnce sync.Once
|
||||
)
|
||||
|
||||
func Init() {
|
||||
initOnce.Do(func() {
|
||||
logger := zap.S().Named("telegram")
|
||||
|
||||
Direct = &directTelegram{
|
||||
baseTelegram: baseTelegram{
|
||||
dialer: net.Dialer{Timeout: telegramDialTimeout},
|
||||
logger: logger.Named("direct"),
|
||||
v4DefaultDC: directV4DefaultIdx,
|
||||
v6DefaultDC: directV6DefaultIdx,
|
||||
v4Addresses: directV4Addresses,
|
||||
v6Addresses: directV6Addresses,
|
||||
},
|
||||
}
|
||||
|
||||
tg := &middleTelegram{
|
||||
baseTelegram: baseTelegram{
|
||||
dialer: net.Dialer{Timeout: telegramDialTimeout},
|
||||
logger: logger.Named("middle"),
|
||||
},
|
||||
}
|
||||
if err := tg.update(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go tg.backgroundUpdate()
|
||||
|
||||
Middle = tg
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package telegram
|
||||
|
||||
import "github.com/9seconds/mtg/conntypes"
|
||||
|
||||
type Telegram interface {
|
||||
Dial(conntypes.DC, conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error)
|
||||
Secret() []byte
|
||||
}
|
||||
+49
-112
@@ -1,139 +1,76 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
"github.com/9seconds/mtg/mtproto/rpc"
|
||||
"github.com/9seconds/mtg/wrappers"
|
||||
"github.com/9seconds/mtg/conntypes"
|
||||
"github.com/9seconds/mtg/telegram/api"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const middleTelegramBackgroundUpdateEvery = time.Hour
|
||||
|
||||
type middleTelegram struct {
|
||||
middleTelegramCaller
|
||||
baseTelegram
|
||||
|
||||
conf *config.Config
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (t *middleTelegram) Init(connOpts *mtproto.ConnectionOpts,
|
||||
conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) {
|
||||
rpcNonceConn := wrappers.NewMTProtoFrame(conn, rpc.SeqNoNonce)
|
||||
func (m *middleTelegram) Secret() []byte {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
rpcNonceReq, err := t.sendRPCNonceRequest(rpcNonceConn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rpcNonceResp, err := t.receiveRPCNonceResponse(rpcNonceConn, rpcNonceReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secureConn := wrappers.NewMiddleProxyCipher(conn, rpcNonceReq, rpcNonceResp, t.proxySecret)
|
||||
frameConn := wrappers.NewMTProtoFrame(secureConn, rpc.SeqNoHandshake)
|
||||
|
||||
rpcHandshakeReq, err := t.sendRPCHandshakeRequest(frameConn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = t.receiveRPCHandshakeResponse(frameConn, rpcHandshakeReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxyConn, err := wrappers.NewMTProtoProxy(frameConn, connOpts, t.conf.AdTag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proxyConn.Logger().Infow("Telegram connection initialized")
|
||||
|
||||
return proxyConn, nil
|
||||
return m.baseTelegram.Secret()
|
||||
}
|
||||
|
||||
func (t *middleTelegram) sendRPCNonceRequest(conn io.Writer) (*rpc.NonceRequest, error) {
|
||||
rpcNonceReq, err := rpc.NewNonceRequest(t.proxySecret)
|
||||
func (m *middleTelegram) update() error {
|
||||
secret, err := api.Secret()
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot create RPC nonce request")
|
||||
}
|
||||
if _, err = conn.Write(rpcNonceReq.Bytes()); err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot send RPC nonce request")
|
||||
return fmt.Errorf("cannot fetch secret: %w", err)
|
||||
}
|
||||
|
||||
return rpcNonceReq, nil
|
||||
v4Addresses, v4DefaultDC, err := api.AddressesV4()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot fetch addresses for ipv4: %w", err)
|
||||
}
|
||||
|
||||
v6Addresses, v6DefaultDC, err := api.AddressesV6()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot fetch addresses for ipv6: %w", err)
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
m.secret = secret
|
||||
m.v4DefaultDC = v4DefaultDC
|
||||
m.v6DefaultDC = v6DefaultDC
|
||||
m.v4Addresses = v4Addresses
|
||||
m.v6Addresses = v6Addresses
|
||||
m.mutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *middleTelegram) receiveRPCNonceResponse(conn wrappers.PacketReader,
|
||||
req *rpc.NonceRequest) (*rpc.NonceResponse, error) {
|
||||
packet, err := conn.Read()
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot read RPC nonce response")
|
||||
}
|
||||
func (m *middleTelegram) backgroundUpdate() {
|
||||
logger := zap.S().Named("telegram")
|
||||
|
||||
rpcNonceResp, err := rpc.NewNonceResponse(packet)
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot initialize RPC nonce response")
|
||||
for range time.Tick(middleTelegramBackgroundUpdateEvery) {
|
||||
if err := m.update(); err != nil {
|
||||
logger.Warnw("Cannot update Telegram proxies", "error", err)
|
||||
}
|
||||
}
|
||||
if err = rpcNonceResp.Valid(req); err != nil {
|
||||
return nil, errors.Annotate(err, "Invalid RPC nonce response")
|
||||
}
|
||||
|
||||
return rpcNonceResp, nil
|
||||
}
|
||||
|
||||
func (t *middleTelegram) sendRPCHandshakeRequest(conn io.Writer) (*rpc.HandshakeRequest, error) {
|
||||
req := rpc.NewHandshakeRequest()
|
||||
if _, err := conn.Write(req.Bytes()); err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot send RPC handshake request")
|
||||
func (m *middleTelegram) Dial(dc conntypes.DC,
|
||||
protocol conntypes.ConnectionProtocol,
|
||||
) (conntypes.StreamReadWriteCloser, error) {
|
||||
if dc == 0 {
|
||||
dc = conntypes.DCDefaultIdx
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
func (t *middleTelegram) receiveRPCHandshakeResponse(conn wrappers.PacketReader,
|
||||
req *rpc.HandshakeRequest) (*rpc.HandshakeResponse, error) {
|
||||
packet, err := conn.Read()
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot read RPC handshake response")
|
||||
}
|
||||
|
||||
rpcHandshakeResp, err := rpc.NewHandshakeResponse(packet)
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot initialize RPC handshake response")
|
||||
}
|
||||
if err = rpcHandshakeResp.Valid(req); err != nil {
|
||||
return nil, errors.Annotate(err, "Invalid RPC handshake response")
|
||||
}
|
||||
|
||||
return rpcHandshakeResp, nil
|
||||
}
|
||||
|
||||
// NewMiddleTelegram creates new instance of Telegram which works with
|
||||
// middle proxies.
|
||||
func NewMiddleTelegram(conf *config.Config) Telegram {
|
||||
tg := &middleTelegram{
|
||||
middleTelegramCaller: middleTelegramCaller{
|
||||
baseTelegram: baseTelegram{
|
||||
dialer: tgDialer{
|
||||
Dialer: net.Dialer{Timeout: telegramDialTimeout},
|
||||
conf: conf,
|
||||
},
|
||||
},
|
||||
httpClient: &http.Client{
|
||||
Timeout: middleTelegramHTTPClientTimeout,
|
||||
},
|
||||
dialerMutex: &sync.RWMutex{},
|
||||
},
|
||||
conf: conf,
|
||||
}
|
||||
|
||||
if err := tg.update(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go tg.autoUpdate()
|
||||
|
||||
return tg
|
||||
return m.baseTelegram.dial(dc, protocol)
|
||||
}
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/juju/errors"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
"github.com/9seconds/mtg/wrappers"
|
||||
)
|
||||
|
||||
const (
|
||||
middleTelegramAutoUpdateInterval = 6 * time.Hour
|
||||
middleTelegramHTTPClientTimeout = 30 * time.Second
|
||||
|
||||
tgAddrProxySecret = "https://core.telegram.org/getProxySecret" // nolint: gas
|
||||
tgAddrProxyV4 = "https://core.telegram.org/getProxyConfig" // nolint: gas
|
||||
tgAddrProxyV6 = "https://core.telegram.org/getProxyConfigV6" // nolint: gas
|
||||
tgUserAgent = "mtg"
|
||||
)
|
||||
|
||||
var middleTelegramProxyConfigSplitter = regexp.MustCompile(`\s+`)
|
||||
|
||||
type middleTelegramCaller struct {
|
||||
baseTelegram
|
||||
|
||||
proxySecret []byte
|
||||
dialerMutex *sync.RWMutex
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func (t *middleTelegramCaller) Dial(ctx context.Context, cancel context.CancelFunc, connID string,
|
||||
connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) {
|
||||
dc := connOpts.DC
|
||||
if dc == 0 {
|
||||
dc = 1
|
||||
}
|
||||
t.dialerMutex.RLock()
|
||||
defer t.dialerMutex.RUnlock()
|
||||
|
||||
return t.baseTelegram.dial(ctx, cancel, dc, connID, connOpts.ConnectionProto)
|
||||
}
|
||||
|
||||
func (t *middleTelegramCaller) autoUpdate() {
|
||||
for range time.Tick(middleTelegramAutoUpdateInterval) {
|
||||
if err := t.update(); err != nil {
|
||||
zap.S().Warnw("Cannot update from Telegram", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *middleTelegramCaller) update() error {
|
||||
secret, err := t.getTelegramProxySecret()
|
||||
if err != nil {
|
||||
return errors.Annotate(err, "Cannot get proxy secret")
|
||||
}
|
||||
|
||||
v4Addresses, v4DefaultIdx, err := t.getTelegramAddresses(tgAddrProxyV4)
|
||||
if err != nil {
|
||||
return errors.Annotate(err, "Cannot get ipv4 addresses")
|
||||
}
|
||||
|
||||
v6Addresses, v6DefaultIdx, err := t.getTelegramAddresses(tgAddrProxyV6)
|
||||
if err != nil {
|
||||
return errors.Annotate(err, "Cannot get ipv6 addresses")
|
||||
}
|
||||
|
||||
t.dialerMutex.Lock()
|
||||
t.proxySecret = secret
|
||||
t.v4DefaultIdx = v4DefaultIdx
|
||||
t.v6DefaultIdx = v6DefaultIdx
|
||||
t.v4Addresses = v4Addresses
|
||||
t.v6Addresses = v6Addresses
|
||||
t.dialerMutex.Unlock()
|
||||
|
||||
zap.S().Infow("Telegram middle proxy data has been updated")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *middleTelegramCaller) getTelegramProxySecret() ([]byte, error) {
|
||||
resp, err := t.call(tgAddrProxySecret)
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot access telegram server")
|
||||
}
|
||||
defer resp.Body.Close() // nolint: errcheck
|
||||
|
||||
secret, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot read response")
|
||||
}
|
||||
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
func (t *middleTelegramCaller) getTelegramAddresses(url string) (map[int16][]string, int16, error) { // nolint: gocyclo
|
||||
resp, err := t.call(url)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Annotate(err, "Cannot access telegram server")
|
||||
}
|
||||
defer resp.Body.Close() // nolint: errcheck
|
||||
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
data := map[int16][]string{}
|
||||
|
||||
var defaultIdx int16 = 1
|
||||
for scanner.Scan() {
|
||||
text := strings.TrimSpace(scanner.Text())
|
||||
switch {
|
||||
case strings.HasPrefix(text, "#"):
|
||||
continue
|
||||
case strings.HasPrefix(text, "proxy_for"):
|
||||
addr, idx, err2 := t.parseProxyFor(text)
|
||||
if err2 != nil {
|
||||
return nil, 0, errors.Annotate(err2, "Cannot parse 'proxy_for' section")
|
||||
}
|
||||
if addresses, ok := data[idx]; ok {
|
||||
data[idx] = append(addresses, addr)
|
||||
} else {
|
||||
data[idx] = []string{addr}
|
||||
}
|
||||
case strings.HasPrefix(text, "default"):
|
||||
idx, err2 := t.parseDefault(text)
|
||||
if err2 != nil {
|
||||
return nil, 0, errors.Annotate(err2, "Cannot parse 'default' section")
|
||||
}
|
||||
defaultIdx = idx
|
||||
default:
|
||||
return nil, 0, errors.Errorf("Unknown config string '%s'", text)
|
||||
}
|
||||
}
|
||||
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
return nil, 0, errors.Annotate(err, "Cannot read response from the telegram")
|
||||
}
|
||||
|
||||
return data, defaultIdx, nil
|
||||
}
|
||||
|
||||
func (t *middleTelegramCaller) parseProxyFor(text string) (string, int16, error) {
|
||||
chunks := middleTelegramProxyConfigSplitter.Split(text, 3)
|
||||
if len(chunks) != 3 || chunks[0] != "proxy_for" {
|
||||
return "", 0, errors.Errorf("Incorrect config '%s'", text)
|
||||
}
|
||||
|
||||
dcIdx, err := strconv.ParseInt(chunks[1], 10, 16)
|
||||
if err != nil {
|
||||
return "", 0, errors.Annotatef(err, "Incorrect config '%s'", text)
|
||||
}
|
||||
|
||||
addr := strings.TrimRight(chunks[2], ";")
|
||||
if _, _, err = net.SplitHostPort(addr); err != nil {
|
||||
return "", 0, errors.Annotatef(err, "Incorrect config '%s'", text)
|
||||
}
|
||||
|
||||
return addr, int16(dcIdx), nil
|
||||
}
|
||||
|
||||
func (t *middleTelegramCaller) parseDefault(text string) (int16, error) {
|
||||
chunks := middleTelegramProxyConfigSplitter.Split(text, 2)
|
||||
if len(chunks) != 2 || chunks[0] != "default" {
|
||||
return 0, errors.Errorf("Incorrect config '%s'", text)
|
||||
}
|
||||
|
||||
dcIdxString := strings.TrimRight(chunks[1], ";")
|
||||
dcIdx, err := strconv.ParseInt(dcIdxString, 10, 16)
|
||||
if err != nil {
|
||||
return 0, errors.Annotatef(err, "Incorrect config '%s'", text)
|
||||
}
|
||||
|
||||
return int16(dcIdx), nil
|
||||
}
|
||||
|
||||
func (t *middleTelegramCaller) call(url string) (*http.Response, error) {
|
||||
req, _ := http.NewRequest("GET", url, nil) // nolint: gosec
|
||||
req.Header.Set("Accept", "text/plain")
|
||||
req.Header.Set("User-Agent", tgUserAgent)
|
||||
|
||||
return t.httpClient.Do(req)
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/9seconds/mtg/mtproto"
|
||||
"github.com/9seconds/mtg/wrappers"
|
||||
)
|
||||
|
||||
// Telegram is an interface for different Telegram work modes.
|
||||
type Telegram interface {
|
||||
Dial(context.Context, context.CancelFunc, string, *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error)
|
||||
Init(*mtproto.ConnectionOpts, wrappers.StreamReadWriteCloser) (wrappers.Wrap, error)
|
||||
}
|
||||
|
||||
type baseTelegram struct {
|
||||
dialer tgDialer
|
||||
|
||||
v4DefaultIdx int16
|
||||
v6DefaultIdx int16
|
||||
v4Addresses map[int16][]string
|
||||
v6Addresses map[int16][]string
|
||||
}
|
||||
|
||||
func (b *baseTelegram) dial(ctx context.Context, cancel context.CancelFunc, dcIdx int16, connID string,
|
||||
proto mtproto.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) {
|
||||
addrs := make([]string, 2)
|
||||
|
||||
if proto&mtproto.ConnectionProtocolIPv6 != 0 {
|
||||
if addr := b.chooseAddress(b.v6Addresses, dcIdx, b.v6DefaultIdx); addr != "" {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
}
|
||||
if proto&mtproto.ConnectionProtocolIPv4 != 0 {
|
||||
if addr := b.chooseAddress(b.v4Addresses, dcIdx, b.v4DefaultIdx); addr != "" {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
if conn, err := b.dialer.dialRWC(ctx, cancel, addr, connID); err == nil {
|
||||
return conn, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("Cannot connect to Telegram")
|
||||
}
|
||||
|
||||
func (b *baseTelegram) chooseAddress(addresses map[int16][]string, idx, defaultIdx int16) string {
|
||||
if addr, ok := addresses[idx]; ok {
|
||||
return b.chooseRandomAddress(addr)
|
||||
} else if addr, ok := addresses[defaultIdx]; ok {
|
||||
return b.chooseRandomAddress(addr)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *baseTelegram) chooseRandomAddress(addresses []string) string {
|
||||
if len(addresses) > 0 {
|
||||
return addresses[rand.Intn(len(addresses))]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package tlstypes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
"github.com/9seconds/mtg/utils"
|
||||
)
|
||||
|
||||
type ClientHello struct {
|
||||
Handshake
|
||||
}
|
||||
|
||||
func (c ClientHello) Digest() []byte {
|
||||
dirtyDigest := c.Random
|
||||
c.Random = [32]byte{}
|
||||
|
||||
rec := Record{
|
||||
Type: RecordTypeHandshake,
|
||||
Version: Version10,
|
||||
Data: &c,
|
||||
}
|
||||
|
||||
mac := hmac.New(sha256.New, config.C.Secret)
|
||||
rec.WriteBytes(mac)
|
||||
computedDigest := mac.Sum(nil)
|
||||
|
||||
for i := range computedDigest {
|
||||
computedDigest[i] ^= dirtyDigest[i]
|
||||
}
|
||||
|
||||
return computedDigest
|
||||
}
|
||||
|
||||
func ParseClientHello(raw []byte) (*ClientHello, error) {
|
||||
rv := &ClientHello{}
|
||||
|
||||
rv.Type = HandshakeType(raw[0])
|
||||
if rv.Type != HandshakeTypeClient {
|
||||
return nil, fmt.Errorf("incorrect handshake type %v", rv.Type)
|
||||
}
|
||||
|
||||
raw = raw[1:]
|
||||
sizeUint24 := utils.Uint24{}
|
||||
copy(sizeUint24[:], utils.ReverseBytes(raw[:3]))
|
||||
size := int(utils.FromUint24(sizeUint24))
|
||||
|
||||
raw = raw[3:]
|
||||
if len(raw) != size {
|
||||
return nil, fmt.Errorf("payload size mismatch (%d != %d)", len(raw), size)
|
||||
}
|
||||
|
||||
versionRaw := raw[:2]
|
||||
|
||||
switch {
|
||||
case bytes.Equal(versionRaw, Version13Bytes):
|
||||
rv.Version = Version13
|
||||
case bytes.Equal(versionRaw, Version12Bytes):
|
||||
rv.Version = Version12
|
||||
case bytes.Equal(versionRaw, Version11Bytes):
|
||||
rv.Version = Version11
|
||||
case bytes.Equal(versionRaw, Version10Bytes):
|
||||
rv.Version = Version10
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown protocol version %v", versionRaw)
|
||||
}
|
||||
|
||||
raw = raw[2:]
|
||||
copy(rv.Random[:], raw[:32])
|
||||
raw = raw[32:]
|
||||
|
||||
sessionIDLength := int(raw[0])
|
||||
raw = raw[1:]
|
||||
rv.SessionID = make([]byte, sessionIDLength)
|
||||
copy(rv.SessionID, raw)
|
||||
raw = raw[sessionIDLength:]
|
||||
|
||||
tail := make([]byte, len(raw))
|
||||
copy(tail, raw)
|
||||
rv.Tail = RawBytes(tail)
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package tlstypes
|
||||
|
||||
import "io"
|
||||
|
||||
type RecordType uint8
|
||||
|
||||
const (
|
||||
RecordTypeHandshake RecordType = 0x16
|
||||
RecordTypeApplicationData RecordType = 0x17
|
||||
RecordTypeChangeCipherSpec RecordType = 0x14
|
||||
)
|
||||
|
||||
type HandshakeType uint8
|
||||
|
||||
const (
|
||||
HandshakeTypeClient HandshakeType = 0x01
|
||||
HandshakeTypeServer HandshakeType = 0x02
|
||||
)
|
||||
|
||||
type CipherSuiteType uint8
|
||||
|
||||
const (
|
||||
CipherSuiteType_TLS_AES_128_GCM_SHA256 CipherSuiteType = iota //nolint: stylecheck,golint,revive,nosnakecase
|
||||
CipherSuiteType_TLS_AES_256_GCM_SHA384 //nolint: stylecheck,golint,revive,nosnakecase
|
||||
CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256 //nolint: stylecheck,golint,revive,nosnakecase
|
||||
)
|
||||
|
||||
func (c CipherSuiteType) Bytes() []byte {
|
||||
switch c {
|
||||
case CipherSuiteType_TLS_AES_128_GCM_SHA256: //nolint: nosnakecase
|
||||
return CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes //nolint: nosnakecase
|
||||
case CipherSuiteType_TLS_AES_256_GCM_SHA384: //nolint: nosnakecase
|
||||
return CipherSuiteType_TLS_AES_256_GCM_SHA384_Bytes //nolint: nosnakecase
|
||||
case CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256: //nolint: nosnakecase
|
||||
return CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256_Bytes //nolint: nosnakecase
|
||||
}
|
||||
|
||||
return CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256_Bytes //nolint: nosnakecase
|
||||
}
|
||||
|
||||
type Version uint8
|
||||
|
||||
func (v Version) Bytes() []byte {
|
||||
switch v {
|
||||
case Version13:
|
||||
return Version13Bytes
|
||||
case Version12:
|
||||
return Version12Bytes
|
||||
case Version11:
|
||||
return Version11Bytes
|
||||
case Version10, VersionUnknown:
|
||||
return Version10Bytes
|
||||
}
|
||||
|
||||
return Version10Bytes
|
||||
}
|
||||
|
||||
const (
|
||||
VersionUnknown Version = iota
|
||||
Version10
|
||||
Version11
|
||||
Version12
|
||||
Version13
|
||||
)
|
||||
|
||||
var (
|
||||
Version10Bytes = []byte{0x03, 0x01}
|
||||
Version11Bytes = []byte{0x03, 0x02}
|
||||
Version12Bytes = []byte{0x03, 0x03}
|
||||
Version13Bytes = []byte{0x03, 0x04}
|
||||
|
||||
CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes = []byte{0x13, 0x01} //nolint: stylecheck,golint,revive,nosnakecase
|
||||
CipherSuiteType_TLS_AES_256_GCM_SHA384_Bytes = []byte{0x13, 0x02} //nolint: stylecheck,golint,revive,nosnakecase
|
||||
CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256_Bytes = []byte{0x13, 0x03} //nolint: stylecheck,golint,revive,nosnakecase
|
||||
)
|
||||
|
||||
type Byter interface {
|
||||
WriteBytes(io.Writer)
|
||||
Len() int
|
||||
}
|
||||
|
||||
type RawBytes []byte
|
||||
|
||||
func (r RawBytes) WriteBytes(writer io.Writer) {
|
||||
writer.Write(r) //nolint: errcheck
|
||||
}
|
||||
|
||||
func (r RawBytes) Len() int {
|
||||
return len(r)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package tlstypes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/9seconds/mtg/utils"
|
||||
)
|
||||
|
||||
type Handshake struct {
|
||||
Type HandshakeType
|
||||
Version Version
|
||||
Random [32]byte
|
||||
SessionID []byte
|
||||
Tail Byter
|
||||
}
|
||||
|
||||
func (h *Handshake) WriteBytes(writer io.Writer) {
|
||||
packetBuf := bytes.Buffer{}
|
||||
|
||||
writer.Write([]byte{byte(h.Type)}) //nolint: errcheck
|
||||
|
||||
packetBuf.Write(h.Version.Bytes())
|
||||
packetBuf.Write(h.Random[:])
|
||||
packetBuf.WriteByte(byte(len(h.SessionID)))
|
||||
packetBuf.Write(h.SessionID)
|
||||
h.Tail.WriteBytes(&packetBuf)
|
||||
|
||||
sizeUint24 := utils.ToUint24(uint32(packetBuf.Len()))
|
||||
sizeUint24Bytes := sizeUint24[:]
|
||||
sizeUint24Bytes[0], sizeUint24Bytes[2] = sizeUint24Bytes[2], sizeUint24Bytes[0]
|
||||
|
||||
writer.Write(sizeUint24Bytes) //nolint: errcheck
|
||||
packetBuf.WriteTo(writer) //nolint: errcheck
|
||||
}
|
||||
|
||||
func (h *Handshake) Len() int {
|
||||
buf := bytes.Buffer{}
|
||||
|
||||
h.WriteBytes(&buf)
|
||||
|
||||
return buf.Len()
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package tlstypes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const recordMaxChunkSize = 16384 + 24
|
||||
|
||||
type Record struct {
|
||||
Type RecordType
|
||||
Version Version
|
||||
Data Byter
|
||||
}
|
||||
|
||||
func (r Record) WriteBytes(writer io.Writer) {
|
||||
writer.Write([]byte{byte(r.Type)}) //nolint: errcheck
|
||||
writer.Write(r.Version.Bytes()) //nolint: errcheck
|
||||
binary.Write(writer, binary.BigEndian, uint16(r.Data.Len())) //nolint: errcheck
|
||||
r.Data.WriteBytes(writer)
|
||||
}
|
||||
|
||||
func (r Record) Len() int {
|
||||
return 1 + 2 + 2 + r.Data.Len()
|
||||
}
|
||||
|
||||
func ReadRecord(reader io.Reader) (Record, error) {
|
||||
buf := [2]byte{}
|
||||
rec := Record{}
|
||||
|
||||
if _, err := io.ReadFull(reader, buf[:1]); err != nil {
|
||||
return rec, fmt.Errorf("cannot read record type: %w", err)
|
||||
}
|
||||
|
||||
rec.Type = RecordType(buf[0])
|
||||
|
||||
if _, err := io.ReadFull(reader, buf[:]); err != nil {
|
||||
return rec, fmt.Errorf("cannot read version: %w", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case bytes.Equal(buf[:], Version13Bytes):
|
||||
rec.Version = Version13
|
||||
case bytes.Equal(buf[:], Version12Bytes):
|
||||
rec.Version = Version12
|
||||
case bytes.Equal(buf[:], Version11Bytes):
|
||||
rec.Version = Version11
|
||||
case bytes.Equal(buf[:], Version10Bytes):
|
||||
rec.Version = Version10
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(reader, buf[:]); err != nil {
|
||||
return rec, fmt.Errorf("cannot read data length: %w", err)
|
||||
}
|
||||
|
||||
data := make([]byte, binary.BigEndian.Uint16(buf[:]))
|
||||
if _, err := io.ReadFull(reader, data); err != nil {
|
||||
return rec, fmt.Errorf("cannot read data: %w", err)
|
||||
}
|
||||
|
||||
rec.Data = RawBytes(data)
|
||||
|
||||
return rec, nil
|
||||
}
|
||||
|
||||
func MakeRecords(raw []byte) []Record {
|
||||
var arr []Record
|
||||
|
||||
for len(raw) > 0 {
|
||||
chunkSize := recordMaxChunkSize
|
||||
if chunkSize > len(raw) {
|
||||
chunkSize = len(raw)
|
||||
}
|
||||
|
||||
arr = append(arr, Record{
|
||||
Type: RecordTypeApplicationData,
|
||||
Version: Version12,
|
||||
Data: RawBytes(raw[:chunkSize]),
|
||||
})
|
||||
raw = raw[chunkSize:]
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package tlstypes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
mrand "math/rand"
|
||||
|
||||
"github.com/9seconds/mtg/config"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
type ServerHello struct {
|
||||
Handshake
|
||||
|
||||
clientHello *ClientHello
|
||||
}
|
||||
|
||||
func (s ServerHello) WelcomePacket() []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
s.Random = [32]byte{}
|
||||
rec := Record{
|
||||
Type: RecordTypeHandshake,
|
||||
Version: Version12,
|
||||
Data: &s,
|
||||
}
|
||||
rec.WriteBytes(buf)
|
||||
|
||||
recChangeCipher := Record{
|
||||
Type: RecordTypeChangeCipherSpec,
|
||||
Version: Version12,
|
||||
Data: RawBytes([]byte{0x01}),
|
||||
}
|
||||
recChangeCipher.WriteBytes(buf)
|
||||
|
||||
hostCert := make([]byte, 1024+mrand.Intn(3092)) //nolint: gosec, gomnd
|
||||
rand.Read(hostCert) //nolint: errcheck
|
||||
|
||||
recData := Record{
|
||||
Type: RecordTypeApplicationData,
|
||||
Version: Version12,
|
||||
Data: RawBytes(hostCert),
|
||||
}
|
||||
recData.WriteBytes(buf)
|
||||
|
||||
packet := buf.Bytes()
|
||||
|
||||
mac := hmac.New(sha256.New, config.C.Secret)
|
||||
mac.Write(s.clientHello.Random[:])
|
||||
mac.Write(packet)
|
||||
copy(packet[11:], mac.Sum(nil))
|
||||
|
||||
return packet
|
||||
}
|
||||
|
||||
func NewServerHello(clientHello *ClientHello) *ServerHello {
|
||||
rv := &ServerHello{
|
||||
clientHello: clientHello,
|
||||
}
|
||||
|
||||
rv.Type = HandshakeTypeServer
|
||||
rv.Version = Version12
|
||||
rv.SessionID = make([]byte, len(clientHello.SessionID))
|
||||
copy(rv.SessionID, clientHello.SessionID)
|
||||
|
||||
tail := bytes.NewBuffer(CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes) //nolint: nosnakecase
|
||||
tail.WriteByte(0x00) //nolint: gomnd // no compression
|
||||
makeTLSExtensions(tail)
|
||||
rv.Tail = RawBytes(tail.Bytes())
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
func makeTLSExtensions(buf io.Writer) {
|
||||
buf.Write([]byte{ //nolint: errcheck
|
||||
0x00, 0x2e, // 46 bytes of data
|
||||
0x00, 0x33, // Extension - Key Share
|
||||
0x00, 0x24, // 36 bytes
|
||||
0x00, 0x1d, // x25519 curve
|
||||
0x00, 0x20, // 32 bytes of key
|
||||
})
|
||||
|
||||
var scalar [32]byte
|
||||
|
||||
rand.Read(scalar[:]) //nolint: errcheck
|
||||
curve, _ := curve25519.X25519(scalar[:], curve25519.Basepoint)
|
||||
buf.Write(curve) //nolint: errcheck
|
||||
|
||||
buf.Write([]byte{ //nolint: errcheck
|
||||
0x00, 0x2b, // Extension - Supported Versions
|
||||
0x00, 0x02, // 2 bytes are following
|
||||
0x03, 0x04, // TLS 1.3
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
func InitTCP(conn net.Conn, readBufferSize, writeBufferSize int) error {
|
||||
tcpConn := conn.(*net.TCPConn) //nolint: forcetypeassert
|
||||
|
||||
if err := tcpConn.SetNoDelay(true); err != nil {
|
||||
return fmt.Errorf("cannot set TCP_NO_DELAY: %w", err)
|
||||
}
|
||||
|
||||
if err := tcpConn.SetReadBuffer(readBufferSize); err != nil {
|
||||
return fmt.Errorf("cannot set read buffer size: %w", err)
|
||||
}
|
||||
|
||||
if err := tcpConn.SetWriteBuffer(writeBufferSize); err != nil {
|
||||
return fmt.Errorf("cannot set write buffer size: %w", err)
|
||||
}
|
||||
|
||||
if err := tcpConn.SetKeepAlive(true); err != nil {
|
||||
return fmt.Errorf("cannot enable keep-alive: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package utils
|
||||
|
||||
import "io"
|
||||
|
||||
const readCurrentDataBufferSize = 1024 + 1 // + 1 because telegram operates with blocks mod 4
|
||||
|
||||
// ReadCurrentData reads all data from io.Reader which is ready to be read.
|
||||
func ReadCurrentData(src io.Reader) (rv []byte, err error) {
|
||||
buf := make([]byte, readCurrentDataBufferSize)
|
||||
n := readCurrentDataBufferSize
|
||||
|
||||
for n == len(buf) {
|
||||
n, err = src.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rv = append(rv, buf[:n]...)
|
||||
}
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package utils
|
||||
|
||||
import "io"
|
||||
|
||||
const readFullBufferSize = 1024 + 1 // +1 because telegram opreates with blocks mod 4
|
||||
|
||||
func ReadFull(src io.Reader) ([]byte, error) {
|
||||
buf := make([]byte, readFullBufferSize)
|
||||
n := readFullBufferSize
|
||||
rv := []byte{}
|
||||
|
||||
for n == len(buf) {
|
||||
n, err := src.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err //nolint: wrapcheck
|
||||
}
|
||||
|
||||
rv = append(rv, buf[:n]...)
|
||||
}
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
@@ -4,9 +4,9 @@ package utils
|
||||
func ReverseBytes(data []byte) []byte {
|
||||
dataLen := len(data)
|
||||
rv := make([]byte, dataLen)
|
||||
|
||||
rv[dataLen/2] = data[dataLen/2]
|
||||
for i := dataLen/2 - 1; i >= 0; i-- {
|
||||
|
||||
for i := dataLen/2 - 1; i >= 0; i-- { //nolint: gomnd
|
||||
opp := dataLen - i - 1
|
||||
rv[i], rv[opp] = data[opp], data[i]
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user