REPOSITORY / ScuroNeko/mtg

Compare commits

DIFF REPOSITORY

Compare commits

...
18 Commits
Author SHA1 Message Date
9seconds 8268620615 Update version of golangci-lint 2018-07-29 17:10:12 +03:00
9seconds 753639beb8 Make session drop even more responsive 2018-07-29 17:08:57 +03:00
9seconds 1ee34fd502 Guarantee of closing sockets after context is done 2018-07-28 15:36:40 +03:00
Sergey ArkhipovandGitHub 243a89a68c Merge pull request #33 from 9seconds/contexts
Use contexts for Conn wrapper
2018-07-28 13:31:53 +03:00
9seconds 9f20e8749a Use contexts for Conn wrapper 2018-07-28 13:21:57 +03:00
Sergey ArkhipovandGitHub b0d86abc74 Small table fix 2018-07-28 12:40:41 +03:00
Sergey ArkhipovandGitHub 6c0030d20e Merge pull request #32 from 9seconds/readme-env-vars
Document environment variables
2018-07-28 12:39:47 +03:00
9seconds 8733f6d191 Optimize dockerfile 2018-07-28 12:30:47 +03:00
9seconds 8906ae0db0 Document environment variables 2018-07-28 12:10:39 +03:00
Sergey ArkhipovandGitHub 2e8c9623de Merge pull request #31 from 9seconds/propagate-buffer-sizes
Propagate buffer sizes
2018-07-27 14:31:43 +03:00
9seconds 0ce325f908 Add possibility to set read/write buffers separately 2018-07-27 14:26:14 +03:00
9seconds b86ad07ba9 Simplify the sources 2018-07-27 13:46:14 +03:00
Sergey ArkhipovandGitHub 6253c21615 Merge pull request #30 from 9seconds/ipv6-fix
Fix ipv6/ipv4 name resolution
2018-07-27 10:10:46 +03:00
9seconds a00408562f Fix ipv6/ipv4 name resolution 2018-07-27 10:04:10 +03:00
9seconds 0d0bdd9fde Reuse buffers for stream cipher 2018-07-20 14:49:22 +03:00
Sergey Arkhipovand9seconds c8da08ecce Allow go master to fail 2018-07-18 17:45:45 +03:00
9seconds c5f99d17b7 Add go-critic 2018-07-18 08:53:04 +03:00
9seconds c1b862c1e9 Switch to golangci-lint 2018-07-18 08:37:37 +03:00
32 changed files with 504 additions and 259 deletions
+3
View File
@@ -0,0 +1,3 @@
version.go
vendor/
tags
+12
View File
@@ -0,0 +1,12 @@
[run]
concurrency = 4
deadline = "2m"
tests = true
skip-dirs = ["vendor"]
skip-files = ["version.go"]
[output]
format = "colored-line-number"
[linters]
enable-all = true
-4
View File
@@ -1,4 +0,0 @@
{
"aggregate": true,
"vendor": true
}
+8
View File
@@ -9,10 +9,18 @@ go:
- "1.10.x" - "1.10.x"
- master - master
before_script: make prepare
script: script:
- make all
- make lint - make lint
- make critic
- make test - make test
cache: cache:
directories: directories:
- vendor - vendor
matrix:
allow_failures:
- go: master
+8 -2
View File
@@ -13,11 +13,17 @@ RUN set -x \
upx \ upx \
&& update-ca-certificates && update-ca-certificates
ADD . /go/src/github.com/9seconds/mtg COPY Gopkg.toml Gopkg.lock Makefile /go/src/github.com/9seconds/mtg/
RUN set -x && \
cd /go/src/github.com/9seconds/mtg && \
make -j 4 prepare && \
make vendor
COPY . /go/src/github.com/9seconds/mtg
RUN set -x \ RUN set -x \
&& cd /go/src/github.com/9seconds/mtg \ && cd /go/src/github.com/9seconds/mtg \
&& make clean \
&& make -j 4 static \ && make -j 4 static \
&& upx --ultra-brute -qq ./mtg && upx --ultra-brute -qq ./mtg
Generated
+48 -8
View File
@@ -3,74 +3,97 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:315c5f2f60c76d89b871c73f9bd5fe689cad96597afd50fb9992228ef80bdd34"
name = "github.com/alecthomas/template" name = "github.com/alecthomas/template"
packages = [ packages = [
".", ".",
"parse" "parse",
] ]
pruneopts = "UT"
revision = "a0175ee3bccc567396460bf5acd36800cb10c49c" revision = "a0175ee3bccc567396460bf5acd36800cb10c49c"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:c198fdc381e898e8fb62b8eb62758195091c313ad18e52a3067366e1dda2fb3c"
name = "github.com/alecthomas/units" name = "github.com/alecthomas/units"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
[[projects]] [[projects]]
digest = "1:a8d622a8049a4aa420e1c509873bb85d4c45c5107f420d922f919bfcb8d08694"
name = "github.com/beevik/ntp" name = "github.com/beevik/ntp"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "62c80a04de2086884d8296004b6d74ee1846c582" revision = "62c80a04de2086884d8296004b6d74ee1846c582"
version = "v0.2.0" version = "v0.2.0"
[[projects]] [[projects]]
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
name = "github.com/davecgh/go-spew" name = "github.com/davecgh/go-spew"
packages = ["spew"] packages = ["spew"]
pruneopts = "UT"
revision = "346938d642f2ec3594ed81d874461961cd0faa76" revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0" version = "v1.1.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:6f9339c912bbdda81302633ad7e99a28dfa5a639c864061f1929510a9a64aa74"
name = "github.com/dustin/go-humanize" name = "github.com/dustin/go-humanize"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "9f541cc9db5d55bce703bd99987c9d5cb8eea45e" revision = "9f541cc9db5d55bce703bd99987c9d5cb8eea45e"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:53bd4347b151fcbedcdda527f7ebf4924f0e21d672131812f857175d8c7a1051"
name = "github.com/juju/errors" name = "github.com/juju/errors"
packages = ["."] packages = ["."]
revision = "c7d06af17c68cd34c835053720b21f6549d9b0ee" pruneopts = "UT"
revision = "812b06ada1776ad4dd95d575e18ffffe3a9ac34a"
[[projects]] [[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib" name = "github.com/pmezard/go-difflib"
packages = ["difflib"] packages = ["difflib"]
pruneopts = "UT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2" revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0" version = "v1.0.0"
[[projects]] [[projects]]
digest = "1:274f67cb6fed9588ea2521ecdac05a6d62a8c51c074c1fccc6a49a40ba80e925"
name = "github.com/satori/go.uuid" name = "github.com/satori/go.uuid"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3" revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
version = "v1.2.0" version = "v1.2.0"
[[projects]] [[projects]]
digest = "1:18752d0b95816a1b777505a97f71c7467a8445b8ffb55631a7bf779f6ba4fa83"
name = "github.com/stretchr/testify" name = "github.com/stretchr/testify"
packages = ["assert"] packages = ["assert"]
pruneopts = "UT"
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2" version = "v1.2.2"
[[projects]] [[projects]]
digest = "1:3c1a69cdae3501bf75e76d0d86dc6f2b0a7421bc205c0cb7b96b19eed464a34d"
name = "go.uber.org/atomic" name = "go.uber.org/atomic"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "1ea20fb1cbb1cc08cbd0d913a96dead89aa18289" revision = "1ea20fb1cbb1cc08cbd0d913a96dead89aa18289"
version = "v1.3.2" version = "v1.3.2"
[[projects]] [[projects]]
digest = "1:60bf2a5e347af463c42ed31a493d817f8a72f102543060ed992754e689805d1a"
name = "go.uber.org/multierr" name = "go.uber.org/multierr"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a" revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a"
version = "v1.1.0" version = "v1.1.0"
[[projects]] [[projects]]
digest = "1:d9a420eae5f76973feeb733fbf58f6a89173255b63b27a7ae4b2124a73dc6c5b"
name = "go.uber.org/zap" name = "go.uber.org/zap"
packages = [ packages = [
".", ".",
@@ -78,37 +101,54 @@
"internal/bufferpool", "internal/bufferpool",
"internal/color", "internal/color",
"internal/exit", "internal/exit",
"zapcore" "zapcore",
] ]
revision = "eeedf312bc6c57391d84767a4cd413f02a917974" pruneopts = "UT"
version = "v1.8.0" revision = "4d45f9617f7d90f7a663ff21c7a4321dbe78098b"
version = "v1.9.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:becb2131aece71c64ebca5ddfd38cf631784fbb3a678d73ead3b42107c9f41ce"
name = "golang.org/x/net" name = "golang.org/x/net"
packages = [ packages = [
"bpf", "bpf",
"internal/iana", "internal/iana",
"internal/socket", "internal/socket",
"ipv4" "ipv4",
] ]
revision = "d0887baf81f4598189d4e12a37c6da86f0bba4d0" pruneopts = "UT"
revision = "3673e40ba22529d22c3fd7c93e97b0ce50fa7bdd"
[[projects]] [[projects]]
digest = "1:c06d9e11d955af78ac3bbb26bd02e01d2f61f689e1a3bce2ef6fb683ef8a7f2d"
name = "gopkg.in/alecthomas/kingpin.v2" name = "gopkg.in/alecthomas/kingpin.v2"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "947dcec5ba9c011838740e680966fd7087a71d0d" revision = "947dcec5ba9c011838740e680966fd7087a71d0d"
version = "v2.2.6" version = "v2.2.6"
[[projects]] [[projects]]
digest = "1:38b469493eb173db9c03321d64adcad4c7991ea0a19b5edc5bdc094f0e8c7384"
name = "gopkg.in/alexcesaro/statsd.v2" name = "gopkg.in/alexcesaro/statsd.v2"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "7fea3f0d2fab1ad973e641e51dba45443a311a90" revision = "7fea3f0d2fab1ad973e641e51dba45443a311a90"
version = "v2.0.0" version = "v2.0.0"
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "7fad0f62feb7737b064d85cc4333a1a3e9298faec2afd864b4404f515fc7f17c" input-imports = [
"github.com/beevik/ntp",
"github.com/dustin/go-humanize",
"github.com/juju/errors",
"github.com/satori/go.uuid",
"github.com/stretchr/testify/assert",
"go.uber.org/zap",
"go.uber.org/zap/zapcore",
"gopkg.in/alecthomas/kingpin.v2",
"gopkg.in/alexcesaro/statsd.v2",
]
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1
+20 -11
View File
@@ -1,12 +1,13 @@
ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
IMAGE_NAME := mtg IMAGE_NAME := mtg
APP_NAME := $(IMAGE_NAME) APP_NAME := $(IMAGE_NAME)
GOMETALINTER := gometalinter
VENDOR_FILES := $(shell find "$(ROOT_DIR)/vendor" 2>/dev/null || echo -n "vendor") VENDOR_FILES := $(shell find "$(ROOT_DIR)/vendor" 2>/dev/null || echo -n "vendor")
CC_BINARIES := $(shell bash -c "echo -n $(APP_NAME)-{linux,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}") CC_BINARIES := $(shell bash -c "echo -n $(APP_NAME)-{linux,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}")
APP_DEPS := version.go $(VENDOR_FILES) APP_DEPS := version.go $(VENDOR_FILES)
GOLANGCI_LINT_VERSION := v1.9.2
COMMON_BUILD_FLAGS := -ldflags="-s -w" COMMON_BUILD_FLAGS := -ldflags="-s -w"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -31,8 +32,8 @@ ccbuilds:
version.go: version.go:
@go generate main.go @go generate main.go
vendor: Gopkg.lock Gopkg.toml install-cli vendor: Gopkg.lock Gopkg.toml
@dep ensure @dep ensure --vendor-only
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -50,12 +51,16 @@ crosscompile-dir:
@rm -rf "$(CC_DIR)" && mkdir -p "$(CC_DIR)" @rm -rf "$(CC_DIR)" && mkdir -p "$(CC_DIR)"
.PHONY: test .PHONY: test
test: vendor install-cli version.go test: vendor version.go
@go test -v ./... @go test -v ./...
.PHONY: lint .PHONY: lint
lint: vendor install-cli version.go lint: version.go
@$(GOMETALINTER) --deadline=2m ./... @golangci-lint run
.PHONY: critic
critic: version.go
@gocritic check-project "$(ROOT_DIR)"
.PHONY: clean .PHONY: clean
clean: clean:
@@ -67,14 +72,18 @@ clean:
docker: docker:
@docker build --pull -t "$(IMAGE_NAME)" "$(ROOT_DIR)" @docker build --pull -t "$(IMAGE_NAME)" "$(ROOT_DIR)"
.PHONY: install-cli .PHONY: prepare
install-cli: install-dep install-lint prepare: install-dep install-lint install-critic
.PHONY: install-dep .PHONY: install-dep
install-dep: install-dep:
@go get github.com/golang/dep/cmd/dep @go get -u github.com/golang/dep/cmd/dep
.PHONY: install-lint .PHONY: install-lint
install-lint: install-lint:
@go get github.com/alecthomas/gometalinter && \ @curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh \
$(GOMETALINTER) --install >/dev/null | bash -s -- -b $(GOPATH)/bin $(GOLANGCI_LINT_VERSION)
.PHONY: install-critic
install-critic:
@go get -u github.com/go-critic/go-critic/...
+48 -15
View File
@@ -5,7 +5,6 @@ Bullshit-free MTPROTO proxy for Telegram
[![Build Status](https://travis-ci.org/9seconds/mtg.svg?branch=master)](https://travis-ci.org/9seconds/mtg) [![Build Status](https://travis-ci.org/9seconds/mtg.svg?branch=master)](https://travis-ci.org/9seconds/mtg)
[![Docker Build Status](https://img.shields.io/docker/build/nineseconds/mtg.svg)](https://hub.docker.com/r/nineseconds/mtg/) [![Docker Build Status](https://img.shields.io/docker/build/nineseconds/mtg.svg)](https://hub.docker.com/r/nineseconds/mtg/)
# Rationale # Rationale
There are several available proxies for Telegram MTPROTO available. Here There are several available proxies for Telegram MTPROTO available. Here
@@ -60,19 +59,19 @@ There are 2 main branches:
# How to build # How to build
```console ```console
$ make make
``` ```
If you want to build for another platform: If you want to build for another platform:
```console ```console
$ make crosscompile make crosscompile
``` ```
If you want to build Docker image (called `mtg`): If you want to build Docker image (called `mtg`):
```console ```console
$ make docker make docker
``` ```
# Docker image # Docker image
@@ -84,15 +83,15 @@ Docker follows the same policy as the source code organization:
- tags are for tagged releases - tags are for tagged releases
```console ```console
$ docker pull nineseconds/mtg:latest docker pull nineseconds/mtg:latest
``` ```
```console ```console
$ docker pull nineseconds/mtg:stable docker pull nineseconds/mtg:stable
``` ```
```console ```console
$ docker pull nineseconds/mtg:0.10 docker pull nineseconds/mtg:0.10
``` ```
# Configuration # Configuration
@@ -102,13 +101,13 @@ Basically, to run this tool you need to configure as less as possible.
First, you need to generate a secret: First, you need to generate a secret:
```console ```console
$ openssl rand -hex 16 openssl rand -hex 16
``` ```
or or
```console ```console
$ head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' ' head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' '
``` ```
## Secure mode ## Secure mode
@@ -122,28 +121,62 @@ suggest to go with this mode.
Oneliners to generate such secrets: Oneliners to generate such secrets:
```console ```console
$ echo dd$(openssl rand -hex 16) echo dd$(openssl rand -hex 16)
``` ```
or or
```console ```console
$ echo dd$(head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' ') echo dd$(head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' ')
``` ```
## 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. |
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
read buffer.
Unfortunately, MTPROTO proxy protocol does not allow us to use splice
or any other neat tricks how to eliminate the need of copying data into
userspace.
# How to run the tool # How to run the tool
Now run the tool: Now run the tool:
```console ```console
$ mtg <secret> mtg <secret>
``` ```
How to run the tool with ADTag: How to run the tool with ADTag:
```console ```console
$ mtg <secret> <adtag> mtg <secret> <adtag>
``` ```
This tool will listen on port 3128 by default with the given secret. This tool will listen on port 3128 by default with the given secret.
@@ -151,13 +184,13 @@ This tool will listen on port 3128 by default with the given secret.
# One-line runner # One-line runner
```console ```console
$ docker run --name mtg --restart=unless-stopped -p 3128:3128 -p 3129:3129 -d nineseconds/mtg:stable $(openssl rand -hex 16) docker run --name mtg --restart=unless-stopped -p 3128:3128 -p 3129:3129 -d nineseconds/mtg:stable $(openssl rand -hex 16)
``` ```
or in secret mode: or in secret mode:
```console ```console
$ docker run --name mtg --restart=unless-stopped -p 3128:3128 -p 3129:3129 -d nineseconds/mtg:stable dd$(openssl rand -hex 16) 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 You will have this tool up and running on port 3128. Now curl
+3 -1
View File
@@ -1,6 +1,7 @@
package client package client
import ( import (
"context"
"net" "net"
"github.com/9seconds/mtg/config" "github.com/9seconds/mtg/config"
@@ -9,4 +10,5 @@ import (
) )
// Init defines common method for initializing client connections. // Init defines common method for initializing client connections.
type Init func(net.Conn, string, *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) type Init func(context.Context, context.CancelFunc, net.Conn, string,
*config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error)
+7 -9
View File
@@ -1,6 +1,7 @@
package client package client
import ( import (
"context"
"net" "net"
"time" "time"
@@ -12,23 +13,20 @@ import (
"github.com/9seconds/mtg/wrappers" "github.com/9seconds/mtg/wrappers"
) )
const ( const handshakeTimeout = 10 * time.Second
handshakeTimeout = 10 * time.Second
readBufferSize = 64 * 1024
writeBufferSize = 64 * 1024
)
// DirectInit initializes client connection for proxy which connects to // DirectInit initializes client connection for proxy which connects to
// Telegram directly. // Telegram directly.
func DirectInit(socket net.Conn, connID string, conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) { 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) tcpSocket := socket.(*net.TCPConn)
if err := tcpSocket.SetNoDelay(false); err != nil { if err := tcpSocket.SetNoDelay(false); err != nil {
return nil, nil, errors.Annotate(err, "Cannot disable NO_DELAY to client socket") return nil, nil, errors.Annotate(err, "Cannot disable NO_DELAY to client socket")
} }
if err := tcpSocket.SetReadBuffer(readBufferSize); err != nil { if err := tcpSocket.SetReadBuffer(conf.ReadBufferSize); err != nil {
return nil, nil, errors.Annotate(err, "Cannot set read buffer size of client socket") return nil, nil, errors.Annotate(err, "Cannot set read buffer size of client socket")
} }
if err := tcpSocket.SetWriteBuffer(writeBufferSize); err != nil { if err := tcpSocket.SetWriteBuffer(conf.WriteBufferSize); err != nil {
return nil, nil, errors.Annotate(err, "Cannot set write buffer size of client socket") return nil, nil, errors.Annotate(err, "Cannot set write buffer size of client socket")
} }
@@ -39,7 +37,7 @@ func DirectInit(socket net.Conn, connID string, conf *config.Config) (wrappers.W
} }
socket.SetReadDeadline(time.Time{}) // nolint: errcheck socket.SetReadDeadline(time.Time{}) // nolint: errcheck
conn := wrappers.NewConn(socket, connID, wrappers.ConnPurposeClient, conf.PublicIPv4, conf.PublicIPv6) conn := wrappers.NewConn(ctx, cancel, socket, connID, wrappers.ConnPurposeClient, conf.PublicIPv4, conf.PublicIPv6)
obfs2, connOpts, err := obfuscated2.ParseObfuscated2ClientFrame(conf.Secret, frame) obfs2, connOpts, err := obfuscated2.ParseObfuscated2ClientFrame(conf.Secret, frame)
if err != nil { if err != nil {
return nil, nil, errors.Annotate(err, "Cannot parse obfuscated frame") return nil, nil, errors.Annotate(err, "Cannot parse obfuscated frame")
+4 -2
View File
@@ -1,6 +1,7 @@
package client package client
import ( import (
"context"
"net" "net"
"github.com/9seconds/mtg/config" "github.com/9seconds/mtg/config"
@@ -10,8 +11,9 @@ import (
// MiddleInit initializes client connection for proxy which has to // MiddleInit initializes client connection for proxy which has to
// support promoted channels, connect to Telegram middle proxies etc. // support promoted channels, connect to Telegram middle proxies etc.
func MiddleInit(socket net.Conn, connID string, conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) { func MiddleInit(ctx context.Context, cancel context.CancelFunc, socket net.Conn,
conn, opts, err := DirectInit(socket, connID, conf) connID string, conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) {
conn, opts, err := DirectInit(ctx, cancel, socket, connID, conf)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
+35 -45
View File
@@ -1,28 +1,25 @@
package config package config
import ( import (
"bytes"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
"strings"
"github.com/juju/errors" "github.com/juju/errors"
statsd "gopkg.in/alexcesaro/statsd.v2" statsd "gopkg.in/alexcesaro/statsd.v2"
) )
// Buffer sizes define internal socket buffer sizes.
const (
BufferWriteSize = 32 * 1024
BufferReadSize = 32 * 1024
)
// Config represents common configuration of mtg. // Config represents common configuration of mtg.
type Config struct { type Config struct {
Debug bool Debug bool
Verbose bool Verbose bool
SecureMode bool SecureMode bool
ReadBufferSize int
WriteBufferSize int
BindPort uint16 BindPort uint16
PublicIPv4Port uint16 PublicIPv4Port uint16
PublicIPv6Port uint16 PublicIPv6Port uint16
@@ -114,33 +111,21 @@ func getAddr(host fmt.Stringer, port uint16) string {
// fetches data from external sources. Parameters passed to this // fetches data from external sources. Parameters passed to this
// function, should come from command line arguments. // function, should come from command line arguments.
func NewConfig(debug, verbose bool, // nolint: gocyclo func NewConfig(debug, verbose bool, // nolint: gocyclo
bindIP net.IP, bindPort uint16, writeBufferSize, readBufferSize uint32,
publicIPv4 net.IP, PublicIPv4Port uint16, bindIP, publicIPv4, publicIPv6, statsIP net.IP,
publicIPv6 net.IP, publicIPv6Port uint16, bindPort, publicIPv4Port, publicIPv6Port, statsPort, statsdPort uint16,
statsIP net.IP, statsPort uint16, statsdIP, statsdNetwork, statsdPrefix, statsdTagsFormat string,
secret, adtag string, statsdTags map[string]string,
statsdIP string, statsdPort uint16, statsdNetwork string, statsdPrefix string, secret, adtag []byte) (*Config, error) {
statsdTagsFormat string, statsdTags map[string]string) (*Config, error) {
secureMode := false secureMode := false
if strings.HasPrefix(secret, "dd") && len(secret) == 34 { if bytes.HasPrefix(secret, []byte{0xdd}) && len(secret) == 17 {
secureMode = true secureMode = true
secret = strings.TrimPrefix(secret, "dd") secret = bytes.TrimPrefix(secret, []byte{0xdd})
} else if len(secret) != 32 { } else if len(secret) != 16 {
return nil, errors.New("Telegram demands secret of length 32") return nil, errors.New("Telegram demands secret of length 32")
} }
secretBytes, err := hex.DecodeString(secret)
if err != nil {
return nil, errors.Annotate(err, "Cannot create config")
}
var adTagBytes []byte
if len(adtag) != 0 {
adTagBytes, err = hex.DecodeString(adtag)
if err != nil {
return nil, errors.Annotate(err, "Cannot create config")
}
}
var err error
if publicIPv4 == nil { if publicIPv4 == nil {
publicIPv4, err = getGlobalIPv4() publicIPv4, err = getGlobalIPv4()
if err != nil { if err != nil {
@@ -149,8 +134,8 @@ func NewConfig(debug, verbose bool, // nolint: gocyclo
return nil, errors.Errorf("IP %s is not IPv4", publicIPv4.String()) return nil, errors.Errorf("IP %s is not IPv4", publicIPv4.String())
} }
} }
if PublicIPv4Port == 0 { if publicIPv4Port == 0 {
PublicIPv4Port = bindPort publicIPv4Port = bindPort
} }
if publicIPv6 == nil { if publicIPv6 == nil {
@@ -170,19 +155,21 @@ func NewConfig(debug, verbose bool, // nolint: gocyclo
} }
conf := &Config{ conf := &Config{
Debug: debug, Debug: debug,
Verbose: verbose, Verbose: verbose,
BindIP: bindIP, BindIP: bindIP,
BindPort: bindPort, BindPort: bindPort,
PublicIPv4: publicIPv4, PublicIPv4: publicIPv4,
PublicIPv4Port: PublicIPv4Port, PublicIPv4Port: publicIPv4Port,
PublicIPv6: publicIPv6, PublicIPv6: publicIPv6,
PublicIPv6Port: publicIPv6Port, PublicIPv6Port: publicIPv6Port,
StatsIP: statsIP, StatsIP: statsIP,
StatsPort: statsPort, StatsPort: statsPort,
Secret: secretBytes, Secret: secret,
AdTag: adTagBytes, AdTag: adtag,
SecureMode: secureMode, SecureMode: secureMode,
ReadBufferSize: int(readBufferSize),
WriteBufferSize: int(writeBufferSize),
} }
if statsdIP != "" { if statsdIP != "" {
@@ -190,7 +177,10 @@ func NewConfig(debug, verbose bool, // nolint: gocyclo
conf.StatsD.Prefix = statsdPrefix conf.StatsD.Prefix = statsdPrefix
conf.StatsD.Tags = statsdTags conf.StatsD.Tags = statsdTags
var addr net.Addr var (
addr net.Addr
err error
)
hostPort := net.JoinHostPort(statsdIP, strconv.Itoa(int(statsdPort))) hostPort := net.JoinHostPort(statsdIP, strconv.Itoa(int(statsdPort)))
switch statsdNetwork { switch statsdNetwork {
case "tcp": case "tcp":
+17 -4
View File
@@ -1,6 +1,7 @@
package config package config
import ( import (
"context"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
@@ -9,16 +10,28 @@ import (
"github.com/juju/errors" "github.com/juju/errors"
) )
const ifconfigAddress = "https://ifconfig.co/ip"
func getGlobalIPv4() (net.IP, error) { func getGlobalIPv4() (net.IP, error) {
return fetchIP("https://v4.ifconfig.co/ip") return fetchIP("tcp4")
} }
func getGlobalIPv6() (net.IP, error) { func getGlobalIPv6() (net.IP, error) {
return fetchIP("https://v6.ifconfig.co/ip") return fetchIP("tcp6")
} }
func fetchIP(url string) (net.IP, error) { func fetchIP(network string) (net.IP, error) {
resp, err := http.Get(url) dialer := &net.Dialer{DualStack: false}
client := &http.Client{
Jar: nil,
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, network, addr)
},
},
}
resp, err := client.Get(ifconfigAddress)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+3 -3
View File
@@ -42,7 +42,7 @@ func makeTMeURL(values url.Values) string {
} }
func makeQRCodeURL(data string) string { func makeQRCodeURL(data string) string {
QRURL := url.URL{ qr := url.URL{
Scheme: "https", Scheme: "https",
Host: "api.qrserver.com", Host: "api.qrserver.com",
Path: "v1/create-qr-code", Path: "v1/create-qr-code",
@@ -52,7 +52,7 @@ func makeQRCodeURL(data string) string {
values.Set("qzone", "4") values.Set("qzone", "4")
values.Set("format", "svg") values.Set("format", "svg")
values.Set("data", data) values.Set("data", data)
QRURL.RawQuery = values.Encode() qr.RawQuery = values.Encode()
return QRURL.String() return qr.String()
} }
+93 -63
View File
@@ -25,86 +25,115 @@ import (
var ( var (
app = kingpin.New("mtg", "Simple MTPROTO proxy.") app = kingpin.New("mtg", "Simple MTPROTO proxy.")
debug = app.Flag("debug", "Run in debug mode."). debug = app.Flag("debug",
"Run in debug mode.").
Short('d'). Short('d').
Envar("MTG_DEBUG"). Envar("MTG_DEBUG").
Bool() Bool()
verbose = app.Flag("verbose", "Run in verbose mode."). verbose = app.Flag("verbose",
"Run in verbose mode.").
Short('v'). Short('v').
Envar("MTG_VERBOSE"). Envar("MTG_VERBOSE").
Bool() Bool()
bindIP = app.Flag("bind-ip", "Which IP to bind to."). bindIP = app.Flag("bind-ip",
"Which IP to bind to.").
Short('b'). Short('b').
Envar("MTG_IP"). Envar("MTG_IP").
Default("127.0.0.1"). Default("127.0.0.1").
IP() IP()
bindPort = app.Flag("bind-port", "Which port to bind to."). bindPort = app.Flag("bind-port",
Short('p'). "Which port to bind to.").
Envar("MTG_PORT"). Short('p').
Default("3128"). Envar("MTG_PORT").
Uint16() Default("3128").
Uint16()
publicIPv4 = app.Flag("public-ipv4", "Which IPv4 address is public."). publicIPv4 = app.Flag("public-ipv4",
Short('4'). "Which IPv4 address is public.").
Envar("MTG_IPV4"). Short('4').
IP() Envar("MTG_IPV4").
publicIPv4Port = app.Flag("public-ipv4-port", "Which IPv4 port is public. Default is 'bind-port' value."). IP()
Envar("MTG_IPV4_PORT"). publicIPv4Port = app.Flag("public-ipv4-port",
Uint16() "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."). publicIPv6 = app.Flag("public-ipv6",
Short('6'). "Which IPv6 address is public.").
Envar("MTG_IPV6"). Short('6').
IP() Envar("MTG_IPV6").
publicIPv6Port = app.Flag("public-ipv6-port", "Which IPv6 port is public. Default is 'bind-port' value."). IP()
Envar("MTG_IPV6_PORT"). publicIPv6Port = app.Flag("public-ipv6-port",
Uint16() "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."). statsIP = app.Flag("stats-ip",
"Which IP bind stats server to.").
Short('t'). Short('t').
Envar("MTG_STATS_IP"). Envar("MTG_STATS_IP").
Default("127.0.0.1"). Default("127.0.0.1").
IP() IP()
statsPort = app.Flag("stats-port", "Which port bind stats to."). statsPort = app.Flag("stats-port",
Short('q'). "Which port bind stats to.").
Envar("MTG_STATS_PORT"). Short('q').
Default("3129"). Envar("MTG_STATS_PORT").
Uint16() Default("3129").
Uint16()
statsdIP = app.Flag("statsd-ip", "Which IP should we use for working with statsd."). statsdIP = app.Flag("statsd-ip",
Envar("MTG_STATSD_IP"). "Which IP should we use for working with statsd.").
String() Envar("MTG_STATSD_IP").
statsdPort = app.Flag("statsd-port", "Which port should we use for working with statsd."). String()
Envar("MTG_STATSD_PORT"). statsdPort = app.Flag("statsd-port",
Default("8125"). "Which port should we use for working with statsd.").
Uint16() Envar("MTG_STATSD_PORT").
statsdNetwork = app.Flag("statsd-network", "Which network is used to work with statsd. Only 'tcp' and 'udp' are supported."). Default("8125").
Envar("MTG_STATSD_NETWORK"). Uint16()
Default("udp"). statsdNetwork = app.Flag("statsd-network",
String() "Which network is used to work with statsd. Only 'tcp' and 'udp' are supported.").
statsdPrefix = app.Flag("statsd-prefix", "Which bucket prefix should we use for sending stats to statsd."). Envar("MTG_STATSD_NETWORK").
Envar("MTG_STATSD_PREFIX"). Default("udp").
Default("mtg"). String()
String() statsdPrefix = app.Flag("statsd-prefix",
statsdTagsFormat = app.Flag("statsd-tags-format", "Which tag format should we use to send stats metrics. Valid options are 'datadog' and 'influxdb'."). "Which bucket prefix should we use for sending stats to statsd.").
Envar("MTG_STATSD_TAGS_FORMAT"). Envar("MTG_STATSD_PREFIX").
String() Default("mtg").
statsdTags = app.Flag("statsd-tags", "Tags to use for working with statsd (specified as 'key=value')."). String()
Envar("MTG_STATSD_TAGS"). statsdTagsFormat = app.Flag("statsd-tags-format",
StringMap() "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",
"Tags to use for working with statsd (specified as 'key=value').").
Envar("MTG_STATSD_TAGS").
StringMap()
secret = app.Arg("secret", "Secret of this proxy.").Required().String() writeBufferSize = app.Flag("write-buffer",
adtag = app.Arg("adtag", "ADTag of the proxy.").String() "Write buffer size in bytes. 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.").
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()
) )
func init() { func init() {
rand.Seed(time.Now().UTC().UnixNano()) rand.Seed(time.Now().UTC().UnixNano())
app.Version(version) app.Version(version)
app.HelpFlag.Short('h')
} }
func main() { func main() { // nolint: gocyclo
kingpin.MustParse(app.Parse(os.Args[1:])) kingpin.MustParse(app.Parse(os.Args[1:]))
err := setRLimit() err := setRLimit()
@@ -113,24 +142,24 @@ func main() {
} }
conf, err := config.NewConfig(*debug, *verbose, conf, err := config.NewConfig(*debug, *verbose,
*bindIP, *bindPort, *writeBufferSize, *readBufferSize,
*publicIPv4, *publicIPv4Port, *bindIP, *publicIPv4, *publicIPv6, *statsIP,
*publicIPv6, *publicIPv6Port, *bindPort, *publicIPv4Port, *publicIPv6Port, *statsPort, *statsdPort,
*statsIP, *statsPort, *statsdIP, *statsdNetwork, *statsdPrefix, *statsdTagsFormat,
*statsdTags,
*secret, *adtag, *secret, *adtag,
*statsdIP, *statsdPort, *statsdNetwork, *statsdPrefix,
*statsdTagsFormat, *statsdTags,
) )
if err != nil { if err != nil {
usage(err.Error()) usage(err.Error())
} }
atom := zap.NewAtomicLevel() atom := zap.NewAtomicLevel()
if conf.Debug { switch {
case conf.Debug:
atom.SetLevel(zapcore.DebugLevel) atom.SetLevel(zapcore.DebugLevel)
} else if conf.Verbose { case conf.Verbose:
atom.SetLevel(zapcore.InfoLevel) atom.SetLevel(zapcore.InfoLevel)
} else { default:
atom.SetLevel(zapcore.ErrorLevel) atom.SetLevel(zapcore.ErrorLevel)
} }
encoderCfg := zap.NewProductionEncoderConfig() encoderCfg := zap.NewProductionEncoderConfig()
@@ -150,7 +179,8 @@ func main() {
zap.S().Warnw("Could not fetch time data from NTP") zap.S().Warnw("Could not fetch time data from NTP")
} else { } else {
if diff >= time.Second { 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)) 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() go ntp.AutoUpdate()
} }
+4 -4
View File
@@ -19,10 +19,10 @@ type HandshakeResponse struct {
func (r *HandshakeResponse) Bytes() []byte { func (r *HandshakeResponse) Bytes() []byte {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
buf.Write(r.Type[:]) buf.Write(r.Type)
buf.Write(r.Flags[:]) buf.Write(r.Flags)
buf.Write(r.SenderPID[:]) buf.Write(r.SenderPID)
buf.Write(r.PeerPID[:]) buf.Write(r.PeerPID)
return buf.Bytes() return buf.Bytes()
} }
+7 -7
View File
@@ -9,13 +9,13 @@ type proxyRequestFlags uint32
const ( const (
proxyRequestFlagsHasAdTag proxyRequestFlags = 0x8 proxyRequestFlagsHasAdTag proxyRequestFlags = 0x8
proxyRequestFlagsEncrypted = 0x2 proxyRequestFlagsEncrypted proxyRequestFlags = 0x2
proxyRequestFlagsMagic = 0x1000 proxyRequestFlagsMagic proxyRequestFlags = 0x1000
proxyRequestFlagsExtMode2 = 0x20000 proxyRequestFlagsExtMode2 proxyRequestFlags = 0x20000
proxyRequestFlagsIntermediate = 0x20000000 proxyRequestFlagsIntermediate proxyRequestFlags = 0x20000000
proxyRequestFlagsAbdridged = 0x40000000 proxyRequestFlagsAbdridged proxyRequestFlags = 0x40000000
proxyRequestFlagsQuickAck = 0x80000000 proxyRequestFlagsQuickAck proxyRequestFlags = 0x80000000
proxyRequestFlagsPad = 0x8000000 proxyRequestFlagsPad proxyRequestFlags = 0x8000000
) )
var proxyRequestFlagsEncryptedPrefix [8]byte var proxyRequestFlagsEncryptedPrefix [8]byte
+2 -1
View File
@@ -64,7 +64,8 @@ func (r *ProxyRequest) MakeHeader(message []byte) (*bytes.Buffer, fmt.Stringer)
} }
// NewProxyRequest build new ProxyRequest data structure. // NewProxyRequest build new ProxyRequest data structure.
func NewProxyRequest(clientAddr, ownAddr *net.TCPAddr, opts *mtproto.ConnectionOpts, adTag []byte) (*ProxyRequest, error) { func NewProxyRequest(clientAddr, ownAddr *net.TCPAddr,
opts *mtproto.ConnectionOpts, adTag []byte) (*ProxyRequest, error) {
flags := proxyRequestFlagsHasAdTag | proxyRequestFlagsMagic | proxyRequestFlagsExtMode2 flags := proxyRequestFlagsHasAdTag | proxyRequestFlagsMagic | proxyRequestFlagsExtMode2
switch opts.ConnectionType { switch opts.ConnectionType {
+4 -4
View File
@@ -47,8 +47,8 @@ func TestObfs2Full(t *testing.T) {
clientFrame := generateFrame(mtproto.ConnectionTypeIntermediate) clientFrame := generateFrame(mtproto.ConnectionTypeIntermediate)
clientHasher := sha256.New() clientHasher := sha256.New()
clientHasher.Write(clientFrame.Key()) clientHasher.Write(clientFrame.Key()) // nolint: errcheck
clientHasher.Write(secret) clientHasher.Write(secret) // nolint: errcheck
clientKey := clientHasher.Sum(nil) clientKey := clientHasher.Sum(nil)
encryptor := makeStreamCipher(clientKey, clientFrame.IV()) encryptor := makeStreamCipher(clientKey, clientFrame.IV())
@@ -58,8 +58,8 @@ func TestObfs2Full(t *testing.T) {
invertedClientFrame := clientFrame.Invert() invertedClientFrame := clientFrame.Invert()
clientHasher = sha256.New() clientHasher = sha256.New()
clientHasher.Write(invertedClientFrame.Key()) clientHasher.Write(invertedClientFrame.Key()) // nolint: errcheck
clientHasher.Write(secret) clientHasher.Write(secret) // nolint: errcheck
invertedClientKey := clientHasher.Sum(nil) invertedClientKey := clientHasher.Sum(nil)
clientDecryptor := makeStreamCipher(invertedClientKey, invertedClientFrame.IV()) clientDecryptor := makeStreamCipher(invertedClientKey, invertedClientFrame.IV())
+22 -9
View File
@@ -1,6 +1,7 @@
package proxy package proxy
import ( import (
"context"
"io" "io"
"net" "net"
"sync" "sync"
@@ -43,8 +44,10 @@ func (p *Proxy) Serve() error {
func (p *Proxy) accept(conn net.Conn) { func (p *Proxy) accept(conn net.Conn) {
connID := uuid.NewV4().String() connID := uuid.NewV4().String()
log := zap.S().With("connection_id", connID).Named("main") log := zap.S().With("connection_id", connID).Named("main")
ctx, cancel := context.WithCancel(context.Background())
defer func() { defer func() {
cancel()
conn.Close() // nolint: errcheck conn.Close() // nolint: errcheck
if err := recover(); err != nil { if err := recover(); err != nil {
@@ -55,7 +58,7 @@ func (p *Proxy) accept(conn net.Conn) {
log.Infow("Client connected", "addr", conn.RemoteAddr()) log.Infow("Client connected", "addr", conn.RemoteAddr())
clientConn, opts, err := p.clientInit(conn, connID, p.conf) clientConn, opts, err := p.clientInit(ctx, cancel, conn, connID, p.conf)
if err != nil { if err != nil {
log.Errorw("Cannot initialize client connection", "error", err) log.Errorw("Cannot initialize client connection", "error", err)
return return
@@ -65,13 +68,19 @@ func (p *Proxy) accept(conn net.Conn) {
stats.ClientConnected(opts.ConnectionType, clientConn.RemoteAddr()) stats.ClientConnected(opts.ConnectionType, clientConn.RemoteAddr())
defer stats.ClientDisconnected(opts.ConnectionType, clientConn.RemoteAddr()) defer stats.ClientDisconnected(opts.ConnectionType, clientConn.RemoteAddr())
serverConn, err := p.getTelegramConn(opts, connID) serverConn, err := p.getTelegramConn(ctx, cancel, opts, connID)
if err != nil { if err != nil {
log.Errorw("Cannot initialize server connection", "error", err) log.Errorw("Cannot initialize server connection", "error", err)
return return
} }
defer serverConn.(io.Closer).Close() // nolint: errcheck defer serverConn.(io.Closer).Close() // nolint: errcheck
go func() {
<-ctx.Done()
serverConn.(io.Closer).Close()
clientConn.(io.Closer).Close()
}()
wait := &sync.WaitGroup{} wait := &sync.WaitGroup{}
wait.Add(2) wait.Add(2)
@@ -83,8 +92,8 @@ func (p *Proxy) accept(conn net.Conn) {
} else { } else {
clientStream := clientConn.(wrappers.StreamReadWriteCloser) clientStream := clientConn.(wrappers.StreamReadWriteCloser)
serverStream := serverConn.(wrappers.StreamReadWriteCloser) serverStream := serverConn.(wrappers.StreamReadWriteCloser)
go p.directPipe(clientStream, serverStream, wait) go p.directPipe(clientStream, serverStream, wait, p.conf.ReadBufferSize)
go p.directPipe(serverStream, clientStream, wait) go p.directPipe(serverStream, clientStream, wait, p.conf.WriteBufferSize)
} }
wait.Wait() wait.Wait()
@@ -92,8 +101,9 @@ func (p *Proxy) accept(conn net.Conn) {
log.Infow("Client disconnected", "addr", conn.RemoteAddr()) log.Infow("Client disconnected", "addr", conn.RemoteAddr())
} }
func (p *Proxy) getTelegramConn(opts *mtproto.ConnectionOpts, connID string) (wrappers.Wrap, error) { func (p *Proxy) getTelegramConn(ctx context.Context, cancel context.CancelFunc,
streamConn, err := p.tg.Dial(connID, opts) opts *mtproto.ConnectionOpts, connID string) (wrappers.Wrap, error) {
streamConn, err := p.tg.Dial(ctx, cancel, connID, opts)
if err != nil { if err != nil {
return nil, errors.Annotate(err, "Cannot dial to Telegram") return nil, errors.Annotate(err, "Cannot dial to Telegram")
} }
@@ -106,7 +116,8 @@ func (p *Proxy) getTelegramConn(opts *mtproto.ConnectionOpts, connID string) (wr
return packetConn, nil return packetConn, nil
} }
func (p *Proxy) middlePipe(src wrappers.PacketReadCloser, dst io.WriteCloser, wait *sync.WaitGroup, hacks *mtproto.Hacks) { func (p *Proxy) middlePipe(src wrappers.PacketReadCloser, dst io.WriteCloser,
wait *sync.WaitGroup, hacks *mtproto.Hacks) {
defer func() { defer func() {
src.Close() // nolint: errcheck src.Close() // nolint: errcheck
dst.Close() // nolint: errcheck dst.Close() // nolint: errcheck
@@ -129,14 +140,16 @@ func (p *Proxy) middlePipe(src wrappers.PacketReadCloser, dst io.WriteCloser, wa
} }
} }
func (p *Proxy) directPipe(src wrappers.StreamReadCloser, dst io.WriteCloser, wait *sync.WaitGroup) { func (p *Proxy) directPipe(src wrappers.StreamReadCloser, dst io.WriteCloser,
wait *sync.WaitGroup, bufferSize int) {
defer func() { defer func() {
src.Close() // nolint: errcheck src.Close() // nolint: errcheck
dst.Close() // nolint: errcheck dst.Close() // nolint: errcheck
wait.Done() wait.Done()
}() }()
if _, err := io.Copy(dst, src); err != nil { buffer := make([]byte, bufferSize)
if _, err := io.CopyBuffer(dst, src, buffer); err != nil {
src.Logger().Warnw("Cannot pump sockets", "error", err) src.Logger().Warnw("Cannot pump sockets", "error", err)
} }
} }
+1 -1
View File
@@ -13,7 +13,7 @@ import (
var instance *stats var instance *stats
// Start starts new statisitcs server. // Start starts new statistics server.
func Start(conf *config.Config) error { func Start(conf *config.Config) error {
log := zap.S().Named("stats") log := zap.S().Named("stats")
+8 -9
View File
@@ -1,6 +1,7 @@
package telegram package telegram
import ( import (
"context"
"net" "net"
"time" "time"
@@ -10,11 +11,7 @@ import (
"github.com/9seconds/mtg/wrappers" "github.com/9seconds/mtg/wrappers"
) )
const ( const telegramDialTimeout = 10 * time.Second
telegramDialTimeout = 10 * time.Second
readBufferSize = 64 * 1024
writeBufferSize = 64 * 1024
)
type tgDialer struct { type tgDialer struct {
net.Dialer net.Dialer
@@ -32,22 +29,24 @@ func (t *tgDialer) dial(addr string) (net.Conn, error) {
if err = tcpSocket.SetNoDelay(true); err != nil { if err = tcpSocket.SetNoDelay(true); err != nil {
return nil, errors.Annotate(err, "Cannot set NO_DELAY to Telegram") return nil, errors.Annotate(err, "Cannot set NO_DELAY to Telegram")
} }
if err = tcpSocket.SetReadBuffer(readBufferSize); err != nil { if err = tcpSocket.SetReadBuffer(t.conf.WriteBufferSize); err != nil {
return nil, errors.Annotate(err, "Cannot set read buffer size on telegram socket") return nil, errors.Annotate(err, "Cannot set read buffer size on telegram socket")
} }
if err = tcpSocket.SetWriteBuffer(writeBufferSize); err != nil { if err = tcpSocket.SetWriteBuffer(t.conf.ReadBufferSize); err != nil {
return nil, errors.Annotate(err, "Cannot set write buffer size on telegram socket") return nil, errors.Annotate(err, "Cannot set write buffer size on telegram socket")
} }
return conn, nil return conn, nil
} }
func (t *tgDialer) dialRWC(addr, connID string) (wrappers.StreamReadWriteCloser, error) { func (t *tgDialer) dialRWC(ctx context.Context, cancel context.CancelFunc,
addr, connID string) (wrappers.StreamReadWriteCloser, error) {
conn, err := t.dial(addr) conn, err := t.dial(addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tgConn := wrappers.NewConn(conn, connID, wrappers.ConnPurposeTelegram, t.conf.PublicIPv4, t.conf.PublicIPv6) tgConn := wrappers.NewConn(ctx, cancel, conn, connID,
wrappers.ConnPurposeTelegram, t.conf.PublicIPv4, t.conf.PublicIPv6)
return tgConn, nil return tgConn, nil
} }
+25 -20
View File
@@ -1,6 +1,7 @@
package telegram package telegram
import ( import (
"context"
"net" "net"
"github.com/juju/errors" "github.com/juju/errors"
@@ -13,18 +14,18 @@ import (
var ( var (
directV4Addresses = map[int16][]string{ directV4Addresses = map[int16][]string{
0: []string{"149.154.175.50:443"}, 0: {"149.154.175.50:443"},
1: []string{"149.154.167.51:443"}, 1: {"149.154.167.51:443"},
2: []string{"149.154.175.100:443"}, 2: {"149.154.175.100:443"},
3: []string{"149.154.167.91:443"}, 3: {"149.154.167.91:443"},
4: []string{"149.154.171.5:443"}, 4: {"149.154.171.5:443"},
} }
directV6Addresses = map[int16][]string{ directV6Addresses = map[int16][]string{
0: []string{"[2001:b28:f23d:f001::a]:443"}, 0: {"[2001:b28:f23d:f001::a]:443"},
1: []string{"[2001:67c:04e8:f002::a]:443"}, 1: {"[2001:67c:04e8:f002::a]:443"},
2: []string{"[2001:b28:f23d:f003::a]:443"}, 2: {"[2001:b28:f23d:f003::a]:443"},
3: []string{"[2001:67c:04e8:f004::a]:443"}, 3: {"[2001:67c:04e8:f004::a]:443"},
4: []string{"[2001:b28:f23f:f005::a]:443"}, 4: {"[2001:b28:f23f:f005::a]:443"},
} }
) )
@@ -32,7 +33,8 @@ type directTelegram struct {
baseTelegram baseTelegram
} }
func (t *directTelegram) Dial(connID string, connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) { func (t *directTelegram) Dial(ctx context.Context, cancel context.CancelFunc,
connID string, connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) {
dc := connOpts.DC dc := connOpts.DC
if dc < 0 { if dc < 0 {
dc = -dc dc = -dc
@@ -40,10 +42,11 @@ func (t *directTelegram) Dial(connID string, connOpts *mtproto.ConnectionOpts) (
dc = 1 dc = 1
} }
return t.baseTelegram.dial(dc-1, connID, connOpts.ConnectionProto) return t.baseTelegram.dial(ctx, cancel, dc-1, connID, connOpts.ConnectionProto)
} }
func (t *directTelegram) Init(connOpts *mtproto.ConnectionOpts, conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) { func (t *directTelegram) Init(connOpts *mtproto.ConnectionOpts,
conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) {
obfs2, frame := obfuscated2.MakeTelegramObfuscated2Frame(connOpts) obfs2, frame := obfuscated2.MakeTelegramObfuscated2Frame(connOpts)
if _, err := conn.Write(frame); err != nil { if _, err := conn.Write(frame); err != nil {
@@ -56,12 +59,14 @@ func (t *directTelegram) Init(connOpts *mtproto.ConnectionOpts, conn wrappers.St
// NewDirectTelegram returns Telegram instance which connects directly // NewDirectTelegram returns Telegram instance which connects directly
// to Telegram bypassing middleproxies. // to Telegram bypassing middleproxies.
func NewDirectTelegram(conf *config.Config) Telegram { func NewDirectTelegram(conf *config.Config) Telegram {
return &directTelegram{baseTelegram{ return &directTelegram{
dialer: tgDialer{ baseTelegram: baseTelegram{
Dialer: net.Dialer{Timeout: telegramDialTimeout}, dialer: tgDialer{
conf: conf, Dialer: net.Dialer{Timeout: telegramDialTimeout},
conf: conf,
},
v4Addresses: directV4Addresses,
v6Addresses: directV6Addresses,
}, },
v4Addresses: directV4Addresses, }
v6Addresses: directV6Addresses,
}}
} }
+6 -3
View File
@@ -20,7 +20,8 @@ type middleTelegram struct {
conf *config.Config conf *config.Config
} }
func (t *middleTelegram) Init(connOpts *mtproto.ConnectionOpts, conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) { func (t *middleTelegram) Init(connOpts *mtproto.ConnectionOpts,
conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) {
rpcNonceConn := wrappers.NewMTProtoFrame(conn, rpc.SeqNoNonce) rpcNonceConn := wrappers.NewMTProtoFrame(conn, rpc.SeqNoNonce)
rpcNonceReq, err := t.sendRPCNonceRequest(rpcNonceConn) rpcNonceReq, err := t.sendRPCNonceRequest(rpcNonceConn)
@@ -65,7 +66,8 @@ func (t *middleTelegram) sendRPCNonceRequest(conn io.Writer) (*rpc.NonceRequest,
return rpcNonceReq, nil return rpcNonceReq, nil
} }
func (t *middleTelegram) receiveRPCNonceResponse(conn wrappers.PacketReader, req *rpc.NonceRequest) (*rpc.NonceResponse, error) { func (t *middleTelegram) receiveRPCNonceResponse(conn wrappers.PacketReader,
req *rpc.NonceRequest) (*rpc.NonceResponse, error) {
packet, err := conn.Read() packet, err := conn.Read()
if err != nil { if err != nil {
return nil, errors.Annotate(err, "Cannot read RPC nonce response") return nil, errors.Annotate(err, "Cannot read RPC nonce response")
@@ -91,7 +93,8 @@ func (t *middleTelegram) sendRPCHandshakeRequest(conn io.Writer) (*rpc.Handshake
return req, nil return req, nil
} }
func (t *middleTelegram) receiveRPCHandshakeResponse(conn wrappers.PacketReader, req *rpc.HandshakeRequest) (*rpc.HandshakeResponse, error) { func (t *middleTelegram) receiveRPCHandshakeResponse(conn wrappers.PacketReader,
req *rpc.HandshakeRequest) (*rpc.HandshakeResponse, error) {
packet, err := conn.Read() packet, err := conn.Read()
if err != nil { if err != nil {
return nil, errors.Annotate(err, "Cannot read RPC handshake response") return nil, errors.Annotate(err, "Cannot read RPC handshake response")
+4 -2
View File
@@ -2,6 +2,7 @@ package telegram
import ( import (
"bufio" "bufio"
"context"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
@@ -38,7 +39,8 @@ type middleTelegramCaller struct {
httpClient *http.Client httpClient *http.Client
} }
func (t *middleTelegramCaller) Dial(connID string, connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) { func (t *middleTelegramCaller) Dial(ctx context.Context, cancel context.CancelFunc, connID string,
connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) {
dc := connOpts.DC dc := connOpts.DC
if dc == 0 { if dc == 0 {
dc = 1 dc = 1
@@ -46,7 +48,7 @@ func (t *middleTelegramCaller) Dial(connID string, connOpts *mtproto.ConnectionO
t.dialerMutex.RLock() t.dialerMutex.RLock()
defer t.dialerMutex.RUnlock() defer t.dialerMutex.RUnlock()
return t.baseTelegram.dial(dc, connID, connOpts.ConnectionProto) return t.baseTelegram.dial(ctx, cancel, dc, connID, connOpts.ConnectionProto)
} }
func (t *middleTelegramCaller) autoUpdate() { func (t *middleTelegramCaller) autoUpdate() {
+5 -3
View File
@@ -1,6 +1,7 @@
package telegram package telegram
import ( import (
"context"
"math/rand" "math/rand"
"github.com/juju/errors" "github.com/juju/errors"
@@ -11,7 +12,7 @@ import (
// Telegram is an interface for different Telegram work modes. // Telegram is an interface for different Telegram work modes.
type Telegram interface { type Telegram interface {
Dial(string, *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) Dial(context.Context, context.CancelFunc, string, *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error)
Init(*mtproto.ConnectionOpts, wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) Init(*mtproto.ConnectionOpts, wrappers.StreamReadWriteCloser) (wrappers.Wrap, error)
} }
@@ -22,7 +23,8 @@ type baseTelegram struct {
v6Addresses map[int16][]string v6Addresses map[int16][]string
} }
func (b *baseTelegram) dial(dcIdx int16, connID string, proto mtproto.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) { func (b *baseTelegram) dial(ctx context.Context, cancel context.CancelFunc, dcIdx int16, connID string,
proto mtproto.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) {
addrs := make([]string, 2) addrs := make([]string, 2)
if proto&mtproto.ConnectionProtocolIPv6 != 0 { if proto&mtproto.ConnectionProtocolIPv6 != 0 {
@@ -37,7 +39,7 @@ func (b *baseTelegram) dial(dcIdx int16, connID string, proto mtproto.Connection
} }
for _, addr := range addrs { for _, addr := range addrs {
if conn, err := b.dialer.dialRWC(addr, connID); err == nil { if conn, err := b.dialer.dialRWC(ctx, cancel, addr, connID); err == nil {
return conn, err return conn, err
} }
} }
+65 -15
View File
@@ -1,15 +1,17 @@
package wrappers package wrappers
import ( import (
"context"
"net" "net"
"time" "time"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/9seconds/mtg/stats" "github.com/9seconds/mtg/stats"
"github.com/juju/errors"
) )
// ConnPurpose is intented to be identifier of connection purpose. We // ConnPurpose is intended to be identifier of connection purpose. We
// sometimes want to treat client/telegram connection differently (for // sometimes want to treat client/telegram connection differently (for
// logging for example). // logging for example).
type ConnPurpose uint8 type ConnPurpose uint8
@@ -36,11 +38,20 @@ const (
connTimeoutWrite = 2 * time.Minute connTimeoutWrite = 2 * time.Minute
) )
type ioResult struct {
n int
err error
}
type ioFunc func([]byte) (int, error)
// Conn is a basic wrapper for net.Conn providing the most low-level // Conn is a basic wrapper for net.Conn providing the most low-level
// logic and management as possible. // logic and management as possible.
type Conn struct { type Conn struct {
connID string
conn net.Conn conn net.Conn
ctx context.Context
cancel context.CancelFunc
connID string
logger *zap.SugaredLogger logger *zap.SugaredLogger
publicIPv4 net.IP publicIPv4 net.IP
@@ -48,28 +59,64 @@ type Conn struct {
} }
func (c *Conn) Write(p []byte) (int, error) { func (c *Conn) Write(p []byte) (int, error) {
c.conn.SetWriteDeadline(time.Now().Add(connTimeoutWrite)) // nolint: errcheck select {
n, err := c.conn.Write(p) case <-c.ctx.Done():
return 0, errors.Annotate(c.ctx.Err(), "Cannot write because context was closed")
default:
n, err := c.doIO(c.conn.Write, p, connTimeoutWrite)
c.logger.Debugw("Write to stream", "bytes", n, "error", err) c.logger.Debugw("Write to stream", "bytes", n, "error", err)
stats.EgressTraffic(n) stats.EgressTraffic(n)
return n, err return n, err
}
} }
func (c *Conn) Read(p []byte) (int, error) { func (c *Conn) Read(p []byte) (int, error) {
c.conn.SetReadDeadline(time.Now().Add(connTimeoutRead)) // nolint: errcheck select {
n, err := c.conn.Read(p) case <-c.ctx.Done():
return 0, errors.Annotate(c.ctx.Err(), "Cannot read because context was closed")
default:
n, err := c.doIO(c.conn.Read, p, connTimeoutRead)
c.logger.Debugw("Read from stream", "bytes", n, "error", err) c.logger.Debugw("Read from stream", "bytes", n, "error", err)
stats.IngressTraffic(n) stats.IngressTraffic(n)
return n, err return n, err
}
}
func (c *Conn) doIO(callback ioFunc, p []byte, timeout time.Duration) (int, error) {
resChan := make(chan ioResult, 1)
timer := time.NewTimer(timeout)
go func() {
n, err := callback(p)
resChan <- ioResult{n: n, err: err}
}()
select {
case res := <-resChan:
timer.Stop()
if res.err != nil {
c.Close()
}
return res.n, res.err
case <-c.ctx.Done():
timer.Stop()
c.Close()
return 0, errors.Annotate(c.ctx.Err(), "Cannot do IO because context is closed")
case <-timer.C:
c.Close()
return 0, errors.Annotate(c.ctx.Err(), "Timeout on IO operation")
}
} }
// Close closes underlying net.Conn instance. // Close closes underlying net.Conn instance.
func (c *Conn) Close() error { func (c *Conn) Close() error {
defer c.logger.Debugw("Close connection") defer c.logger.Debugw("Close connection")
c.cancel()
return c.conn.Close() return c.conn.Close()
} }
@@ -100,7 +147,8 @@ func (c *Conn) RemoteAddr() *net.TCPAddr {
} }
// NewConn initializes Conn wrapper for net.Conn. // NewConn initializes Conn wrapper for net.Conn.
func NewConn(conn net.Conn, connID string, purpose ConnPurpose, publicIPv4, publicIPv6 net.IP) StreamReadWriteCloser { func NewConn(ctx context.Context, cancel context.CancelFunc, conn net.Conn,
connID string, purpose ConnPurpose, publicIPv4, publicIPv6 net.IP) StreamReadWriteCloser {
logger := zap.S().With( logger := zap.S().With(
"connection_id", connID, "connection_id", connID,
"local_address", conn.LocalAddr(), "local_address", conn.LocalAddr(),
@@ -109,9 +157,11 @@ func NewConn(conn net.Conn, connID string, purpose ConnPurpose, publicIPv4, publ
).Named("conn") ).Named("conn")
wrapper := Conn{ wrapper := Conn{
logger: logger,
connID: connID,
conn: conn, conn: conn,
ctx: ctx,
cancel: cancel,
connID: connID,
logger: logger,
publicIPv4: publicIPv4, publicIPv4: publicIPv4,
publicIPv6: publicIPv6, publicIPv6: publicIPv6,
} }
+9 -7
View File
@@ -24,7 +24,8 @@ var emptyIP = [4]byte{0x00, 0x00, 0x00, 0x00}
// NewMiddleProxyCipher creates new block cipher to proxy<->telegram // NewMiddleProxyCipher creates new block cipher to proxy<->telegram
// connection. // connection.
func NewMiddleProxyCipher(conn StreamReadWriteCloser, req *rpc.NonceRequest, resp *rpc.NonceResponse, secret []byte) StreamReadWriteCloser { func NewMiddleProxyCipher(conn StreamReadWriteCloser,
req *rpc.NonceRequest, resp *rpc.NonceResponse, secret []byte) StreamReadWriteCloser {
localAddr := conn.LocalAddr() localAddr := conn.LocalAddr()
remoteAddr := conn.RemoteAddr() remoteAddr := conn.RemoteAddr()
@@ -37,11 +38,12 @@ func NewMiddleProxyCipher(conn StreamReadWriteCloser, req *rpc.NonceRequest, res
return NewBlockCipher(conn, enc, dec) return NewBlockCipher(conn, enc, dec)
} }
func deriveKeys(purpose cipherPurpose, req *rpc.NonceRequest, resp *rpc.NonceResponse, client *net.TCPAddr, remote *net.TCPAddr, secret []byte) ([]byte, []byte) { func deriveKeys(purpose cipherPurpose, req *rpc.NonceRequest, resp *rpc.NonceResponse,
client, remote *net.TCPAddr, secret []byte) ([]byte, []byte) {
message := bytes.Buffer{} message := bytes.Buffer{}
message.Write(resp.Nonce[:]) message.Write(resp.Nonce)
message.Write(req.Nonce[:]) message.Write(req.Nonce)
message.Write(req.CryptoTS[:]) message.Write(req.CryptoTS)
clientIPv4 := emptyIP[:] clientIPv4 := emptyIP[:]
serverIPv4 := emptyIP[:] serverIPv4 := emptyIP[:]
@@ -68,13 +70,13 @@ func deriveKeys(purpose cipherPurpose, req *rpc.NonceRequest, resp *rpc.NonceRes
binary.LittleEndian.PutUint16(port[:], uint16(remote.Port)) binary.LittleEndian.PutUint16(port[:], uint16(remote.Port))
message.Write(port[:]) message.Write(port[:])
message.Write(secret) message.Write(secret)
message.Write(resp.Nonce[:]) message.Write(resp.Nonce)
if client.IP.To4() == nil { if client.IP.To4() == nil {
message.Write(client.IP.To16()) message.Write(client.IP.To16())
message.Write(remote.IP.To16()) message.Write(remote.IP.To16())
} }
message.Write(req.Nonce[:]) message.Write(req.Nonce)
data := message.Bytes() data := message.Bytes()
md5sum := md5.Sum(data[1:]) // nolint: gas md5sum := md5.Sum(data[1:]) // nolint: gas
+2 -1
View File
@@ -61,7 +61,8 @@ func (m *MTProtoFrame) Read() ([]byte, error) { // nolint: gocyclo
"messageLength", messageLength, "messageLength", messageLength,
"sequence_number", m.readSeqNo, "sequence_number", m.readSeqNo,
) )
if messageLength%4 != 0 || messageLength < mtprotoFrameMinMessageLength || messageLength > mtprotoFrameMaxMessageLength { if messageLength%4 != 0 || messageLength < mtprotoFrameMinMessageLength ||
messageLength > mtprotoFrameMaxMessageLength {
return nil, errors.Errorf("Incorrect frame message length %d", messageLength) return nil, errors.Errorf("Incorrect frame message length %d", messageLength)
} }
+4 -3
View File
@@ -56,7 +56,7 @@ func (m *MTProtoProxy) Read() ([]byte, error) {
case bytes.Equal(tag, rpc.TagSimpleAck): case bytes.Equal(tag, rpc.TagSimpleAck):
return m.readSimpleAck(packet) return m.readSimpleAck(packet)
case bytes.Equal(tag, rpc.TagCloseExt): case bytes.Equal(tag, rpc.TagCloseExt):
return m.readCloseExt(packet) return m.readCloseExt()
} }
return nil, errors.Errorf("Unknown RPC answer %v", tag) return nil, errors.Errorf("Unknown RPC answer %v", tag)
@@ -91,7 +91,7 @@ func (m *MTProtoProxy) readSimpleAck(data []byte) ([]byte, error) {
return data, nil return data, nil
} }
func (m *MTProtoProxy) readCloseExt(data []byte) ([]byte, error) { func (m *MTProtoProxy) readCloseExt() ([]byte, error) {
m.logger.Debugw("Read RPC_CLOSE_EXT", "counter", m.readCounter) m.logger.Debugw("Read RPC_CLOSE_EXT", "counter", m.readCounter)
return nil, errors.New("Connection has been closed remotely by RPC call") return nil, errors.New("Connection has been closed remotely by RPC call")
@@ -150,7 +150,8 @@ func (m *MTProtoProxy) Close() error {
} }
// NewMTProtoProxy creates new RPC wrapper. // NewMTProtoProxy creates new RPC wrapper.
func NewMTProtoProxy(conn PacketReadWriteCloser, connOpts *mtproto.ConnectionOpts, adTag []byte) (PacketReadWriteCloser, error) { func NewMTProtoProxy(conn PacketReadWriteCloser, connOpts *mtproto.ConnectionOpts,
adTag []byte) (PacketReadWriteCloser, error) {
req, err := rpc.NewProxyRequest(connOpts.ClientAddr, conn.LocalAddr(), connOpts, adTag) req, err := rpc.NewProxyRequest(connOpts.ClientAddr, conn.LocalAddr(), connOpts, adTag)
if err != nil { if err != nil {
return nil, errors.Annotate(err, "Cannot create new RPC proxy request") return nil, errors.Annotate(err, "Cannot create new RPC proxy request")
+11 -3
View File
@@ -1,6 +1,7 @@
package wrappers package wrappers
import ( import (
"bytes"
"crypto/cipher" "crypto/cipher"
"net" "net"
@@ -28,10 +29,17 @@ func (s *StreamCipher) Read(p []byte) (int, error) {
} }
func (s *StreamCipher) Write(p []byte) (int, error) { func (s *StreamCipher) Write(p []byte) (int, error) {
encrypted := make([]byte, len(p)) buf := streamCipherBufferPool.Get().(*bytes.Buffer)
s.encryptor.XORKeyStream(encrypted, p) defer streamCipherBufferPool.Put(buf)
return s.conn.Write(encrypted) buf.Reset()
buf.Grow(len(p))
buf.Write(p)
data := buf.Bytes()
s.encryptor.XORKeyStream(data, data)
return s.conn.Write(data)
} }
// Logger returns an instance of the logger for this wrapper. // Logger returns an instance of the logger for this wrapper.
+16
View File
@@ -0,0 +1,16 @@
package wrappers
import (
"bytes"
"sync"
)
var streamCipherBufferPool sync.Pool
func init() {
streamCipherBufferPool = sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
}
}