Преглед на файлове

Merge remote-tracking branch 'origin/master' into stable

tags/1.0
9seconds преди 6 години
родител
ревизия
68e1d66017
променени са 100 файла, в които са добавени 3667 реда и са изтрити 2619 реда
  1. 1
    3
      .travis.yml
  2. 1
    1
      Dockerfile
  3. 1
    5
      Makefile
  4. 111
    141
      README.md
  5. 23
    24
      antireplay/cache.go
  6. 0
    9
      antireplay/hasher.go
  7. 20
    0
      antireplay/init.go
  8. 26
    0
      cli/generate.go
  9. 102
    0
      cli/proxy.go
  10. 43
    0
      cli/utils.go
  11. 0
    15
      client/client.go
  12. 0
    61
      client/direct.go
  13. 0
    42
      client/middle.go
  14. 190
    179
      config/config.go
  15. 38
    12
      config/global_ips.go
  16. 36
    3
      config/urls.go
  17. 6
    0
      conntypes/acks.go
  18. 5
    0
      conntypes/dc.go
  19. 24
    0
      conntypes/id.go
  20. 3
    0
      conntypes/packet.go
  21. 20
    0
      conntypes/protocol.go
  22. 27
    0
      conntypes/type.go
  23. 14
    0
      conntypes/wrap_interfaces.go
  24. 41
    0
      conntypes/wrap_packet_ack_interfaces.go
  25. 51
    0
      conntypes/wrap_packet_interfaces.go
  26. 56
    0
      conntypes/wrap_stream_interfaces.go
  27. 134
    0
      faketls/client_protocol.go
  28. 30
    0
      faketls/consts.go
  29. 14
    24
      go.mod
  30. 94
    31
      go.sum
  31. 163
    0
      hub/connection.go
  32. 70
    0
      hub/connection_list.go
  33. 40
    0
      hub/hub.go
  34. 24
    0
      hub/init.go
  35. 7
    0
      hub/interface.go
  36. 81
    0
      hub/mux.go
  37. 79
    0
      hub/proxy_conn.go
  38. 101
    203
      main.go
  39. 0
    87
      mtproto/connection_options.go
  40. 102
    0
      mtproto/protocol.go
  41. 0
    0
      mtproto/rpc/consts.go
  42. 3
    24
      mtproto/rpc/handshake_request.go
  43. 7
    8
      mtproto/rpc/handshake_response.go
  44. 8
    10
      mtproto/rpc/nonce_request.go
  45. 8
    8
      mtproto/rpc/nonce_response.go
  46. 27
    20
      mtproto/rpc/proxy_flags.go
  47. 0
    105
      mtproto/rpc/proxy_request.go
  48. 53
    0
      mtproto/rpc/proxy_response.go
  49. 5
    3
      ntp/ntp.go
  50. 113
    0
      obfuscated2/client_protocol.go
  51. 22
    89
      obfuscated2/frame.go
  52. 0
    106
      obfuscated2/frame_test.go
  53. 0
    81
      obfuscated2/obfuscated2.go
  54. 0
    97
      obfuscated2/obfuscated2_test.go
  55. 68
    0
      obfuscated2/telegram_protocol.go
  56. 12
    0
      protocol/interfaces.go
  57. 18
    0
      protocol/request.go
  58. 49
    0
      proxy/direct.go
  59. 68
    0
      proxy/middle.go
  60. 55
    135
      proxy/proxy.go
  61. 0
    35
      run-mtg.sh
  62. 58
    0
      run.sh
  63. 0
    76
      stats/channels.go
  64. 0
    28
      stats/init.go
  65. 50
    0
      stats/interfaces.go
  66. 57
    0
      stats/multi_stats.go
  67. 0
    91
      stats/prometheus.go
  68. 0
    40
      stats/server.go
  69. 22
    156
      stats/stats.go
  70. 131
    0
      stats/stats_prometheus.go
  71. 194
    0
      stats/stats_statsd.go
  72. 0
    77
      stats/statsd.go
  73. 109
    0
      telegram/api/addresses.go
  74. 40
    0
      telegram/api/api.go
  75. 24
    0
      telegram/api/secret.go
  76. 65
    0
      telegram/base.go
  77. 0
    52
      telegram/dialer.go
  78. 12
    50
      telegram/direct.go
  79. 42
    0
      telegram/init.go
  80. 8
    0
      telegram/interfaces.go
  81. 41
    104
      telegram/middle.go
  82. 0
    191
      telegram/middle_caller.go
  83. 0
    68
      telegram/telegram.go
  84. 86
    0
      tlstypes/client_hello.go
  85. 79
    0
      tlstypes/consts.go
  86. 37
    0
      tlstypes/handshake.go
  87. 85
    0
      tlstypes/record.go
  88. 95
    0
      tlstypes/server_hello.go
  89. 26
    0
      utils/init_tcp.go
  90. 0
    21
      utils/read_current_data.go
  91. 21
    0
      utils/read_full.go
  92. 1
    1
      utils/reverse_bytes.go
  93. 24
    0
      utils/rlimit.go
  94. 7
    0
      utils/rlimit_windows.go
  95. 25
    0
      utils/signal_context.go
  96. 23
    0
      utils/signal_context_windows.go
  97. 11
    0
      utils/stream_cipher.go
  98. 0
    4
      utils/uint24.go
  99. 0
    99
      wrappers/blockcipher.go
  100. 0
    0
      wrappers/conn.go

+ 1
- 3
.travis.yml Целия файл

@@ -5,8 +5,7 @@ sudo: false
5 5
 dist: trusty
6 6
 
7 7
 go:
8
-  - "1.11.x"
9
-  - 1.12.x
8
+  - 1.13.x
10 9
   - master
11 10
 
12 11
 before_script: make prepare
@@ -14,7 +13,6 @@ before_script: make prepare
14 13
 script:
15 14
   - make all
16 15
   - make lint
17
-  - make test
18 16
 
19 17
 matrix:
20 18
   allow_failures:

+ 1
- 1
Dockerfile Целия файл

@@ -1,7 +1,7 @@
1 1
 ###############################################################################
2 2
 # BUILD STAGE
3 3
 
4
-FROM golang:1.12-alpine
4
+FROM golang:1.13-alpine
5 5
 
6 6
 RUN set -x \
7 7
   && apk --no-cache --update add \

+ 1
- 5
Makefile Целия файл

@@ -4,7 +4,7 @@ APP_NAME     := $(IMAGE_NAME)
4 4
 
5 5
 CC_BINARIES  := $(shell bash -c "echo -n $(APP_NAME)-{linux,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}")
6 6
 
7
-GOLANGCI_LINT_VERSION := v1.15.0
7
+GOLANGCI_LINT_VERSION := v1.21.0
8 8
 
9 9
 VERSION_GO         := $(shell go version)
10 10
 VERSION_DATE       := $(shell date -Ru)
@@ -51,10 +51,6 @@ crosscompile: $(CC_BINARIES)
51 51
 crosscompile-dir:
52 52
 	@rm -rf "$(CC_DIR)" && mkdir -p "$(CC_DIR)"
53 53
 
54
-.PHONY: test
55
-test: vendor
56
-	@$(MOD_ON) go test -v ./...
57
-
58 54
 .PHONY: lint
59 55
 lint: vendor
60 56
 	@$(MOD_OFF) golangci-lint run

+ 111
- 141
README.md Целия файл

@@ -6,6 +6,8 @@ Bullshit-free MTPROTO proxy for Telegram
6 6
 [![Go Report Card](https://goreportcard.com/badge/github.com/9seconds/mtg)](https://goreportcard.com/report/github.com/9seconds/mtg)
7 7
 [![Docker Build Status](https://img.shields.io/docker/build/nineseconds/mtg.svg)](https://hub.docker.com/r/nineseconds/mtg/)
8 8
 
9
+**Please see a guide on upgrading to 1.0 at the end of this README.**
10
+
9 11
 # Rationale
10 12
 
11 13
 There are several available proxies for Telegram MTPROTO available. Here
@@ -15,33 +17,33 @@ are the most notable:
15 17
 * [Python](https://github.com/alexbers/mtprotoproxy)
16 18
 * [Erlang](https://github.com/seriyps/mtproto_proxy)
17 19
 
18
-Almost all of them follow the way how official proxy was build. This
19
-includes support of multiple secrets, support of promoted channels etc.
20
+Almost all of them follow the way how official proxy was built. This
21
+includes support of multiple secrets, support of promoted channels, etc.
20 22
 
21 23
 mtg is an implementation in golang which is intended to be:
22 24
 
23 25
 * **Lightweight**
24
-  It has to consume as less resources as possible but not by losing
26
+  It has to consume as few resources as possible but not by losing
25 27
   maintainability.
26 28
 * **Easily deployable**
27 29
   I strongly believe that Telegram proxies should follow the way of
28 30
   ShadowSocks: promoted channels is a strange way of doing business
29 31
   I suppose. I think the only viable way is to have a proxy with
30 32
   minimum configuration which should work everywhere.
31
-* **Single secret**
32
-  I think that multiple secrets solves no problems and just complexify
33
-  software. I also believe that in case of throwout proxies, this feature
34
-  is useless luxury.
33
+* **A single secret**
34
+  I think that multiple secrets solve no problems and just complexify
35
+  software. I also believe that in the case of throwout proxies, this
36
+  feature is a useless luxury.
35 37
 * **Minimum docker image size**
36
-  Official image is less than 3 megabytes. Literally.
38
+  Official image is less than 3.5 megabytes. Literally.
37 39
 * **No management WebUI**
38
-  This is an implementation of simple lightweight proxy. I won't do that.
40
+  This is an implementation of a simple lightweight proxy. I won't do that.
39 41
 
40 42
 This proxy supports 2 modes of work: direct connection to Telegram and
41 43
 promoted channel mode. If you do not need promoted channels, I would
42
-recommend you to go with direct mode: this is way more robust.
44
+recommend you to go with direct mode: this way is more robust.
43 45
 
44
-To run proxy in direct mode, all you need to do is just provide a
46
+To run a proxy in direct mode, all you need to do is just provide a
45 47
 secret. If you do not provide ADTag as a second parameter, promoted
46 48
 channels mode won't be activated.
47 49
 
@@ -102,95 +104,71 @@ Also, there is another project on Ansible Galaxy: https://galaxy.ansible.com/iva
102 104
 
103 105
 # Configuration
104 106
 
105
-Basically, to run this tool you need to configure as less as possible.
107
+To run this tool you need to configure as less as possible. Telegram
108
+clients support 3 different secret types:
109
+
110
+* Simple - basically, it is just a flow of frames ciphered by AES-CTR stream
111
+  cipher.
112
+* Secured - the same stream as simple but with some random noise to prevent
113
+  statistical analysis of traffic flow.
114
+* FakeTLS - this mode envelops telegram stream in TLS so it looks (in theory)
115
+  the same as any TLS1.3 traffic from DPI point of view.
116
+
117
+If you do not have preferences, go with FakeTLS or at least secured.
118
+Simple mode is a little bit naive and traffic flow can be easily
119
+identified as Telegram one.
120
+
121
+Unlike the rest of implementation, mtg is quite strict about the
122
+execution mode: if you run a proxy instance with FakeTLS secret, you
123
+can't connect to it with simple or secured clients. You can't connect
124
+to the proxy with secured secret with FakeTLS key. It forces one mode
125
+of working. So, unfortunately, there is no way how to connect to the
126
+deployed proxy with another secret (if you know how to construct and
127
+convert them). But at the same time, old clients can't connect so they
128
+won't expose the type of the service.
106 129
 
107 130
 First, you need to generate a secret:
108 131
 
109 132
 ```console
110
-openssl rand -hex 16
133
+$ mtg generate-secret simple
134
+52a493bdfb90eea55739eabff2d92a14
111 135
 ```
112 136
 
113
-or
114
-
115 137
 ```console
116
-head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' '
138
+$ mtg generate-secret secured
139
+ddf05fb7acb549be047a7c585116581418
117 140
 ```
118 141
 
119
-## Secure mode
120
-
121
-_tl;dr - use secret mode for all new installation of proxy; only clients
122
-with dd-secrets will be able to connect. This mode abuses attempts to
123
-DPI MTPROTO traffic._
124
-
125
-Secure mode is not the best name and of course, it creates a lot of
126
-confusion. To explain what it means, we need to tell you some bits on
127
-dd-secrets.
128
-
129
-MTPROTO proxy protocol requires 16-byte secret. You usually
130
-propagate it as a 32 characters hexadecimal string like
131
-`282831900f371ca182feb0e4e1e1aeef` (if you decode this string
132
-to bytes, you will get a real secret which is used in the
133
-protocol). Everything went quite good until the moment when
134
-developers found an evidence that [protocol is quite weak to
135
-DPI](https://github.com/TelegramMessenger/MTProxy/issues/35) and some
136
-enthusiasts even created simple proofs of concepts on [detecting MTPROTO
137
-traffic](https://github.com/darkk/poormansmtproto).
138
-
139
-Telegram team has introduced a patch called dd-secrets. If you have
140
-a secret `282831900f371ca182feb0e4e1e1aeef` then your dd-secret is
141
-`dd282831900f371ca182feb0e4e1e1aeef`. That is, you just add dd prefix
142
-to the secret, prepend it with dd. In that case, original secret
143
-`282831900f371ca182feb0e4e1e1aeef` is used but client and server start
144
-to act a little bit different: they start to add random noise to the
145
-packets so they can't be detected by their length. In order to keep
146
-backward compatibility, all proxies a quite liberal to the secrets to
147
-use: if the client uses plain secret, without dd prefix, they fall back
148
-to the normal behavior. If dd-secret is used (proxy can extract this
149
-information on the handshake), then more secured, the hardened behavior
150
-is used.
151
-
152
-Yes, it can look like a hack but it is as it is.
153
-
154
-Now going back to the secure mode: if you do not pass `-s` flag to the
155
-mtg, then it checks what mode is requested by the client. If the client
156
-uses plain secret, without dd prefix, then proxy falls back to the
157
-original behavior and do not play with paddings. If dd-secret is used
158
-and client demands this mode, then proxy start to add that random noise
159
-to the packets. But if you pass `-s`, then only clients with dd-secrets
160
-can connect. How to migrate existing clients then? If a client is new
161
-enough, you can just prepend the secret with dd string in the settings.
162
-If it is an old guy, then nothing to do, sorry.
163
-
164
-Why this mode matters? We do not have evidence but there is quite a big
165
-suspicion that some ISPs start to filter MTPROTO traffic. If they detect
166
-the IP address which acts as a proxy, they block it and no clients can
167
-use this proxy. This is an attempt to prevent such a situation.
168
-
169
-General rule of thumb: with all new installation of proxies I would
170
-advise to go with secure mode by default. But please do remember that it
171
-means that clients, which do not pass dd-prefix to their secrets, will
172
-not be able to connect. *Secure mode works only with dd-prefixes!*
173
-
174
-Oneliners to generate such secrets:
175
-
176 142
 ```console
177
-echo dd$(openssl rand -hex 16)
143
+$ mtg generate-secret -c google.com tls
144
+ee852380f362a09343efb4690c4e17862e676f6f676c652e636f6d
178 145
 ```
179 146
 
180
-or
147
+Or, if you prefer docker:
181 148
 
182 149
 ```console
183
-echo dd$(head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' ')
150
+$ docker run --rm nineseconds/mtg generate-secret tls -c bing.com
151
+eedf71035a8ed48a623d8e83e66aec4d0562696e672e636f6d
184 152
 ```
185 153
 
186
-
187 154
 ## Antireplay cache
188 155
 
189
-In order to prevent replay attacks, we have internal storage of first
190
-frames messages for connected clients. These frames are generated
191
-randomly by design and we have negligible possibility of duplication
192
-(probability is 1/(2^64)) but it could be quite effective in order to
193
-prevent replays.
156
+To prevent replay attacks, we have internal storage of first frames
157
+messages for connected clients. These frames are generated randomly
158
+by design and we have the negligible possibility of duplication
159
+(probability is 1/(2^64)) but it could be quite effective to prevent
160
+replays.
161
+
162
+
163
+## FakeTLS
164
+
165
+If you run this a proxy in faketls mode, this proxy will try to hide
166
+itself cloaking a host provided as a part of the generated secret. It
167
+means that if you cloak google.com then you can curl this proxy and
168
+you'll get a google.com response back.
169
+
170
+mtg proxies L3 traffic. In other words, only TCP, without interfering in
171
+TLS, HTTP or any other high-level protocol.
194 172
 
195 173
 
196 174
 ## Environment variables
@@ -199,30 +177,25 @@ It is possible to configure this tool using environment variables. You
199 177
 can configure any flag but not secret or adtag. Here is the list of
200 178
 supported environment variables:
201 179
 
202
-| Environment variable          | Corresponding flags         | Default value                     | Description                                                                                                                                                                                                                                                                |
203
-|-------------------------------|-----------------------------|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
204
-| `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.                                                                                                              |
205
-| `MTG_VERBOSE`                 | `-v`, `--verbose`           | `false`                           | Run in verbose mode. This is way less chatty than debug mode.                                                                                                                                                                                                              |
206
-| `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.                                                                                                                            |
207
-| `MTG_PORT`                    | `-p`, `--bind-port`         | `3128`                            | Which port should we bind to (listen on).                                                                                                                                                                                                                                  |
208
-| `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. |
209
-| `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.                                                                                                         |
210
-| `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. |
211
-| `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.                                                                                                         |
212
-| `MTG_STATS_IP`                | `-t`, `--stats-ip`          | `127.0.0.1`                       | Which IP should we bind the internal statistics HTTP server.                                                                                                                                                                                                               |
213
-| `MTG_STATS_PORT`              | `-q`, `--stats-port`        | `3129`                            | Which port should we bind the internal statistics HTTP server.                                                                                                                                                                                                             |
214
-| `MTG_STATSD_IP`               | `--statsd-ip`               |                                   | IP/host addresses of statsd service. No defaults, by defaults we do not send anything there.                                                                                                                                                                               |
215
-| `MTG_STATSD_PORT`             | `--statsd-port`             | `8125`                            | Which port should we use to work with statsd.                                                                                                                                                                                                                              |
216
-| `MTG_STATSD_NETWORK`          | `--statsd-network`          | `udp`                             | Which protocol should we use to work with statsd. Possible options are `udp` and `tcp`.                                                                                                                                                                                    |
217
-| `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`.                                                                                                                                    |
218
-| `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`.                                              |
219
-| `MTG_STATSD_TAGS`             | `--statsd-tags`             |                                   | Which tags should we send to statsd with our metrics. Please specify them as `key=value` pairs.                                                                                                                                                                            |
220
-| `MTG_PROMETHEUS_PREFIX`       | `--prometheus-prefix`       | `mtg`                             | Which namespace should be used for prometheus metrics.                                                                                                                                                                                                                     |
221
-| `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.                                                                                                                                                    |
222
-| `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.                                                                                                                                                                      |
223
-| `MTG_SECURE_ONLY`             | `-s`, `--secure-only`       | `false`                           | Support only clients with secure mode (i.e only clients with dd-secrets).                                                                                                                                                                                                  |
224
-| `MTG_ANTIREPLAY_MAXSIZE`      | `anti-replay-max-size`      | `128`                             | Max size of antireplay cache in megabytes.                                                                                                                                                                                                                                 |
225
-| `MTG_ANTIREPLAY_EVICTIONTIME` | `anti-replay-eviction-time` | `168h`                            | Eviction time for antireplay cache entries.                                                                                                                                                                                                                                |
180
+| Environment variable          | Corresponding flags          | Default value                     | Description                                                                                                                                                                                                                                                                     |
181
+|-------------------------------|------------------------------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
182
+| `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.                                                                                                                   |
183
+| `MTG_VERBOSE`                 | `-v`, `--verbose`            | `false`                           | Run in verbose mode. This is way less chatty than debug mode.                                                                                                                                                                                                                   |
184
+| `MTG_BIND`                    | `-b`, `--bind`               | `0.0.0.0:3128`                    | Which host/port pair should we bind to (listen on).                                                                                                                                                                                                                             |
185
+| `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. |
186
+| `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. |
187
+| `MTG_STATS_BIND`              | `-t`, `--stats-bind`         | `127.0.0.1:3129`                  | Which hist:port should we bind the internal statistics HTTP server (Prometheus).                                                                                                                                                                                                |
188
+| `MTG_STATS_NAMESPACE`         | `--stats-namespace`          | `mtg`                             | Which namespace should be used for prometheus metrics.                                                                                                                                                                                                                          |
189
+| `MTG_STATSD_ADDR`             | `--statsd-addr`              |                                   | IP:host addresses of statsd service. No defaults, by defaults we do not send anything there.                                                                                                                                                                                    |
190
+| `MTG_STATSD_PORT`             | `--statsd-port`              | `8125`                            | Which port should we use to work with statsd.                                                                                                                                                                                                                                   |
191
+| `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`.                                                                                                                                         |
192
+| `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`.                                                   |
193
+| `MTG_STATSD_TAGS`             | `--statsd-tags`              |                                   | Which tags should we send to statsd with our metrics. Please specify them as `key=value` pairs.                                                                                                                                                                                 |
194
+| `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.                                                                                                                                                         |
195
+| `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.                                                                                                                                                                           |
196
+| `MTG_ANTIREPLAY_MAXSIZE`      | `--anti-replay-max-size`     | `128MB`                           | Max size of antireplay cache.                                                                                                                                                                                                                                                   |
197
+| `MTG_CLOAK_PORT`              | `--cloak-port`               | `443`                             | Which port we should use to connect to cloaked host in FakeTLS mode.                                                                                                                                                                                                            |
198
+| `MTG_MULTIPLEX_PERCONNECTION` | `--multiplex-per-connection` | `50`                              | How many client connections can share a single Telegram connection in adtag mode                                                                                                                                                                                                |
226 199
 
227 200
 Usually you want to modify only read/write buffer sizes. If you feel
228 201
 that proxy is slow, try to increase both sizes giving more priority to
@@ -237,35 +210,26 @@ userspace.
237 210
 Now run the tool:
238 211
 
239 212
 ```console
240
-mtg <secret>
213
+$ mtg run <secret>
241 214
 ```
242 215
 
243 216
 How to run the tool with ADTag:
244 217
 
245 218
 ```console
246
-mtg <secret> <adtag>
219
+$ mtg run <secret> <adtag>
247 220
 ```
248 221
 
249 222
 This tool will listen on port 3128 by default with the given secret.
250 223
 
251
-# One-line runner
252 224
 
253
-```console
254
-docker run --name mtg --restart=unless-stopped -p 3128:3128 -p 3129:3129 -d nineseconds/mtg:stable $(openssl rand -hex 16)
255
-```
225
+# oneliner to run this proxy
256 226
 
257
-or in secret mode:
227
+Please ensure that docker is installed. After that just execute
258 228
 
259 229
 ```console
260
-docker run --name mtg --restart=unless-stopped -p 3128:3128 -p 3129:3129 -d nineseconds/mtg:stable dd$(openssl rand -hex 16)
230
+curl -sfL --compressed https://raw.githubusercontent.com/9seconds/mtg/master/run.sh | bash
261 231
 ```
262 232
 
263
-You will have this tool up and running on port 3128. Now curl
264
-`localhost:3129` to get `tg://` links or do `docker logs mtg`. Also,
265
-port 3129 will show you some statistics if you are interested in.
266
-
267
-Also, you can use [run-mtg.sh](https://github.com/9seconds/mtg/blob/master/run-mtg.sh) script
268
-
269 233
 
270 234
 # statsd integration
271 235
 
@@ -278,31 +242,37 @@ and [Datadog](https://docs.datadoghq.com/developers/dogstatsd/).
278 242
 
279 243
 All metrics are gauges. Here is the list of metrics and their meaning:
280 244
 
281
-| Metric name                     | Unit    | Description                                               |
282
-|---------------------------------|---------|-----------------------------------------------------------|
283
-| `connections.abridged.ipv4`     | number  | The number of active abridged IPv4 connections            |
284
-| `connections.abridged.ipv6`     | number  | The number of active abridged IPv6 connections            |
285
-| `connections.intermediate.ipv4` | number  | The number of active intermediate IPv4 connections        |
286
-| `connections.intermediate.ipv6` | number  | The number of active intermediate IPv6 connections        |
287
-| `connections.secure.ipv4`       | number  | The number of active secure intermediate IPv4 connections |
288
-| `connections.secure.ipv6`       | number  | The number of active secure intermediate IPv6 connections |
289
-| `crashes`                       | number  | An amount of crashes in client handlers                   |
290
-| `traffic.ingress`               | bytes   | Ingress traffic from the start of application (incoming)  |
291
-| `traffic.egress`                | bytes   | Egress traffic from the start of application (outgoing)   |
292
-| `speed.ingress`                 | bytes/s | Ingress bandwidth of the latest second (incoming traffic) |
293
-| `speed.egress`                  | bytes/s | Egress bandwidth of the latest second (outgoing traffic)  |
245
+| Metric name            | Unit    | Description                                |
246
+|------------------------|---------|--------------------------------------------|
247
+| `connections`          | number  | The number of active connections.          |
248
+| `telegram_connections` | number  | The number of active telegram connections. |
249
+| `crashes`              | number  | An amount of crashes in client handlers.   |
250
+| `traffic.egress`       | bytes   | Traffic from the start of application.     |
251
+| `replay_attacks`       | number  | The number of prevented replay attacks.    |
294 252
 
295 253
 All metrics are prefixed with given prefix. Default prefix is `mtg`.
296
-With such prefix metric name `traffic.ingress`, for example, would be
297
-`mtg.traffic.ingress`.
254
+Also, metrics provide tags (ipv4/ipv6, dc indexes etc).
298 255
 
299 256
 
300 257
 # Prometheus integration
301 258
 
302 259
 [Prometheus](https://prometheus.io) integration comes out of
303
-the box, you do not need to setup anything special. Prometheus
304
-scrape endpoint lives on the same IP/port where generic stats
305
-service (`http://${MTG_STATS_IP}:${MTG_STATS_PORT}`) but on
306
-`/prometheus` path. So, if you access http stats service as `curl
307
-http://localhost:3129/`, then your prometheus endpoint is `curl
308
-http://localhost:3129/prometheus/`.
260
+the box, you do not need to setup anything special.
261
+
262
+
263
+# Upgrade to 1.0
264
+
265
+Version 1.0 breaks compatibility with previous versions so please read
266
+this chapter carefully:
267
+
268
+1. mtg now uses subcommands. Please use `mtg run` instead of just
269
+   `mtg` to run a proxy.
270
+2. Options which set host and port separately were removed in a
271
+   favor of fused `host:port` options.
272
+3. Own stats server was removed. Prometheus endpoint is moved to
273
+   default stats endpoint.
274
+4. It is possible to connect to this proxy only with a secret which
275
+   was used to run it. So, no backward compatibility of clients.
276
+5. Multiplexing involves connectivity with middle proxies and involves
277
+   the most complex code path of this proxy. To avoid potential bugs,
278
+   we still recommend using direct mode.

+ 23
- 24
antireplay/cache.go Целия файл

@@ -1,37 +1,36 @@
1 1
 package antireplay
2 2
 
3
-import (
4
-	"github.com/allegro/bigcache"
5
-	"github.com/juju/errors"
3
+import "github.com/VictoriaMetrics/fastcache"
6 4
 
7
-	"github.com/9seconds/mtg/config"
5
+var (
6
+	prefixObfuscated2 = []byte{0x00}
7
+	prefixTLS         = []byte{0x01}
8 8
 )
9 9
 
10
-// Cache defines storage for obfuscated2 handshake frames.
11
-type Cache struct {
12
-	cache *bigcache.BigCache
10
+type cache struct {
11
+	data *fastcache.Cache
13 12
 }
14 13
 
15
-func (a Cache) Add(frame []byte) {
16
-	a.cache.Set(string(frame), nil) // nolint: errcheck
14
+func (c *cache) AddObfuscated2(data []byte) {
15
+	c.data.Set(keyObfuscated2(data), nil)
17 16
 }
18 17
 
19
-func (a Cache) Has(frame []byte) bool {
20
-	_, err := a.cache.Get(string(frame))
18
+func (c *cache) AddTLS(data []byte) {
19
+	c.data.Set(keyTLS(data), nil)
20
+}
21
+
22
+func (c *cache) HasObfuscated2(data []byte) bool {
23
+	return c.data.Has(keyObfuscated2(data))
24
+}
25
+
26
+func (c *cache) HasTLS(data []byte) bool {
27
+	return c.data.Has(keyTLS(data))
28
+}
21 29
 
22
-	return err == nil
30
+func keyObfuscated2(data []byte) []byte {
31
+	return append(prefixObfuscated2, data...)
23 32
 }
24 33
 
25
-func NewCache(config *config.Config) (Cache, error) {
26
-	cache, err := bigcache.NewBigCache(bigcache.Config{
27
-		Shards:           1024,
28
-		LifeWindow:       config.AntiReplayEvictionTime,
29
-		Hasher:           hasher{},
30
-		HardMaxCacheSize: config.AntiReplayMaxSize,
31
-	})
32
-	if err != nil {
33
-		return Cache{}, errors.Annotate(err, "Cannot make cache")
34
-	}
35
-
36
-	return Cache{cache}, nil
34
+func keyTLS(data []byte) []byte {
35
+	return append(prefixTLS, data...)
37 36
 }

+ 0
- 9
antireplay/hasher.go Целия файл

@@ -1,9 +0,0 @@
1
-package antireplay
2
-
3
-import "github.com/cespare/xxhash"
4
-
5
-type hasher struct{}
6
-
7
-func (h hasher) Sum64(value string) uint64 {
8
-	return xxhash.Sum64String(value)
9
-}

+ 20
- 0
antireplay/init.go Целия файл

@@ -0,0 +1,20 @@
1
+package antireplay
2
+
3
+import (
4
+	"sync"
5
+
6
+	"github.com/VictoriaMetrics/fastcache"
7
+
8
+	"mtg/config"
9
+)
10
+
11
+var (
12
+	Cache    cache
13
+	initOnce sync.Once
14
+)
15
+
16
+func Init() {
17
+	initOnce.Do(func() {
18
+		Cache.data = fastcache.New(config.C.AntiReplayMaxSize)
19
+	})
20
+}

+ 26
- 0
cli/generate.go Целия файл

@@ -0,0 +1,26 @@
1
+package cli
2
+
3
+import (
4
+	"crypto/rand"
5
+	"encoding/hex"
6
+
7
+	"mtg/config"
8
+)
9
+
10
+func Generate(secretType, hostname string) {
11
+	data := make([]byte, config.SimpleSecretLength)
12
+	if _, err := rand.Read(data); err != nil {
13
+		panic(err)
14
+	}
15
+
16
+	secret := hex.EncodeToString(data)
17
+
18
+	switch secretType {
19
+	case "simple":
20
+		PrintStdout(secret)
21
+	case "secured":
22
+		PrintStdout("dd" + secret)
23
+	default:
24
+		PrintStdout("ee" + secret + hex.EncodeToString([]byte(hostname)))
25
+	}
26
+}

+ 102
- 0
cli/proxy.go Целия файл

@@ -0,0 +1,102 @@
1
+package cli
2
+
3
+import (
4
+	"net"
5
+	"os"
6
+	"time"
7
+
8
+	"go.uber.org/zap"
9
+	"go.uber.org/zap/zapcore"
10
+
11
+	"mtg/antireplay"
12
+	"mtg/config"
13
+	"mtg/faketls"
14
+	"mtg/hub"
15
+	"mtg/ntp"
16
+	"mtg/obfuscated2"
17
+	"mtg/proxy"
18
+	"mtg/stats"
19
+	"mtg/telegram"
20
+	"mtg/utils"
21
+)
22
+
23
+func Proxy() error { // nolint: funlen
24
+	ctx := utils.GetSignalContext()
25
+
26
+	atom := zap.NewAtomicLevel()
27
+
28
+	switch {
29
+	case config.C.Debug:
30
+		atom.SetLevel(zapcore.DebugLevel)
31
+	case config.C.Verbose:
32
+		atom.SetLevel(zapcore.InfoLevel)
33
+	default:
34
+		atom.SetLevel(zapcore.ErrorLevel)
35
+	}
36
+
37
+	encoderCfg := zap.NewProductionEncoderConfig()
38
+	logger := zap.New(zapcore.NewCore(
39
+		zapcore.NewJSONEncoder(encoderCfg),
40
+		zapcore.Lock(os.Stderr),
41
+		atom,
42
+	))
43
+
44
+	zap.ReplaceGlobals(logger)
45
+	defer logger.Sync() // nolint: errcheck
46
+
47
+	if err := config.InitPublicAddress(ctx); err != nil {
48
+		Fatal(err)
49
+	}
50
+
51
+	zap.S().Debugw("Configuration", "config", config.Printable())
52
+
53
+	if len(config.C.AdTag) > 0 {
54
+		zap.S().Infow("Use middle proxy connection to Telegram")
55
+
56
+		diff, err := ntp.Fetch()
57
+		if err != nil {
58
+			Fatal("Cannot fetch time data from NTP")
59
+		}
60
+
61
+		if diff > time.Second {
62
+			Fatal("Your local time is skewed and drift is bigger than a second. Please sync your time.")
63
+		}
64
+
65
+		go ntp.AutoUpdate()
66
+	} else {
67
+		zap.S().Infow("Use direct connection to Telegram")
68
+	}
69
+
70
+	PrintJSONStdout(config.GetURLs())
71
+
72
+	if err := stats.Init(ctx); err != nil {
73
+		Fatal(err)
74
+	}
75
+
76
+	antireplay.Init()
77
+	telegram.Init()
78
+	hub.Init(ctx)
79
+
80
+	proxyListener, err := net.Listen("tcp", config.C.Bind.String())
81
+	if err != nil {
82
+		Fatal(err)
83
+	}
84
+
85
+	go func() {
86
+		<-ctx.Done()
87
+		proxyListener.Close()
88
+	}()
89
+
90
+	app := &proxy.Proxy{
91
+		Logger:              zap.S().Named("proxy"),
92
+		Context:             ctx,
93
+		ClientProtocolMaker: obfuscated2.MakeClientProtocol,
94
+	}
95
+	if config.C.SecretMode == config.SecretModeTLS {
96
+		app.ClientProtocolMaker = faketls.MakeClientProtocol
97
+	}
98
+
99
+	app.Serve(proxyListener)
100
+
101
+	return nil
102
+}

+ 43
- 0
cli/utils.go Целия файл

@@ -0,0 +1,43 @@
1
+package cli
2
+
3
+import (
4
+	"encoding/json"
5
+	"fmt"
6
+	"io"
7
+	"os"
8
+)
9
+
10
+func Fatal(arg interface{}) {
11
+	if value, ok := arg.(error); ok {
12
+		arg = fmt.Errorf("fatal error: %+v", value)
13
+	}
14
+
15
+	PrintStderr(arg)
16
+	os.Exit(1)
17
+}
18
+
19
+func PrintStderr(args ...interface{}) {
20
+	fmt.Fprintln(os.Stderr, args...)
21
+}
22
+
23
+func PrintStdout(args ...interface{}) {
24
+	fmt.Println(args...)
25
+}
26
+
27
+func PrintJSONStderr(data interface{}) {
28
+	printJSON(os.Stderr, data)
29
+}
30
+
31
+func PrintJSONStdout(data interface{}) {
32
+	printJSON(os.Stdout, data)
33
+}
34
+
35
+func printJSON(writer io.Writer, data interface{}) {
36
+	encoder := json.NewEncoder(writer)
37
+	encoder.SetEscapeHTML(false)
38
+	encoder.SetIndent("", "  ")
39
+
40
+	if err := encoder.Encode(data); err != nil {
41
+		panic(err)
42
+	}
43
+}

+ 0
- 15
client/client.go Целия файл

@@ -1,15 +0,0 @@
1
-package client
2
-
3
-import (
4
-	"context"
5
-	"net"
6
-
7
-	"github.com/9seconds/mtg/antireplay"
8
-	"github.com/9seconds/mtg/config"
9
-	"github.com/9seconds/mtg/mtproto"
10
-	"github.com/9seconds/mtg/wrappers"
11
-)
12
-
13
-// Init defines common method for initializing client connections.
14
-type Init func(context.Context, context.CancelFunc, net.Conn, string,
15
-	antireplay.Cache, *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error)

+ 0
- 61
client/direct.go Целия файл

@@ -1,61 +0,0 @@
1
-package client
2
-
3
-import (
4
-	"context"
5
-	"net"
6
-	"time"
7
-
8
-	"github.com/juju/errors"
9
-
10
-	"github.com/9seconds/mtg/antireplay"
11
-	"github.com/9seconds/mtg/config"
12
-	"github.com/9seconds/mtg/mtproto"
13
-	"github.com/9seconds/mtg/obfuscated2"
14
-	"github.com/9seconds/mtg/wrappers"
15
-)
16
-
17
-const handshakeTimeout = 10 * time.Second
18
-
19
-// DirectInit initializes client connection for proxy which connects to
20
-// Telegram directly.
21
-func DirectInit(ctx context.Context, cancel context.CancelFunc, socket net.Conn,
22
-	connID string, antiReplayCache antireplay.Cache,
23
-	conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) {
24
-	tcpSocket := socket.(*net.TCPConn)
25
-	if err := tcpSocket.SetNoDelay(false); err != nil {
26
-		return nil, nil, errors.Annotate(err, "Cannot disable NO_DELAY to client socket")
27
-	}
28
-	if err := tcpSocket.SetReadBuffer(conf.ReadBufferSize); err != nil {
29
-		return nil, nil, errors.Annotate(err, "Cannot set read buffer size of client socket")
30
-	}
31
-	if err := tcpSocket.SetWriteBuffer(conf.WriteBufferSize); err != nil {
32
-		return nil, nil, errors.Annotate(err, "Cannot set write buffer size of client socket")
33
-	}
34
-
35
-	socket.SetReadDeadline(time.Now().Add(handshakeTimeout)) // nolint: errcheck, gosec
36
-	frame, err := obfuscated2.ExtractFrame(socket)
37
-	if err != nil {
38
-		return nil, nil, errors.Annotate(err, "Cannot extract frame")
39
-	}
40
-	socket.SetReadDeadline(time.Time{}) // nolint: errcheck, gosec
41
-
42
-	conn := wrappers.NewConn(ctx, cancel, socket, connID, wrappers.ConnPurposeClient, conf.PublicIPv4, conf.PublicIPv6)
43
-	obfs2, connOpts, err := obfuscated2.ParseObfuscated2ClientFrame(conf.Secret, frame)
44
-	if err != nil {
45
-		return nil, nil, errors.Annotate(err, "Cannot parse obfuscated frame")
46
-	}
47
-
48
-	if antiReplayCache.Has([]byte(frame)) {
49
-		return nil, nil, errors.New("Replay attack is detected")
50
-	}
51
-	antiReplayCache.Add([]byte(frame))
52
-
53
-	connOpts.ConnectionProto = mtproto.ConnectionProtocolAny
54
-	connOpts.ClientAddr = conn.RemoteAddr()
55
-
56
-	conn = wrappers.NewStreamCipher(conn, obfs2.Encryptor, obfs2.Decryptor)
57
-
58
-	conn.Logger().Infow("Client connection initialized")
59
-
60
-	return conn, connOpts, nil
61
-}

+ 0
- 42
client/middle.go Целия файл

@@ -1,42 +0,0 @@
1
-package client
2
-
3
-import (
4
-	"context"
5
-	"net"
6
-
7
-	"github.com/9seconds/mtg/antireplay"
8
-	"github.com/9seconds/mtg/config"
9
-	"github.com/9seconds/mtg/mtproto"
10
-	"github.com/9seconds/mtg/wrappers"
11
-)
12
-
13
-// MiddleInit initializes client connection for proxy which has to
14
-// support promoted channels, connect to Telegram middle proxies etc.
15
-func MiddleInit(ctx context.Context, cancel context.CancelFunc, socket net.Conn,
16
-	connID string, antiReplayCache antireplay.Cache,
17
-	conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) {
18
-	conn, opts, err := DirectInit(ctx, cancel, socket, connID, antiReplayCache, conf)
19
-	if err != nil {
20
-		return nil, nil, err
21
-	}
22
-	connStream := conn.(wrappers.StreamReadWriteCloser)
23
-
24
-	var newConn wrappers.PacketReadWriteCloser
25
-	switch opts.ConnectionType {
26
-	case mtproto.ConnectionTypeAbridged:
27
-		newConn = wrappers.NewMTProtoAbridged(connStream, opts)
28
-	case mtproto.ConnectionTypeIntermediate:
29
-		newConn = wrappers.NewMTProtoIntermediate(connStream, opts)
30
-	case mtproto.ConnectionTypeSecure:
31
-		newConn = wrappers.NewMTProtoIntermediateSecure(connStream, opts)
32
-	default:
33
-		panic("Unknown connection type")
34
-	}
35
-
36
-	opts.ConnectionProto = mtproto.ConnectionProtocolIPv4
37
-	if socket.LocalAddr().(*net.TCPAddr).IP.To4() == nil {
38
-		opts.ConnectionProto = mtproto.ConnectionProtocolIPv6
39
-	}
40
-
41
-	return newConn, opts, err
42
-}

+ 190
- 179
config/config.go Целия файл

@@ -2,223 +2,234 @@ package config
2 2
 
3 3
 import (
4 4
 	"bytes"
5
-	"encoding/hex"
5
+	"context"
6
+	"encoding/json"
7
+	"errors"
6 8
 	"fmt"
7 9
 	"net"
8
-	"strconv"
9
-	"time"
10 10
 
11
-	"github.com/juju/errors"
12
-	statsd "gopkg.in/alexcesaro/statsd.v2"
11
+	"github.com/alecthomas/units"
12
+	statsd "github.com/smira/go-statsd"
13
+	"go.uber.org/zap"
13 14
 )
14 15
 
15
-// Config represents common configuration of mtg.
16
-type Config struct {
17
-	Debug      bool
18
-	Verbose    bool
19
-	SecureMode bool
20
-	SecureOnly bool
21
-
22
-	ReadBufferSize  int
23
-	WriteBufferSize int
24
-
25
-	BindPort       uint16
26
-	PublicIPv4Port uint16
27
-	PublicIPv6Port uint16
28
-	StatsPort      uint16
29
-
30
-	BindIP     net.IP
31
-	PublicIPv4 net.IP
32
-	PublicIPv6 net.IP
33
-	StatsIP    net.IP
34
-
35
-	AntiReplayMaxSize      int
36
-	AntiReplayEvictionTime time.Duration
37
-
38
-	StatsD struct {
39
-		Addr       net.Addr
40
-		Prefix     string
41
-		Tags       map[string]string
42
-		TagsFormat statsd.TagFormat
43
-		Enabled    bool
44
-	}
45
-	Prometheus struct {
46
-		Prefix string
16
+type SecretMode uint8
17
+
18
+func (s SecretMode) String() string {
19
+	switch s {
20
+	case SecretModeSimple:
21
+		return "simple"
22
+	case SecretModeSecured:
23
+		return "secured"
47 24
 	}
48 25
 
49
-	Secret []byte
50
-	AdTag  []byte
26
+	return "tls"
51 27
 }
52 28
 
53
-// URLs contains links to the proxy (tg://, t.me) and their QR codes.
54
-type URLs struct {
55
-	TG        string `json:"tg_url"`
56
-	TMe       string `json:"tme_url"`
57
-	TGQRCode  string `json:"tg_qrcode"`
58
-	TMeQRCode string `json:"tme_qrcode"`
59
-}
29
+const (
30
+	SecretModeSimple SecretMode = iota
31
+	SecretModeSecured
32
+	SecretModeTLS
33
+)
60 34
 
61
-// IPURLs contains links to both ipv4 and ipv6 of the proxy.
62
-type IPURLs struct {
63
-	IPv4      URLs   `json:"ipv4"`
64
-	IPv6      URLs   `json:"ipv6"`
65
-	BotSecret string `json:"secret_for_mtproxybot"`
66
-}
35
+const SimpleSecretLength = 16
67 36
 
68
-// BindAddr returns connection for this server to bind to.
69
-func (c *Config) BindAddr() string {
70
-	return getAddr(c.BindIP, c.BindPort)
71
-}
37
+type OptionType uint8
72 38
 
73
-// StatAddr returns connection string to the stats API.
74
-func (c *Config) StatAddr() string {
75
-	return getAddr(c.StatsIP, c.StatsPort)
76
-}
39
+const (
40
+	OptionTypeDebug OptionType = iota
41
+	OptionTypeVerbose
42
+
43
+	OptionTypeBind
44
+	OptionTypePublicIPv4
45
+	OptionTypePublicIPv6
46
+
47
+	OptionTypeStatsBind
48
+	OptionTypeStatsNamespace
49
+	OptionTypeStatsdAddress
50
+	OptionTypeStatsdTagsFormat
51
+	OptionTypeStatsdTags
52
+
53
+	OptionTypeWriteBufferSize
54
+	OptionTypeReadBufferSize
55
+
56
+	OptionTypeCloakPort
57
+
58
+	OptionTypeAntiReplayMaxSize
59
+
60
+	OptionTypeMultiplexPerConnection
61
+
62
+	OptionTypeSecret
63
+	OptionTypeAdtag
64
+)
65
+
66
+type Config struct {
67
+	Bind             *net.TCPAddr      `json:"bind"`
68
+	PublicIPv4       *net.TCPAddr      `json:"public_ipv4"`
69
+	PublicIPv6       *net.TCPAddr      `json:"public_ipv6"`
70
+	StatsBind        *net.TCPAddr      `json:"stats_bind"`
71
+	StatsdAddr       *net.TCPAddr      `json:"stats_addr"`
72
+	StatsdTagsFormat *statsd.TagFormat `json:"statsd_tags_format"`
73
+
74
+	StatsNamespace string            `json:"stats_namespace"`
75
+	CloakHost      string            `json:"cloak_host"`
76
+	StatsdTags     map[string]string `json:"statsd_tags"`
77
+
78
+	WriteBuffer int `json:"write_buffer"`
79
+	ReadBuffer  int `json:"read_buffer"`
80
+	CloakPort   int `json:"cloak_port"`
81
+
82
+	AntiReplayMaxSize int `json:"anti_replay_max_size"`
83
+
84
+	MultiplexPerConnection int `json:"multiplex_per_connection"`
77 85
 
78
-// UseMiddleProxy defines if this proxy has to connect middle proxies
79
-// which supports promoted channels or directly access Telegram.
80
-func (c *Config) UseMiddleProxy() bool {
81
-	return len(c.AdTag) > 0
86
+	Debug      bool       `json:"debug"`
87
+	Verbose    bool       `json:"verbose"`
88
+	SecretMode SecretMode `json:"secret_mode"`
89
+
90
+	Secret []byte `json:"secret"`
91
+	AdTag  []byte `json:"adtag"`
82 92
 }
83 93
 
84
-// BotSecretString returns secret string which should work with MTProxybot.
85
-func (c *Config) BotSecretString() string {
86
-	return hex.EncodeToString(c.Secret)
94
+type Opt struct {
95
+	Option OptionType
96
+	Value  interface{}
87 97
 }
88 98
 
89
-// SecretString returns a secret in a form entered on the start of the
90
-// application.
91
-func (c *Config) SecretString() string {
92
-	secret := c.BotSecretString()
93
-	if c.SecureMode {
94
-		return "dd" + secret
99
+var C = Config{}
100
+
101
+func Init(options ...Opt) error { // nolint: gocyclo, funlen
102
+	for _, opt := range options {
103
+		switch opt.Option {
104
+		case OptionTypeDebug:
105
+			C.Debug = opt.Value.(bool)
106
+		case OptionTypeVerbose:
107
+			C.Verbose = opt.Value.(bool)
108
+		case OptionTypeBind:
109
+			C.Bind = opt.Value.(*net.TCPAddr)
110
+		case OptionTypePublicIPv4:
111
+			C.PublicIPv4 = opt.Value.(*net.TCPAddr)
112
+			if C.PublicIPv4 == nil {
113
+				C.PublicIPv4 = &net.TCPAddr{}
114
+			}
115
+		case OptionTypePublicIPv6:
116
+			C.PublicIPv6 = opt.Value.(*net.TCPAddr)
117
+			if C.PublicIPv6 == nil {
118
+				C.PublicIPv6 = &net.TCPAddr{}
119
+			}
120
+		case OptionTypeStatsBind:
121
+			C.StatsBind = opt.Value.(*net.TCPAddr)
122
+		case OptionTypeStatsNamespace:
123
+			C.StatsNamespace = opt.Value.(string)
124
+		case OptionTypeStatsdAddress:
125
+			C.StatsdAddr = opt.Value.(*net.TCPAddr)
126
+		case OptionTypeStatsdTagsFormat:
127
+			value := opt.Value.(string)
128
+			switch value {
129
+			case "datadog":
130
+				C.StatsdTagsFormat = statsd.TagFormatDatadog
131
+			case "influxdb":
132
+				C.StatsdTagsFormat = statsd.TagFormatInfluxDB
133
+			default:
134
+				return fmt.Errorf("incorrect statsd tag %s", value)
135
+			}
136
+		case OptionTypeStatsdTags:
137
+			C.StatsdTags = opt.Value.(map[string]string)
138
+		case OptionTypeWriteBufferSize:
139
+			C.WriteBuffer = int(opt.Value.(units.Base2Bytes))
140
+		case OptionTypeReadBufferSize:
141
+			C.ReadBuffer = int(opt.Value.(units.Base2Bytes))
142
+		case OptionTypeCloakPort:
143
+			C.CloakPort = int(opt.Value.(uint16))
144
+		case OptionTypeAntiReplayMaxSize:
145
+			C.AntiReplayMaxSize = int(opt.Value.(units.Base2Bytes))
146
+		case OptionTypeMultiplexPerConnection:
147
+			C.MultiplexPerConnection = int(opt.Value.(uint))
148
+		case OptionTypeSecret:
149
+			C.Secret = opt.Value.([]byte)
150
+		case OptionTypeAdtag:
151
+			C.AdTag = opt.Value.([]byte)
152
+		default:
153
+			return fmt.Errorf("unknown tag %v", opt.Option)
154
+		}
95 155
 	}
96
-	return secret
97
-}
98 156
 
99
-// GetURLs returns configured IPURLs instance with links to this server.
100
-func (c *Config) GetURLs() IPURLs {
101
-	urls := IPURLs{}
102
-	secret := c.SecretString()
103
-	if c.PublicIPv4 != nil {
104
-		urls.IPv4 = getURLs(c.PublicIPv4, c.PublicIPv4Port, secret)
157
+	switch {
158
+	case len(C.Secret) == 1+SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xdd}):
159
+		C.SecretMode = SecretModeSecured
160
+		C.Secret = bytes.TrimPrefix(C.Secret, []byte{0xdd})
161
+	case len(C.Secret) > SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xee}):
162
+		C.SecretMode = SecretModeTLS
163
+		secret := bytes.TrimPrefix(C.Secret, []byte{0xee})
164
+		C.Secret = secret[:SimpleSecretLength]
165
+		C.CloakHost = string(secret[SimpleSecretLength:])
166
+	case len(C.Secret) == SimpleSecretLength:
167
+		C.SecretMode = SecretModeSimple
168
+	default:
169
+		return errors.New("incorrect secret")
105 170
 	}
106
-	if c.PublicIPv6 != nil {
107
-		urls.IPv6 = getURLs(c.PublicIPv6, c.PublicIPv6Port, secret)
171
+
172
+	if C.MultiplexPerConnection == 0 {
173
+		return errors.New("cannot use 0 clients per connection for multiplexing")
108 174
 	}
109
-	urls.BotSecret = c.BotSecretString()
110 175
 
111
-	return urls
112
-}
176
+	if C.CloakHost != "" {
177
+		if _, err := net.LookupHost(C.CloakHost); err != nil {
178
+			zap.S().Warnw("Cannot resolve address of host", "hostname", C.CloakHost, "error", err)
179
+		}
180
+	}
113 181
 
114
-func getAddr(host fmt.Stringer, port uint16) string {
115
-	return net.JoinHostPort(host.String(), strconv.Itoa(int(port)))
182
+	return nil
116 183
 }
117 184
 
118
-// NewConfig returns new configuration. If required, it manages and
119
-// fetches data from external sources. Parameters passed to this
120
-// function, should come from command line arguments.
121
-func NewConfig(debug, verbose bool, // nolint: gocyclo
122
-	writeBufferSize, readBufferSize uint32,
123
-	bindIP, publicIPv4, publicIPv6, statsIP net.IP,
124
-	bindPort, publicIPv4Port, publicIPv6Port, statsPort, statsdPort uint16,
125
-	statsdIP, statsdNetwork, statsdPrefix, statsdTagsFormat string,
126
-	statsdTags map[string]string, prometheusPrefix string,
127
-	secureOnly bool,
128
-	antiReplayMaxSize int, antiReplayEvictionTime time.Duration,
129
-	secret, adtag []byte) (*Config, error) {
130
-	secureMode := secureOnly
131
-	if bytes.HasPrefix(secret, []byte{0xdd}) && len(secret) == 17 {
132
-		secureMode = true
133
-		secret = bytes.TrimPrefix(secret, []byte{0xdd})
134
-	} else if len(secret) != 16 {
135
-		return nil, errors.New("Telegram demands secret of length 32")
185
+func InitPublicAddress(ctx context.Context) error {
186
+	if C.PublicIPv4.Port == 0 {
187
+		C.PublicIPv4.Port = C.Bind.Port
136 188
 	}
137 189
 
138
-	var err error
139
-	if publicIPv4 == nil {
140
-		publicIPv4, err = getGlobalIPv4()
190
+	if C.PublicIPv6.Port == 0 {
191
+		C.PublicIPv6.Port = C.Bind.Port
192
+	}
193
+
194
+	foundAddress := C.PublicIPv4.IP != nil || C.PublicIPv6.IP != nil
195
+
196
+	if C.PublicIPv4.IP == nil {
197
+		ip, err := getGlobalIPv4(ctx)
141 198
 		if err != nil {
142
-			publicIPv4 = nil
143
-		} else if publicIPv4.To4() == nil {
144
-			return nil, errors.Errorf("IP %s is not IPv4", publicIPv4.String())
199
+			zap.S().Warnw("Cannot resolve public address", "error", err)
200
+		} else {
201
+			C.PublicIPv4.IP = ip
202
+			foundAddress = true
145 203
 		}
146 204
 	}
147
-	if publicIPv4Port == 0 {
148
-		publicIPv4Port = bindPort
149
-	}
150 205
 
151
-	if publicIPv6 == nil {
152
-		publicIPv6, err = getGlobalIPv6()
206
+	if C.PublicIPv6.IP == nil {
207
+		ip, err := getGlobalIPv6(ctx)
153 208
 		if err != nil {
154
-			publicIPv6 = nil
155
-		} else if publicIPv6.To4() != nil {
156
-			return nil, errors.Errorf("IP %s is not IPv6", publicIPv6.String())
209
+			zap.S().Warnw("Cannot resolve public address", "error", err)
210
+		} else {
211
+			C.PublicIPv6.IP = ip
212
+			foundAddress = true
157 213
 		}
158 214
 	}
159
-	if publicIPv6Port == 0 {
160
-		publicIPv6Port = bindPort
161
-	}
162 215
 
163
-	if statsIP == nil {
164
-		statsIP = publicIPv4
216
+	if !foundAddress {
217
+		return errors.New("cannot resolve any public address")
165 218
 	}
166 219
 
167
-	conf := &Config{
168
-		Debug:                  debug,
169
-		Verbose:                verbose,
170
-		SecureOnly:             secureOnly,
171
-		BindIP:                 bindIP,
172
-		BindPort:               bindPort,
173
-		PublicIPv4:             publicIPv4,
174
-		PublicIPv4Port:         publicIPv4Port,
175
-		PublicIPv6:             publicIPv6,
176
-		PublicIPv6Port:         publicIPv6Port,
177
-		StatsIP:                statsIP,
178
-		StatsPort:              statsPort,
179
-		Secret:                 secret,
180
-		AdTag:                  adtag,
181
-		SecureMode:             secureMode,
182
-		ReadBufferSize:         int(readBufferSize),
183
-		WriteBufferSize:        int(writeBufferSize),
184
-		AntiReplayMaxSize:      antiReplayMaxSize,
185
-		AntiReplayEvictionTime: antiReplayEvictionTime,
220
+	return nil
221
+}
222
+
223
+func Printable() interface{} {
224
+	data, err := json.Marshal(C)
225
+	if err != nil {
226
+		panic(err)
186 227
 	}
187
-	conf.Prometheus.Prefix = prometheusPrefix
188
-
189
-	if statsdIP != "" {
190
-		conf.StatsD.Enabled = true
191
-		conf.StatsD.Prefix = statsdPrefix
192
-		conf.StatsD.Tags = statsdTags
193
-
194
-		var (
195
-			addr net.Addr
196
-			err  error
197
-		)
198
-		hostPort := net.JoinHostPort(statsdIP, strconv.Itoa(int(statsdPort)))
199
-		switch statsdNetwork {
200
-		case "tcp":
201
-			addr, err = net.ResolveTCPAddr("tcp", hostPort)
202
-		case "udp":
203
-			addr, err = net.ResolveUDPAddr("udp", hostPort)
204
-		default:
205
-			err = errors.Errorf("Unknown network %s", statsdNetwork)
206
-		}
207
-		if err != nil {
208
-			return nil, errors.Annotate(err, "Cannot resolve statsd address")
209
-		}
210
-		conf.StatsD.Addr = addr
211
-
212
-		switch statsdTagsFormat {
213
-		case "datadog":
214
-			conf.StatsD.TagsFormat = statsd.Datadog
215
-		case "influxdb":
216
-			conf.StatsD.TagsFormat = statsd.InfluxDB
217
-		case "":
218
-		default:
219
-			return nil, errors.Errorf("Unknown tags format %s", statsdTagsFormat)
220
-		}
228
+
229
+	rv := map[string]interface{}{}
230
+	if err := json.Unmarshal(data, &rv); err != nil {
231
+		panic(err)
221 232
 	}
222 233
 
223
-	return conf, nil
234
+	return rv
224 235
 }

+ 38
- 12
config/global_ips.go Целия файл

@@ -2,28 +2,43 @@ package config
2 2
 
3 3
 import (
4 4
 	"context"
5
+	"fmt"
6
+	"io"
5 7
 	"io/ioutil"
6 8
 	"net"
7 9
 	"net/http"
8 10
 	"strings"
11
+	"time"
12
+)
9 13
 
10
-	"github.com/juju/errors"
14
+const (
15
+	ifconfigAddress = "https://ifconfig.co/ip"
16
+	ifconfigTimeout = 10 * time.Second
11 17
 )
12 18
 
13
-const ifconfigAddress = "https://ifconfig.co/ip"
19
+func getGlobalIPv4(ctx context.Context) (net.IP, error) {
20
+	ip, err := fetchIP(ctx, "tcp4")
21
+	if err != nil || ip.To4() == nil {
22
+		return nil, fmt.Errorf("cannot find public ipv4 address: %w", err)
23
+	}
14 24
 
15
-func getGlobalIPv4() (net.IP, error) {
16
-	return fetchIP("tcp4")
25
+	return ip, nil
17 26
 }
18 27
 
19
-func getGlobalIPv6() (net.IP, error) {
20
-	return fetchIP("tcp6")
28
+func getGlobalIPv6(ctx context.Context) (net.IP, error) {
29
+	ip, err := fetchIP(ctx, "tcp6")
30
+	if err != nil || ip.To4() != nil {
31
+		return nil, fmt.Errorf("cannot find public ipv6 address: %w", err)
32
+	}
33
+
34
+	return ip, nil
21 35
 }
22 36
 
23
-func fetchIP(network string) (net.IP, error) {
37
+func fetchIP(ctx context.Context, network string) (net.IP, error) {
24 38
 	dialer := &net.Dialer{FallbackDelay: -1}
25 39
 	client := &http.Client{
26
-		Jar: nil,
40
+		Jar:     nil,
41
+		Timeout: ifconfigTimeout,
27 42
 		Transport: &http.Transport{
28 43
 			DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) {
29 44
 				return dialer.DialContext(ctx, network, addr)
@@ -31,21 +46,32 @@ func fetchIP(network string) (net.IP, error) {
31 46
 		},
32 47
 	}
33 48
 
34
-	resp, err := client.Get(ifconfigAddress)
49
+	req, err := http.NewRequest("GET", ifconfigAddress, nil)
35 50
 	if err != nil {
36
-		return nil, err
51
+		return nil, fmt.Errorf("cannot create a request: %w", err)
37 52
 	}
53
+
54
+	resp, err := client.Do(req.WithContext(ctx))
55
+	if err != nil {
56
+		if resp != nil {
57
+			io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck
58
+		}
59
+
60
+		return nil, fmt.Errorf("cannot perform a request: %w", err)
61
+	}
62
+
38 63
 	defer resp.Body.Close() // nolint: errcheck
39 64
 
40 65
 	respDataBytes, err := ioutil.ReadAll(resp.Body)
41 66
 	if err != nil {
42
-		return nil, err
67
+		return nil, fmt.Errorf("cannot read response body: %w", err)
43 68
 	}
69
+
44 70
 	respData := strings.TrimSpace(string(respDataBytes))
45 71
 
46 72
 	ip := net.ParseIP(respData)
47 73
 	if ip == nil {
48
-		return nil, errors.Errorf("ifconfig.co returns incorrect IP %s", respData)
74
+		return nil, fmt.Errorf("ifconfig.co returns incorrect IP %s", respData)
49 75
 	}
50 76
 
51 77
 	return ip, nil

+ 36
- 3
config/urls.go Целия файл

@@ -1,15 +1,48 @@
1 1
 package config
2 2
 
3 3
 import (
4
+	"encoding/hex"
4 5
 	"net"
5 6
 	"net/url"
6 7
 	"strconv"
7 8
 )
8 9
 
9
-func getURLs(addr net.IP, port uint16, secret string) (urls URLs) {
10
+type URLs struct {
11
+	TG        string `json:"tg_url"`
12
+	TMe       string `json:"tme_url"`
13
+	TGQRCode  string `json:"tg_qrcode"`
14
+	TMeQRCode string `json:"tme_qrcode"`
15
+}
16
+
17
+type IPURLs struct {
18
+	IPv4      URLs   `json:"ipv4"`
19
+	IPv6      URLs   `json:"ipv6"`
20
+	BotSecret string `json:"secret_for_mtproxybot"`
21
+}
22
+
23
+func GetURLs() (urls IPURLs) {
24
+	secret := ""
25
+
26
+	switch C.SecretMode {
27
+	case SecretModeSimple:
28
+		secret = hex.EncodeToString(C.Secret)
29
+	case SecretModeSecured:
30
+		secret = "dd" + hex.EncodeToString(C.Secret)
31
+	case SecretModeTLS:
32
+		secret = "ee" + hex.EncodeToString(C.Secret) + hex.EncodeToString([]byte(C.CloakHost))
33
+	}
34
+
35
+	urls.IPv4 = makeURLs(C.PublicIPv4, secret)
36
+	urls.IPv6 = makeURLs(C.PublicIPv6, secret)
37
+	urls.BotSecret = hex.EncodeToString(C.Secret)
38
+
39
+	return urls
40
+}
41
+
42
+func makeURLs(addr *net.TCPAddr, secret string) (urls URLs) {
10 43
 	values := url.Values{}
11
-	values.Set("server", addr.String())
12
-	values.Set("port", strconv.Itoa(int(port)))
44
+	values.Set("server", addr.IP.String())
45
+	values.Set("port", strconv.Itoa(addr.Port))
13 46
 	values.Set("secret", secret)
14 47
 
15 48
 	urls.TG = makeTGURL(values)

+ 6
- 0
conntypes/acks.go Целия файл

@@ -0,0 +1,6 @@
1
+package conntypes
2
+
3
+type ConnectionAcks struct {
4
+	Simple bool
5
+	Quick  bool
6
+}

+ 5
- 0
conntypes/dc.go Целия файл

@@ -0,0 +1,5 @@
1
+package conntypes
2
+
3
+type DC int16
4
+
5
+const DCDefaultIdx DC = 1

+ 24
- 0
conntypes/id.go Целия файл

@@ -0,0 +1,24 @@
1
+package conntypes
2
+
3
+import (
4
+	"crypto/rand"
5
+	"encoding/hex"
6
+)
7
+
8
+const ConnIDLength = 8
9
+
10
+type ConnID [ConnIDLength]byte
11
+
12
+func (c ConnID) String() string {
13
+	return hex.EncodeToString(c[:])
14
+}
15
+
16
+func NewConnID() ConnID {
17
+	var id ConnID
18
+
19
+	if _, err := rand.Read(id[:]); err != nil {
20
+		panic(err)
21
+	}
22
+
23
+	return id
24
+}

+ 3
- 0
conntypes/packet.go Целия файл

@@ -0,0 +1,3 @@
1
+package conntypes
2
+
3
+type Packet []byte

+ 20
- 0
conntypes/protocol.go Целия файл

@@ -0,0 +1,20 @@
1
+package conntypes
2
+
3
+type ConnectionProtocol uint8
4
+
5
+func (c ConnectionProtocol) String() string {
6
+	switch c {
7
+	case ConnectionProtocolAny:
8
+		return "any"
9
+	case ConnectionProtocolIPv4:
10
+		return "ipv4"
11
+	}
12
+
13
+	return "ipv6"
14
+}
15
+
16
+const (
17
+	ConnectionProtocolIPv4 ConnectionProtocol = 1
18
+	ConnectionProtocolIPv6                    = ConnectionProtocolIPv4 << 1
19
+	ConnectionProtocolAny                     = ConnectionProtocolIPv4 | ConnectionProtocolIPv6
20
+)

+ 27
- 0
conntypes/type.go Целия файл

@@ -0,0 +1,27 @@
1
+package conntypes
2
+
3
+type ConnectionType uint8
4
+
5
+const (
6
+	ConnectionTypeUnknown ConnectionType = iota
7
+	ConnectionTypeAbridged
8
+	ConnectionTypeIntermediate
9
+	ConnectionTypeSecure
10
+)
11
+
12
+var (
13
+	ConnectionTagAbridged     = []byte{0xef, 0xef, 0xef, 0xef}
14
+	ConnectionTagIntermediate = []byte{0xee, 0xee, 0xee, 0xee}
15
+	ConnectionTagSecure       = []byte{0xdd, 0xdd, 0xdd, 0xdd}
16
+)
17
+
18
+func (t ConnectionType) Tag() []byte {
19
+	switch t {
20
+	case ConnectionTypeAbridged:
21
+		return ConnectionTagAbridged
22
+	case ConnectionTypeIntermediate:
23
+		return ConnectionTagIntermediate
24
+	default:
25
+		return ConnectionTagSecure
26
+	}
27
+}

+ 14
- 0
conntypes/wrap_interfaces.go Целия файл

@@ -0,0 +1,14 @@
1
+package conntypes
2
+
3
+import (
4
+	"net"
5
+
6
+	"go.uber.org/zap"
7
+)
8
+
9
+type Wrap interface {
10
+	Conn() net.Conn
11
+	Logger() *zap.SugaredLogger
12
+	LocalAddr() *net.TCPAddr
13
+	RemoteAddr() *net.TCPAddr
14
+}

+ 41
- 0
conntypes/wrap_packet_ack_interfaces.go Целия файл

@@ -0,0 +1,41 @@
1
+package conntypes
2
+
3
+import "io"
4
+
5
+type PacketAckReader interface {
6
+	Read(*ConnectionAcks) (Packet, error)
7
+}
8
+
9
+type PacketAckWriter interface {
10
+	Write(Packet, *ConnectionAcks) error
11
+}
12
+
13
+type PacketAckCloser interface {
14
+	io.Closer
15
+}
16
+
17
+type PacketAckReadCloser interface {
18
+	PacketAckReader
19
+	PacketAckCloser
20
+}
21
+
22
+type PacketAckWriteCloser interface {
23
+	PacketAckWriter
24
+	PacketAckCloser
25
+}
26
+
27
+type PacketAckReadWriter interface {
28
+	PacketAckReader
29
+	PacketAckWriter
30
+}
31
+
32
+type PacketAckReadWriteCloser interface {
33
+	PacketAckReader
34
+	PacketAckWriter
35
+	PacketAckCloser
36
+}
37
+
38
+type PacketAckFullReadWriteCloser interface {
39
+	Wrap
40
+	PacketAckReadWriteCloser
41
+}

+ 51
- 0
conntypes/wrap_packet_interfaces.go Целия файл

@@ -0,0 +1,51 @@
1
+package conntypes
2
+
3
+import "io"
4
+
5
+type BasePacketReader interface {
6
+	Read() (Packet, error)
7
+}
8
+
9
+type BasePacketWriter interface {
10
+	Write(Packet) error
11
+}
12
+
13
+type PacketReader interface {
14
+	Wrap
15
+	BasePacketReader
16
+}
17
+
18
+type PacketWriter interface {
19
+	Wrap
20
+	BasePacketWriter
21
+}
22
+
23
+type PacketCloser interface {
24
+	Wrap
25
+	io.Closer
26
+}
27
+
28
+type PacketReadCloser interface {
29
+	Wrap
30
+	BasePacketReader
31
+	io.Closer
32
+}
33
+
34
+type PacketWriteCloser interface {
35
+	Wrap
36
+	BasePacketWriter
37
+	io.Closer
38
+}
39
+
40
+type PacketReadWriter interface {
41
+	Wrap
42
+	BasePacketWriter
43
+	BasePacketReader
44
+}
45
+
46
+type PacketReadWriteCloser interface {
47
+	Wrap
48
+	BasePacketWriter
49
+	BasePacketReader
50
+	io.Closer
51
+}

+ 56
- 0
conntypes/wrap_stream_interfaces.go Целия файл

@@ -0,0 +1,56 @@
1
+package conntypes
2
+
3
+import (
4
+	"io"
5
+	"time"
6
+)
7
+
8
+type BaseStreamReaderWithTimeout interface {
9
+	ReadTimeout([]byte, time.Duration) (int, error)
10
+}
11
+
12
+type BaseStreamWriterWithTimeout interface {
13
+	WriteTimeout([]byte, time.Duration) (int, error)
14
+}
15
+
16
+type StreamReader interface {
17
+	Wrap
18
+	io.Reader
19
+	BaseStreamReaderWithTimeout
20
+}
21
+
22
+type StreamWriter interface {
23
+	Wrap
24
+	io.Writer
25
+	BaseStreamWriterWithTimeout
26
+}
27
+
28
+type StreamCloser interface {
29
+	Wrap
30
+	io.Closer
31
+}
32
+
33
+type StreamReadCloser interface {
34
+	Wrap
35
+	io.ReadCloser
36
+	BaseStreamReaderWithTimeout
37
+}
38
+
39
+type StreamWriteCloser interface {
40
+	Wrap
41
+	io.WriteCloser
42
+	BaseStreamWriterWithTimeout
43
+}
44
+
45
+type StreamReadWriter interface {
46
+	Wrap
47
+	io.ReadWriter
48
+	BaseStreamReaderWithTimeout
49
+}
50
+
51
+type StreamReadWriteCloser interface {
52
+	Wrap
53
+	io.ReadWriteCloser
54
+	BaseStreamReaderWithTimeout
55
+	BaseStreamWriterWithTimeout
56
+}

+ 134
- 0
faketls/client_protocol.go Целия файл

@@ -0,0 +1,134 @@
1
+package faketls
2
+
3
+import (
4
+	"bufio"
5
+	"encoding/binary"
6
+	"errors"
7
+	"fmt"
8
+	"io"
9
+	"net"
10
+	"strconv"
11
+	"sync"
12
+	"time"
13
+
14
+	"mtg/antireplay"
15
+	"mtg/config"
16
+	"mtg/conntypes"
17
+	"mtg/obfuscated2"
18
+	"mtg/protocol"
19
+	"mtg/stats"
20
+	"mtg/tlstypes"
21
+	"mtg/wrappers/stream"
22
+)
23
+
24
+type ClientProtocol struct {
25
+	obfuscated2.ClientProtocol
26
+}
27
+
28
+func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error) {
29
+	rewinded := stream.NewRewind(socket)
30
+	bufferedReader := bufio.NewReader(rewinded)
31
+
32
+	for _, expected := range faketlsStartBytes {
33
+		if actual, err := bufferedReader.ReadByte(); err != nil || actual != expected {
34
+			rewinded.Rewind()
35
+			c.cloakHost(rewinded)
36
+
37
+			return nil, errors.New("failed first bytes of tls handshake")
38
+		}
39
+	}
40
+
41
+	rewinded.Rewind()
42
+	rewinded = stream.NewRewind(rewinded)
43
+
44
+	if err := c.tlsHandshake(rewinded); err != nil {
45
+		rewinded.Rewind()
46
+		c.cloakHost(rewinded)
47
+
48
+		return nil, fmt.Errorf("failed tls handshake: %w", err)
49
+	}
50
+
51
+	conn := stream.NewFakeTLS(socket)
52
+	conn, err := c.ClientProtocol.Handshake(conn)
53
+
54
+	if err != nil {
55
+		return nil, err
56
+	}
57
+
58
+	return conn, err
59
+}
60
+
61
+func (c *ClientProtocol) tlsHandshake(conn io.ReadWriter) error {
62
+	helloRecord, err := tlstypes.ReadRecord(conn)
63
+	if err != nil {
64
+		return fmt.Errorf("cannot read initial record: %w", err)
65
+	}
66
+
67
+	clientHello, err := tlstypes.ParseClientHello(helloRecord.Data.Bytes())
68
+	if err != nil {
69
+		return fmt.Errorf("cannot parse client hello: %w", err)
70
+	}
71
+
72
+	digest := clientHello.Digest()
73
+	for i := 0; i < len(digest)-4; i++ {
74
+		if digest[i] != 0 {
75
+			return errBadDigest
76
+		}
77
+	}
78
+
79
+	timestamp := int64(binary.LittleEndian.Uint32(digest[len(digest)-4:]))
80
+	createdAt := time.Unix(timestamp, 0)
81
+	timeDiff := time.Since(createdAt)
82
+
83
+	if (timeDiff > TimeSkew || timeDiff < -TimeSkew) && timestamp > TimeFromBoot {
84
+		return errBadTime
85
+	}
86
+
87
+	if antireplay.Cache.HasTLS(clientHello.Random[:]) {
88
+		stats.Stats.ReplayDetected()
89
+		return errors.New("replay attack is detected")
90
+	}
91
+
92
+	antireplay.Cache.AddTLS(clientHello.Random[:])
93
+	serverHello := tlstypes.NewServerHello(clientHello)
94
+	serverHelloPacket := serverHello.WelcomePacket()
95
+
96
+	if _, err := conn.Write(serverHelloPacket); err != nil {
97
+		return fmt.Errorf("cannot send welcome packet: %w", err)
98
+	}
99
+
100
+	return nil
101
+}
102
+
103
+func (c *ClientProtocol) cloakHost(clientConn io.ReadWriteCloser) {
104
+	addr := net.JoinHostPort(config.C.CloakHost, strconv.Itoa(config.C.CloakPort))
105
+	hostConn, err := net.Dial("tcp", addr)
106
+
107
+	if err != nil {
108
+		return
109
+	}
110
+
111
+	defer hostConn.Close()
112
+
113
+	wg := &sync.WaitGroup{}
114
+	wg.Add(2)
115
+
116
+	go c.pipe(hostConn, clientConn, wg)
117
+
118
+	go c.pipe(clientConn, hostConn, wg)
119
+
120
+	wg.Wait()
121
+}
122
+
123
+func (c *ClientProtocol) pipe(dst io.WriteCloser, src io.Reader, wg *sync.WaitGroup) {
124
+	defer func() {
125
+		wg.Done()
126
+		dst.Close()
127
+	}()
128
+
129
+	io.Copy(dst, src) // nolint: errcheck
130
+}
131
+
132
+func MakeClientProtocol() protocol.ClientProtocol {
133
+	return &ClientProtocol{}
134
+}

+ 30
- 0
faketls/consts.go Целия файл

@@ -0,0 +1,30 @@
1
+package faketls
2
+
3
+import (
4
+	"errors"
5
+	"time"
6
+)
7
+
8
+const (
9
+	TimeSkew     = 5 * time.Second
10
+	TimeFromBoot = 24 * 60 * 60
11
+)
12
+
13
+var (
14
+	errBadDigest = errors.New("bad digest")
15
+	errBadTime   = errors.New("bad time")
16
+
17
+	faketlsStartBytes = [...]byte{
18
+		0x16,
19
+		0x03,
20
+		0x01,
21
+		0x02,
22
+		0x00,
23
+		0x01,
24
+		0x00,
25
+		0x01,
26
+		0xfc,
27
+		0x03,
28
+		0x03,
29
+	}
30
+)

+ 14
- 24
go.mod Целия файл

@@ -1,30 +1,20 @@
1
-module github.com/9seconds/mtg
1
+module mtg
2 2
 
3
-replace github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8f45f776aaf1
3
+go 1.13
4 4
 
5 5
 require (
6
-	github.com/OneOfOne/xxhash v1.2.5 // indirect
7
-	github.com/allegro/bigcache v1.2.0
6
+	github.com/VictoriaMetrics/fastcache v1.5.2
7
+	github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d
8 8
 	github.com/beevik/ntp v0.2.0
9
-	github.com/cespare/xxhash v1.1.0
10
-	github.com/dustin/go-humanize v1.0.0
11
-	github.com/gofrs/uuid v3.2.0+incompatible
12
-	github.com/juju/errors v0.0.0-20190207033735-e65537c515d7
13
-	github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
14
-	github.com/juju/testing v0.0.0-20190429233213-dfc56b8c09fc // indirect
15
-	github.com/kr/pretty v0.1.0 // indirect
16
-	github.com/pkg/errors v0.8.1 // indirect
17
-	github.com/prometheus/client_golang v0.9.4
18
-	github.com/spaolacci/murmur3 v1.1.0 // indirect
19
-	github.com/stretchr/testify v1.3.0
20
-	go.uber.org/atomic v1.4.0 // indirect
21
-	go.uber.org/multierr v1.1.0 // indirect
22
-	go.uber.org/zap v1.10.0
23
-	golang.org/x/net v0.0.0-20190607181551-461777fb6f67 // indirect
24
-	golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae // indirect
9
+	github.com/cespare/xxhash/v2 v2.1.1 // indirect
10
+	github.com/prometheus/client_golang v1.2.1
11
+	github.com/prometheus/procfs v0.0.7 // indirect
12
+	github.com/smira/go-statsd v1.3.1
13
+	go.uber.org/multierr v1.4.0 // indirect
14
+	go.uber.org/zap v1.13.0
15
+	golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f
16
+	golang.org/x/net v0.0.0-20191116160921-f9c825593386 // indirect
17
+	golang.org/x/sys v0.0.0-20191118013547-6254a7c3cac6
18
+	golang.org/x/tools v0.0.0-20191118051429-5a76f03bc7c3 // indirect
25 19
 	gopkg.in/alecthomas/kingpin.v2 v2.2.6
26
-	gopkg.in/alexcesaro/statsd.v2 v2.0.0
27
-	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
28
-	gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect
29
-	gopkg.in/yaml.v2 v2.2.2 // indirect
30 20
 )

+ 94
- 31
go.sum Целия файл

@@ -1,44 +1,64 @@
1
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
2
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
1 3
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
2
-github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
3 4
 github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
5
+github.com/VictoriaMetrics/fastcache v1.5.2 h1:Erd8iIuBAL9kke8JzM4+WxkKuFkHh3ktwLanJvDgR44=
6
+github.com/VictoriaMetrics/fastcache v1.5.2/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE=
4 7
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
5 8
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
9
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
10
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
6 11
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
7 12
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
8
-github.com/allegro/bigcache v1.2.0 h1:qDaE0QoF29wKBb3+pXFrJFy1ihe5OT9OiXhg1t85SxM=
9
-github.com/allegro/bigcache v1.2.0/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
13
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
14
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
15
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
16
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
17
+github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
18
+github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
10 19
 github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw=
11 20
 github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
12 21
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
13 22
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
14 23
 github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
15 24
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
25
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
26
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
16 27
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
17 28
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
29
+github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18/go.mod h1:HD5P3vAIAh+Y2GAxg0PrPN1P8WkepXGpjbUPDHJqqKM=
30
+github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
31
+github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
32
+github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
33
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
18 34
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
19 35
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
20 36
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
21
-github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
22
-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
23 37
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
38
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
24 39
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
40
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
25 41
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
26
-github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
27
-github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
28 42
 github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
29 43
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
30 44
 github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
31 45
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
32 46
 github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
33 47
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
48
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
49
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
50
+github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
51
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
52
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
53
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
54
+github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
55
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
56
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
57
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
34 58
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
35
-github.com/juju/errors v0.0.0-20190207033735-e65537c515d7 h1:dMIPRDg6gi7CUp0Kj2+HxqJ5kTr1iAdzsXYIrLCNSmU=
36
-github.com/juju/errors v0.0.0-20190207033735-e65537c515d7/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
37
-github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
38
-github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
39
-github.com/juju/testing v0.0.0-20190429233213-dfc56b8c09fc h1:5xUWujf6ES9tEpFHFzI34vcHm8U07lGjxAuJML3qwqM=
40
-github.com/juju/testing v0.0.0-20190429233213-dfc56b8c09fc/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
59
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
41 60
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
61
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
42 62
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
43 63
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
44 64
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -48,7 +68,9 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
48 68
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
49 69
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
50 70
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
71
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
51 72
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
73
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
52 74
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
53 75
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
54 76
 github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
@@ -58,58 +80,99 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
58 80
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
59 81
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
60 82
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
61
-github.com/prometheus/client_golang v0.9.4 h1:Y8E/JaaPbmFSW2V81Ab/d8yZFYQQGbni1b1jPcG9Y6A=
62
-github.com/prometheus/client_golang v0.9.4/go.mod h1:oCXIBxdI62A4cR6aTRJCgetEjecSIYzOEaeAn4iYEpM=
83
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
84
+github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
85
+github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
63 86
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
64 87
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
65 88
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
66 89
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
90
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
91
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
67 92
 github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
68 93
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
94
+github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
95
+github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
69 96
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ=
70 97
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
71 98
 github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
72 99
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
100
+github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
101
+github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
102
+github.com/prometheus/procfs v0.0.7 h1:RS5GAlMbnkWkhs4+bPocMTmGjYkuCY5djjqEDdXOhcQ=
103
+github.com/prometheus/procfs v0.0.7/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
104
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
73 105
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
106
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
107
+github.com/smira/go-statsd v1.3.1 h1:JalGiHNdK7GqVAPpg7j0Kwp2jZrz/fCg/B4ZuNuBY2w=
108
+github.com/smira/go-statsd v1.3.1/go.mod h1:1srXJ9/pbnN04G8f4F1jUzsGOnwkPKXciyqpewGlkC4=
74 109
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
75
-github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
76
-github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
110
+github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
77 111
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
78 112
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
79 113
 github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
80 114
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
81 115
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
82 116
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
83
-go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
84
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
85
-go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
86
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
87
-go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
88
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
117
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
118
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
119
+go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
120
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
121
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
122
+go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E=
123
+go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
124
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
125
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
126
+go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
127
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
89 128
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
129
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
90 130
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
131
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
132
+golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f h1:kz4KIr+xcPUsI3VMoqWfPMvtnJ6MGfiVwsWSVzphMO4=
133
+golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
134
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
135
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
136
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
91 137
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
92
-golang.org/x/net v0.0.0-20190607181551-461777fb6f67 h1:rJJxsykSlULwd2P2+pg/rtnwN2FrWp4IuCxOSyS0V00=
93
-golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
138
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
139
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
140
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
141
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
142
+golang.org/x/net v0.0.0-20191116160921-f9c825593386 h1:ktbWvQrW08Txdxno1PiDpSxPXG6ndGsfnJjRRtkM0LQ=
143
+golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
94 144
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
95 145
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
96 146
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
147
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
148
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
97 149
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
98 150
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
99 151
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
100
-golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae h1:xiXzMMEQdQcric9hXtr1QU98MHunKK7OTtsoU6bYWs4=
101
-golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
152
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
153
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
154
+golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
155
+golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
156
+golang.org/x/sys v0.0.0-20191118013547-6254a7c3cac6 h1:8mlr2HX+lfl0eaQcjiHfVeM2FHxWkuYQ5a2Wcy8mE1s=
157
+golang.org/x/sys v0.0.0-20191118013547-6254a7c3cac6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
102 158
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
159
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
160
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
161
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
162
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
163
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
164
+golang.org/x/tools v0.0.0-20191118051429-5a76f03bc7c3 h1:3gzOmNy3PLCZ+3Ru/n5Gh7pPjsieiytYSDxFj6QY/oI=
165
+golang.org/x/tools v0.0.0-20191118051429-5a76f03bc7c3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
166
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
103 167
 gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
104 168
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
105
-gopkg.in/alexcesaro/statsd.v2 v2.0.0 h1:FXkZSCZIH17vLCO5sO2UucTHsH9pc+17F6pl3JVCwMc=
106
-gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7SLonnE4drU=
107 169
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
108 170
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
109 171
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
110
-gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=
111
-gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
172
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
112 173
 gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
113 174
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
114 175
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
115 176
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
177
+honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
178
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

+ 163
- 0
hub/connection.go Целия файл

@@ -0,0 +1,163 @@
1
+package hub
2
+
3
+import (
4
+	"fmt"
5
+	"math/rand"
6
+	"sync"
7
+
8
+	"go.uber.org/zap"
9
+
10
+	"mtg/conntypes"
11
+	"mtg/mtproto"
12
+	"mtg/mtproto/rpc"
13
+	"mtg/protocol"
14
+)
15
+
16
+type connection struct {
17
+	conn            conntypes.PacketReadWriteCloser
18
+	proxyConns      map[string]*ProxyConn
19
+	closeOnce       sync.Once
20
+	proxyConnsMutex sync.RWMutex
21
+	id              int
22
+	logger          *zap.SugaredLogger
23
+
24
+	channelDone       chan struct{}
25
+	channelWrite      chan conntypes.Packet
26
+	channelRead       chan *rpc.ProxyResponse
27
+	channelConnAttach chan *ProxyConn
28
+	channelConnDetach chan conntypes.ConnID
29
+}
30
+
31
+func (c *connection) run() {
32
+	defer c.Close()
33
+
34
+	for {
35
+		select {
36
+		case <-c.channelDone:
37
+			for _, v := range c.proxyConns {
38
+				v.Close()
39
+			}
40
+
41
+			return
42
+		case resp := <-c.channelRead:
43
+			if channel, ok := c.proxyConns[string(resp.ConnID[:])]; ok {
44
+				if resp.Type == rpc.ProxyResponseTypeCloseExt {
45
+					channel.Close()
46
+				} else {
47
+					channel.put(resp)
48
+				}
49
+			}
50
+		case packet := <-c.channelWrite:
51
+			if err := c.conn.Write(packet); err != nil {
52
+				c.logger.Debugw("Cannot write packet", "error", err)
53
+				c.Close()
54
+			}
55
+		case conn := <-c.channelConnAttach:
56
+			c.proxyConnsMutex.Lock()
57
+			c.proxyConns[string(conn.req.ConnID[:])] = conn
58
+			c.proxyConnsMutex.Unlock()
59
+			conn.channelWrite = c.channelWrite
60
+		case connID := <-c.channelConnDetach:
61
+			if conn, ok := c.proxyConns[string(connID[:])]; ok {
62
+				c.proxyConnsMutex.Lock()
63
+				delete(c.proxyConns, string(connID[:]))
64
+				c.proxyConnsMutex.Unlock()
65
+				conn.Close()
66
+			}
67
+		}
68
+	}
69
+}
70
+
71
+func (c *connection) readLoop() {
72
+	for {
73
+		packet, err := c.conn.Read()
74
+		if err != nil {
75
+			c.logger.Debugw("Cannot read packet", "error", err)
76
+			c.Close()
77
+
78
+			return
79
+		}
80
+
81
+		response, err := rpc.ParseProxyResponse(packet)
82
+		if err != nil {
83
+			c.logger.Debugw("Failed response", "error", err)
84
+			continue
85
+		}
86
+
87
+		select {
88
+		case <-c.channelDone:
89
+			return
90
+		case c.channelRead <- response:
91
+		}
92
+	}
93
+}
94
+
95
+func (c *connection) Close() {
96
+	c.closeOnce.Do(func() {
97
+		c.logger.Debugw("Closing connection")
98
+
99
+		close(c.channelDone)
100
+		c.conn.Close()
101
+	})
102
+}
103
+
104
+func (c *connection) Done() bool {
105
+	select {
106
+	case <-c.channelDone:
107
+		return true
108
+	default:
109
+		return c.Len() == 0
110
+	}
111
+}
112
+
113
+func (c *connection) Len() int {
114
+	c.proxyConnsMutex.RLock()
115
+	defer c.proxyConnsMutex.RUnlock()
116
+
117
+	return len(c.proxyConns)
118
+}
119
+
120
+func (c *connection) Attach(conn *ProxyConn) error {
121
+	select {
122
+	case <-c.channelDone:
123
+		return ErrClosed
124
+	case c.channelConnAttach <- conn:
125
+		return nil
126
+	}
127
+}
128
+
129
+func (c *connection) Detach(connID conntypes.ConnID) {
130
+	select {
131
+	case <-c.channelDone:
132
+	case c.channelConnDetach <- connID:
133
+	}
134
+}
135
+
136
+func newConnection(req *protocol.TelegramRequest) (*connection, error) {
137
+	conn, err := mtproto.TelegramProtocol(req)
138
+	if err != nil {
139
+		return nil, fmt.Errorf("cannot create a new connection: %w", err)
140
+	}
141
+
142
+	id := rand.Int() // nolint: gosec
143
+	rv := &connection{
144
+		conn: conn,
145
+		id:   id,
146
+		logger: zap.S().Named("hub-connection").With("id", id,
147
+			"dc", req.ClientProtocol.DC(),
148
+			"protocol", req.ClientProtocol.ConnectionProtocol()),
149
+		proxyConns: make(map[string]*ProxyConn),
150
+
151
+		channelRead:       make(chan *rpc.ProxyResponse, 1),
152
+		channelDone:       make(chan struct{}),
153
+		channelWrite:      make(chan conntypes.Packet),
154
+		channelConnAttach: make(chan *ProxyConn),
155
+		channelConnDetach: make(chan conntypes.ConnID),
156
+	}
157
+
158
+	go rv.readLoop()
159
+
160
+	go rv.run()
161
+
162
+	return rv, nil
163
+}

+ 70
- 0
hub/connection_list.go Целия файл

@@ -0,0 +1,70 @@
1
+package hub
2
+
3
+import (
4
+	"fmt"
5
+	"sort"
6
+
7
+	"mtg/config"
8
+)
9
+
10
+type connectionList struct {
11
+	connections []*connection
12
+}
13
+
14
+func (c *connectionList) Get(conn *ProxyConn) (*connection, error) {
15
+	if len(c.connections) > 0 {
16
+		c.gc()
17
+	}
18
+
19
+	if len(c.connections) > 0 && c.connections[0].Len() < config.C.MultiplexPerConnection {
20
+		if err := c.connections[0].Attach(conn); err == nil {
21
+			return c.connections[0], nil
22
+		}
23
+	}
24
+
25
+	newConn, err := newConnection(conn.req)
26
+	if err != nil {
27
+		return nil, fmt.Errorf("cannot allocate a new connection: %w", err)
28
+	}
29
+
30
+	if err = newConn.Attach(conn); err != nil {
31
+		newConn.Close()
32
+		return nil, fmt.Errorf("cannot attach to the newly created connection: %w", err)
33
+	}
34
+
35
+	c.connections = append(c.connections, newConn)
36
+	lastIndex := len(c.connections) - 1
37
+	c.connections[0], c.connections[lastIndex] = c.connections[lastIndex], c.connections[0]
38
+
39
+	return newConn, nil
40
+}
41
+
42
+func (c *connectionList) gc() {
43
+	prevLen := len(c.connections)
44
+
45
+	for i := len(c.connections) - 1; i >= 0; i-- {
46
+		lastIndex := len(c.connections) - 1
47
+
48
+		if c.connections[i].Done() {
49
+			c.connections[i].Close()
50
+
51
+			if len(c.connections)-1 == i {
52
+				c.connections = c.connections[:lastIndex]
53
+			} else {
54
+				c.connections[i], c.connections[lastIndex] = c.connections[lastIndex], c.connections[i]
55
+			}
56
+		}
57
+	}
58
+
59
+	if prevLen != len(c.connections) {
60
+		c.sort()
61
+	}
62
+}
63
+
64
+func (c *connectionList) sort() {
65
+	if len(c.connections) > 1 {
66
+		sort.Slice(c.connections, func(i, j int) bool {
67
+			return c.connections[i].Len() < c.connections[j].Len()
68
+		})
69
+	}
70
+}

+ 40
- 0
hub/hub.go Целия файл

@@ -0,0 +1,40 @@
1
+package hub
2
+
3
+import (
4
+	"context"
5
+	"sync"
6
+
7
+	"mtg/protocol"
8
+)
9
+
10
+type hub struct {
11
+	muxes map[int32]*mux
12
+	mutex sync.RWMutex
13
+	ctx   context.Context
14
+}
15
+
16
+func (h *hub) Register(req *protocol.TelegramRequest) (*ProxyConn, error) {
17
+	return h.getMux(req).Get(req)
18
+}
19
+
20
+func (h *hub) getMux(req *protocol.TelegramRequest) *mux {
21
+	var key int32 = 32767 + int32(req.ClientProtocol.DC()) + 100000*int32(req.ClientProtocol.ConnectionProtocol())
22
+
23
+	h.mutex.RLock()
24
+	m, ok := h.muxes[key]
25
+	h.mutex.RUnlock()
26
+
27
+	if !ok {
28
+		h.mutex.Lock()
29
+		m, ok = h.muxes[key]
30
+
31
+		if !ok {
32
+			m = newMux(h.ctx)
33
+			h.muxes[key] = m
34
+		}
35
+
36
+		h.mutex.Unlock()
37
+	}
38
+
39
+	return m
40
+}

+ 24
- 0
hub/init.go Целия файл

@@ -0,0 +1,24 @@
1
+package hub
2
+
3
+import (
4
+	"context"
5
+	"errors"
6
+	"sync"
7
+)
8
+
9
+var (
10
+	ErrTimeout = errors.New("timeout")
11
+	ErrClosed  = errors.New("context is closed")
12
+
13
+	Hub      Interface
14
+	initOnce sync.Once
15
+)
16
+
17
+func Init(ctx context.Context) {
18
+	initOnce.Do(func() {
19
+		Hub = &hub{
20
+			muxes: make(map[int32]*mux),
21
+			ctx:   ctx,
22
+		}
23
+	})
24
+}

+ 7
- 0
hub/interface.go Целия файл

@@ -0,0 +1,7 @@
1
+package hub
2
+
3
+import "mtg/protocol"
4
+
5
+type Interface interface {
6
+	Register(*protocol.TelegramRequest) (*ProxyConn, error)
7
+}

+ 81
- 0
hub/mux.go Целия файл

@@ -0,0 +1,81 @@
1
+package hub
2
+
3
+import (
4
+	"context"
5
+
6
+	"mtg/conntypes"
7
+	"mtg/protocol"
8
+)
9
+
10
+type muxNewRequest struct {
11
+	req  *protocol.TelegramRequest
12
+	resp chan<- muxNewResponse
13
+}
14
+
15
+type muxNewResponse struct {
16
+	conn *ProxyConn
17
+	err  error
18
+}
19
+
20
+type mux struct {
21
+	connections   connectionList
22
+	clients       map[string]*connection
23
+	ctx           context.Context
24
+	channelClosed chan conntypes.ConnID
25
+	channelNew    chan muxNewRequest
26
+}
27
+
28
+func (m *mux) run() {
29
+	for {
30
+		select {
31
+		case <-m.ctx.Done():
32
+			for _, v := range m.clients {
33
+				v.Close()
34
+			}
35
+
36
+			return
37
+		case req := <-m.channelNew:
38
+			proxyConn := newProxyConn(req.req, m.channelClosed)
39
+			conn, err := m.connections.Get(proxyConn)
40
+
41
+			if err == nil {
42
+				m.clients[string(req.req.ConnID[:])] = conn
43
+			}
44
+
45
+			req.resp <- muxNewResponse{
46
+				conn: proxyConn,
47
+				err:  err,
48
+			}
49
+			close(req.resp)
50
+		case connID := <-m.channelClosed:
51
+			if conn, ok := m.clients[string(connID[:])]; ok {
52
+				conn.Detach(connID)
53
+				delete(m.clients, string(connID[:]))
54
+			}
55
+		}
56
+	}
57
+}
58
+
59
+func (m *mux) Get(req *protocol.TelegramRequest) (*ProxyConn, error) {
60
+	resp := make(chan muxNewResponse)
61
+	m.channelNew <- muxNewRequest{
62
+		req:  req,
63
+		resp: resp,
64
+	}
65
+
66
+	rv := <-resp
67
+
68
+	return rv.conn, rv.err
69
+}
70
+
71
+func newMux(ctx context.Context) *mux {
72
+	m := &mux{
73
+		ctx:           ctx,
74
+		clients:       make(map[string]*connection),
75
+		channelClosed: make(chan conntypes.ConnID, 1),
76
+		channelNew:    make(chan muxNewRequest),
77
+	}
78
+	go m.run()
79
+
80
+	return m
81
+}

+ 79
- 0
hub/proxy_conn.go Целия файл

@@ -0,0 +1,79 @@
1
+package hub
2
+
3
+import (
4
+	"sync"
5
+	"time"
6
+
7
+	"mtg/conntypes"
8
+	"mtg/mtproto/rpc"
9
+	"mtg/protocol"
10
+)
11
+
12
+const (
13
+	proxyConnWriteTimeout = 2 * time.Minute
14
+	proxyConnReadTimeout  = 2 * time.Minute
15
+
16
+	proxyConnBackpressureAfter = 10
17
+)
18
+
19
+type ProxyConn struct {
20
+	closeOnce       sync.Once
21
+	req             *protocol.TelegramRequest
22
+	channelResponse chan *rpc.ProxyResponse
23
+	channelClosed   chan<- conntypes.ConnID
24
+	channelWrite    chan<- conntypes.Packet
25
+	channelDone     chan struct{}
26
+}
27
+
28
+func (p *ProxyConn) Read() (*rpc.ProxyResponse, error) {
29
+	timer := time.NewTimer(proxyConnReadTimeout)
30
+	defer timer.Stop()
31
+
32
+	select {
33
+	case <-timer.C:
34
+		return nil, ErrTimeout
35
+	case <-p.channelDone:
36
+		return nil, ErrClosed
37
+	case packet := <-p.channelResponse:
38
+		return packet, nil
39
+	}
40
+}
41
+
42
+func (p *ProxyConn) Write(packet conntypes.Packet) error {
43
+	timer := time.NewTimer(proxyConnWriteTimeout)
44
+	defer timer.Stop()
45
+
46
+	select {
47
+	case <-timer.C:
48
+		return ErrTimeout
49
+	case <-p.channelDone:
50
+		return ErrClosed
51
+	case p.channelWrite <- packet:
52
+		return nil
53
+	}
54
+}
55
+
56
+func (p *ProxyConn) put(response *rpc.ProxyResponse) {
57
+	select {
58
+	case <-p.channelDone:
59
+	case p.channelResponse <- response:
60
+	}
61
+}
62
+
63
+func (p *ProxyConn) Close() {
64
+	p.closeOnce.Do(func() {
65
+		close(p.channelDone)
66
+		go func() {
67
+			p.channelClosed <- p.req.ConnID
68
+		}()
69
+	})
70
+}
71
+
72
+func newProxyConn(req *protocol.TelegramRequest, channelClosed chan<- conntypes.ConnID) *ProxyConn {
73
+	return &ProxyConn{
74
+		channelResponse: make(chan *rpc.ProxyResponse, proxyConnBackpressureAfter),
75
+		channelDone:     make(chan struct{}),
76
+		channelClosed:   channelClosed,
77
+		req:             req,
78
+	}
79
+}

+ 101
- 203
main.go Целия файл

@@ -1,23 +1,15 @@
1 1
 package main
2 2
 
3 3
 import (
4
-	"encoding/json"
5
-	"fmt"
6
-	"io"
7 4
 	"math/rand"
8 5
 	"os"
9
-	"syscall"
10 6
 	"time"
11 7
 
12
-	"github.com/juju/errors"
13
-	"go.uber.org/zap"
14
-	"go.uber.org/zap/zapcore"
15 8
 	kingpin "gopkg.in/alecthomas/kingpin.v2"
16 9
 
17
-	"github.com/9seconds/mtg/config"
18
-	"github.com/9seconds/mtg/ntp"
19
-	"github.com/9seconds/mtg/proxy"
20
-	"github.com/9seconds/mtg/stats"
10
+	"mtg/cli"
11
+	"mtg/config"
12
+	"mtg/utils"
21 13
 )
22 14
 
23 15
 var version = "dev" // this has to be set by build ld flags
@@ -25,233 +17,139 @@ var version = "dev" // this has to be set by build ld flags
25 17
 var (
26 18
 	app = kingpin.New("mtg", "Simple MTPROTO proxy.")
27 19
 
28
-	debug = app.Flag("debug",
20
+	generateSecretCommand = app.Command("generate-secret",
21
+		"Generate new secret")
22
+	generateCloakHost = generateSecretCommand.Flag("cloak-host",
23
+		"A host to use for TLS cloaking.").
24
+		Short('c').
25
+		Default("storage.googleapis.com").
26
+		String()
27
+	generateSecretType = generateSecretCommand.Arg("type",
28
+		"A type of secret to generate. Valid options are 'simple', 'secured' and 'tls'").
29
+		Required().
30
+		Enum("simple", "secured", "tls")
31
+
32
+	runCommand = app.Command("run",
33
+		"Run new proxy instance")
34
+	runDebug = runCommand.Flag("debug",
29 35
 		"Run in debug mode.").
30 36
 		Short('d').
31 37
 		Envar("MTG_DEBUG").
32 38
 		Bool()
33
-	verbose = app.Flag("verbose",
39
+	runVerbose = runCommand.Flag("verbose",
34 40
 		"Run in verbose mode.").
35 41
 		Short('v').
36 42
 		Envar("MTG_VERBOSE").
37 43
 		Bool()
38
-
39
-	bindIP = app.Flag("bind-ip",
40
-		"Which IP to bind to.").
44
+	runBind = runCommand.Flag("bind",
45
+		"Host:Port to bind proxy to.").
41 46
 		Short('b').
42
-		Envar("MTG_IP").
43
-		Default("127.0.0.1").
44
-		IP()
45
-	bindPort = app.Flag("bind-port",
46
-		"Which port to bind to.").
47
-		Short('p').
48
-		Envar("MTG_PORT").
49
-		Default("3128").
50
-		Uint16()
51
-
52
-	publicIPv4 = app.Flag("public-ipv4",
53
-		"Which IPv4 address is public.").
47
+		Envar("MTG_BIND").
48
+		Default("0.0.0.0:3128").
49
+		TCP()
50
+	runPublicIPv4 = runCommand.Flag("public-ipv4",
51
+		"Which IPv4 host:port to use.").
54 52
 		Short('4').
55 53
 		Envar("MTG_IPV4").
56
-		IP()
57
-	publicIPv4Port = app.Flag("public-ipv4-port",
58
-		"Which IPv4 port is public. Default is 'bind-port' value.").
59
-		Envar("MTG_IPV4_PORT").
60
-		Uint16()
61
-
62
-	publicIPv6 = app.Flag("public-ipv6",
63
-		"Which IPv6 address is public.").
54
+		TCP()
55
+	runPublicIPv6 = runCommand.Flag("public-ipv6",
56
+		"Which IPv6 host:port to use.").
64 57
 		Short('6').
65 58
 		Envar("MTG_IPV6").
66
-		IP()
67
-	publicIPv6Port = app.Flag("public-ipv6-port",
68
-		"Which IPv6 port is public. Default is 'bind-port' value.").
69
-		Envar("MTG_IPV6_PORT").
70
-		Uint16()
71
-
72
-	statsIP = app.Flag("stats-ip",
73
-		"Which IP bind stats server to.").
59
+		TCP()
60
+	runStatsBind = runCommand.Flag("stats-bind",
61
+		"Which Host:Port to bind stats server to.").
74 62
 		Short('t').
75
-		Envar("MTG_STATS_IP").
76
-		Default("127.0.0.1").
77
-		IP()
78
-	statsPort = app.Flag("stats-port",
79
-		"Which port bind stats to.").
80
-		Short('q').
81
-		Envar("MTG_STATS_PORT").
82
-		Default("3129").
83
-		Uint16()
84
-
85
-	statsdIP = app.Flag("statsd-ip",
86
-		"Which IP should we use for working with statsd.").
87
-		Envar("MTG_STATSD_IP").
88
-		String()
89
-	statsdPort = app.Flag("statsd-port",
90
-		"Which port should we use for working with statsd.").
91
-		Envar("MTG_STATSD_PORT").
92
-		Default("8125").
93
-		Uint16()
94
-	statsdNetwork = app.Flag("statsd-network",
95
-		"Which network is used to work with statsd. Only 'tcp' and 'udp' are supported.").
96
-		Envar("MTG_STATSD_NETWORK").
97
-		Default("udp").
98
-		String()
99
-	statsdPrefix = app.Flag("statsd-prefix",
100
-		"Which bucket prefix should we use for sending stats to statsd.").
101
-		Envar("MTG_STATSD_PREFIX").
63
+		Envar("MTG_STATS_BIND").
64
+		Default("127.0.0.1:3129").
65
+		TCP()
66
+	runStatsNamespace = runCommand.Flag("stats-namespace",
67
+		"Which namespace to use for Prometheus.").
68
+		Envar("MTG_STATS_NAMESPACE").
102 69
 		Default("mtg").
103 70
 		String()
104
-	statsdTagsFormat = app.Flag("statsd-tags-format",
71
+	runStatsdAddress = runCommand.Flag("statsd-addr",
72
+		"Host:port of statsd server").
73
+		Envar("MTG_STATSD_ADDR").
74
+		TCP()
75
+	runStatsdTagsFormat = runCommand.Flag("statsd-tags-format",
105 76
 		"Which tag format should we use to send stats metrics. Valid options are 'datadog' and 'influxdb'.").
106 77
 		Envar("MTG_STATSD_TAGS_FORMAT").
107
-		String()
108
-	statsdTags = app.Flag("statsd-tags",
78
+		Default("influxdb").
79
+		Enum("datadog", "influxdb")
80
+	runStatsdTags = runCommand.Flag("statsd-tags",
109 81
 		"Tags to use for working with statsd (specified as 'key=value').").
110 82
 		Envar("MTG_STATSD_TAGS").
111 83
 		StringMap()
112
-
113
-	prometheusPrefix = app.Flag("prometheus-prefix",
114
-		"Which namespace to use to send stats to Prometheus.").
115
-		Envar("MTG_PROMETHEUS_PREFIX").
116
-		Default("mtg").
117
-		String()
118
-
119
-	writeBufferSize = app.Flag("write-buffer",
120
-		"Write buffer size in bytes. You can think about it as a buffer from client to Telegram.").
84
+	runWriteBufferSize = runCommand.Flag("write-buffer",
85
+		"Write buffer size. You can think about it as a buffer from client to Telegram.").
121 86
 		Short('w').
122 87
 		Envar("MTG_BUFFER_WRITE").
123
-		Default("65536").
124
-		Uint32()
125
-	readBufferSize = app.Flag("read-buffer",
126
-		"Read buffer size in bytes. You can think about it as a buffer from Telegram to client.").
88
+		Default("64KB").
89
+		Bytes()
90
+	runReadBufferSize = runCommand.Flag("read-buffer",
91
+		"Read buffer size. You can think about it as a buffer from Telegram to client.").
127 92
 		Short('r').
128 93
 		Envar("MTG_BUFFER_READ").
129
-		Default("131072").
130
-		Uint32()
131
-	secureOnly = app.Flag("secure-only",
132
-		"Support clients with dd-secrets only.").
133
-		Short('s').
134
-		Envar("MTG_SECURE_ONLY").
135
-		Bool()
136
-
137
-	antiReplayMaxSize = app.Flag("anti-replay-max-size",
138
-		"Max size of antireplay cache in megabytes.").
94
+		Default("128KB").
95
+		Bytes()
96
+	runTLSCloakPort = runCommand.Flag("cloak-port",
97
+		"Port which should be used for host cloaking.").
98
+		Envar("MTG_CLOAK_PORT").
99
+		Default("443").
100
+		Uint16()
101
+	runAntiReplayMaxSize = runCommand.Flag("anti-replay-max-size",
102
+		"Max size of antireplay cache.").
139 103
 		Envar("MTG_ANTIREPLAY_MAXSIZE").
140
-		Default("128").
141
-		Int()
142
-	antiReplayEvictionTime = app.Flag("anti-replay-eviction-time",
143
-		"Eviction time period for obfuscated2 handshakes").
144
-		Envar("MTG_ANTIREPLAY_EVICTIONTIME").
145
-		Default("168h").
146
-		Duration()
147
-
148
-	secret = app.Arg("secret", "Secret of this proxy.").Required().HexBytes()
149
-	adtag  = app.Arg("adtag", "ADTag of the proxy.").HexBytes()
104
+		Default("128MB").
105
+		Bytes()
106
+	runMultiplexPerConnection = runCommand.Flag("multiplex-per-connection",
107
+		"How many clients can share a single connection to Telegram.").
108
+		Envar("MTG_MULTIPLEX_PERCONNECTION").
109
+		Default("50").
110
+		Uint()
111
+	runSecret = runCommand.Arg("secret", "Secret of this proxy.").Required().HexBytes()
112
+	runAdtag  = runCommand.Arg("adtag", "ADTag of the proxy.").HexBytes()
150 113
 )
151 114
 
152
-func main() { // nolint: gocyclo
115
+func main() {
153 116
 	rand.Seed(time.Now().UTC().UnixNano())
154 117
 	app.Version(version)
155 118
 	app.HelpFlag.Short('h')
156 119
 
157
-	kingpin.MustParse(app.Parse(os.Args[1:]))
158
-
159
-	err := setRLimit()
160
-	if err != nil {
161
-		usage(err.Error())
120
+	if err := utils.SetLimits(); err != nil {
121
+		cli.Fatal(err)
162 122
 	}
163 123
 
164
-	conf, err := config.NewConfig(*debug, *verbose,
165
-		*writeBufferSize, *readBufferSize,
166
-		*bindIP, *publicIPv4, *publicIPv6, *statsIP,
167
-		*bindPort, *publicIPv4Port, *publicIPv6Port, *statsPort, *statsdPort,
168
-		*statsdIP, *statsdNetwork, *statsdPrefix, *statsdTagsFormat,
169
-		*statsdTags, *prometheusPrefix, *secureOnly,
170
-		*antiReplayMaxSize, *antiReplayEvictionTime,
171
-		*secret, *adtag,
172
-	)
173
-	if err != nil {
174
-		usage(err.Error())
175
-	}
176
-
177
-	atom := zap.NewAtomicLevel()
178
-	switch {
179
-	case conf.Debug:
180
-		atom.SetLevel(zapcore.DebugLevel)
181
-	case conf.Verbose:
182
-		atom.SetLevel(zapcore.InfoLevel)
183
-	default:
184
-		atom.SetLevel(zapcore.ErrorLevel)
185
-	}
186
-	encoderCfg := zap.NewProductionEncoderConfig()
187
-	logger := zap.New(zapcore.NewCore(
188
-		zapcore.NewJSONEncoder(encoderCfg),
189
-		zapcore.Lock(os.Stderr),
190
-		atom,
191
-	))
192
-	zap.ReplaceGlobals(logger)
193
-	defer logger.Sync() // nolint: errcheck
194
-
195
-	printURLs(conf.GetURLs())
196
-	zap.S().Debugw("Configuration", "config", conf)
197
-
198
-	if conf.UseMiddleProxy() {
199
-		zap.S().Infow("Use middle proxy connection to Telegram")
200
-		if diff, err := ntp.Fetch(); err != nil {
201
-			zap.S().Warnw("Could not fetch time data from NTP")
202
-		} else {
203
-			if diff >= time.Second {
204
-				usage(fmt.Sprintf("You choose to use middle proxy but your clock drift (%s) "+
205
-					"is bigger than 1 second. Please, sync your time", diff))
206
-			}
207
-			go ntp.AutoUpdate()
124
+	switch kingpin.MustParse(app.Parse(os.Args[1:])) {
125
+	case generateSecretCommand.FullCommand():
126
+		cli.Generate(*generateSecretType, *generateCloakHost)
127
+	case runCommand.FullCommand():
128
+		err := config.Init(
129
+			config.Opt{Option: config.OptionTypeDebug, Value: *runDebug},
130
+			config.Opt{Option: config.OptionTypeVerbose, Value: *runVerbose},
131
+			config.Opt{Option: config.OptionTypeBind, Value: *runBind},
132
+			config.Opt{Option: config.OptionTypePublicIPv4, Value: *runPublicIPv4},
133
+			config.Opt{Option: config.OptionTypePublicIPv6, Value: *runPublicIPv6},
134
+			config.Opt{Option: config.OptionTypeStatsBind, Value: *runStatsBind},
135
+			config.Opt{Option: config.OptionTypeStatsNamespace, Value: *runStatsNamespace},
136
+			config.Opt{Option: config.OptionTypeStatsdAddress, Value: *runStatsdAddress},
137
+			config.Opt{Option: config.OptionTypeStatsdTagsFormat, Value: *runStatsdTagsFormat},
138
+			config.Opt{Option: config.OptionTypeStatsdTags, Value: *runStatsdTags},
139
+			config.Opt{Option: config.OptionTypeWriteBufferSize, Value: *runWriteBufferSize},
140
+			config.Opt{Option: config.OptionTypeReadBufferSize, Value: *runReadBufferSize},
141
+			config.Opt{Option: config.OptionTypeCloakPort, Value: *runTLSCloakPort},
142
+			config.Opt{Option: config.OptionTypeAntiReplayMaxSize, Value: *runAntiReplayMaxSize},
143
+			config.Opt{Option: config.OptionTypeMultiplexPerConnection, Value: *runMultiplexPerConnection},
144
+			config.Opt{Option: config.OptionTypeSecret, Value: *runSecret},
145
+			config.Opt{Option: config.OptionTypeAdtag, Value: *runAdtag},
146
+		)
147
+		if err != nil {
148
+			cli.Fatal(err)
208 149
 		}
209
-	} else {
210
-		zap.S().Infow("Use direct connection to Telegram")
211
-	}
212
-
213
-	if err := stats.Init(conf); err != nil {
214
-		panic(err)
215
-	}
216
-
217
-	server, err := proxy.NewProxy(conf)
218
-	if err != nil {
219
-		panic(err)
220
-	}
221
-	if err := server.Serve(); err != nil {
222
-		zap.S().Fatalw("Server stopped", "error", err)
223
-	}
224
-}
225
-
226
-func setRLimit() (err error) {
227
-	rLimit := syscall.Rlimit{}
228
-	err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
229
-	if err != nil {
230
-		err = errors.Annotate(err, "Cannot get rlimit")
231
-		return
232
-	}
233
-	rLimit.Cur = rLimit.Max
234 150
 
235
-	err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
236
-	if err != nil {
237
-		err = errors.Annotate(err, "Cannot set rlimit")
238
-	}
239
-
240
-	return
241
-}
242
-
243
-func printURLs(data interface{}) {
244
-	encoder := json.NewEncoder(os.Stdout)
245
-	encoder.SetEscapeHTML(false)
246
-	encoder.SetIndent("", "  ")
247
-
248
-	err := encoder.Encode(data)
249
-	if err != nil {
250
-		panic(err)
151
+		if err := cli.Proxy(); err != nil {
152
+			cli.Fatal(err)
153
+		}
251 154
 	}
252 155
 }
253
-
254
-func usage(msg string) {
255
-	io.WriteString(os.Stderr, msg+"\n") // nolint: errcheck, gosec
256
-	os.Exit(1)
257
-}

+ 0
- 87
mtproto/connection_options.go Целия файл

@@ -1,87 +0,0 @@
1
-package mtproto
2
-
3
-import (
4
-	"bytes"
5
-	"net"
6
-
7
-	"github.com/juju/errors"
8
-)
9
-
10
-// ConnectionType is a type of obfuscated2/mtproto connection requested
11
-// by the user.
12
-type ConnectionType uint8
13
-
14
-// ConnectionProtocol is a type of IP protocol to use.
15
-type ConnectionProtocol uint8
16
-
17
-// Hacks is a simple structure to store flags for packet transmission.
18
-type Hacks struct {
19
-	SimpleAck bool
20
-	QuickAck  bool
21
-}
22
-
23
-// ConnectionOpts presents an options, metadata on connection requested
24
-// by the user on handshake.
25
-type ConnectionOpts struct {
26
-	DC              int16
27
-	ConnectionType  ConnectionType
28
-	ConnectionProto ConnectionProtocol
29
-	// Read and Write means direction related to the client.
30
-	// ReadHacks are meant to be flushed on client read
31
-	// WriteHacks are meant to be flushed on client write.
32
-	ReadHacks  Hacks
33
-	WriteHacks Hacks
34
-	ClientAddr *net.TCPAddr
35
-}
36
-
37
-// Different connection types which user requests from Telegram.
38
-const (
39
-	ConnectionTypeUnknown ConnectionType = iota
40
-	ConnectionTypeAbridged
41
-	ConnectionTypeIntermediate
42
-	ConnectionTypeSecure
43
-)
44
-
45
-// ConnectionProtocol* define which connection protocols to use.
46
-// ConnectionProtocolAny means that any is suitable.
47
-const (
48
-	ConnectionProtocolIPv4 ConnectionProtocol = 1
49
-	ConnectionProtocolIPv6                    = ConnectionProtocolIPv4 << 1
50
-	ConnectionProtocolAny                     = ConnectionProtocolIPv4 | ConnectionProtocolIPv6
51
-)
52
-
53
-// Connection tags for mtproto handshakes.
54
-var (
55
-	ConnectionTagAbridged     = []byte{0xef, 0xef, 0xef, 0xef}
56
-	ConnectionTagIntermediate = []byte{0xee, 0xee, 0xee, 0xee}
57
-	ConnectionTagSecure       = []byte{0xdd, 0xdd, 0xdd, 0xdd}
58
-)
59
-
60
-// Tag maps connection type to the corresponding handshake tag.
61
-func (t ConnectionType) Tag() ([]byte, error) {
62
-	switch t {
63
-	case ConnectionTypeAbridged:
64
-		return ConnectionTagAbridged, nil
65
-	case ConnectionTypeIntermediate:
66
-		return ConnectionTagIntermediate, nil
67
-	case ConnectionTypeSecure:
68
-		return ConnectionTagSecure, nil
69
-	default:
70
-		return nil, errors.Errorf("Unknown connection type %d", t)
71
-	}
72
-}
73
-
74
-// ConnectionTagFromHandshake maps magic bytes to the connection type.
75
-func ConnectionTagFromHandshake(magic []byte) (ConnectionType, error) {
76
-	if bytes.Equal(magic, ConnectionTagIntermediate) {
77
-		return ConnectionTypeIntermediate, nil
78
-	}
79
-	if bytes.Equal(magic, ConnectionTagAbridged) {
80
-		return ConnectionTypeAbridged, nil
81
-	}
82
-	if bytes.Equal(magic, ConnectionTagSecure) {
83
-		return ConnectionTypeSecure, nil
84
-	}
85
-
86
-	return ConnectionTypeUnknown, errors.New("Unknown handshake protocol")
87
-}

+ 102
- 0
mtproto/protocol.go Целия файл

@@ -0,0 +1,102 @@
1
+package mtproto
2
+
3
+import (
4
+	"fmt"
5
+
6
+	"mtg/conntypes"
7
+	"mtg/mtproto/rpc"
8
+	"mtg/protocol"
9
+	"mtg/telegram"
10
+	"mtg/wrappers/packet"
11
+	"mtg/wrappers/stream"
12
+)
13
+
14
+func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.PacketReadWriteCloser, error) {
15
+	conn, err := telegram.Middle.Dial(req.ClientProtocol.DC(),
16
+		req.ClientProtocol.ConnectionProtocol())
17
+	if err != nil {
18
+		return nil, fmt.Errorf("cannot connect to telegram: %w", err)
19
+	}
20
+
21
+	rpcNonceConn := packet.NewMtprotoFrame(conn, rpc.SeqNoNonce)
22
+	rpcNonceReq, err := doRPCNonceRequest(rpcNonceConn)
23
+
24
+	if err != nil {
25
+		return nil, fmt.Errorf("cannot do nonce request: %w", err)
26
+	}
27
+
28
+	rpcNonceResp, err := getRPCNonceResponse(rpcNonceConn, rpcNonceReq)
29
+	if err != nil {
30
+		return nil, fmt.Errorf("cannot get nonce response: %w", err)
31
+	}
32
+
33
+	secureConn := stream.NewMiddleProxyCipher(conn, rpcNonceReq, rpcNonceResp, telegram.Middle.Secret())
34
+	frameConn := packet.NewMtprotoFrame(secureConn, rpc.SeqNoHandshake)
35
+
36
+	if err := doRPCHandshakeRequest(frameConn); err != nil {
37
+		return nil, fmt.Errorf("cannot do handshake request: %w", err)
38
+	}
39
+
40
+	if err := getRPCHandshakeResponse(frameConn); err != nil {
41
+		return nil, fmt.Errorf("cannot get handshake response: %w", err)
42
+	}
43
+
44
+	return frameConn, nil
45
+}
46
+
47
+func doRPCNonceRequest(conn conntypes.BasePacketWriter) (*rpc.NonceRequest, error) {
48
+	rpcNonceReq, err := rpc.NewNonceRequest(telegram.Middle.Secret())
49
+	if err != nil {
50
+		panic(err)
51
+	}
52
+
53
+	if err := conn.Write(rpcNonceReq.Bytes()); err != nil {
54
+		return nil, err
55
+	}
56
+
57
+	return rpcNonceReq, nil
58
+}
59
+
60
+func getRPCNonceResponse(conn conntypes.BasePacketReader, req *rpc.NonceRequest) (*rpc.NonceResponse, error) {
61
+	packet, err := conn.Read()
62
+	if err != nil {
63
+		return nil, fmt.Errorf("cannot read from connection: %w", err)
64
+	}
65
+
66
+	resp, err := rpc.NewNonceResponse(packet)
67
+	if err != nil {
68
+		return nil, fmt.Errorf("cannot build rpc nonce response: %w", err)
69
+	}
70
+
71
+	if err = resp.Valid(req); err != nil {
72
+		return nil, fmt.Errorf("invalid nonce response: %w", err)
73
+	}
74
+
75
+	return resp, nil
76
+}
77
+
78
+func doRPCHandshakeRequest(conn conntypes.BasePacketWriter) error {
79
+	if err := conn.Write(rpc.HandshakeRequest); err != nil {
80
+		return fmt.Errorf("cannot make a request: %w", err)
81
+	}
82
+
83
+	return nil
84
+}
85
+
86
+func getRPCHandshakeResponse(conn conntypes.BasePacketReader) error {
87
+	packet, err := conn.Read()
88
+	if err != nil {
89
+		return fmt.Errorf("cannot read a response: %w", err)
90
+	}
91
+
92
+	resp, err := rpc.NewHandshakeResponse(packet)
93
+	if err != nil {
94
+		return fmt.Errorf("cannot build a handshake response: %w", err)
95
+	}
96
+
97
+	if err := resp.Valid(); err != nil {
98
+		return fmt.Errorf("invalid handshake response: %w", err)
99
+	}
100
+
101
+	return nil
102
+}

mtproto/rpc/rpc.go → mtproto/rpc/consts.go Целия файл


+ 3
- 24
mtproto/rpc/handshake_request.go Целия файл

@@ -1,26 +1,5 @@
1 1
 package rpc
2 2
 
3
-import "bytes"
4
-
5
-// HandshakeRequest is the data type which is responsible for
6
-// constructing of correct handshake request.
7
-type HandshakeRequest struct {
8
-}
9
-
10
-// Bytes returns serialized handshake request.
11
-func (r *HandshakeRequest) Bytes() []byte {
12
-	buf := &bytes.Buffer{}
13
-	buf.Grow(len(TagHandshake) + len(HandshakeFlags) + len(HandshakeSenderPID) + len(HandshakePeerPID))
14
-
15
-	buf.Write(TagHandshake)       // nolint: gosec
16
-	buf.Write(HandshakeFlags)     // nolint: gosec
17
-	buf.Write(HandshakeSenderPID) // nolint: gosec
18
-	buf.Write(HandshakePeerPID)   // nolint: gosec
19
-
20
-	return buf.Bytes()
21
-}
22
-
23
-// NewHandshakeRequest creates new HandshakeRequest instance.
24
-func NewHandshakeRequest() *HandshakeRequest {
25
-	return &HandshakeRequest{}
26
-}
3
+var HandshakeRequest = append(TagHandshake,
4
+	append(HandshakeFlags,
5
+		append(HandshakeSenderPID, HandshakePeerPID...)...)...)

+ 7
- 8
mtproto/rpc/handshake_response.go Целия файл

@@ -2,12 +2,10 @@ package rpc
2 2
 
3 3
 import (
4 4
 	"bytes"
5
-
6
-	"github.com/juju/errors"
5
+	"errors"
6
+	"fmt"
7 7
 )
8 8
 
9
-// HandshakeResponse defines data structure which is used for storage of
10
-// handshake response.
11 9
 type HandshakeResponse struct {
12 10
 	Type      []byte
13 11
 	Flags     []byte
@@ -28,12 +26,13 @@ func (r *HandshakeResponse) Bytes() []byte {
28 26
 }
29 27
 
30 28
 // Valid checks that handshake response compliments request.
31
-func (r *HandshakeResponse) Valid(req *HandshakeRequest) error {
29
+func (r *HandshakeResponse) Valid() error {
32 30
 	if !bytes.Equal(r.Type, TagHandshake) {
33
-		return errors.New("Unexpected handshake tag")
31
+		return errors.New("unexpected handshake tag")
34 32
 	}
33
+
35 34
 	if !bytes.Equal(r.PeerPID, HandshakeSenderPID) {
36
-		return errors.New("Incorrect sender PID")
35
+		return errors.New("incorrect sender PID")
37 36
 	}
38 37
 
39 38
 	return nil
@@ -43,7 +42,7 @@ func (r *HandshakeResponse) Valid(req *HandshakeRequest) error {
43 42
 // data.
44 43
 func NewHandshakeResponse(data []byte) (*HandshakeResponse, error) {
45 44
 	if len(data) != 32 {
46
-		return nil, errors.New("Incorrect handshake response length")
45
+		return nil, fmt.Errorf("incorrect handshake response length %d", len(data))
47 46
 	}
48 47
 
49 48
 	return &HandshakeResponse{

+ 8
- 10
mtproto/rpc/nonce_request.go Целия файл

@@ -4,13 +4,10 @@ import (
4 4
 	"bytes"
5 5
 	"crypto/rand"
6 6
 	"encoding/binary"
7
+	"fmt"
7 8
 	"time"
8
-
9
-	"github.com/juju/errors"
10 9
 )
11 10
 
12
-// NonceRequest is the data type which contains all the data for correct
13
-// nonce request.
14 11
 type NonceRequest struct {
15 12
 	KeySelector []byte
16 13
 	CryptoTS    []byte
@@ -21,11 +18,11 @@ type NonceRequest struct {
21 18
 func (r *NonceRequest) Bytes() []byte {
22 19
 	buf := &bytes.Buffer{}
23 20
 
24
-	buf.Write(TagNonce)       // nolint: gosec
25
-	buf.Write(r.KeySelector)  // nolint: gosec
26
-	buf.Write(NonceCryptoAES) // nolint: gosec
27
-	buf.Write(r.CryptoTS)     // nolint: gosec
28
-	buf.Write(r.Nonce)        // nolint: gosec
21
+	buf.Write(TagNonce)
22
+	buf.Write(r.KeySelector)
23
+	buf.Write(NonceCryptoAES)
24
+	buf.Write(r.CryptoTS)
25
+	buf.Write(r.Nonce)
29 26
 
30 27
 	return buf.Bytes()
31 28
 }
@@ -37,8 +34,9 @@ func NewNonceRequest(proxySecret []byte) (*NonceRequest, error) {
37 34
 	cryptoTS := make([]byte, 4)
38 35
 
39 36
 	if _, err := rand.Read(nonce); err != nil {
40
-		return nil, errors.Annotate(err, "Cannot generate nonce")
37
+		return nil, fmt.Errorf("cannot generate nonce: %w", err)
41 38
 	}
39
+
42 40
 	copy(keySelector, proxySecret)
43 41
 
44 42
 	timestamp := time.Now().Truncate(time.Second).Unix() % 4294967296 // 256 ^ 4 - do not know how to name

+ 8
- 8
mtproto/rpc/nonce_response.go Целия файл

@@ -2,11 +2,10 @@ package rpc
2 2
 
3 3
 import (
4 4
 	"bytes"
5
-
6
-	"github.com/juju/errors"
5
+	"errors"
6
+	"fmt"
7 7
 )
8 8
 
9
-// NonceResponse is the data type which contains data of nonce response.
10 9
 type NonceResponse struct {
11 10
 	NonceRequest
12 11
 
@@ -27,16 +26,17 @@ func (r *NonceResponse) Bytes() []byte {
27 26
 	return buf.Bytes()
28 27
 }
29 28
 
30
-// Valid checks that nonce response compliments nonce request.
31 29
 func (r *NonceResponse) Valid(req *NonceRequest) error {
32 30
 	if !bytes.Equal(r.Type, TagNonce) {
33
-		return errors.New("Unexpected RPC type")
31
+		return errors.New("unexpected RPC type")
34 32
 	}
33
+
35 34
 	if !bytes.Equal(r.Crypto, NonceCryptoAES) {
36
-		return errors.New("Unexpected crypto type")
35
+		return errors.New("unexpected crypto type")
37 36
 	}
37
+
38 38
 	if !bytes.Equal(r.KeySelector, req.KeySelector) {
39
-		return errors.New("Unexpected key selector")
39
+		return errors.New("unexpected key selector")
40 40
 	}
41 41
 
42 42
 	return nil
@@ -45,7 +45,7 @@ func (r *NonceResponse) Valid(req *NonceRequest) error {
45 45
 // NewNonceResponse build new nonce response based on the given data.
46 46
 func NewNonceResponse(data []byte) (*NonceResponse, error) {
47 47
 	if len(data) != 32 {
48
-		return nil, errors.New("Unexpected message length")
48
+		return nil, fmt.Errorf("unexpected message length %d", len(data))
49 49
 	}
50 50
 
51 51
 	return &NonceResponse{

+ 27
- 20
mtproto/rpc/proxy_flags.go Целия файл

@@ -5,53 +5,60 @@ import (
5 5
 	"strings"
6 6
 )
7 7
 
8
-type proxyRequestFlags uint32
8
+type ProxyRequestFlags uint32
9 9
 
10 10
 const (
11
-	proxyRequestFlagsHasAdTag     proxyRequestFlags = 0x8
12
-	proxyRequestFlagsEncrypted    proxyRequestFlags = 0x2
13
-	proxyRequestFlagsMagic        proxyRequestFlags = 0x1000
14
-	proxyRequestFlagsExtMode2     proxyRequestFlags = 0x20000
15
-	proxyRequestFlagsIntermediate proxyRequestFlags = 0x20000000
16
-	proxyRequestFlagsAbdridged    proxyRequestFlags = 0x40000000
17
-	proxyRequestFlagsQuickAck     proxyRequestFlags = 0x80000000
18
-	proxyRequestFlagsPad          proxyRequestFlags = 0x8000000
11
+	ProxyRequestFlagsHasAdTag     ProxyRequestFlags = 0x8
12
+	ProxyRequestFlagsEncrypted    ProxyRequestFlags = 0x2
13
+	ProxyRequestFlagsMagic        ProxyRequestFlags = 0x1000
14
+	ProxyRequestFlagsExtMode2     ProxyRequestFlags = 0x20000
15
+	ProxyRequestFlagsIntermediate ProxyRequestFlags = 0x20000000
16
+	ProxyRequestFlagsAbdridged    ProxyRequestFlags = 0x40000000
17
+	ProxyRequestFlagsQuickAck     ProxyRequestFlags = 0x80000000
18
+	ProxyRequestFlagsPad          ProxyRequestFlags = 0x8000000
19 19
 )
20 20
 
21
-var proxyRequestFlagsEncryptedPrefix [8]byte
21
+var ProxyRequestFlagsEncryptedPrefix [8]byte
22 22
 
23
-func (r proxyRequestFlags) Bytes() []byte {
23
+func (r ProxyRequestFlags) Bytes() []byte {
24 24
 	converted := make([]byte, 4)
25 25
 	binary.LittleEndian.PutUint32(converted, uint32(r))
26 26
 
27 27
 	return converted
28 28
 }
29 29
 
30
-func (r proxyRequestFlags) String() string {
30
+func (r ProxyRequestFlags) String() string {
31 31
 	flags := make([]string, 0, 7)
32 32
 
33
-	if r&proxyRequestFlagsHasAdTag != 0 {
33
+	if r&ProxyRequestFlagsHasAdTag != 0 {
34 34
 		flags = append(flags, "HAS_AD_TAG")
35 35
 	}
36
-	if r&proxyRequestFlagsEncrypted != 0 {
36
+
37
+	if r&ProxyRequestFlagsEncrypted != 0 {
37 38
 		flags = append(flags, "ENCRYPTED")
38 39
 	}
39
-	if r&proxyRequestFlagsMagic != 0 {
40
+
41
+	if r&ProxyRequestFlagsMagic != 0 {
40 42
 		flags = append(flags, "MAGIC")
41 43
 	}
42
-	if r&proxyRequestFlagsExtMode2 != 0 {
44
+
45
+	if r&ProxyRequestFlagsExtMode2 != 0 {
43 46
 		flags = append(flags, "EXT_MODE_2")
44 47
 	}
45
-	if r&proxyRequestFlagsIntermediate != 0 {
48
+
49
+	if r&ProxyRequestFlagsIntermediate != 0 {
46 50
 		flags = append(flags, "INTERMEDIATE")
47 51
 	}
48
-	if r&proxyRequestFlagsAbdridged != 0 {
52
+
53
+	if r&ProxyRequestFlagsAbdridged != 0 {
49 54
 		flags = append(flags, "ABRIDGED")
50 55
 	}
51
-	if r&proxyRequestFlagsQuickAck != 0 {
56
+
57
+	if r&ProxyRequestFlagsQuickAck != 0 {
52 58
 		flags = append(flags, "QUICK_ACK")
53 59
 	}
54
-	if r&proxyRequestFlagsPad != 0 {
60
+
61
+	if r&ProxyRequestFlagsPad != 0 {
55 62
 		flags = append(flags, "PAD")
56 63
 	}
57 64
 

+ 0
- 105
mtproto/rpc/proxy_request.go Целия файл

@@ -1,105 +0,0 @@
1
-package rpc
2
-
3
-import (
4
-	"bytes"
5
-	"crypto/rand"
6
-	"encoding/binary"
7
-	"fmt"
8
-	"net"
9
-
10
-	"github.com/juju/errors"
11
-
12
-	"github.com/9seconds/mtg/mtproto"
13
-)
14
-
15
-// ProxyRequest is the data type for storing data required to compose
16
-// RPC_PROXY_REQ request.
17
-type ProxyRequest struct {
18
-	Flags        proxyRequestFlags
19
-	ConnectionID []byte
20
-	OurIPPort    []byte
21
-	ClientIPPort []byte
22
-	ADTag        []byte
23
-	Options      *mtproto.ConnectionOpts
24
-}
25
-
26
-// MakeHeader makes RPC_PROXY_REQ header. We need only to append the
27
-// data for it.
28
-func (r *ProxyRequest) MakeHeader(message []byte) (*bytes.Buffer, fmt.Stringer) {
29
-	bufferLength := len(TagProxyRequest) +
30
-		4 + // len(flags)
31
-		len(r.ConnectionID) +
32
-		len(r.ClientIPPort) +
33
-		len(r.OurIPPort) +
34
-		len(ProxyRequestExtraSize) +
35
-		len(ProxyRequestProxyTag) +
36
-		1 + // len(AdTag)
37
-		len(r.ADTag)
38
-	bufferLength += bufferLength % 4
39
-
40
-	buf := &bytes.Buffer{}
41
-	buf.Grow(bufferLength + len(message))
42
-
43
-	flags := r.Flags
44
-	if r.Options.ReadHacks.QuickAck {
45
-		flags |= proxyRequestFlagsQuickAck
46
-	}
47
-
48
-	if bytes.HasPrefix(message, proxyRequestFlagsEncryptedPrefix[:]) {
49
-		flags |= proxyRequestFlagsEncrypted
50
-	}
51
-
52
-	buf.Write(TagProxyRequest)                 // nolint: gosec
53
-	buf.Write(flags.Bytes())                   // nolint: gosec
54
-	buf.Write(r.ConnectionID)                  // nolint: gosec
55
-	buf.Write(r.ClientIPPort)                  // nolint: gosec
56
-	buf.Write(r.OurIPPort)                     // nolint: gosec
57
-	buf.Write(ProxyRequestExtraSize)           // nolint: gosec
58
-	buf.Write(ProxyRequestProxyTag)            // nolint: gosec
59
-	buf.WriteByte(byte(len(r.ADTag)))          // nolint: gosec
60
-	buf.Write(r.ADTag)                         // nolint: gosec
61
-	buf.Write(make([]byte, (4-buf.Len()%4)%4)) // nolint: gosec
62
-
63
-	return buf, flags
64
-}
65
-
66
-// NewProxyRequest build new ProxyRequest data structure.
67
-func NewProxyRequest(clientAddr, ownAddr *net.TCPAddr,
68
-	opts *mtproto.ConnectionOpts, adTag []byte) (*ProxyRequest, error) {
69
-	flags := proxyRequestFlagsHasAdTag | proxyRequestFlagsMagic | proxyRequestFlagsExtMode2
70
-
71
-	switch opts.ConnectionType {
72
-	case mtproto.ConnectionTypeAbridged:
73
-		flags |= proxyRequestFlagsAbdridged
74
-	case mtproto.ConnectionTypeIntermediate:
75
-		flags |= proxyRequestFlagsIntermediate
76
-	case mtproto.ConnectionTypeSecure:
77
-		flags |= proxyRequestFlagsIntermediate | proxyRequestFlagsPad
78
-	default:
79
-		panic("Unknown connection type")
80
-	}
81
-
82
-	request := &ProxyRequest{
83
-		Flags:        flags,
84
-		ADTag:        adTag,
85
-		Options:      opts,
86
-		ConnectionID: make([]byte, 8),
87
-		ClientIPPort: make([]byte, 16+4),
88
-		OurIPPort:    make([]byte, 16+4),
89
-	}
90
-
91
-	if _, err := rand.Read(request.ConnectionID); err != nil {
92
-		return nil, errors.Annotate(err, "Cannot generate connection ID")
93
-	}
94
-
95
-	port := [4]byte{}
96
-	copy(request.ClientIPPort[:16], clientAddr.IP.To16())
97
-	binary.LittleEndian.PutUint32(port[:], uint32(clientAddr.Port))
98
-	copy(request.ClientIPPort[16:], port[:])
99
-
100
-	copy(request.OurIPPort[:16], ownAddr.IP.To16())
101
-	binary.LittleEndian.PutUint32(port[:], uint32(ownAddr.Port))
102
-	copy(request.OurIPPort[16:], port[:])
103
-
104
-	return request, nil
105
-}

+ 53
- 0
mtproto/rpc/proxy_response.go Целия файл

@@ -0,0 +1,53 @@
1
+package rpc
2
+
3
+import (
4
+	"bytes"
5
+	"fmt"
6
+
7
+	"mtg/conntypes"
8
+)
9
+
10
+type ProxyResponseType uint8
11
+
12
+const (
13
+	ProxyResponseTypeAns ProxyResponseType = iota
14
+	ProxyResponseTypeSimpleAck
15
+	ProxyResponseTypeCloseExt
16
+)
17
+
18
+type ProxyResponse struct {
19
+	Type    ProxyResponseType
20
+	ConnID  conntypes.ConnID
21
+	Payload conntypes.Packet
22
+}
23
+
24
+func ParseProxyResponse(packet conntypes.Packet) (*ProxyResponse, error) {
25
+	var response ProxyResponse
26
+
27
+	if len(packet) < 4 {
28
+		return nil, fmt.Errorf("incorrect packet length: %d", len(packet))
29
+	}
30
+
31
+	tag := packet[:4]
32
+
33
+	switch {
34
+	case bytes.Equal(tag, TagProxyAns):
35
+		response.Type = ProxyResponseTypeAns
36
+		copy(response.ConnID[:], packet[8:16])
37
+		response.Payload = packet[16:]
38
+
39
+		return &response, nil
40
+	case bytes.Equal(tag, TagSimpleAck):
41
+		response.Type = ProxyResponseTypeSimpleAck
42
+		copy(response.ConnID[:], packet[4:12])
43
+		response.Payload = packet[12:]
44
+
45
+		return &response, nil
46
+	case bytes.Equal(tag, TagCloseExt):
47
+		response.Type = ProxyResponseTypeCloseExt
48
+
49
+		return &response, nil
50
+	}
51
+
52
+	return nil, fmt.Errorf("unknown response type %x", tag)
53
+}

+ 5
- 3
ntp/ntp.go Целия файл

@@ -1,17 +1,17 @@
1 1
 package ntp
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"math/rand"
5 6
 	"time"
6 7
 
7 8
 	"github.com/beevik/ntp"
8
-	"github.com/juju/errors"
9 9
 	"go.uber.org/zap"
10 10
 )
11 11
 
12 12
 const autoUpdatePeriod = time.Minute
13 13
 
14
-var ntpEndpoints = []string{
14
+var ntpEndpoints = [...]string{
15 15
 	"0.pool.ntp.org",
16 16
 	"1.pool.ntp.org",
17 17
 	"2.pool.ntp.org",
@@ -21,15 +21,17 @@ var ntpEndpoints = []string{
21 21
 // Fetch fetches the data on time drift.
22 22
 func Fetch() (time.Duration, error) {
23 23
 	url := ntpEndpoints[rand.Intn(len(ntpEndpoints))]
24
+
24 25
 	resp, err := ntp.Query(url)
25 26
 	if err != nil {
26
-		return 0, errors.Annotatef(err, "Cannot fetch NTP server %s", url)
27
+		return 0, fmt.Errorf("cannot fetch NTP server %s: %w", url, err)
27 28
 	}
28 29
 
29 30
 	offsetInt := int64(resp.ClockOffset)
30 31
 	if offsetInt < 0 {
31 32
 		offsetInt = -offsetInt
32 33
 	}
34
+
33 35
 	offset := time.Duration(offsetInt)
34 36
 
35 37
 	return offset, nil

+ 113
- 0
obfuscated2/client_protocol.go Целия файл

@@ -0,0 +1,113 @@
1
+package obfuscated2
2
+
3
+import (
4
+	"bytes"
5
+	"crypto/sha256"
6
+	"encoding/binary"
7
+	"errors"
8
+	"fmt"
9
+	"io"
10
+	"time"
11
+
12
+	"mtg/antireplay"
13
+	"mtg/config"
14
+	"mtg/conntypes"
15
+	"mtg/protocol"
16
+	"mtg/stats"
17
+	"mtg/utils"
18
+	"mtg/wrappers/stream"
19
+)
20
+
21
+const clientProtocolHandshakeTimeout = 10 * time.Second
22
+
23
+type ClientProtocol struct {
24
+	connectionType     conntypes.ConnectionType
25
+	connectionProtocol conntypes.ConnectionProtocol
26
+	dc                 conntypes.DC
27
+}
28
+
29
+func (c *ClientProtocol) ConnectionType() conntypes.ConnectionType {
30
+	return c.connectionType
31
+}
32
+
33
+func (c *ClientProtocol) ConnectionProtocol() conntypes.ConnectionProtocol {
34
+	return c.connectionProtocol
35
+}
36
+
37
+func (c *ClientProtocol) DC() conntypes.DC {
38
+	return c.dc
39
+}
40
+
41
+func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error) {
42
+	fm, err := c.ReadFrame(socket)
43
+	if err != nil {
44
+		return nil, fmt.Errorf("cannot make a client handshake: %w", err)
45
+	}
46
+
47
+	decHasher := sha256.New()
48
+	decHasher.Write(fm.Key())        // nolint: errcheck
49
+	decHasher.Write(config.C.Secret) // nolint: errcheck
50
+	decryptor := utils.MakeStreamCipher(decHasher.Sum(nil), fm.IV())
51
+
52
+	invertedFrame := fm.Invert()
53
+	encHasher := sha256.New()
54
+	encHasher.Write(invertedFrame.Key()) // nolint: errcheck
55
+	encHasher.Write(config.C.Secret)     // nolint: errcheck
56
+	encryptor := utils.MakeStreamCipher(encHasher.Sum(nil), invertedFrame.IV())
57
+
58
+	decryptedFrame := Frame{}
59
+	decryptor.XORKeyStream(decryptedFrame.Bytes(), fm.Bytes())
60
+
61
+	magic := decryptedFrame.Magic()
62
+
63
+	switch {
64
+	case bytes.Equal(magic, conntypes.ConnectionTagAbridged):
65
+		c.connectionType = conntypes.ConnectionTypeAbridged
66
+	case bytes.Equal(magic, conntypes.ConnectionTagIntermediate):
67
+		c.connectionType = conntypes.ConnectionTypeIntermediate
68
+	case bytes.Equal(magic, conntypes.ConnectionTagSecure):
69
+		c.connectionType = conntypes.ConnectionTypeSecure
70
+	default:
71
+		return nil, errors.New("unknown connection type")
72
+	}
73
+
74
+	c.connectionProtocol = conntypes.ConnectionProtocolIPv4
75
+	if socket.LocalAddr().IP.To4() == nil {
76
+		c.connectionProtocol = conntypes.ConnectionProtocolIPv6
77
+	}
78
+
79
+	buf := bytes.NewReader(decryptedFrame.DC())
80
+	if err := binary.Read(buf, binary.LittleEndian, &c.dc); err != nil {
81
+		c.dc = conntypes.DCDefaultIdx
82
+	}
83
+
84
+	replayKey := decryptedFrame.Unique()
85
+	if antireplay.Cache.HasObfuscated2(replayKey) {
86
+		stats.Stats.ReplayDetected()
87
+		return nil, errors.New("replay attack is detected")
88
+	}
89
+
90
+	antireplay.Cache.AddObfuscated2(replayKey)
91
+
92
+	return stream.NewObfuscated2(socket, encryptor, decryptor), nil
93
+}
94
+
95
+func (c *ClientProtocol) ReadFrame(socket conntypes.StreamReader) (fm Frame, err error) {
96
+	if _, err = io.ReadFull(handshakeReader{socket}, fm.Bytes()); err != nil {
97
+		err = fmt.Errorf("cannot extract obfuscated2 frame: %w", err)
98
+	}
99
+
100
+	return
101
+}
102
+
103
+type handshakeReader struct {
104
+	parent conntypes.StreamReader
105
+}
106
+
107
+func (h handshakeReader) Read(p []byte) (int, error) {
108
+	return h.parent.ReadTimeout(p, clientProtocolHandshakeTimeout)
109
+}
110
+
111
+func MakeClientProtocol() protocol.ClientProtocol {
112
+	return &ClientProtocol{}
113
+}

+ 22
- 89
obfuscated2/frame.go Целия файл

@@ -1,17 +1,5 @@
1 1
 package obfuscated2
2 2
 
3
-import (
4
-	"bytes"
5
-	"crypto/rand"
6
-	"encoding/binary"
7
-	"io"
8
-
9
-	"github.com/juju/errors"
10
-
11
-	"github.com/9seconds/mtg/mtproto"
12
-)
13
-
14
-// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd]
15 3
 const (
16 4
 	frameLenKey   = 32
17 5
 	frameLenIV    = 16
@@ -24,98 +12,43 @@ const (
24 12
 	frameOffsetMagic = frameOffsetIV + frameLenMagic
25 13
 	frameOffsetDC    = frameOffsetMagic + frameLenDC
26 14
 
27
-	FrameLen = 64
15
+	frameLen = 64
28 16
 )
29 17
 
30
-// Frame represents handshake frame. Telegram sends 64 bytes of obfuscated2
31
-// initialization data first.
32
-// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
33
-type Frame []byte
34
-
35
-// Key returns AES encryption key.
36
-func (f Frame) Key() []byte {
37
-	return f[frameOffsetFirst:frameOffsetKey]
18
+// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd]
19
+type Frame struct {
20
+	data [frameLen]byte
38 21
 }
39 22
 
40
-// IV returns AES encryption initialization vector
41
-func (f Frame) IV() []byte {
42
-	return f[frameOffsetKey:frameOffsetIV]
23
+func (f *Frame) Bytes() []byte {
24
+	return f.data[:]
43 25
 }
44 26
 
45
-// Magic returns magic bytes from last 8 bytes of frame. Telegram checks
46
-// for values there. If after decryption magic is not as expected,
47
-// connection considered as failed.
48
-func (f Frame) Magic() []byte {
49
-	return f[frameOffsetIV:frameOffsetMagic]
27
+func (f *Frame) Key() []byte {
28
+	return f.data[frameOffsetFirst:frameOffsetKey]
50 29
 }
51 30
 
52
-// DC returns number of datacenter IP client wants to use.
53
-func (f Frame) DC() (n int16) {
54
-	buf := bytes.NewReader(f[frameOffsetMagic:frameOffsetDC])
55
-	if err := binary.Read(buf, binary.LittleEndian, &n); err != nil {
56
-		n = 1
57
-	}
58
-
59
-	return
31
+func (f *Frame) IV() []byte {
32
+	return f.data[frameOffsetKey:frameOffsetIV]
60 33
 }
61 34
 
62
-// ConnectionType identifies connection type of the handshake frame.
63
-func (f Frame) ConnectionType() (mtproto.ConnectionType, error) {
64
-	return mtproto.ConnectionTagFromHandshake(f.Magic())
35
+func (f *Frame) Magic() []byte {
36
+	return f.data[frameOffsetIV:frameOffsetMagic]
65 37
 }
66 38
 
67
-// Invert inverts frame for extracting encryption keys. Pkease check that link:
68
-// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
69
-func (f Frame) Invert() Frame {
70
-	reversed := make(Frame, FrameLen)
71
-	copy(reversed, f)
72
-
73
-	for i := 0; i < frameLenKey+frameLenIV; i++ {
74
-		reversed[frameOffsetFirst+i] = f[frameOffsetIV-1-i]
75
-	}
76
-
77
-	return reversed
39
+func (f *Frame) DC() []byte {
40
+	return f.data[frameOffsetMagic:frameOffsetDC]
78 41
 }
79 42
 
80
-// ExtractFrame extracts exact obfuscated2 handshake frame from given reader.
81
-func ExtractFrame(conn io.Reader) (Frame, error) {
82
-	frame := make(Frame, FrameLen)
83
-	buf := bytes.NewBuffer(frame)
84
-	buf.Reset()
85
-
86
-	if _, err := io.CopyN(buf, conn, FrameLen); err != nil {
87
-		return nil, errors.Annotate(err, "Cannot extract obfuscated header")
88
-	}
89
-	copy(frame, buf.Bytes())
90
-
91
-	return frame, nil
43
+func (f *Frame) Unique() []byte {
44
+	return f.data[frameOffsetFirst:frameOffsetDC]
92 45
 }
93 46
 
94
-func generateFrame(connectionType mtproto.ConnectionType) Frame {
95
-	frame := make(Frame, FrameLen)
96
-
97
-	for {
98
-		if _, err := rand.Read(frame); err != nil {
99
-			continue
100
-		}
101
-		if frame[0] == 0xef {
102
-			continue
103
-		}
104
-
105
-		val := (uint32(frame[3]) << 24) | (uint32(frame[2]) << 16) | (uint32(frame[1]) << 8) | uint32(frame[0])
106
-		if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee {
107
-			continue
108
-		}
109
-
110
-		val = (uint32(frame[7]) << 24) | (uint32(frame[6]) << 16) | (uint32(frame[5]) << 8) | uint32(frame[4])
111
-		if val == 0x00000000 {
112
-			continue
113
-		}
114
-
115
-		// error has to be checked before calling this function
116
-		tag, _ := connectionType.Tag() // nolint: errcheck, gosec
117
-		copy(frame.Magic(), tag)
118
-
119
-		return frame
47
+func (f *Frame) Invert() (nf Frame) {
48
+	nf = *f
49
+	for i := 0; i < frameLenKey+frameLenIV; i++ {
50
+		nf.data[frameOffsetFirst+i] = f.data[frameOffsetIV-1-i]
120 51
 	}
52
+
53
+	return
121 54
 }

+ 0
- 106
obfuscated2/frame_test.go Целия файл

@@ -1,106 +0,0 @@
1
-package obfuscated2
2
-
3
-import (
4
-	"bytes"
5
-	"strconv"
6
-	"testing"
7
-
8
-	"github.com/stretchr/testify/assert"
9
-
10
-	"github.com/9seconds/mtg/mtproto"
11
-)
12
-
13
-func TestFrameKey(t *testing.T) {
14
-	toCompare := make([]byte, 32)
15
-	for i := 0; i < 32; i++ {
16
-		toCompare[i] = byte(1)
17
-	}
18
-
19
-	assert.Equal(t, toCompare, makeFrame().Key())
20
-}
21
-
22
-func TestFrameIV(t *testing.T) {
23
-	toCompare := make([]byte, 16)
24
-	for i := 0; i < 16; i++ {
25
-		toCompare[i] = byte(2)
26
-	}
27
-
28
-	assert.Equal(t, toCompare, makeFrame().IV())
29
-}
30
-
31
-func TestFrameMagic(t *testing.T) {
32
-	toCompare := make([]byte, 4)
33
-	for i := 0; i < 4; i++ {
34
-		toCompare[i] = 0xee
35
-	}
36
-
37
-	assert.Equal(t, toCompare, makeFrame().Magic())
38
-}
39
-
40
-func TestFrameDC(t *testing.T) {
41
-	assert.Equal(t, int16(771), makeFrame().DC())
42
-}
43
-
44
-func TestFrameValid(t *testing.T) {
45
-	frame := makeFrame()
46
-	connType, err := frame.ConnectionType()
47
-	assert.Nil(t, err)
48
-	assert.Equal(t, connType, mtproto.ConnectionTypeIntermediate)
49
-
50
-	frame[8+32+16+2] = byte(3)
51
-	_, err = frame.ConnectionType()
52
-	assert.NotNil(t, err)
53
-}
54
-
55
-func TestFrameDoubleInvert(t *testing.T) {
56
-	frame := makeFrame()
57
-	assert.True(t, bytes.Equal(frame, frame.Invert().Invert()))
58
-}
59
-
60
-func TestFrameInvert(t *testing.T) {
61
-	frame := makeFrame()
62
-	reversed := frame.Invert()
63
-
64
-	assert.Exactly(t, frame[:8], reversed[:8])
65
-	assert.Exactly(t, frame[56:], reversed[56:])
66
-
67
-	toCompare := make([]byte, 48)
68
-	for i := 0; i < 48; i++ {
69
-		toCompare[i] = frame[55-i]
70
-	}
71
-	assert.Equal(t, []byte(reversed[8:56]), toCompare)
72
-}
73
-
74
-func TestFrameGenerateValid(t *testing.T) {
75
-	validTests := []mtproto.ConnectionType{
76
-		mtproto.ConnectionTypeIntermediate,
77
-		mtproto.ConnectionTypeAbridged,
78
-	}
79
-	for _, test := range validTests {
80
-		t.Run(strconv.Itoa(int(test)), func(tt *testing.T) {
81
-			frame := generateFrame(test) // nolint: scopelint
82
-			conType, err := frame.ConnectionType()
83
-			assert.Nil(tt, err)
84
-			assert.Equal(tt, conType, test) // nolint: scopelint
85
-		})
86
-	}
87
-}
88
-
89
-func makeFrame() Frame {
90
-	f := make(Frame, FrameLen)
91
-
92
-	for i := 8; i < (8 + 32); i++ {
93
-		f[i] = byte(1)
94
-	}
95
-	for i := (8 + 32); i < (8 + 32 + 16); i++ {
96
-		f[i] = byte(2)
97
-	}
98
-	for i := (8 + 32 + 16); i < (8 + 32 + 16 + 4); i++ {
99
-		f[i] = 0xee
100
-	}
101
-	for i := (8 + 32 + 16 + 4); i < (8 + 32 + 16 + 4 + 2); i++ {
102
-		f[i] = byte(3)
103
-	}
104
-
105
-	return f
106
-}

+ 0
- 81
obfuscated2/obfuscated2.go Целия файл

@@ -1,81 +0,0 @@
1
-package obfuscated2
2
-
3
-import (
4
-	"crypto/aes"
5
-	"crypto/cipher"
6
-	"crypto/sha256"
7
-
8
-	"github.com/juju/errors"
9
-
10
-	"github.com/9seconds/mtg/mtproto"
11
-)
12
-
13
-// Obfuscated2 contains AES CTR encryption and decryption streams
14
-// for telegram connection.
15
-type Obfuscated2 struct {
16
-	Decryptor cipher.Stream
17
-	Encryptor cipher.Stream
18
-}
19
-
20
-// ParseObfuscated2ClientFrame parses client frame. Please check this link for
21
-// details: http://telegra.ph/telegram-blocks-wtf-05-26
22
-//
23
-// Beware, link above is in russian.
24
-func ParseObfuscated2ClientFrame(secret []byte, frame Frame) (*Obfuscated2, *mtproto.ConnectionOpts, error) {
25
-	decHasher := sha256.New()
26
-	decHasher.Write(frame.Key()) // nolint: errcheck, gosec
27
-	decHasher.Write(secret)      // nolint: errcheck, gosec
28
-	decryptor := makeStreamCipher(decHasher.Sum(nil), frame.IV())
29
-
30
-	invertedFrame := frame.Invert()
31
-	encHasher := sha256.New()
32
-	encHasher.Write(invertedFrame.Key()) // nolint: errcheck, gosec
33
-	encHasher.Write(secret)              // nolint: errcheck, gosec
34
-	encryptor := makeStreamCipher(encHasher.Sum(nil), invertedFrame.IV())
35
-
36
-	decryptedFrame := make(Frame, FrameLen)
37
-	decryptor.XORKeyStream(decryptedFrame, frame)
38
-	connType, err := decryptedFrame.ConnectionType()
39
-	if err != nil {
40
-		return nil, nil, errors.Annotate(err, "Unknown protocol")
41
-	}
42
-
43
-	obfs := &Obfuscated2{
44
-		Decryptor: decryptor,
45
-		Encryptor: encryptor,
46
-	}
47
-	connOpts := &mtproto.ConnectionOpts{
48
-		DC:             decryptedFrame.DC(),
49
-		ConnectionType: connType,
50
-	}
51
-
52
-	return obfs, connOpts, nil
53
-}
54
-
55
-// MakeTelegramObfuscated2Frame creates new handshake frame to send to
56
-// Telegram.
57
-// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
58
-func MakeTelegramObfuscated2Frame(opts *mtproto.ConnectionOpts) (*Obfuscated2, Frame) {
59
-	frame := generateFrame(opts.ConnectionType)
60
-
61
-	encryptor := makeStreamCipher(frame.Key(), frame.IV())
62
-	decryptorFrame := frame.Invert()
63
-	decryptor := makeStreamCipher(decryptorFrame.Key(), decryptorFrame.IV())
64
-
65
-	copyFrame := make(Frame, FrameLen)
66
-	copy(copyFrame[:frameOffsetIV], frame[:frameOffsetIV])
67
-	encryptor.XORKeyStream(frame, frame)
68
-	copy(frame[:frameOffsetIV], copyFrame[:frameOffsetIV])
69
-
70
-	obfs := &Obfuscated2{
71
-		Decryptor: decryptor,
72
-		Encryptor: encryptor,
73
-	}
74
-
75
-	return obfs, frame
76
-}
77
-
78
-func makeStreamCipher(key, iv []byte) cipher.Stream {
79
-	block, _ := aes.NewCipher(key) // nolint: gosec
80
-	return cipher.NewCTR(block, iv)
81
-}

+ 0
- 97
obfuscated2/obfuscated2_test.go Целия файл

@@ -1,97 +0,0 @@
1
-package obfuscated2
2
-
3
-import (
4
-	"crypto/sha256"
5
-	"testing"
6
-
7
-	"github.com/stretchr/testify/assert"
8
-
9
-	"github.com/9seconds/mtg/mtproto"
10
-)
11
-
12
-func TestObfs2TelegramFrameDecrypt(t *testing.T) {
13
-	connOpts := &mtproto.ConnectionOpts{
14
-		DC:             1,
15
-		ConnectionType: mtproto.ConnectionTypeIntermediate,
16
-	}
17
-	_, frame := MakeTelegramObfuscated2Frame(connOpts)
18
-	decryptor := makeStreamCipher(frame.Key(), frame.IV())
19
-
20
-	decrypted := make(Frame, FrameLen)
21
-	decryptor.XORKeyStream(decrypted, frame)
22
-
23
-	_, err := decrypted.ConnectionType()
24
-	assert.Nil(t, err)
25
-}
26
-
27
-func TestObfs2TelegramDecryptEncryptDecrypt(t *testing.T) {
28
-	connOpts := &mtproto.ConnectionOpts{
29
-		DC:             1,
30
-		ConnectionType: mtproto.ConnectionTypeIntermediate,
31
-	}
32
-	obfs2, frame := MakeTelegramObfuscated2Frame(connOpts)
33
-	inverted := frame.Invert()
34
-	encryptor := makeStreamCipher(inverted.Key(), inverted.IV())
35
-
36
-	data := []byte{1, 2, 3}
37
-	encrypted := make([]byte, 3)
38
-	encryptor.XORKeyStream(encrypted, data)
39
-	decrypted := make([]byte, 3)
40
-	obfs2.Decryptor.XORKeyStream(decrypted, encrypted)
41
-
42
-	assert.Equal(t, data, decrypted)
43
-}
44
-
45
-func TestObfs2Full(t *testing.T) {
46
-	secret := []byte{1, 2, 3, 4, 5}
47
-
48
-	clientFrame := generateFrame(mtproto.ConnectionTypeIntermediate)
49
-	clientHasher := sha256.New()
50
-	clientHasher.Write(clientFrame.Key()) // nolint: errcheck, gosec
51
-	clientHasher.Write(secret)            // nolint: errcheck, gosec
52
-	clientKey := clientHasher.Sum(nil)
53
-
54
-	encryptor := makeStreamCipher(clientKey, clientFrame.IV())
55
-	encrypted := make(Frame, FrameLen)
56
-	encryptor.XORKeyStream(encrypted, clientFrame)
57
-	copy(encrypted[:56], clientFrame[:56])
58
-
59
-	invertedClientFrame := clientFrame.Invert()
60
-	clientHasher = sha256.New()
61
-	clientHasher.Write(invertedClientFrame.Key()) // nolint: errcheck, gosec
62
-	clientHasher.Write(secret)                    // nolint: errcheck, gosec
63
-	invertedClientKey := clientHasher.Sum(nil)
64
-	clientDecryptor := makeStreamCipher(invertedClientKey, invertedClientFrame.IV())
65
-
66
-	clientObfs, _, err := ParseObfuscated2ClientFrame(secret, encrypted)
67
-	assert.Nil(t, err)
68
-
69
-	connOpts := &mtproto.ConnectionOpts{
70
-		DC:             1,
71
-		ConnectionType: mtproto.ConnectionTypeIntermediate,
72
-	}
73
-	tgObfs, tgFrame := MakeTelegramObfuscated2Frame(connOpts)
74
-	tgDecryptor := makeStreamCipher(tgFrame.Key(), tgFrame.IV())
75
-	decrypted := make(Frame, FrameLen)
76
-	tgDecryptor.XORKeyStream(decrypted, tgFrame)
77
-	_, err = decrypted.ConnectionType()
78
-	assert.Nil(t, err)
79
-
80
-	tgInvertedFrame := tgFrame.Invert()
81
-	tgEncryptor := makeStreamCipher(tgInvertedFrame.Key(), tgInvertedFrame.IV())
82
-
83
-	message := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}
84
-	tgEncryptedMessage := make([]byte, len(message))
85
-	tgEncryptor.XORKeyStream(tgEncryptedMessage, message)
86
-
87
-	tgEncDecryptedMessage := make([]byte, len(tgEncryptedMessage))
88
-	tgObfs.Decryptor.XORKeyStream(tgEncDecryptedMessage, tgEncryptedMessage)
89
-	assert.Equal(t, message, tgEncDecryptedMessage)
90
-
91
-	clientEncryptedMessage := make([]byte, len(tgEncDecryptedMessage))
92
-	clientObfs.Encryptor.XORKeyStream(clientEncryptedMessage, tgEncDecryptedMessage)
93
-	finalMessage := make([]byte, len(clientEncryptedMessage))
94
-	clientDecryptor.XORKeyStream(finalMessage, clientEncryptedMessage)
95
-
96
-	assert.Equal(t, finalMessage, message)
97
-}

+ 68
- 0
obfuscated2/telegram_protocol.go Целия файл

@@ -0,0 +1,68 @@
1
+package obfuscated2
2
+
3
+import (
4
+	"crypto/rand"
5
+	"fmt"
6
+
7
+	"mtg/conntypes"
8
+	"mtg/protocol"
9
+	"mtg/telegram"
10
+	"mtg/utils"
11
+	"mtg/wrappers/stream"
12
+)
13
+
14
+func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteCloser, error) {
15
+	conn, err := telegram.Direct.Dial(req.ClientProtocol.DC(),
16
+		req.ClientProtocol.ConnectionProtocol())
17
+	if err != nil {
18
+		return nil, fmt.Errorf("cannot dial to telegram: %w", err)
19
+	}
20
+
21
+	conn = stream.NewTimeout(conn)
22
+	conn = stream.NewCtx(req.Ctx, req.Cancel, conn)
23
+	fm := generateFrame(req.ClientProtocol)
24
+	data := fm.Bytes()
25
+
26
+	encryptor := utils.MakeStreamCipher(fm.Key(), fm.IV())
27
+	decryptedFrame := fm.Invert()
28
+	decryptor := utils.MakeStreamCipher(decryptedFrame.Key(), decryptedFrame.IV())
29
+
30
+	copyFrame := make([]byte, frameLen)
31
+	copy(copyFrame[:frameOffsetIV], data[:frameOffsetIV])
32
+	encryptor.XORKeyStream(data, data)
33
+	copy(data[:frameOffsetIV], copyFrame[:frameOffsetIV])
34
+
35
+	if _, err := conn.Write(data); err != nil {
36
+		return nil, fmt.Errorf("cannot write handshake frame to telegram: %w", err)
37
+	}
38
+
39
+	return stream.NewObfuscated2(conn, encryptor, decryptor), nil
40
+}
41
+
42
+func generateFrame(cp protocol.ClientProtocol) (fm Frame) {
43
+	data := fm.Bytes()
44
+
45
+	for {
46
+		if _, err := rand.Read(data); err != nil {
47
+			continue
48
+		}
49
+
50
+		if data[0] == 0xef {
51
+			continue
52
+		}
53
+
54
+		val := (uint32(data[3]) << 24) | (uint32(data[2]) << 16) | (uint32(data[1]) << 8) | uint32(data[0])
55
+		if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee {
56
+			continue
57
+		}
58
+
59
+		val = (uint32(data[7]) << 24) | (uint32(data[6]) << 16) | (uint32(data[5]) << 8) | uint32(data[4])
60
+		if val == 0x00000000 {
61
+			continue
62
+		}
63
+
64
+		copy(fm.Magic(), cp.ConnectionType().Tag())
65
+
66
+		return
67
+	}
68
+}

+ 12
- 0
protocol/interfaces.go Целия файл

@@ -0,0 +1,12 @@
1
+package protocol
2
+
3
+import "mtg/conntypes"
4
+
5
+type ClientProtocol interface {
6
+	Handshake(conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error)
7
+	ConnectionType() conntypes.ConnectionType
8
+	ConnectionProtocol() conntypes.ConnectionProtocol
9
+	DC() conntypes.DC
10
+}
11
+
12
+type ClientProtocolMaker func() ClientProtocol

+ 18
- 0
protocol/request.go Целия файл

@@ -0,0 +1,18 @@
1
+package protocol
2
+
3
+import (
4
+	"context"
5
+
6
+	"go.uber.org/zap"
7
+
8
+	"mtg/conntypes"
9
+)
10
+
11
+type TelegramRequest struct {
12
+	Logger         *zap.SugaredLogger
13
+	ClientConn     conntypes.StreamReadWriteCloser
14
+	ConnID         conntypes.ConnID
15
+	Ctx            context.Context
16
+	Cancel         context.CancelFunc
17
+	ClientProtocol ClientProtocol
18
+}

+ 49
- 0
proxy/direct.go Целия файл

@@ -0,0 +1,49 @@
1
+package proxy
2
+
3
+import (
4
+	"io"
5
+	"sync"
6
+
7
+	"go.uber.org/zap"
8
+
9
+	"mtg/conntypes"
10
+	"mtg/obfuscated2"
11
+	"mtg/protocol"
12
+)
13
+
14
+const directPipeBufferSize = 1024 * 1024
15
+
16
+func directConnection(request *protocol.TelegramRequest) error {
17
+	telegramConnRaw, err := obfuscated2.TelegramProtocol(request)
18
+	if err != nil {
19
+		return err
20
+	}
21
+
22
+	telegramConn := telegramConnRaw.(conntypes.StreamReadWriteCloser)
23
+
24
+	defer telegramConn.Close()
25
+
26
+	wg := &sync.WaitGroup{}
27
+	wg.Add(2)
28
+
29
+	go directPipe(telegramConn, request.ClientConn, wg, request.Logger)
30
+
31
+	go directPipe(request.ClientConn, telegramConn, wg, request.Logger)
32
+
33
+	wg.Wait()
34
+
35
+	return nil
36
+}
37
+
38
+func directPipe(dst io.WriteCloser, src io.ReadCloser, wg *sync.WaitGroup, logger *zap.SugaredLogger) {
39
+	defer func() {
40
+		dst.Close()
41
+		src.Close()
42
+		wg.Done()
43
+	}()
44
+
45
+	buf := make([]byte, directPipeBufferSize)
46
+	if _, err := io.CopyBuffer(dst, src, buf); err != nil {
47
+		logger.Debugw("Cannot pump sockets", "error", err)
48
+	}
49
+}

+ 68
- 0
proxy/middle.go Целия файл

@@ -0,0 +1,68 @@
1
+package proxy
2
+
3
+import (
4
+	"sync"
5
+
6
+	"go.uber.org/zap"
7
+
8
+	"mtg/conntypes"
9
+	"mtg/protocol"
10
+	"mtg/wrappers/packetack"
11
+)
12
+
13
+func middleConnection(request *protocol.TelegramRequest) {
14
+	telegramConn, err := packetack.NewProxy(request)
15
+	if err != nil {
16
+		request.Logger.Debugw("Cannot dial to Telegram", "error", err)
17
+		return
18
+	}
19
+	defer telegramConn.Close()
20
+
21
+	var clientConn conntypes.PacketAckFullReadWriteCloser
22
+
23
+	switch request.ClientProtocol.ConnectionType() {
24
+	case conntypes.ConnectionTypeAbridged:
25
+		clientConn = packetack.NewClientAbridged(request.ClientConn)
26
+	case conntypes.ConnectionTypeIntermediate:
27
+		clientConn = packetack.NewClientIntermediate(request.ClientConn)
28
+	case conntypes.ConnectionTypeSecure:
29
+		clientConn = packetack.NewClientIntermediateSecure(request.ClientConn)
30
+	default:
31
+		panic("unknown connection type")
32
+	}
33
+
34
+	wg := &sync.WaitGroup{}
35
+	wg.Add(2)
36
+
37
+	go middlePipe(telegramConn, clientConn, wg, request.Logger)
38
+
39
+	go middlePipe(clientConn, telegramConn, wg, request.Logger)
40
+
41
+	wg.Wait()
42
+}
43
+
44
+func middlePipe(dst conntypes.PacketAckWriteCloser,
45
+	src conntypes.PacketAckReadCloser,
46
+	wg *sync.WaitGroup,
47
+	logger *zap.SugaredLogger) {
48
+	defer func() {
49
+		dst.Close()
50
+		src.Close()
51
+		wg.Done()
52
+	}()
53
+
54
+	for {
55
+		acks := conntypes.ConnectionAcks{}
56
+		packet, err := src.Read(&acks)
57
+
58
+		if err != nil {
59
+			logger.Debugw("Cannot read packet", "error", err)
60
+			return
61
+		}
62
+
63
+		if err = dst.Write(packet, &acks); err != nil {
64
+			logger.Debugw("Cannot send packet", "error", err)
65
+			return
66
+		}
67
+	}
68
+}

+ 55
- 135
proxy/proxy.go Целия файл

@@ -2,177 +2,97 @@ package proxy
2 2
 
3 3
 import (
4 4
 	"context"
5
-	"io"
6 5
 	"net"
7
-	"sync"
8 6
 
9
-	"github.com/gofrs/uuid"
10
-	"github.com/juju/errors"
11 7
 	"go.uber.org/zap"
12 8
 
13
-	"github.com/9seconds/mtg/antireplay"
14
-	"github.com/9seconds/mtg/client"
15
-	"github.com/9seconds/mtg/config"
16
-	"github.com/9seconds/mtg/mtproto"
17
-	"github.com/9seconds/mtg/stats"
18
-	"github.com/9seconds/mtg/telegram"
19
-	"github.com/9seconds/mtg/wrappers"
9
+	"mtg/config"
10
+	"mtg/conntypes"
11
+	"mtg/protocol"
12
+	"mtg/stats"
13
+	"mtg/utils"
14
+	"mtg/wrappers/stream"
20 15
 )
21 16
 
22
-// Proxy is a core of this program.
23 17
 type Proxy struct {
24
-	antiReplayCache antireplay.Cache
25
-	clientInit      client.Init
26
-	tg              telegram.Telegram
27
-	conf            *config.Config
18
+	Logger              *zap.SugaredLogger
19
+	Context             context.Context
20
+	ClientProtocolMaker protocol.ClientProtocolMaker
28 21
 }
29 22
 
30
-// Serve runs TCP proxy server.
31
-func (p *Proxy) Serve() error {
32
-	lsock, err := net.Listen("tcp", p.conf.BindAddr())
33
-	if err != nil {
34
-		return errors.Annotate(err, "Cannot create listen socket")
35
-	}
23
+func (p *Proxy) Serve(listener net.Listener) {
24
+	doneChan := p.Context.Done()
36 25
 
37 26
 	for {
38
-		if conn, err := lsock.Accept(); err != nil {
39
-			zap.S().Errorw("Cannot allocate incoming connection", "error", err)
40
-		} else {
41
-			go p.accept(conn)
27
+		conn, err := listener.Accept()
28
+		if err != nil {
29
+			select {
30
+			case <-doneChan:
31
+				return
32
+			default:
33
+				p.Logger.Errorw("Cannot allocate incoming connection", "error", err)
34
+				continue
35
+			}
42 36
 		}
37
+
38
+		go p.accept(conn)
43 39
 	}
44 40
 }
45 41
 
46 42
 func (p *Proxy) accept(conn net.Conn) {
47
-	connID := uuid.Must(uuid.NewV4()).String()
48
-	log := zap.S().With("connection_id", connID).Named("main")
49
-	ctx, cancel := context.WithCancel(context.Background())
50
-
51 43
 	defer func() {
52
-		cancel()
53
-		conn.Close() // nolint: errcheck, gosec
54
-
44
+		conn.Close()
55 45
 		if err := recover(); err != nil {
56
-			stats.NewCrash()
57
-			log.Errorw("Crash of accept handler", "error", err)
46
+			stats.Stats.Crash()
47
+			p.Logger.Errorw("Crash of accept handler", "error", err)
58 48
 		}
59 49
 	}()
60 50
 
61
-	log.Infow("Client connected", "addr", conn.RemoteAddr())
62
-
63
-	clientConn, opts, err := p.clientInit(ctx, cancel, conn, connID, p.antiReplayCache, p.conf)
64
-	if err != nil {
65
-		log.Errorw("Cannot initialize client connection", "error", err)
66
-		return
67
-	}
68
-	defer clientConn.(io.Closer).Close() // nolint: errcheck
69
-
70
-	if p.conf.SecureOnly && opts.ConnectionType != mtproto.ConnectionTypeSecure {
71
-		log.Errorw("Proxy supports only secure connections", "connection_type", opts.ConnectionType)
72
-		return
73
-	}
51
+	connID := conntypes.NewConnID()
52
+	logger := p.Logger.With("connection_id", connID)
74 53
 
75
-	stats.ClientConnected(opts.ConnectionType, clientConn.RemoteAddr())
76
-	defer stats.ClientDisconnected(opts.ConnectionType, clientConn.RemoteAddr())
77
-
78
-	serverConn, err := p.getTelegramConn(ctx, cancel, opts, connID)
79
-	if err != nil {
80
-		log.Errorw("Cannot initialize server connection", "error", err)
54
+	if err := utils.InitTCP(conn); err != nil {
55
+		logger.Errorw("Cannot initialize client TCP connection", "error", err)
81 56
 		return
82 57
 	}
83
-	defer serverConn.(io.Closer).Close() // nolint: errcheck
84 58
 
85
-	go func() {
86
-		<-ctx.Done()
87
-		serverConn.(io.Closer).Close() // nolint: gosec
88
-		clientConn.(io.Closer).Close() // nolint: gosec
89
-	}()
90
-
91
-	wait := &sync.WaitGroup{}
92
-	wait.Add(2)
93
-
94
-	if p.conf.UseMiddleProxy() {
95
-		clientPacket := clientConn.(wrappers.PacketReadWriteCloser)
96
-		serverPacket := serverConn.(wrappers.PacketReadWriteCloser)
97
-		go p.middlePipe(clientPacket, serverPacket, wait, &opts.ReadHacks)
98
-		p.middlePipe(serverPacket, clientPacket, wait, &opts.WriteHacks)
99
-	} else {
100
-		clientStream := clientConn.(wrappers.StreamReadWriteCloser)
101
-		serverStream := serverConn.(wrappers.StreamReadWriteCloser)
102
-		go p.directPipe(clientStream, serverStream, wait, p.conf.ReadBufferSize)
103
-		p.directPipe(serverStream, clientStream, wait, p.conf.WriteBufferSize)
104
-	}
59
+	ctx, cancel := context.WithCancel(p.Context)
60
+	defer cancel()
105 61
 
106
-	wait.Wait()
62
+	clientConn := stream.NewClientConn(conn, connID)
63
+	clientConn = stream.NewCtx(ctx, cancel, clientConn)
64
+	clientConn = stream.NewTimeout(clientConn)
107 65
 
108
-	log.Infow("Client disconnected", "addr", conn.RemoteAddr())
109
-}
66
+	defer clientConn.Close()
110 67
 
111
-func (p *Proxy) getTelegramConn(ctx context.Context, cancel context.CancelFunc,
112
-	opts *mtproto.ConnectionOpts, connID string) (wrappers.Wrap, error) {
113
-	streamConn, err := p.tg.Dial(ctx, cancel, connID, opts)
114
-	if err != nil {
115
-		return nil, errors.Annotate(err, "Cannot dial to Telegram")
116
-	}
68
+	clientProtocol := p.ClientProtocolMaker()
69
+	clientConn, err := clientProtocol.Handshake(clientConn)
117 70
 
118
-	packetConn, err := p.tg.Init(opts, streamConn)
119 71
 	if err != nil {
120
-		return nil, errors.Annotate(err, "Cannot handshake telegram")
121
-	}
122
-
123
-	return packetConn, nil
124
-}
125
-
126
-func (p *Proxy) middlePipe(src wrappers.PacketReadCloser, dst io.Writer, wait *sync.WaitGroup, hacks *mtproto.Hacks) {
127
-	defer wait.Done()
128
-
129
-	for {
130
-		hacks.SimpleAck = false
131
-		hacks.QuickAck = false
132
-
133
-		packet, err := src.Read()
134
-		if err != nil {
135
-			src.Logger().Warnw("Cannot read packet", "error", err)
136
-			return
137
-		}
138
-		if _, err = dst.Write(packet); err != nil {
139
-			src.Logger().Warnw("Cannot write packet", "error", err)
140
-			return
141
-		}
72
+		logger.Warnw("Cannot perform client handshake", "error", err)
73
+		return
142 74
 	}
143
-}
144 75
 
145
-func (p *Proxy) directPipe(src wrappers.StreamReadCloser, dst io.Writer, wait *sync.WaitGroup, bufferSize int) {
146
-	defer wait.Done()
147
-
148
-	buffer := make([]byte, bufferSize)
149
-	if _, err := io.CopyBuffer(dst, src, buffer); err != nil {
150
-		src.Logger().Warnw("Cannot pump sockets", "error", err)
76
+	stats.Stats.ClientConnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr())
77
+	defer stats.Stats.ClientDisconnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr())
78
+	logger.Infow("Client connected", "addr", conn.RemoteAddr())
79
+
80
+	req := &protocol.TelegramRequest{
81
+		Logger:         logger,
82
+		ClientConn:     clientConn,
83
+		ConnID:         connID,
84
+		Ctx:            ctx,
85
+		Cancel:         cancel,
86
+		ClientProtocol: clientProtocol,
151 87
 	}
152
-}
153
-
154
-// NewProxy returns new proxy instance.
155
-func NewProxy(conf *config.Config) (*Proxy, error) {
156
-	var clientInit client.Init
157
-	var tg telegram.Telegram
158 88
 
159
-	cache, err := antireplay.NewCache(conf)
160
-	if err != nil {
161
-		return nil, errors.Annotate(err, "Cannot make proxy")
162
-	}
89
+	err = nil
163 90
 
164
-	if conf.UseMiddleProxy() {
165
-		clientInit = client.MiddleInit
166
-		tg = telegram.NewMiddleTelegram(conf)
91
+	if len(config.C.AdTag) > 0 {
92
+		middleConnection(req)
167 93
 	} else {
168
-		clientInit = client.DirectInit
169
-		tg = telegram.NewDirectTelegram(conf)
94
+		err = directConnection(req)
170 95
 	}
171 96
 
172
-	return &Proxy{
173
-		antiReplayCache: cache,
174
-		conf:            conf,
175
-		clientInit:      clientInit,
176
-		tg:              tg,
177
-	}, nil
97
+	logger.Infow("Client disconnected", "error", err, "addr", conn.RemoteAddr())
178 98
 }

+ 0
- 35
run-mtg.sh Целия файл

@@ -1,35 +0,0 @@
1
-#!/bin/bash
2
-set -eu -o pipefail
3
-
4
-IMAGE_NAME="nineseconds/mtg"
5
-CONTAINER_NAME="mtg"
6
-SECRET_PATH="$HOME/.mtg.secret"
7
-PROXY_PORT=444
8
-STAT_PORT=3129
9
-
10
-[[ -e "$SECRET_PATH" ]] || (
11
-  openssl rand -hex 16 > "$SECRET_PATH"
12
-  chmod 0400 "$SECRET_PATH"
13
-)
14
-
15
-docker pull "$IMAGE_NAME"
16
-docker ps --filter "Name=$CONTAINER_NAME" -aq | xargs -r docker rm -fv
17
-docker run \
18
-    -d \
19
-    --name "$CONTAINER_NAME" \
20
-    --sysctl 'net.ipv4.ip_local_port_range=10000 65000' \
21
-    --sysctl net.ipv4.tcp_congestion_control=bbr \
22
-    --sysctl net.ipv4.tcp_fastopen=3 \
23
-    --sysctl net.ipv4.tcp_fin_timeout=30 \
24
-    --sysctl net.ipv4.tcp_max_syn_backlog=4096 \
25
-    --sysctl net.ipv4.tcp_max_tw_buckets=5000 \
26
-    --sysctl net.ipv4.tcp_mtu_probing=1 \
27
-    --sysctl 'net.ipv4.tcp_rmem=4096 87380 67108864' \
28
-    --sysctl net.ipv4.tcp_syncookies=1 \
29
-    --sysctl net.ipv4.tcp_tw_reuse=1 \
30
-    --sysctl 'net.ipv4.tcp_wmem=4096 65536 67108864' \
31
-    --ulimit nofile=51200:51200 \
32
-    --restart=unless-stopped \
33
-    -p $PROXY_PORT:3128 \
34
-    -p $STAT_PORT:3129 \
35
-  "$IMAGE_NAME" "$(cat "$SECRET_PATH")"

+ 58
- 0
run.sh Целия файл

@@ -0,0 +1,58 @@
1
+#!/bin/bash
2
+#
3
+# Configuration options (set by environment variables during script execution)
4
+#   - MTG_CONFIG    - directory where mtg stores its configuration
5
+#   - MTG_IMAGENAME - a name of the docker image to use
6
+#   - MTG_PORT      - which port of the host system should be used
7
+#   - MTG_CONTAINER - a name of the container to use
8
+#
9
+# Example:
10
+#   export MTG_CONFIG="$HOME/mtg_config"
11
+#   export MTG_IMAGENAME="nineseconds/mtg:latest"
12
+#   curl -sfL --compressed https://raw.githubusercontent.com/9seconds/mtg/master/run.sh | bash
13
+
14
+set -eu -o pipefail
15
+
16
+export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
17
+export MTG_CONFIG="${MTG_CONFIG:-$XDG_CONFIG_HOME/mtg}"
18
+
19
+mkdir -p "$MTG_CONFIG" || true
20
+
21
+MTG_SECRET="$MTG_CONFIG/secret"
22
+MTG_ENV="$MTG_CONFIG/env"
23
+
24
+if [ ! -f "$MTG_ENV" ]; then
25
+    MTG_IMAGENAME="${MTG_IMAGENAME:-nineseconds/mtg:latest}"
26
+    MTG_PORT="${MTG_PORT:-3128}"
27
+    MTG_CONTAINER="${MTG_CONTAINER:-mtg}"
28
+
29
+    echo "MTG_IMAGENAME=${MTG_IMAGENAME}" > "$MTG_ENV"
30
+    echo "MTG_PORT=${MTG_PORT}" >> "$MTG_ENV"
31
+    echo "MTG_CONTAINER=${MTG_CONTAINER}" >> "$MTG_ENV"
32
+fi
33
+
34
+set -a
35
+source "$MTG_ENV"
36
+set +a
37
+
38
+docker pull "$MTG_IMAGENAME"
39
+if [ ! -f "$MTG_SECRET" ]; then
40
+    docker run \
41
+            --rm \
42
+            "$MTG_IMAGENAME" \
43
+        generate-secret tls -c "$(openssl rand -hex 16).com" \
44
+    > "$MTG_SECRET"
45
+fi
46
+
47
+echo
48
+echo "Proxy secret is $(cat "$MTG_SECRET"). Port is $MTG_PORT."
49
+echo
50
+
51
+docker ps --filter "Name=$MTG_CONTAINER" -aq | xargs -r docker rm -fv
52
+docker run \
53
+        -d \
54
+        --restart=unless-stopped \
55
+        --name "$MTG_CONTAINER" \
56
+        --ulimit nofile=51200:51200 \
57
+        -p "$MTG_PORT:3128" \
58
+    "$MTG_IMAGENAME" run "$(cat "$MTG_SECRET")"

+ 0
- 76
stats/channels.go Целия файл

@@ -1,76 +0,0 @@
1
-package stats
2
-
3
-import (
4
-	"net"
5
-
6
-	"github.com/9seconds/mtg/mtproto"
7
-)
8
-
9
-const (
10
-	connectionsChanLength = 10
11
-	trafficChanLength     = 10
12
-)
13
-
14
-var (
15
-	crashesChan     = make(chan struct{})
16
-	statsChan       = make(chan chan<- Stats)
17
-	connectionsChan = make(chan connectionData, connectionsChanLength)
18
-	trafficChan     = make(chan trafficData, trafficChanLength)
19
-)
20
-
21
-type connectionData struct {
22
-	connectionType mtproto.ConnectionType
23
-	connected      bool
24
-	addr           *net.TCPAddr
25
-}
26
-
27
-type trafficData struct {
28
-	traffic int
29
-	ingress bool
30
-}
31
-
32
-// NewCrash indicates new crash.
33
-func NewCrash() {
34
-	crashesChan <- struct{}{}
35
-}
36
-
37
-// ClientConnected indicates that new client was connected.
38
-func ClientConnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr) {
39
-	connectionsChan <- connectionData{
40
-		connectionType: connectionType,
41
-		addr:           addr,
42
-		connected:      true,
43
-	}
44
-}
45
-
46
-// ClientDisconnected indicates that client was disconnected.
47
-func ClientDisconnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr) {
48
-	connectionsChan <- connectionData{
49
-		connectionType: connectionType,
50
-		addr:           addr,
51
-		connected:      false,
52
-	}
53
-}
54
-
55
-// IngressTraffic accounts new ingress traffic.
56
-func IngressTraffic(traffic int) {
57
-	trafficChan <- trafficData{
58
-		traffic: traffic,
59
-		ingress: true,
60
-	}
61
-}
62
-
63
-// EgressTraffic accounts new ingress traffic.
64
-func EgressTraffic(traffic int) {
65
-	trafficChan <- trafficData{
66
-		traffic: traffic,
67
-		ingress: false,
68
-	}
69
-}
70
-
71
-// GetStats returns a snapshot of Stats instance.
72
-func GetStats() Stats {
73
-	rpcChan := make(chan Stats)
74
-	statsChan <- rpcChan
75
-	return <-rpcChan
76
-}

+ 0
- 28
stats/init.go Целия файл

@@ -1,28 +0,0 @@
1
-package stats
2
-
3
-import (
4
-	"github.com/juju/errors"
5
-
6
-	"github.com/9seconds/mtg/config"
7
-)
8
-
9
-// Init initializes stats subsystem.
10
-func Init(conf *config.Config) error {
11
-	if conf.StatsD.Enabled {
12
-		client, err := newStatsd(conf)
13
-		if err != nil {
14
-			return errors.Annotate(err, "Cannot initialize statsd client")
15
-		}
16
-		go client.run()
17
-	}
18
-	prometheus, err := newPrometheus(conf)
19
-	if err != nil {
20
-		return errors.Annotate(err, "Cannot initialize prometheus client")
21
-	}
22
-	go prometheus.run()
23
-
24
-	go NewStats(conf).start()
25
-	go startServer(conf, prometheus.getHTTPHandler())
26
-
27
-	return nil
28
-}

+ 50
- 0
stats/interfaces.go Целия файл

@@ -0,0 +1,50 @@
1
+package stats
2
+
3
+import (
4
+	"net"
5
+
6
+	"mtg/conntypes"
7
+)
8
+
9
+type IngressTrafficInterface interface {
10
+	IngressTraffic(int)
11
+}
12
+
13
+type EgressTrafficInterface interface {
14
+	EgressTraffic(int)
15
+}
16
+
17
+type ClientConnectedInterface interface {
18
+	ClientConnected(conntypes.ConnectionType, *net.TCPAddr)
19
+}
20
+
21
+type ClientDisconnectedInterface interface {
22
+	ClientDisconnected(conntypes.ConnectionType, *net.TCPAddr)
23
+}
24
+
25
+type TelegramConnectedInterface interface {
26
+	TelegramConnected(conntypes.DC, *net.TCPAddr)
27
+}
28
+
29
+type TelegramDisconnectedInterface interface {
30
+	TelegramDisconnected(conntypes.DC, *net.TCPAddr)
31
+}
32
+
33
+type CrashInterface interface {
34
+	Crash()
35
+}
36
+
37
+type ReplayDetectedInterface interface {
38
+	ReplayDetected()
39
+}
40
+
41
+type Interface interface {
42
+	IngressTrafficInterface
43
+	EgressTrafficInterface
44
+	ClientConnectedInterface
45
+	ClientDisconnectedInterface
46
+	TelegramConnectedInterface
47
+	TelegramDisconnectedInterface
48
+	CrashInterface
49
+	ReplayDetectedInterface
50
+}

+ 57
- 0
stats/multi_stats.go Целия файл

@@ -0,0 +1,57 @@
1
+package stats
2
+
3
+import (
4
+	"net"
5
+
6
+	"mtg/conntypes"
7
+)
8
+
9
+type multiStats []Interface
10
+
11
+func (m multiStats) IngressTraffic(traffic int) {
12
+	for i := range m {
13
+		go m[i].IngressTraffic(traffic)
14
+	}
15
+}
16
+
17
+func (m multiStats) EgressTraffic(traffic int) {
18
+	for i := range m {
19
+		go m[i].EgressTraffic(traffic)
20
+	}
21
+}
22
+
23
+func (m multiStats) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
24
+	for i := range m {
25
+		go m[i].ClientConnected(connectionType, addr)
26
+	}
27
+}
28
+
29
+func (m multiStats) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
30
+	for i := range m {
31
+		go m[i].ClientDisconnected(connectionType, addr)
32
+	}
33
+}
34
+
35
+func (m multiStats) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) {
36
+	for i := range m {
37
+		go m[i].TelegramConnected(dc, addr)
38
+	}
39
+}
40
+
41
+func (m multiStats) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) {
42
+	for i := range m {
43
+		go m[i].TelegramDisconnected(dc, addr)
44
+	}
45
+}
46
+
47
+func (m multiStats) Crash() {
48
+	for i := range m {
49
+		go m[i].Crash()
50
+	}
51
+}
52
+
53
+func (m multiStats) ReplayDetected() {
54
+	for i := range m {
55
+		go m[i].ReplayDetected()
56
+	}
57
+}

+ 0
- 91
stats/prometheus.go Целия файл

@@ -1,91 +0,0 @@
1
-package stats
2
-
3
-import (
4
-	"net/http"
5
-	"time"
6
-
7
-	"github.com/juju/errors"
8
-	"github.com/prometheus/client_golang/prometheus"
9
-	"github.com/prometheus/client_golang/prometheus/promhttp"
10
-
11
-	"github.com/9seconds/mtg/config"
12
-)
13
-
14
-const prometheusPollTime = time.Second
15
-
16
-type prometheusExporter struct {
17
-	registry prometheus.Gatherer
18
-
19
-	connections *prometheus.GaugeVec
20
-	traffic     *prometheus.GaugeVec
21
-	speed       *prometheus.GaugeVec
22
-	crashes     prometheus.Gauge
23
-}
24
-
25
-func (p *prometheusExporter) run() {
26
-	for range time.Tick(prometheusPollTime) {
27
-		instance := GetStats()
28
-
29
-		p.connections.WithLabelValues("abridged", "v4").Set(float64(instance.Connections.Abridged.IPv4))
30
-		p.connections.WithLabelValues("abridged", "v6").Set(float64(instance.Connections.Abridged.IPv6))
31
-		p.connections.WithLabelValues("intermediate", "v4").Set(float64(instance.Connections.Intermediate.IPv4))
32
-		p.connections.WithLabelValues("intermediate", "v6").Set(float64(instance.Connections.Intermediate.IPv6))
33
-		p.connections.WithLabelValues("secure", "v4").Set(float64(instance.Connections.Secure.IPv4))
34
-		p.connections.WithLabelValues("secure", "v6").Set(float64(instance.Connections.Secure.IPv6))
35
-		p.traffic.WithLabelValues("ingress").Set(float64(instance.Traffic.ingress))
36
-		p.traffic.WithLabelValues("egress").Set(float64(instance.Traffic.egress))
37
-		p.speed.WithLabelValues("ingress").Set(float64(instance.Speed.ingress))
38
-		p.speed.WithLabelValues("egress").Set(float64(instance.Speed.egress))
39
-		p.crashes.Set(float64(instance.Crashes))
40
-	}
41
-}
42
-
43
-func (p *prometheusExporter) getHTTPHandler() http.Handler {
44
-	return promhttp.HandlerFor(p.registry, promhttp.HandlerOpts{})
45
-}
46
-
47
-func newPrometheus(conf *config.Config) (*prometheusExporter, error) {
48
-	registry := prometheus.NewRegistry()
49
-
50
-	connections := prometheus.NewGaugeVec(prometheus.GaugeOpts{
51
-		Namespace: conf.Prometheus.Prefix,
52
-		Name:      "connections",
53
-		Help:      "Current number of connections to the proxy.",
54
-	}, []string{"type", "protocol"})
55
-	traffic := prometheus.NewGaugeVec(prometheus.GaugeOpts{
56
-		Namespace: conf.Prometheus.Prefix,
57
-		Name:      "traffic",
58
-		Help:      "Traffic passed through the proxy in bytes.",
59
-	}, []string{"direction"})
60
-	speed := prometheus.NewGaugeVec(prometheus.GaugeOpts{
61
-		Namespace: conf.Prometheus.Prefix,
62
-		Name:      "speed",
63
-		Help:      "Current throughput in bytes per second.",
64
-	}, []string{"direction"})
65
-	crashes := prometheus.NewGauge(prometheus.GaugeOpts{
66
-		Namespace: conf.Prometheus.Prefix,
67
-		Name:      "crashes",
68
-		Help:      "How many crashes happened.",
69
-	})
70
-
71
-	if err := registry.Register(connections); err != nil {
72
-		return nil, errors.Annotate(err, "Cannot register connections collector")
73
-	}
74
-	if err := registry.Register(traffic); err != nil {
75
-		return nil, errors.Annotate(err, "cannot register traffic collector")
76
-	}
77
-	if err := registry.Register(speed); err != nil {
78
-		return nil, errors.Annotate(err, "cannot register speed collector")
79
-	}
80
-	if err := registry.Register(crashes); err != nil {
81
-		return nil, errors.Annotate(err, "cannot register crashes collector")
82
-	}
83
-
84
-	return &prometheusExporter{
85
-		registry:    registry,
86
-		connections: connections,
87
-		traffic:     traffic,
88
-		speed:       speed,
89
-		crashes:     crashes,
90
-	}, nil
91
-}

+ 0
- 40
stats/server.go Целия файл

@@ -1,40 +0,0 @@
1
-package stats
2
-
3
-import (
4
-	"encoding/json"
5
-	"net/http"
6
-
7
-	"go.uber.org/zap"
8
-
9
-	"github.com/9seconds/mtg/config"
10
-)
11
-
12
-func startServer(conf *config.Config, prometheusHandler http.Handler) {
13
-	log := zap.S().Named("stats")
14
-
15
-	http.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
16
-		w.Header().Set("Content-Type", "application/json")
17
-
18
-		first, err := json.Marshal(GetStats())
19
-		if err != nil {
20
-			log.Errorw("Cannot encode json", "error", err)
21
-			http.Error(w, "Internal server error", 500)
22
-			return
23
-		}
24
-
25
-		interim := map[string]interface{}{}
26
-		json.Unmarshal(first, &interim) // nolint: errcheck, gosec
27
-
28
-		encoder := json.NewEncoder(w)
29
-		encoder.SetEscapeHTML(false)
30
-		encoder.SetIndent("", "  ")
31
-		if err = encoder.Encode(interim); err != nil {
32
-			log.Errorw("Cannot encode json", "error", err)
33
-		}
34
-	})
35
-	http.Handle("/prometheus/", prometheusHandler)
36
-
37
-	if err := http.ListenAndServe(conf.StatAddr(), nil); err != nil {
38
-		log.Fatalw("Stats server has been stopped", "error", err)
39
-	}
40
-}

+ 22
- 156
stats/stats.go Целия файл

@@ -1,175 +1,41 @@
1 1
 package stats
2 2
 
3 3
 import (
4
-	"encoding/json"
4
+	"context"
5 5
 	"fmt"
6
-	"strconv"
7
-	"time"
6
+	"net"
7
+	"net/http"
8 8
 
9
-	humanize "github.com/dustin/go-humanize"
10
-
11
-	"github.com/9seconds/mtg/config"
12
-	"github.com/9seconds/mtg/mtproto"
9
+	"mtg/config"
13 10
 )
14 11
 
15
-type uptime time.Time
16
-
17
-func (u uptime) MarshalJSON() ([]byte, error) {
18
-	duration := time.Since(time.Time(u))
19
-	value := map[string]string{
20
-		"seconds": strconv.Itoa(int(duration.Seconds())),
21
-		"human":   humanize.Time(time.Time(u)),
22
-	}
23
-
24
-	return json.Marshal(value)
25
-}
26
-
27
-type connectionType struct {
28
-	IPv6 uint32 `json:"ipv6"`
29
-	IPv4 uint32 `json:"ipv4"`
30
-}
31
-
32
-type baseConnections struct {
33
-	All          connectionType `json:"all"`
34
-	Abridged     connectionType `json:"abridged"`
35
-	Intermediate connectionType `json:"intermediate"`
36
-	Secure       connectionType `json:"secure"`
37
-}
38
-
39
-type connections struct {
40
-	baseConnections
41
-}
42
-
43
-func (c connections) MarshalJSON() ([]byte, error) {
44
-	c.All.IPv4 = c.Abridged.IPv4 + c.Intermediate.IPv4 + c.Secure.IPv4
45
-	c.All.IPv6 = c.Abridged.IPv6 + c.Intermediate.IPv6 + c.Secure.IPv6
46
-
47
-	return json.Marshal(c.baseConnections)
48
-}
49
-
50
-type traffic struct {
51
-	ingress uint64
52
-	egress  uint64
53
-}
54
-
55
-func (t *traffic) dumpValue(value uint64) map[string]interface{} {
56
-	return map[string]interface{}{
57
-		"bytes": value,
58
-		"human": humanize.Bytes(value),
59
-	}
60
-}
61
-
62
-func (t traffic) MarshalJSON() ([]byte, error) {
63
-	value := map[string]map[string]interface{}{
64
-		"ingress": t.dumpValue(t.ingress),
65
-		"egress":  t.dumpValue(t.egress),
66
-	}
67
-
68
-	return json.Marshal(value)
69
-}
70
-
71
-type speed struct {
72
-	ingress uint64
73
-	egress  uint64
74
-}
12
+var Stats Interface
75 13
 
76
-func (s *speed) dumpValue(value uint64) map[string]interface{} {
77
-	return map[string]interface{}{
78
-		"bytes/s": value,
79
-		"human":   fmt.Sprintf("%s/s", humanize.Bytes(value)),
80
-	}
81
-}
14
+func Init(ctx context.Context) error {
15
+	mux := http.NewServeMux()
82 16
 
83
-func (s speed) MarshalJSON() ([]byte, error) {
84
-	value := map[string]map[string]interface{}{
85
-		"ingress": s.dumpValue(s.ingress),
86
-		"egress":  s.dumpValue(s.egress),
17
+	stats := []Interface{newStatsPrometheus(mux)}
18
+	if config.C.StatsdAddr != nil {
19
+		stats = append(stats, newStatsStatsd())
87 20
 	}
88 21
 
89
-	return json.Marshal(value)
90
-}
91
-
92
-// Stats represents a statistics of the proxy.
93
-type Stats struct {
94
-	URLs        config.IPURLs `json:"urls"`
95
-	Connections connections   `json:"connections"`
96
-	Traffic     traffic       `json:"traffic"`
97
-	Speed       speed         `json:"speed"`
98
-	Uptime      uptime        `json:"uptime"`
99
-	Crashes     uint32        `json:"crashes"`
100
-
101
-	previousTraffic traffic
102
-}
103
-
104
-func (s *Stats) start() {
105
-	speedChan := time.Tick(time.Second)
106
-
107
-	for {
108
-		select {
109
-		case <-speedChan:
110
-			s.handleSpeed()
111
-		case event := <-trafficChan:
112
-			s.handleTraffic(event)
113
-		case event := <-connectionsChan:
114
-			s.handleConnection(event)
115
-		case getStatsChan := <-statsChan:
116
-			s.handleGetStats(getStatsChan)
117
-		case <-crashesChan:
118
-			s.handleCrash()
119
-		}
22
+	listener, err := net.Listen("tcp", config.C.StatsBind.String())
23
+	if err != nil {
24
+		return fmt.Errorf("cannot initialize stats server: %w", err)
120 25
 	}
121
-}
122 26
 
123
-func (s *Stats) handleTraffic(evt trafficData) {
124
-	if evt.ingress {
125
-		s.Traffic.ingress += uint64(evt.traffic)
126
-	} else {
127
-		s.Traffic.egress += uint64(evt.traffic)
27
+	srv := http.Server{
28
+		Handler: mux,
128 29
 	}
129
-}
130 30
 
131
-func (s *Stats) handleSpeed() {
132
-	s.Speed.ingress = s.Traffic.ingress - s.previousTraffic.ingress
133
-	s.Speed.egress = s.Traffic.egress - s.previousTraffic.egress
134
-	s.previousTraffic.ingress = s.Traffic.ingress
135
-	s.previousTraffic.egress = s.Traffic.egress
136
-}
137
-
138
-func (s *Stats) handleConnection(evt connectionData) {
139
-	var inc uint32 = 1
140
-	if !evt.connected {
141
-		inc = ^uint32(0)
142
-	}
31
+	go srv.Serve(listener) // nolint: errcheck
143 32
 
144
-	var conn *connectionType
145
-	switch evt.connectionType {
146
-	case mtproto.ConnectionTypeAbridged:
147
-		conn = &s.Connections.Abridged
148
-	case mtproto.ConnectionTypeSecure:
149
-		conn = &s.Connections.Secure
150
-	default:
151
-		conn = &s.Connections.Intermediate
152
-	}
33
+	go func() {
34
+		<-ctx.Done()
35
+		srv.Shutdown(context.Background()) // nolint: errcheck
36
+	}()
153 37
 
154
-	if evt.addr.IP.To4() != nil {
155
-		conn.IPv4 += inc
156
-	} else {
157
-		conn.IPv6 += inc
158
-	}
159
-}
38
+	Stats = multiStats(stats)
160 39
 
161
-func (s *Stats) handleGetStats(getStatsChan chan<- Stats) {
162
-	getStatsChan <- *s
163
-}
164
-
165
-func (s *Stats) handleCrash() {
166
-	s.Crashes++
167
-}
168
-
169
-// NewStats creates a new instance of Stats structure.
170
-func NewStats(conf *config.Config) *Stats {
171
-	return &Stats{
172
-		URLs:   conf.GetURLs(),
173
-		Uptime: uptime(time.Now()),
174
-	}
40
+	return nil
175 41
 }

+ 131
- 0
stats/stats_prometheus.go Целия файл

@@ -0,0 +1,131 @@
1
+package stats
2
+
3
+import (
4
+	"net"
5
+	"net/http"
6
+	"strconv"
7
+
8
+	"github.com/prometheus/client_golang/prometheus"
9
+	"github.com/prometheus/client_golang/prometheus/promhttp"
10
+
11
+	"mtg/config"
12
+	"mtg/conntypes"
13
+)
14
+
15
+type statsPrometheus struct {
16
+	connections         *prometheus.GaugeVec
17
+	telegramConnections *prometheus.GaugeVec
18
+	traffic             *prometheus.GaugeVec
19
+	crashes             prometheus.Counter
20
+	replayAttacks       prometheus.Counter
21
+}
22
+
23
+func (s *statsPrometheus) IngressTraffic(traffic int) {
24
+	s.traffic.WithLabelValues("ingress").Add(float64(traffic))
25
+}
26
+
27
+func (s *statsPrometheus) EgressTraffic(traffic int) {
28
+	s.traffic.WithLabelValues("egress").Add(float64(traffic))
29
+}
30
+
31
+func (s *statsPrometheus) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
32
+	s.changeConnections(connectionType, addr, 1.0)
33
+}
34
+
35
+func (s *statsPrometheus) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
36
+	s.changeConnections(connectionType, addr, -1.0)
37
+}
38
+
39
+func (s *statsPrometheus) changeConnections(connectionType conntypes.ConnectionType,
40
+	addr *net.TCPAddr,
41
+	increment float64) {
42
+	labels := [...]string{
43
+		"intermediate",
44
+		"ipv4",
45
+	}
46
+
47
+	switch connectionType {
48
+	case conntypes.ConnectionTypeAbridged:
49
+		labels[0] = "abridged"
50
+	case conntypes.ConnectionTypeSecure:
51
+		labels[0] = "secured"
52
+	}
53
+
54
+	if addr.IP.To4() == nil {
55
+		labels[1] = "ipv6" // nolint: goconst
56
+	}
57
+
58
+	s.connections.WithLabelValues(labels[:]...).Add(increment)
59
+}
60
+
61
+func (s *statsPrometheus) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) {
62
+	s.changeTelegramConnections(dc, addr, 1.0)
63
+}
64
+
65
+func (s *statsPrometheus) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) {
66
+	s.changeTelegramConnections(dc, addr, -1.0)
67
+}
68
+
69
+func (s *statsPrometheus) changeTelegramConnections(dc conntypes.DC, addr *net.TCPAddr, increment float64) {
70
+	labels := [...]string{
71
+		strconv.Itoa(int(dc)),
72
+		"ipv4",
73
+	}
74
+
75
+	if addr.IP.To4() == nil {
76
+		labels[1] = "ipv6"
77
+	}
78
+
79
+	s.telegramConnections.WithLabelValues(labels[:]...).Add(increment)
80
+}
81
+
82
+func (s *statsPrometheus) Crash() {
83
+	s.crashes.Inc()
84
+}
85
+
86
+func (s *statsPrometheus) ReplayDetected() {
87
+	s.replayAttacks.Inc()
88
+}
89
+
90
+func newStatsPrometheus(mux *http.ServeMux) Interface {
91
+	registry := prometheus.NewPedanticRegistry()
92
+
93
+	instance := &statsPrometheus{
94
+		connections: prometheus.NewGaugeVec(prometheus.GaugeOpts{
95
+			Namespace: config.C.StatsNamespace,
96
+			Name:      "connections",
97
+			Help:      "Current number of client connections to the proxy.",
98
+		}, []string{"type", "protocol"}),
99
+		telegramConnections: prometheus.NewGaugeVec(prometheus.GaugeOpts{
100
+			Namespace: config.C.StatsNamespace,
101
+			Name:      "telegram_connections",
102
+			Help:      "Current number of telegram connections established by this proxy.",
103
+		}, []string{"dc", "protocol"}),
104
+		traffic: prometheus.NewGaugeVec(prometheus.GaugeOpts{
105
+			Namespace: config.C.StatsNamespace,
106
+			Name:      "traffic",
107
+			Help:      "Traffic passed through the proxy in bytes.",
108
+		}, []string{"direction"}),
109
+		crashes: prometheus.NewCounter(prometheus.CounterOpts{
110
+			Namespace: config.C.StatsNamespace,
111
+			Name:      "crashes",
112
+			Help:      "How many crashes happened.",
113
+		}),
114
+		replayAttacks: prometheus.NewCounter(prometheus.CounterOpts{
115
+			Namespace: config.C.StatsNamespace,
116
+			Name:      "replay_attacks",
117
+			Help:      "How many replay attacks were prevented.",
118
+		}),
119
+	}
120
+
121
+	registry.MustRegister(instance.connections)
122
+	registry.MustRegister(instance.telegramConnections)
123
+	registry.MustRegister(instance.traffic)
124
+	registry.MustRegister(instance.crashes)
125
+	registry.MustRegister(instance.replayAttacks)
126
+
127
+	handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
128
+	mux.Handle("/", handler)
129
+
130
+	return instance
131
+}

+ 194
- 0
stats/stats_statsd.go Целия файл

@@ -0,0 +1,194 @@
1
+package stats
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+	"strconv"
7
+	"strings"
8
+	"sync"
9
+	"time"
10
+
11
+	statsd "github.com/smira/go-statsd"
12
+	"go.uber.org/zap"
13
+
14
+	"mtg/config"
15
+	"mtg/conntypes"
16
+)
17
+
18
+var (
19
+	tagTrafficIngress = &statsStatsdTag{
20
+		name: "ingress",
21
+		tag:  statsd.StringTag("type", "ingress"),
22
+	}
23
+	tagTrafficEgress = &statsStatsdTag{
24
+		name: "egress",
25
+		tag:  statsd.StringTag("type", "egress"),
26
+	}
27
+
28
+	tagConnectionTypeAbridged = &statsStatsdTag{
29
+		name: "abridged",
30
+		tag:  statsd.StringTag("type", "abridged"),
31
+	}
32
+	tagConnectionTypeIntermediate = &statsStatsdTag{
33
+		name: "intermediate",
34
+		tag:  statsd.StringTag("type", "intermediate"),
35
+	}
36
+	tagConnectionTypeSecured = &statsStatsdTag{
37
+		name: "secured",
38
+		tag:  statsd.StringTag("type", "secured"),
39
+	}
40
+
41
+	tagConnectionProtocol4 = &statsStatsdTag{
42
+		name: "ipv4",
43
+		tag:  statsd.StringTag("protocol", "ipv4"),
44
+	}
45
+	tagConnectionProtocol6 = &statsStatsdTag{
46
+		name: "ipv6",
47
+		tag:  statsd.StringTag("protocol", "ipv6"),
48
+	}
49
+)
50
+
51
+type statsStatsdTag struct {
52
+	tag  statsd.Tag
53
+	name string
54
+}
55
+
56
+type statsStatsdLogger struct {
57
+	log *zap.SugaredLogger
58
+}
59
+
60
+func (s statsStatsdLogger) Printf(msg string, args ...interface{}) {
61
+	s.log.Debugw(fmt.Sprintf(msg, args...))
62
+}
63
+
64
+type statsStatsd struct {
65
+	seen      map[string]struct{}
66
+	seenMutex sync.RWMutex
67
+	client    *statsd.Client
68
+}
69
+
70
+func (s *statsStatsd) IngressTraffic(traffic int) {
71
+	s.gauge("traffic", int64(traffic), tagTrafficIngress)
72
+}
73
+
74
+func (s *statsStatsd) EgressTraffic(traffic int) {
75
+	s.gauge("traffic", int64(traffic), tagTrafficEgress)
76
+}
77
+
78
+func (s *statsStatsd) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
79
+	s.changeConnections(connectionType, addr, 1)
80
+}
81
+
82
+func (s *statsStatsd) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
83
+	s.changeConnections(connectionType, addr, -1)
84
+}
85
+
86
+func (s *statsStatsd) changeConnections(connectionType conntypes.ConnectionType, addr *net.TCPAddr, increment int64) {
87
+	tags := make([]*statsStatsdTag, 0, 2)
88
+
89
+	switch connectionType {
90
+	case conntypes.ConnectionTypeAbridged:
91
+		tags = append(tags, tagConnectionTypeAbridged)
92
+	case conntypes.ConnectionTypeIntermediate:
93
+		tags = append(tags, tagConnectionTypeIntermediate)
94
+	default:
95
+		tags = append(tags, tagConnectionTypeSecured)
96
+	}
97
+
98
+	if addr.IP.To4() == nil {
99
+		tags = append(tags, tagConnectionProtocol6)
100
+	} else {
101
+		tags = append(tags, tagConnectionProtocol4)
102
+	}
103
+
104
+	s.gauge("connections", increment, tags...)
105
+}
106
+
107
+func (s *statsStatsd) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) {
108
+	s.changeTelegramConnections(dc, addr, 1)
109
+}
110
+
111
+func (s *statsStatsd) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) {
112
+	s.changeTelegramConnections(dc, addr, -1)
113
+}
114
+
115
+func (s *statsStatsd) changeTelegramConnections(dc conntypes.DC, addr *net.TCPAddr, increment int64) {
116
+	tags := []*statsStatsdTag{
117
+		{
118
+			name: "dc" + strconv.Itoa(int(dc)),
119
+			tag:  statsd.IntTag("dc", int(dc)),
120
+		},
121
+	}
122
+
123
+	if addr.IP.To4() == nil {
124
+		tags = append(tags, tagConnectionProtocol6)
125
+	} else {
126
+		tags = append(tags, tagConnectionProtocol4)
127
+	}
128
+
129
+	s.gauge("telegram_connections", increment, tags...)
130
+}
131
+
132
+func (s *statsStatsd) Crash() {
133
+	s.gauge("crashes", 1)
134
+}
135
+
136
+func (s *statsStatsd) ReplayDetected() {
137
+	s.gauge("replay_attacks", 1)
138
+}
139
+
140
+func (s *statsStatsd) gauge(metric string, value int64, tags ...*statsStatsdTag) {
141
+	key, tagList := s.prepareVals(metric, tags)
142
+	s.initGauge(metric, key, tagList)
143
+	s.client.GaugeDelta(metric, value, tagList...)
144
+}
145
+
146
+func (s *statsStatsd) prepareVals(metric string, tags []*statsStatsdTag) (string, []statsd.Tag) {
147
+	tagList := make([]statsd.Tag, len(tags))
148
+	builder := strings.Builder{}
149
+	builder.WriteString(metric)
150
+
151
+	for i, v := range tags {
152
+		builder.WriteRune('.')
153
+		builder.WriteString(v.name)
154
+		tagList[i] = v.tag
155
+	}
156
+
157
+	return builder.String(), tagList
158
+}
159
+
160
+func (s *statsStatsd) initGauge(metric, key string, tags []statsd.Tag) {
161
+	s.seenMutex.RLock()
162
+	_, ok := s.seen[key]
163
+	s.seenMutex.RUnlock()
164
+
165
+	if ok {
166
+		return
167
+	}
168
+
169
+	s.seenMutex.Lock()
170
+	defer s.seenMutex.Unlock()
171
+
172
+	if _, ok = s.seen[key]; !ok {
173
+		s.seen[key] = struct{}{}
174
+		s.client.Gauge(metric, 0, tags...)
175
+	}
176
+}
177
+
178
+func newStatsStatsd() Interface {
179
+	prefix := strings.TrimSuffix(config.C.StatsNamespace, ".") + "."
180
+	logger := statsStatsdLogger{
181
+		log: zap.S().Named("stats").Named("statsd"),
182
+	}
183
+
184
+	return &statsStatsd{
185
+		seen: make(map[string]struct{}),
186
+		client: statsd.NewClient(config.C.StatsdAddr.String(),
187
+			statsd.SendLoopCount(2),
188
+			statsd.ReconnectInterval(10*time.Second),
189
+			statsd.Logger(logger),
190
+			statsd.MetricPrefix(prefix),
191
+			statsd.TagStyle(config.C.StatsdTagsFormat),
192
+		),
193
+	}
194
+}

+ 0
- 77
stats/statsd.go Целия файл

@@ -1,77 +0,0 @@
1
-package stats
2
-
3
-import (
4
-	"time"
5
-
6
-	"github.com/juju/errors"
7
-	statsd "gopkg.in/alexcesaro/statsd.v2"
8
-
9
-	"github.com/9seconds/mtg/config"
10
-)
11
-
12
-const (
13
-	statsdConnectionsAbridgedV4 = "connections.abridged.ipv4"
14
-	statsdConnectionsAbridgedV6 = "connections.abridged.ipv6"
15
-
16
-	statsdConnectionsIntermediateV4 = "connections.intermediate.ipv4"
17
-	statsdConnectionsIntermediateV6 = "connections.intermediate.ipv6"
18
-
19
-	statsdConnectionsSecureV4 = "connections.secure.ipv4"
20
-	statsdConnectionsSecureV6 = "connections.secure.ipv6"
21
-
22
-	statsdTrafficIngress = "traffic.ingress"
23
-	statsdTrafficEgress  = "traffic.egress"
24
-
25
-	statsdSpeedIngress = "speed.ingress"
26
-	statsdSpeedEgress  = "speed.egress"
27
-
28
-	statsdCrashes = "crashes"
29
-)
30
-
31
-const statsdPollTime = time.Second
32
-
33
-type statsdExporter struct {
34
-	client *statsd.Client
35
-}
36
-
37
-func (s *statsdExporter) run() {
38
-	for range time.Tick(statsdPollTime) {
39
-		instance := GetStats()
40
-
41
-		s.client.Gauge(statsdConnectionsAbridgedV4, instance.Connections.Abridged.IPv4)
42
-		s.client.Gauge(statsdConnectionsAbridgedV6, instance.Connections.Abridged.IPv6)
43
-		s.client.Gauge(statsdConnectionsIntermediateV4, instance.Connections.Intermediate.IPv4)
44
-		s.client.Gauge(statsdConnectionsIntermediateV6, instance.Connections.Intermediate.IPv6)
45
-		s.client.Gauge(statsdConnectionsSecureV4, instance.Connections.Secure.IPv4)
46
-		s.client.Gauge(statsdConnectionsSecureV6, instance.Connections.Secure.IPv6)
47
-		s.client.Gauge(statsdTrafficIngress, instance.Traffic.ingress)
48
-		s.client.Gauge(statsdTrafficEgress, instance.Traffic.egress)
49
-		s.client.Gauge(statsdSpeedIngress, instance.Speed.ingress)
50
-		s.client.Gauge(statsdSpeedEgress, instance.Speed.egress)
51
-		s.client.Gauge(statsdCrashes, instance.Crashes)
52
-	}
53
-}
54
-
55
-func newStatsd(conf *config.Config) (*statsdExporter, error) {
56
-	options := []statsd.Option{
57
-		statsd.Network(conf.StatsD.Addr.Network()),
58
-		statsd.Address(conf.StatsD.Addr.String()),
59
-		statsd.Prefix(conf.StatsD.Prefix),
60
-	}
61
-
62
-	if conf.StatsD.TagsFormat > 0 {
63
-		options = append(options, statsd.TagsFormat(conf.StatsD.TagsFormat))
64
-		tags := make([]string, len(conf.StatsD.Tags)*2)
65
-		for k, v := range conf.StatsD.Tags {
66
-			tags = append(tags, k, v)
67
-		}
68
-		options = append(options, statsd.Tags(tags...))
69
-	}
70
-
71
-	client, err := statsd.New(options...)
72
-	if err != nil {
73
-		return nil, errors.Annotate(err, "Cannot create statsd client")
74
-	}
75
-
76
-	return &statsdExporter{client: client}, nil
77
-}

+ 109
- 0
telegram/api/addresses.go Целия файл

@@ -0,0 +1,109 @@
1
+package api
2
+
3
+import (
4
+	"bufio"
5
+	"fmt"
6
+	"net"
7
+	"regexp"
8
+	"strconv"
9
+	"strings"
10
+
11
+	"mtg/conntypes"
12
+)
13
+
14
+const (
15
+	addressesURLV4 = "https://core.telegram.org/getProxyConfig"   // nolint: gas
16
+	addressesURLV6 = "https://core.telegram.org/getProxyConfigV6" // nolint: gas
17
+)
18
+
19
+var addressesProxyForSplitter = regexp.MustCompile(`\s+`)
20
+
21
+func AddressesV4() (map[conntypes.DC][]string, conntypes.DC, error) {
22
+	return getAddresses(addressesURLV4)
23
+}
24
+
25
+func AddressesV6() (map[conntypes.DC][]string, conntypes.DC, error) {
26
+	return getAddresses(addressesURLV6)
27
+}
28
+
29
+func getAddresses(url string) (map[conntypes.DC][]string, conntypes.DC, error) {
30
+	resp, err := request(url)
31
+	if err != nil {
32
+		return nil, 0, fmt.Errorf("cannot get http response: %w", err)
33
+	}
34
+
35
+	defer resp.Close()
36
+
37
+	scanner := bufio.NewScanner(resp)
38
+	data := map[conntypes.DC][]string{}
39
+	defaultDC := conntypes.DCDefaultIdx
40
+
41
+	for scanner.Scan() {
42
+		text := strings.TrimSpace(scanner.Text())
43
+
44
+		switch {
45
+		case strings.HasPrefix(text, "#"):
46
+			continue
47
+		case strings.HasPrefix(text, "proxy_for"):
48
+			addr, idx, err := addressesParseProxyFor(text)
49
+			if err != nil {
50
+				return nil, 0, fmt.Errorf("cannot parse 'proxy_for' section: %w", err)
51
+			}
52
+
53
+			if addresses, ok := data[idx]; ok {
54
+				data[idx] = append(addresses, addr)
55
+			} else {
56
+				data[idx] = []string{addr}
57
+			}
58
+		case strings.HasPrefix(text, "default"):
59
+			idx, err := addressesParseDefault(text)
60
+			if err != nil {
61
+				return nil, 0, fmt.Errorf("cannot parse 'default' section: %w", err)
62
+			}
63
+
64
+			defaultDC = idx
65
+		}
66
+	}
67
+
68
+	err = scanner.Err()
69
+	if err != nil {
70
+		return nil, 0, fmt.Errorf("cannot parse http response: %w", err)
71
+	}
72
+
73
+	return data, defaultDC, nil
74
+}
75
+
76
+func addressesParseProxyFor(text string) (string, conntypes.DC, error) {
77
+	chunks := addressesProxyForSplitter.Split(text, 3)
78
+	if len(chunks) != 3 || chunks[0] != "proxy_for" {
79
+		return "", 0, fmt.Errorf("incorrect config %s", text)
80
+	}
81
+
82
+	dc, err := strconv.ParseInt(chunks[1], 10, 16)
83
+	if err != nil {
84
+		return "", 0, fmt.Errorf("incorrect config '%s': %w", text, err)
85
+	}
86
+
87
+	addr := strings.TrimRight(chunks[2], ";")
88
+	if _, _, err = net.SplitHostPort(addr); err != nil {
89
+		return "", 0, fmt.Errorf("incorrect config '%s': %w", text, err)
90
+	}
91
+
92
+	return addr, conntypes.DC(dc), nil
93
+}
94
+
95
+func addressesParseDefault(text string) (conntypes.DC, error) {
96
+	chunks := addressesProxyForSplitter.Split(text, 2)
97
+	if len(chunks) != 2 || chunks[0] != "default" {
98
+		return 0, fmt.Errorf("incorrect config '%s'", text)
99
+	}
100
+
101
+	dcString := strings.TrimRight(chunks[1], ";")
102
+
103
+	dc, err := strconv.ParseInt(dcString, 10, 16)
104
+	if err != nil {
105
+		return 0, fmt.Errorf("incorrect config '%s': %w", text, err)
106
+	}
107
+
108
+	return conntypes.DC(dc), nil
109
+}

+ 40
- 0
telegram/api/api.go Целия файл

@@ -0,0 +1,40 @@
1
+package api
2
+
3
+import (
4
+	"fmt"
5
+	"io"
6
+	"io/ioutil"
7
+	"net/http"
8
+	"time"
9
+)
10
+
11
+const (
12
+	apiUserAgent   = "mtg"
13
+	apiHTTPTimeout = 30 * time.Second
14
+)
15
+
16
+var httpClient = http.Client{
17
+	Timeout: apiHTTPTimeout,
18
+}
19
+
20
+func request(url string) (io.ReadCloser, error) {
21
+	req, err := http.NewRequest("GET", url, nil)
22
+	if err != nil {
23
+		panic(err)
24
+	}
25
+
26
+	req.Header.Set("Accept", "text/plan")
27
+	req.Header.Set("User-Agent", apiUserAgent)
28
+
29
+	resp, err := httpClient.Do(req)
30
+	if err != nil {
31
+		if resp != nil {
32
+			io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck
33
+			resp.Body.Close()
34
+		}
35
+
36
+		return nil, fmt.Errorf("cannot perform a request: %w", err)
37
+	}
38
+
39
+	return resp.Body, err
40
+}

+ 24
- 0
telegram/api/secret.go Целия файл

@@ -0,0 +1,24 @@
1
+package api
2
+
3
+import (
4
+	"fmt"
5
+	"io/ioutil"
6
+)
7
+
8
+const secretURL = "https://core.telegram.org/getProxySecret" // nolint: gas
9
+
10
+func Secret() ([]byte, error) {
11
+	resp, err := request(secretURL)
12
+	if err != nil {
13
+		return nil, fmt.Errorf("cannot access telegram server: %w", err)
14
+	}
15
+
16
+	defer resp.Close()
17
+
18
+	secret, err := ioutil.ReadAll(resp)
19
+	if err != nil {
20
+		return nil, fmt.Errorf("cannot read response: %w", err)
21
+	}
22
+
23
+	return secret, nil
24
+}

+ 65
- 0
telegram/base.go Целия файл

@@ -0,0 +1,65 @@
1
+package telegram
2
+
3
+import (
4
+	"fmt"
5
+	"math/rand"
6
+	"net"
7
+
8
+	"mtg/conntypes"
9
+	"mtg/utils"
10
+	"mtg/wrappers/stream"
11
+)
12
+
13
+type baseTelegram struct {
14
+	dialer net.Dialer
15
+
16
+	secret      []byte
17
+	v4DefaultDC conntypes.DC
18
+	V6DefaultDC conntypes.DC
19
+	v4Addresses map[conntypes.DC][]string
20
+	v6Addresses map[conntypes.DC][]string
21
+}
22
+
23
+func (b *baseTelegram) Secret() []byte {
24
+	return b.secret
25
+}
26
+
27
+func (b *baseTelegram) dial(dc conntypes.DC,
28
+	protocol conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) {
29
+	addr := ""
30
+
31
+	switch protocol {
32
+	case conntypes.ConnectionProtocolIPv4:
33
+		addr = b.chooseAddress(b.v4Addresses, dc, b.v4DefaultDC)
34
+	default:
35
+		addr = b.chooseAddress(b.v6Addresses, dc, b.V6DefaultDC)
36
+	}
37
+
38
+	conn, err := b.dialer.Dial("tcp", addr)
39
+	if err != nil {
40
+		return nil, fmt.Errorf("dial has failed: %w", err)
41
+	}
42
+
43
+	if err := utils.InitTCP(conn); err != nil {
44
+		return nil, fmt.Errorf("cannot initialize tcp socket: %w", err)
45
+	}
46
+
47
+	return stream.NewTelegramConn(dc, conn), nil
48
+}
49
+
50
+func (b *baseTelegram) chooseAddress(addresses map[conntypes.DC][]string,
51
+	dc, defaultDC conntypes.DC) string {
52
+	addrs, ok := addresses[dc]
53
+	if !ok {
54
+		addrs = addresses[defaultDC]
55
+	}
56
+
57
+	switch {
58
+	case len(addrs) == 1:
59
+		return addrs[0]
60
+	case len(addrs) > 1:
61
+		return addrs[rand.Intn(len(addrs))]
62
+	}
63
+
64
+	return ""
65
+}

+ 0
- 52
telegram/dialer.go Целия файл

@@ -1,52 +0,0 @@
1
-package telegram
2
-
3
-import (
4
-	"context"
5
-	"net"
6
-	"time"
7
-
8
-	"github.com/juju/errors"
9
-
10
-	"github.com/9seconds/mtg/config"
11
-	"github.com/9seconds/mtg/wrappers"
12
-)
13
-
14
-const telegramDialTimeout = 10 * time.Second
15
-
16
-type tgDialer struct {
17
-	net.Dialer
18
-
19
-	conf *config.Config
20
-}
21
-
22
-func (t *tgDialer) dial(addr string) (net.Conn, error) {
23
-	conn, err := t.Dialer.Dial("tcp", addr)
24
-	if err != nil {
25
-		return nil, errors.Annotate(err, "Cannot connect to Telegram")
26
-	}
27
-
28
-	tcpSocket := conn.(*net.TCPConn)
29
-	if err = tcpSocket.SetNoDelay(true); err != nil {
30
-		return nil, errors.Annotate(err, "Cannot set NO_DELAY to Telegram")
31
-	}
32
-	if err = tcpSocket.SetReadBuffer(t.conf.WriteBufferSize); err != nil {
33
-		return nil, errors.Annotate(err, "Cannot set read buffer size on telegram socket")
34
-	}
35
-	if err = tcpSocket.SetWriteBuffer(t.conf.ReadBufferSize); err != nil {
36
-		return nil, errors.Annotate(err, "Cannot set write buffer size on telegram socket")
37
-	}
38
-
39
-	return conn, nil
40
-}
41
-
42
-func (t *tgDialer) dialRWC(ctx context.Context, cancel context.CancelFunc,
43
-	addr, connID string) (wrappers.StreamReadWriteCloser, error) {
44
-	conn, err := t.dial(addr)
45
-	if err != nil {
46
-		return nil, err
47
-	}
48
-	tgConn := wrappers.NewConn(ctx, cancel, conn, connID,
49
-		wrappers.ConnPurposeTelegram, t.conf.PublicIPv4, t.conf.PublicIPv6)
50
-
51
-	return tgConn, nil
52
-}

+ 12
- 50
telegram/direct.go Целия файл

@@ -1,31 +1,21 @@
1 1
 package telegram
2 2
 
3
-import (
4
-	"context"
5
-	"net"
6
-
7
-	"github.com/juju/errors"
8
-
9
-	"github.com/9seconds/mtg/config"
10
-	"github.com/9seconds/mtg/mtproto"
11
-	"github.com/9seconds/mtg/obfuscated2"
12
-	"github.com/9seconds/mtg/wrappers"
13
-)
3
+import "mtg/conntypes"
14 4
 
15 5
 const (
16
-	directV4DefaultIdx = 1
17
-	directV6DefaultIdx = 1
6
+	directV4DefaultIdx conntypes.DC = 1
7
+	directV6DefaultIdx conntypes.DC = 1
18 8
 )
19 9
 
20 10
 var (
21
-	directV4Addresses = map[int16][]string{
11
+	directV4Addresses = map[conntypes.DC][]string{
22 12
 		0: {"149.154.175.50:443"},
23 13
 		1: {"149.154.167.51:443"},
24 14
 		2: {"149.154.175.100:443"},
25 15
 		3: {"149.154.167.91:443"},
26 16
 		4: {"149.154.171.5:443"},
27 17
 	}
28
-	directV6Addresses = map[int16][]string{
18
+	directV6Addresses = map[conntypes.DC][]string{
29 19
 		0: {"[2001:b28:f23d:f001::a]:443"},
30 20
 		1: {"[2001:67c:04e8:f002::a]:443"},
31 21
 		2: {"[2001:b28:f23d:f003::a]:443"},
@@ -38,42 +28,14 @@ type directTelegram struct {
38 28
 	baseTelegram
39 29
 }
40 30
 
41
-func (t *directTelegram) Dial(ctx context.Context, cancel context.CancelFunc,
42
-	connID string, connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) {
43
-	dc := connOpts.DC
44
-	if dc < 0 {
31
+func (d *directTelegram) Dial(dc conntypes.DC,
32
+	protocol conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) {
33
+	switch {
34
+	case dc < 0:
45 35
 		dc = -dc
46
-	} else if dc == 0 {
47
-		dc = 1
36
+	case dc == 0:
37
+		dc = conntypes.DCDefaultIdx
48 38
 	}
49 39
 
50
-	return t.baseTelegram.dial(ctx, cancel, dc-1, connID, connOpts.ConnectionProto)
51
-}
52
-
53
-func (t *directTelegram) Init(connOpts *mtproto.ConnectionOpts,
54
-	conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) {
55
-	obfs2, frame := obfuscated2.MakeTelegramObfuscated2Frame(connOpts)
56
-
57
-	if _, err := conn.Write(frame); err != nil {
58
-		return nil, errors.Annotate(err, "Cannot write hadnshake frame")
59
-	}
60
-
61
-	return wrappers.NewStreamCipher(conn, obfs2.Encryptor, obfs2.Decryptor), nil
62
-}
63
-
64
-// NewDirectTelegram returns Telegram instance which connects directly
65
-// to Telegram bypassing middleproxies.
66
-func NewDirectTelegram(conf *config.Config) Telegram {
67
-	return &directTelegram{
68
-		baseTelegram: baseTelegram{
69
-			dialer: tgDialer{
70
-				Dialer: net.Dialer{Timeout: telegramDialTimeout},
71
-				conf:   conf,
72
-			},
73
-			v4DefaultIdx: directV4DefaultIdx,
74
-			v6DefaultIdx: directV6DefaultIdx,
75
-			v4Addresses:  directV4Addresses,
76
-			v6Addresses:  directV6Addresses,
77
-		},
78
-	}
40
+	return d.baseTelegram.dial(dc-1, protocol)
79 41
 }

+ 42
- 0
telegram/init.go Целия файл

@@ -0,0 +1,42 @@
1
+package telegram
2
+
3
+import (
4
+	"net"
5
+	"sync"
6
+	"time"
7
+)
8
+
9
+const telegramDialTimeout = 10 * time.Second
10
+
11
+var (
12
+	Direct Telegram
13
+	Middle Telegram
14
+
15
+	initOnce sync.Once
16
+)
17
+
18
+func Init() {
19
+	initOnce.Do(func() {
20
+		Direct = &directTelegram{
21
+			baseTelegram: baseTelegram{
22
+				dialer:      net.Dialer{Timeout: telegramDialTimeout},
23
+				v4DefaultDC: directV4DefaultIdx,
24
+				V6DefaultDC: directV6DefaultIdx,
25
+				v4Addresses: directV4Addresses,
26
+				v6Addresses: directV6Addresses,
27
+			},
28
+		}
29
+
30
+		tg := &middleTelegram{
31
+			baseTelegram: baseTelegram{
32
+				dialer: net.Dialer{Timeout: telegramDialTimeout},
33
+			},
34
+		}
35
+		if err := tg.update(); err != nil {
36
+			panic(err)
37
+		}
38
+		go tg.backgroundUpdate()
39
+
40
+		Middle = tg
41
+	})
42
+}

+ 8
- 0
telegram/interfaces.go Целия файл

@@ -0,0 +1,8 @@
1
+package telegram
2
+
3
+import "mtg/conntypes"
4
+
5
+type Telegram interface {
6
+	Dial(conntypes.DC, conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error)
7
+	Secret() []byte
8
+}

+ 41
- 104
telegram/middle.go Целия файл

@@ -1,139 +1,76 @@
1 1
 package telegram
2 2
 
3 3
 import (
4
-	"io"
5
-	"net"
6
-	"net/http"
4
+	"fmt"
7 5
 	"sync"
6
+	"time"
8 7
 
9
-	"github.com/juju/errors"
8
+	"go.uber.org/zap"
10 9
 
11
-	"github.com/9seconds/mtg/config"
12
-	"github.com/9seconds/mtg/mtproto"
13
-	"github.com/9seconds/mtg/mtproto/rpc"
14
-	"github.com/9seconds/mtg/wrappers"
10
+	"mtg/conntypes"
11
+	"mtg/telegram/api"
15 12
 )
16 13
 
14
+const middleTelegramBackgroundUpdateEvery = time.Hour
15
+
17 16
 type middleTelegram struct {
18
-	middleTelegramCaller
17
+	baseTelegram
19 18
 
20
-	conf *config.Config
19
+	mutex sync.RWMutex
21 20
 }
22 21
 
23
-func (t *middleTelegram) Init(connOpts *mtproto.ConnectionOpts,
24
-	conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) {
25
-	rpcNonceConn := wrappers.NewMTProtoFrame(conn, rpc.SeqNoNonce)
26
-
27
-	rpcNonceReq, err := t.sendRPCNonceRequest(rpcNonceConn)
28
-	if err != nil {
29
-		return nil, err
30
-	}
31
-	rpcNonceResp, err := t.receiveRPCNonceResponse(rpcNonceConn, rpcNonceReq)
32
-	if err != nil {
33
-		return nil, err
34
-	}
35
-
36
-	secureConn := wrappers.NewMiddleProxyCipher(conn, rpcNonceReq, rpcNonceResp, t.proxySecret)
37
-	frameConn := wrappers.NewMTProtoFrame(secureConn, rpc.SeqNoHandshake)
38
-
39
-	rpcHandshakeReq, err := t.sendRPCHandshakeRequest(frameConn)
40
-	if err != nil {
41
-		return nil, err
42
-	}
43
-	_, err = t.receiveRPCHandshakeResponse(frameConn, rpcHandshakeReq)
44
-	if err != nil {
45
-		return nil, err
46
-	}
47
-
48
-	proxyConn, err := wrappers.NewMTProtoProxy(frameConn, connOpts, t.conf.AdTag)
49
-	if err != nil {
50
-		return nil, err
51
-	}
52
-	proxyConn.Logger().Infow("Telegram connection initialized")
22
+func (m *middleTelegram) Secret() []byte {
23
+	m.mutex.RLock()
24
+	defer m.mutex.RUnlock()
53 25
 
54
-	return proxyConn, nil
26
+	return m.baseTelegram.Secret()
55 27
 }
56 28
 
57
-func (t *middleTelegram) sendRPCNonceRequest(conn io.Writer) (*rpc.NonceRequest, error) {
58
-	rpcNonceReq, err := rpc.NewNonceRequest(t.proxySecret)
29
+func (m *middleTelegram) update() error {
30
+	secret, err := api.Secret()
59 31
 	if err != nil {
60
-		return nil, errors.Annotate(err, "Cannot create RPC nonce request")
61
-	}
62
-	if _, err = conn.Write(rpcNonceReq.Bytes()); err != nil {
63
-		return nil, errors.Annotate(err, "Cannot send RPC nonce request")
32
+		return fmt.Errorf("cannot fetch secret: %w", err)
64 33
 	}
65 34
 
66
-	return rpcNonceReq, nil
67
-}
68
-
69
-func (t *middleTelegram) receiveRPCNonceResponse(conn wrappers.PacketReader,
70
-	req *rpc.NonceRequest) (*rpc.NonceResponse, error) {
71
-	packet, err := conn.Read()
35
+	v4Addresses, v4DefaultDC, err := api.AddressesV4()
72 36
 	if err != nil {
73
-		return nil, errors.Annotate(err, "Cannot read RPC nonce response")
37
+		return fmt.Errorf("cannot fetch addresses for ipv4: %w", err)
74 38
 	}
75 39
 
76
-	rpcNonceResp, err := rpc.NewNonceResponse(packet)
40
+	v6Addresses, v6DefaultDC, err := api.AddressesV6()
77 41
 	if err != nil {
78
-		return nil, errors.Annotate(err, "Cannot initialize RPC nonce response")
42
+		return fmt.Errorf("cannot fetch addresses for ipv6: %w", err)
79 43
 	}
80
-	if err = rpcNonceResp.Valid(req); err != nil {
81
-		return nil, errors.Annotate(err, "Invalid RPC nonce response")
82
-	}
83
-
84
-	return rpcNonceResp, nil
85
-}
86 44
 
87
-func (t *middleTelegram) sendRPCHandshakeRequest(conn io.Writer) (*rpc.HandshakeRequest, error) {
88
-	req := rpc.NewHandshakeRequest()
89
-	if _, err := conn.Write(req.Bytes()); err != nil {
90
-		return nil, errors.Annotate(err, "Cannot send RPC handshake request")
91
-	}
45
+	m.mutex.Lock()
46
+	m.secret = secret
47
+	m.v4DefaultDC = v4DefaultDC
48
+	m.V6DefaultDC = v6DefaultDC
49
+	m.v4Addresses = v4Addresses
50
+	m.v6Addresses = v6Addresses
51
+	m.mutex.Unlock()
92 52
 
93
-	return req, nil
53
+	return nil
94 54
 }
95 55
 
96
-func (t *middleTelegram) receiveRPCHandshakeResponse(conn wrappers.PacketReader,
97
-	req *rpc.HandshakeRequest) (*rpc.HandshakeResponse, error) {
98
-	packet, err := conn.Read()
99
-	if err != nil {
100
-		return nil, errors.Annotate(err, "Cannot read RPC handshake response")
101
-	}
56
+func (m *middleTelegram) backgroundUpdate() {
57
+	logger := zap.S().Named("telegram")
102 58
 
103
-	rpcHandshakeResp, err := rpc.NewHandshakeResponse(packet)
104
-	if err != nil {
105
-		return nil, errors.Annotate(err, "Cannot initialize RPC handshake response")
59
+	for range time.Tick(middleTelegramBackgroundUpdateEvery) {
60
+		if err := m.update(); err != nil {
61
+			logger.Warnw("Cannot update Telegram proxies", "error", err)
62
+		}
106 63
 	}
107
-	if err = rpcHandshakeResp.Valid(req); err != nil {
108
-		return nil, errors.Annotate(err, "Invalid RPC handshake response")
109
-	}
110
-
111
-	return rpcHandshakeResp, nil
112 64
 }
113 65
 
114
-// NewMiddleTelegram creates new instance of Telegram which works with
115
-// middle proxies.
116
-func NewMiddleTelegram(conf *config.Config) Telegram {
117
-	tg := &middleTelegram{
118
-		middleTelegramCaller: middleTelegramCaller{
119
-			baseTelegram: baseTelegram{
120
-				dialer: tgDialer{
121
-					Dialer: net.Dialer{Timeout: telegramDialTimeout},
122
-					conf:   conf,
123
-				},
124
-			},
125
-			httpClient: &http.Client{
126
-				Timeout: middleTelegramHTTPClientTimeout,
127
-			},
128
-			dialerMutex: &sync.RWMutex{},
129
-		},
130
-		conf: conf,
66
+func (m *middleTelegram) Dial(dc conntypes.DC,
67
+	protocol conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) {
68
+	if dc == 0 {
69
+		dc = conntypes.DCDefaultIdx
131 70
 	}
132 71
 
133
-	if err := tg.update(); err != nil {
134
-		panic(err)
135
-	}
136
-	go tg.autoUpdate()
72
+	m.mutex.RLock()
73
+	defer m.mutex.RUnlock()
137 74
 
138
-	return tg
75
+	return m.baseTelegram.dial(dc, protocol)
139 76
 }

+ 0
- 191
telegram/middle_caller.go Целия файл

@@ -1,191 +0,0 @@
1
-package telegram
2
-
3
-import (
4
-	"bufio"
5
-	"context"
6
-	"io/ioutil"
7
-	"net"
8
-	"net/http"
9
-	"regexp"
10
-	"strconv"
11
-	"strings"
12
-	"sync"
13
-	"time"
14
-
15
-	"github.com/juju/errors"
16
-	"go.uber.org/zap"
17
-
18
-	"github.com/9seconds/mtg/mtproto"
19
-	"github.com/9seconds/mtg/wrappers"
20
-)
21
-
22
-const (
23
-	middleTelegramAutoUpdateInterval = 6 * time.Hour
24
-	middleTelegramHTTPClientTimeout  = 30 * time.Second
25
-
26
-	tgAddrProxySecret = "https://core.telegram.org/getProxySecret"   // nolint: gas
27
-	tgAddrProxyV4     = "https://core.telegram.org/getProxyConfig"   // nolint: gas
28
-	tgAddrProxyV6     = "https://core.telegram.org/getProxyConfigV6" // nolint: gas
29
-	tgUserAgent       = "mtg"
30
-)
31
-
32
-var middleTelegramProxyConfigSplitter = regexp.MustCompile(`\s+`)
33
-
34
-type middleTelegramCaller struct {
35
-	baseTelegram
36
-
37
-	proxySecret []byte
38
-	dialerMutex *sync.RWMutex
39
-	httpClient  *http.Client
40
-}
41
-
42
-func (t *middleTelegramCaller) Dial(ctx context.Context, cancel context.CancelFunc, connID string,
43
-	connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) {
44
-	dc := connOpts.DC
45
-	if dc == 0 {
46
-		dc = 1
47
-	}
48
-	t.dialerMutex.RLock()
49
-	defer t.dialerMutex.RUnlock()
50
-
51
-	return t.baseTelegram.dial(ctx, cancel, dc, connID, connOpts.ConnectionProto)
52
-}
53
-
54
-func (t *middleTelegramCaller) autoUpdate() {
55
-	for range time.Tick(middleTelegramAutoUpdateInterval) {
56
-		if err := t.update(); err != nil {
57
-			zap.S().Warnw("Cannot update from Telegram", "error", err)
58
-		}
59
-	}
60
-}
61
-
62
-func (t *middleTelegramCaller) update() error {
63
-	secret, err := t.getTelegramProxySecret()
64
-	if err != nil {
65
-		return errors.Annotate(err, "Cannot get proxy secret")
66
-	}
67
-
68
-	v4Addresses, v4DefaultIdx, err := t.getTelegramAddresses(tgAddrProxyV4)
69
-	if err != nil {
70
-		return errors.Annotate(err, "Cannot get ipv4 addresses")
71
-	}
72
-
73
-	v6Addresses, v6DefaultIdx, err := t.getTelegramAddresses(tgAddrProxyV6)
74
-	if err != nil {
75
-		return errors.Annotate(err, "Cannot get ipv6 addresses")
76
-	}
77
-
78
-	t.dialerMutex.Lock()
79
-	t.proxySecret = secret
80
-	t.v4DefaultIdx = v4DefaultIdx
81
-	t.v6DefaultIdx = v6DefaultIdx
82
-	t.v4Addresses = v4Addresses
83
-	t.v6Addresses = v6Addresses
84
-	t.dialerMutex.Unlock()
85
-
86
-	zap.S().Infow("Telegram middle proxy data has been updated")
87
-
88
-	return nil
89
-}
90
-
91
-func (t *middleTelegramCaller) getTelegramProxySecret() ([]byte, error) {
92
-	resp, err := t.call(tgAddrProxySecret)
93
-	if err != nil {
94
-		return nil, errors.Annotate(err, "Cannot access telegram server")
95
-	}
96
-	defer resp.Body.Close() // nolint: errcheck
97
-
98
-	secret, err := ioutil.ReadAll(resp.Body)
99
-	if err != nil {
100
-		return nil, errors.Annotate(err, "Cannot read response")
101
-	}
102
-
103
-	return secret, nil
104
-}
105
-
106
-func (t *middleTelegramCaller) getTelegramAddresses(url string) (map[int16][]string, int16, error) { // nolint: gocyclo
107
-	resp, err := t.call(url)
108
-	if err != nil {
109
-		return nil, 0, errors.Annotate(err, "Cannot access telegram server")
110
-	}
111
-	defer resp.Body.Close() // nolint: errcheck
112
-
113
-	scanner := bufio.NewScanner(resp.Body)
114
-	data := map[int16][]string{}
115
-
116
-	var defaultIdx int16 = 1
117
-	for scanner.Scan() {
118
-		text := strings.TrimSpace(scanner.Text())
119
-		switch {
120
-		case strings.HasPrefix(text, "#"):
121
-			continue
122
-		case strings.HasPrefix(text, "proxy_for"):
123
-			addr, idx, err2 := t.parseProxyFor(text)
124
-			if err2 != nil {
125
-				return nil, 0, errors.Annotate(err2, "Cannot parse 'proxy_for' section")
126
-			}
127
-			if addresses, ok := data[idx]; ok {
128
-				data[idx] = append(addresses, addr)
129
-			} else {
130
-				data[idx] = []string{addr}
131
-			}
132
-		case strings.HasPrefix(text, "default"):
133
-			idx, err2 := t.parseDefault(text)
134
-			if err2 != nil {
135
-				return nil, 0, errors.Annotate(err2, "Cannot parse 'default' section")
136
-			}
137
-			defaultIdx = idx
138
-		default:
139
-			return nil, 0, errors.Errorf("Unknown config string '%s'", text)
140
-		}
141
-	}
142
-
143
-	err = scanner.Err()
144
-	if err != nil {
145
-		return nil, 0, errors.Annotate(err, "Cannot read response from the telegram")
146
-	}
147
-
148
-	return data, defaultIdx, nil
149
-}
150
-
151
-func (t *middleTelegramCaller) parseProxyFor(text string) (string, int16, error) {
152
-	chunks := middleTelegramProxyConfigSplitter.Split(text, 3)
153
-	if len(chunks) != 3 || chunks[0] != "proxy_for" {
154
-		return "", 0, errors.Errorf("Incorrect config '%s'", text)
155
-	}
156
-
157
-	dcIdx, err := strconv.ParseInt(chunks[1], 10, 16)
158
-	if err != nil {
159
-		return "", 0, errors.Annotatef(err, "Incorrect config '%s'", text)
160
-	}
161
-
162
-	addr := strings.TrimRight(chunks[2], ";")
163
-	if _, _, err = net.SplitHostPort(addr); err != nil {
164
-		return "", 0, errors.Annotatef(err, "Incorrect config '%s'", text)
165
-	}
166
-
167
-	return addr, int16(dcIdx), nil
168
-}
169
-
170
-func (t *middleTelegramCaller) parseDefault(text string) (int16, error) {
171
-	chunks := middleTelegramProxyConfigSplitter.Split(text, 2)
172
-	if len(chunks) != 2 || chunks[0] != "default" {
173
-		return 0, errors.Errorf("Incorrect config '%s'", text)
174
-	}
175
-
176
-	dcIdxString := strings.TrimRight(chunks[1], ";")
177
-	dcIdx, err := strconv.ParseInt(dcIdxString, 10, 16)
178
-	if err != nil {
179
-		return 0, errors.Annotatef(err, "Incorrect config '%s'", text)
180
-	}
181
-
182
-	return int16(dcIdx), nil
183
-}
184
-
185
-func (t *middleTelegramCaller) call(url string) (*http.Response, error) {
186
-	req, _ := http.NewRequest("GET", url, nil) // nolint: gosec
187
-	req.Header.Set("Accept", "text/plain")
188
-	req.Header.Set("User-Agent", tgUserAgent)
189
-
190
-	return t.httpClient.Do(req)
191
-}

+ 0
- 68
telegram/telegram.go Целия файл

@@ -1,68 +0,0 @@
1
-package telegram
2
-
3
-import (
4
-	"context"
5
-	"math/rand"
6
-
7
-	"github.com/juju/errors"
8
-
9
-	"github.com/9seconds/mtg/mtproto"
10
-	"github.com/9seconds/mtg/wrappers"
11
-)
12
-
13
-// Telegram is an interface for different Telegram work modes.
14
-type Telegram interface {
15
-	Dial(context.Context, context.CancelFunc, string, *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error)
16
-	Init(*mtproto.ConnectionOpts, wrappers.StreamReadWriteCloser) (wrappers.Wrap, error)
17
-}
18
-
19
-type baseTelegram struct {
20
-	dialer tgDialer
21
-
22
-	v4DefaultIdx int16
23
-	v6DefaultIdx int16
24
-	v4Addresses  map[int16][]string
25
-	v6Addresses  map[int16][]string
26
-}
27
-
28
-func (b *baseTelegram) dial(ctx context.Context, cancel context.CancelFunc, dcIdx int16, connID string,
29
-	proto mtproto.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) {
30
-	addrs := make([]string, 2)
31
-
32
-	if proto&mtproto.ConnectionProtocolIPv6 != 0 {
33
-		if addr := b.chooseAddress(b.v6Addresses, dcIdx, b.v6DefaultIdx); addr != "" {
34
-			addrs = append(addrs, addr)
35
-		}
36
-	}
37
-	if proto&mtproto.ConnectionProtocolIPv4 != 0 {
38
-		if addr := b.chooseAddress(b.v4Addresses, dcIdx, b.v4DefaultIdx); addr != "" {
39
-			addrs = append(addrs, addr)
40
-		}
41
-	}
42
-
43
-	for _, addr := range addrs {
44
-		if conn, err := b.dialer.dialRWC(ctx, cancel, addr, connID); err == nil {
45
-			return conn, err
46
-		}
47
-	}
48
-
49
-	return nil, errors.New("Cannot connect to Telegram")
50
-}
51
-
52
-func (b *baseTelegram) chooseAddress(addresses map[int16][]string, idx, defaultIdx int16) string {
53
-	if addr, ok := addresses[idx]; ok {
54
-		return b.chooseRandomAddress(addr)
55
-	} else if addr, ok := addresses[defaultIdx]; ok {
56
-		return b.chooseRandomAddress(addr)
57
-	}
58
-
59
-	return ""
60
-}
61
-
62
-func (b *baseTelegram) chooseRandomAddress(addresses []string) string {
63
-	if len(addresses) > 0 {
64
-		return addresses[rand.Intn(len(addresses))]
65
-	}
66
-
67
-	return ""
68
-}

+ 86
- 0
tlstypes/client_hello.go Целия файл

@@ -0,0 +1,86 @@
1
+package tlstypes
2
+
3
+import (
4
+	"bytes"
5
+	"crypto/hmac"
6
+	"crypto/sha256"
7
+	"fmt"
8
+
9
+	"mtg/config"
10
+	"mtg/utils"
11
+)
12
+
13
+type ClientHello struct {
14
+	Handshake
15
+}
16
+
17
+func (c ClientHello) Digest() []byte {
18
+	dirtyDigest := c.Random
19
+	c.Random = [32]byte{}
20
+
21
+	rec := Record{
22
+		Type:    RecordTypeHandshake,
23
+		Version: Version10,
24
+		Data:    &c,
25
+	}
26
+
27
+	mac := hmac.New(sha256.New, config.C.Secret)
28
+	mac.Write(rec.Bytes()) // nolint: errcheck
29
+	computedDigest := mac.Sum(nil)
30
+
31
+	for i := range computedDigest {
32
+		computedDigest[i] ^= dirtyDigest[i]
33
+	}
34
+
35
+	return computedDigest
36
+}
37
+
38
+func ParseClientHello(raw []byte) (*ClientHello, error) {
39
+	rv := &ClientHello{}
40
+
41
+	rv.Type = HandshakeType(raw[0])
42
+	if rv.Type != HandshakeTypeClient {
43
+		return nil, fmt.Errorf("incorrect handshake type %v", rv.Type)
44
+	}
45
+
46
+	raw = raw[1:]
47
+	sizeUint24 := utils.Uint24{}
48
+	copy(sizeUint24[:], utils.ReverseBytes(raw[:3]))
49
+	size := int(utils.FromUint24(sizeUint24))
50
+
51
+	raw = raw[3:]
52
+	if len(raw) != size {
53
+		return nil, fmt.Errorf("payload size mismatch (%d != %d)", len(raw), size)
54
+	}
55
+
56
+	versionRaw := raw[:2]
57
+
58
+	switch {
59
+	case bytes.Equal(versionRaw, Version13Bytes):
60
+		rv.Version = Version13
61
+	case bytes.Equal(versionRaw, Version12Bytes):
62
+		rv.Version = Version12
63
+	case bytes.Equal(versionRaw, Version11Bytes):
64
+		rv.Version = Version11
65
+	case bytes.Equal(versionRaw, Version10Bytes):
66
+		rv.Version = Version10
67
+	default:
68
+		return nil, fmt.Errorf("unknown protocol version %v", versionRaw)
69
+	}
70
+
71
+	raw = raw[2:]
72
+	copy(rv.Random[:], raw[:32])
73
+	raw = raw[32:]
74
+
75
+	sessionIDLength := int(raw[0])
76
+	raw = raw[1:]
77
+	rv.SessionID = make([]byte, sessionIDLength)
78
+	copy(rv.SessionID, raw)
79
+	raw = raw[sessionIDLength:]
80
+
81
+	tail := make([]byte, len(raw))
82
+	copy(tail, raw)
83
+	rv.Tail = RawBytes(tail)
84
+
85
+	return rv, nil
86
+}

+ 79
- 0
tlstypes/consts.go Целия файл

@@ -0,0 +1,79 @@
1
+package tlstypes
2
+
3
+type RecordType uint8
4
+
5
+const (
6
+	RecordTypeHandshake        RecordType = 0x16
7
+	RecordTypeApplicationData  RecordType = 0x17
8
+	RecordTypeChangeCipherSpec RecordType = 0x14
9
+)
10
+
11
+type HandshakeType uint8
12
+
13
+const (
14
+	HandshakeTypeClient HandshakeType = 0x01
15
+	HandshakeTypeServer HandshakeType = 0x02
16
+)
17
+
18
+type CipherSuiteType uint8
19
+
20
+const (
21
+	CipherSuiteType_TLS_AES_128_GCM_SHA256       CipherSuiteType = iota // nolint: stylecheck, golint
22
+	CipherSuiteType_TLS_AES_256_GCM_SHA384                              // nolint: stylecheck, golint
23
+	CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256                        // nolint: stylecheck, golint
24
+)
25
+
26
+func (c CipherSuiteType) Bytes() []byte {
27
+	switch c {
28
+	case CipherSuiteType_TLS_AES_128_GCM_SHA256:
29
+		return CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes
30
+	case CipherSuiteType_TLS_AES_256_GCM_SHA384:
31
+		return CipherSuiteType_TLS_AES_256_GCM_SHA384_Bytes
32
+	}
33
+
34
+	return CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256_Bytes
35
+}
36
+
37
+type Version uint8
38
+
39
+func (v Version) Bytes() []byte {
40
+	switch v {
41
+	case Version13:
42
+		return Version13Bytes
43
+	case Version12:
44
+		return Version12Bytes
45
+	case Version11:
46
+		return Version11Bytes
47
+	}
48
+
49
+	return Version10Bytes
50
+}
51
+
52
+const (
53
+	VersionUnknown Version = iota
54
+	Version10
55
+	Version11
56
+	Version12
57
+	Version13
58
+)
59
+
60
+var (
61
+	Version10Bytes = []byte{0x03, 0x01}
62
+	Version11Bytes = []byte{0x03, 0x02}
63
+	Version12Bytes = []byte{0x03, 0x03}
64
+	Version13Bytes = []byte{0x03, 0x04}
65
+
66
+	CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes       = []byte{0x13, 0x01} // nolint: stylecheck, golint
67
+	CipherSuiteType_TLS_AES_256_GCM_SHA384_Bytes       = []byte{0x13, 0x02} // nolint: stylecheck, golint
68
+	CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256_Bytes = []byte{0x13, 0x03} // nolint; stylecheck, golint
69
+)
70
+
71
+type Byter interface {
72
+	Bytes() []byte
73
+}
74
+
75
+type RawBytes []byte
76
+
77
+func (r RawBytes) Bytes() []byte {
78
+	return []byte(r)
79
+}

+ 37
- 0
tlstypes/handshake.go Целия файл

@@ -0,0 +1,37 @@
1
+package tlstypes
2
+
3
+import (
4
+	"bytes"
5
+
6
+	"mtg/utils"
7
+)
8
+
9
+type Handshake struct {
10
+	Type      HandshakeType
11
+	Version   Version
12
+	Random    [32]byte
13
+	SessionID []byte
14
+	Tail      Byter
15
+}
16
+
17
+func (h *Handshake) Bytes() []byte {
18
+	buf := bytes.Buffer{}
19
+	packetBuf := bytes.Buffer{}
20
+
21
+	buf.WriteByte(byte(h.Type))
22
+
23
+	packetBuf.Write(h.Version.Bytes())
24
+	packetBuf.Write(h.Random[:])
25
+	packetBuf.WriteByte(byte(len(h.SessionID)))
26
+	packetBuf.Write(h.SessionID)
27
+	packetBuf.Write(h.Tail.Bytes())
28
+
29
+	sizeUint24 := utils.ToUint24(uint32(packetBuf.Len()))
30
+	sizeUint24Bytes := sizeUint24[:]
31
+	sizeUint24Bytes[0], sizeUint24Bytes[2] = sizeUint24Bytes[2], sizeUint24Bytes[0]
32
+
33
+	buf.Write(sizeUint24Bytes)
34
+	packetBuf.WriteTo(&buf) // nolint: errcheck
35
+
36
+	return buf.Bytes()
37
+}

+ 85
- 0
tlstypes/record.go Целия файл

@@ -0,0 +1,85 @@
1
+package tlstypes
2
+
3
+import (
4
+	"bytes"
5
+	"encoding/binary"
6
+	"fmt"
7
+	"io"
8
+)
9
+
10
+const recordMaxChunkSize = 16384 + 24
11
+
12
+type Record struct {
13
+	Type    RecordType
14
+	Version Version
15
+	Data    Byter
16
+}
17
+
18
+func (r Record) Bytes() []byte {
19
+	buf := bytes.Buffer{}
20
+	data := r.Data.Bytes()
21
+
22
+	buf.WriteByte(byte(r.Type))
23
+	buf.Write(r.Version.Bytes())
24
+	binary.Write(&buf, binary.BigEndian, uint16(len(data))) // nolint: errcheck
25
+	buf.Write(data)
26
+
27
+	return buf.Bytes()
28
+}
29
+
30
+func ReadRecord(reader io.Reader) (Record, error) {
31
+	buf := [2]byte{}
32
+	rec := Record{}
33
+
34
+	if _, err := io.ReadFull(reader, buf[:1]); err != nil {
35
+		return rec, fmt.Errorf("cannot read record type: %w", err)
36
+	}
37
+
38
+	rec.Type = RecordType(buf[0])
39
+
40
+	if _, err := io.ReadFull(reader, buf[:]); err != nil {
41
+		return rec, fmt.Errorf("cannot read version: %w", err)
42
+	}
43
+
44
+	switch {
45
+	case bytes.Equal(buf[:], Version13Bytes):
46
+		rec.Version = Version13
47
+	case bytes.Equal(buf[:], Version12Bytes):
48
+		rec.Version = Version12
49
+	case bytes.Equal(buf[:], Version11Bytes):
50
+		rec.Version = Version11
51
+	case bytes.Equal(buf[:], Version10Bytes):
52
+		rec.Version = Version10
53
+	}
54
+
55
+	if _, err := io.ReadFull(reader, buf[:]); err != nil {
56
+		return rec, fmt.Errorf("cannot read data length: %w", err)
57
+	}
58
+
59
+	data := make([]byte, binary.BigEndian.Uint16(buf[:]))
60
+	if _, err := io.ReadFull(reader, data); err != nil {
61
+		return rec, fmt.Errorf("cannot read data: %w", err)
62
+	}
63
+
64
+	rec.Data = RawBytes(data)
65
+
66
+	return rec, nil
67
+}
68
+
69
+func MakeRecords(raw []byte) (arr []Record) {
70
+	for len(raw) > 0 {
71
+		chunkSize := recordMaxChunkSize
72
+		if chunkSize > len(raw) {
73
+			chunkSize = len(raw)
74
+		}
75
+
76
+		arr = append(arr, Record{
77
+			Type:    RecordTypeApplicationData,
78
+			Version: Version12,
79
+			Data:    RawBytes(raw[:chunkSize]),
80
+		})
81
+		raw = raw[chunkSize:]
82
+	}
83
+
84
+	return
85
+}

+ 95
- 0
tlstypes/server_hello.go Целия файл

@@ -0,0 +1,95 @@
1
+package tlstypes
2
+
3
+import (
4
+	"bytes"
5
+	"crypto/hmac"
6
+	"crypto/rand"
7
+	"crypto/sha256"
8
+	"io"
9
+	mrand "math/rand"
10
+
11
+	"golang.org/x/crypto/curve25519"
12
+
13
+	"mtg/config"
14
+)
15
+
16
+type ServerHello struct {
17
+	Handshake
18
+
19
+	clientHello *ClientHello
20
+}
21
+
22
+func (s ServerHello) WelcomePacket() []byte {
23
+	s.Random = [32]byte{}
24
+	rec := Record{
25
+		Type:    RecordTypeHandshake,
26
+		Version: Version12,
27
+		Data:    &s,
28
+	}
29
+	buf := bytes.NewBuffer(rec.Bytes())
30
+
31
+	recChangeCipher := Record{
32
+		Type:    RecordTypeChangeCipherSpec,
33
+		Version: Version12,
34
+		Data:    RawBytes([]byte{0x01}),
35
+	}
36
+	buf.Write(recChangeCipher.Bytes())
37
+
38
+	hostCert := make([]byte, 1024+mrand.Intn(3092))
39
+	rand.Read(hostCert) // nolint: errcheck
40
+
41
+	recData := Record{
42
+		Type:    RecordTypeApplicationData,
43
+		Version: Version12,
44
+		Data:    RawBytes(hostCert),
45
+	}
46
+	buf.Write(recData.Bytes())
47
+	packet := buf.Bytes()
48
+
49
+	mac := hmac.New(sha256.New, config.C.Secret)
50
+	mac.Write(s.clientHello.Random[:]) // nolint: errcheck
51
+	mac.Write(packet)                  // nolint: errcheck
52
+	copy(packet[11:], mac.Sum(nil))
53
+
54
+	return packet
55
+}
56
+
57
+func NewServerHello(clientHello *ClientHello) *ServerHello {
58
+	rv := &ServerHello{
59
+		clientHello: clientHello,
60
+	}
61
+
62
+	rv.Type = HandshakeTypeServer
63
+	rv.Version = Version12
64
+	rv.SessionID = make([]byte, len(clientHello.SessionID))
65
+	copy(rv.SessionID, clientHello.SessionID)
66
+
67
+	tail := bytes.NewBuffer(CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes)
68
+	tail.WriteByte(0x00) // no compression
69
+	makeTLSExtensions(tail)
70
+	rv.Tail = RawBytes(tail.Bytes())
71
+
72
+	return rv
73
+}
74
+
75
+func makeTLSExtensions(buf io.Writer) {
76
+	buf.Write([]byte{ // nolint: errcheck
77
+		0x00, 0x2e, // 46 bytes of data
78
+		0x00, 0x33, // Extension - Key Share
79
+		0x00, 0x24, // 36 bytes
80
+		0x00, 0x1d, // x25519 curve
81
+		0x00, 0x20, // 32 bytes of key
82
+	})
83
+
84
+	var scalar [32]byte
85
+
86
+	rand.Read(scalar[:]) // nolint: errcheck
87
+	curve, _ := curve25519.X25519(scalar[:], curve25519.Basepoint)
88
+	buf.Write(curve) // nolint: errcheck
89
+
90
+	buf.Write([]byte{ // nolint: errcheck
91
+		0x00, 0x2b, // Extension - Supported Versions
92
+		0x00, 0x02, // 2 bytes are following
93
+		0x03, 0x04, // TLS 1.3
94
+	})
95
+}

+ 26
- 0
utils/init_tcp.go Целия файл

@@ -0,0 +1,26 @@
1
+package utils
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+
7
+	"mtg/config"
8
+)
9
+
10
+func InitTCP(conn net.Conn) error {
11
+	tcpConn := conn.(*net.TCPConn)
12
+
13
+	if err := tcpConn.SetNoDelay(true); err != nil {
14
+		return fmt.Errorf("cannot set TCP_NO_DELAY: %w", err)
15
+	}
16
+
17
+	if err := tcpConn.SetReadBuffer(config.C.ReadBuffer); err != nil {
18
+		return fmt.Errorf("cannot set read buffer size: %w", err)
19
+	}
20
+
21
+	if err := tcpConn.SetWriteBuffer(config.C.WriteBuffer); err != nil {
22
+		return fmt.Errorf("cannot set write buffer size: %w", err)
23
+	}
24
+
25
+	return nil
26
+}

+ 0
- 21
utils/read_current_data.go Целия файл

@@ -1,21 +0,0 @@
1
-package utils
2
-
3
-import "io"
4
-
5
-const readCurrentDataBufferSize = 1024 + 1 // + 1 because telegram operates with blocks mod 4
6
-
7
-// ReadCurrentData reads all data from io.Reader which is ready to be read.
8
-func ReadCurrentData(src io.Reader) (rv []byte, err error) {
9
-	buf := make([]byte, readCurrentDataBufferSize)
10
-	n := readCurrentDataBufferSize
11
-
12
-	for n == len(buf) {
13
-		n, err = src.Read(buf)
14
-		if err != nil {
15
-			return nil, err
16
-		}
17
-		rv = append(rv, buf[:n]...)
18
-	}
19
-
20
-	return rv, nil
21
-}

+ 21
- 0
utils/read_full.go Целия файл

@@ -0,0 +1,21 @@
1
+package utils
2
+
3
+import "io"
4
+
5
+const readFullBufferSize = 1024 + 1 // +1 because telegram opreates with blocks mod 4
6
+
7
+func ReadFull(src io.Reader) (rv []byte, err error) {
8
+	buf := make([]byte, readFullBufferSize)
9
+	n := readFullBufferSize
10
+
11
+	for n == len(buf) {
12
+		n, err = src.Read(buf)
13
+		if err != nil {
14
+			return nil, err
15
+		}
16
+
17
+		rv = append(rv, buf[:n]...)
18
+	}
19
+
20
+	return rv, nil
21
+}

+ 1
- 1
utils/reverse_bytes.go Целия файл

@@ -4,8 +4,8 @@ package utils
4 4
 func ReverseBytes(data []byte) []byte {
5 5
 	dataLen := len(data)
6 6
 	rv := make([]byte, dataLen)
7
-
8 7
 	rv[dataLen/2] = data[dataLen/2]
8
+
9 9
 	for i := dataLen/2 - 1; i >= 0; i-- {
10 10
 		opp := dataLen - i - 1
11 11
 		rv[i], rv[opp] = data[opp], data[i]

+ 24
- 0
utils/rlimit.go Целия файл

@@ -0,0 +1,24 @@
1
+// +build !windows
2
+
3
+package utils
4
+
5
+import (
6
+	"fmt"
7
+
8
+	"golang.org/x/sys/unix"
9
+)
10
+
11
+func SetLimits() error {
12
+	rLimit := unix.Rlimit{}
13
+	if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil {
14
+		return fmt.Errorf("cannot get rlimit: %w", err)
15
+	}
16
+
17
+	rLimit.Cur = rLimit.Max
18
+
19
+	if err := unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil {
20
+		return fmt.Errorf("cannot set rlimit: %w", err)
21
+	}
22
+
23
+	return nil
24
+}

+ 7
- 0
utils/rlimit_windows.go Целия файл

@@ -0,0 +1,7 @@
1
+// +build windows
2
+
3
+package utils
4
+
5
+func SetLimits() error {
6
+	return nil
7
+}

+ 25
- 0
utils/signal_context.go Целия файл

@@ -0,0 +1,25 @@
1
+// +build !windows
2
+
3
+package utils
4
+
5
+import (
6
+	"context"
7
+	"os"
8
+	"os/signal"
9
+	"syscall"
10
+)
11
+
12
+func GetSignalContext() context.Context {
13
+	ctx, cancel := context.WithCancel(context.Background())
14
+	sigChan := make(chan os.Signal, 1)
15
+
16
+	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
17
+
18
+	go func() {
19
+		for range sigChan {
20
+			cancel()
21
+		}
22
+	}()
23
+
24
+	return ctx
25
+}

+ 23
- 0
utils/signal_context_windows.go Целия файл

@@ -0,0 +1,23 @@
1
+// +build windows
2
+
3
+package utils
4
+
5
+import (
6
+	"context"
7
+	"os"
8
+	"os/signal"
9
+)
10
+
11
+func GetSignalContext() context.Context {
12
+	ctx, cancel := context.WithCancel(context.Background())
13
+	sigChan := make(chan os.Signal, 1)
14
+
15
+	signal.Notify(sigChan, os.Interrupt)
16
+	go func() {
17
+		for range sigChan {
18
+			cancel()
19
+		}
20
+	}()
21
+
22
+	return ctx
23
+}

+ 11
- 0
utils/stream_cipher.go Целия файл

@@ -0,0 +1,11 @@
1
+package utils
2
+
3
+import (
4
+	"crypto/aes"
5
+	"crypto/cipher"
6
+)
7
+
8
+func MakeStreamCipher(key, iv []byte) cipher.Stream {
9
+	block, _ := aes.NewCipher(key) // nolint: gosec
10
+	return cipher.NewCTR(block, iv)
11
+}

+ 0
- 4
utils/uint24.go Целия файл

@@ -1,15 +1,11 @@
1 1
 package utils
2 2
 
3
-// Uint24 is a replacement for the absent Go uint24 data type.
4
-// This data type is little endian.
5 3
 type Uint24 [3]byte
6 4
 
7
-// ToUint24 converts number to Uint24.
8 5
 func ToUint24(number uint32) Uint24 {
9 6
 	return Uint24{byte(number), byte(number >> 8), byte(number >> 16)}
10 7
 }
11 8
 
12
-// FromUint24 converts Uint24 to number.
13 9
 func FromUint24(number Uint24) uint32 {
14 10
 	return uint32(number[0]) + (uint32(number[1]) << 8) + (uint32(number[2]) << 16)
15 11
 }

+ 0
- 99
wrappers/blockcipher.go Целия файл

@@ -1,99 +0,0 @@
1
-package wrappers
2
-
3
-import (
4
-	"bytes"
5
-	"crypto/aes"
6
-	"crypto/cipher"
7
-	"net"
8
-
9
-	"go.uber.org/zap"
10
-
11
-	"github.com/9seconds/mtg/utils"
12
-	"github.com/juju/errors"
13
-)
14
-
15
-// BlockCipher is a stream writer which encrypts/decrypts blocks of data
16
-// with AES CBC. This also is buffered reader. It means, that block
17
-// reading is transparent for it, you can assume you are working with
18
-// good old io.Reader.
19
-type BlockCipher struct {
20
-	buf *bytes.Buffer
21
-
22
-	logger    *zap.SugaredLogger
23
-	conn      StreamReadWriteCloser
24
-	encryptor cipher.BlockMode
25
-	decryptor cipher.BlockMode
26
-}
27
-
28
-func (b *BlockCipher) Read(p []byte) (int, error) {
29
-	if b.buf.Len() > 0 {
30
-		return b.flush(p)
31
-	}
32
-
33
-	buf := []byte{}
34
-	for len(buf) == 0 || len(buf)%aes.BlockSize != 0 {
35
-		rv, err := utils.ReadCurrentData(b.conn)
36
-		if err != nil {
37
-			return 0, errors.Annotate(err, "Cannot read from socket")
38
-		}
39
-		buf = append(buf, rv...)
40
-	}
41
-
42
-	b.decryptor.CryptBlocks(buf, buf)
43
-	b.buf.Write(buf) // nolint: gosec
44
-
45
-	return b.flush(p)
46
-}
47
-
48
-func (b *BlockCipher) flush(p []byte) (int, error) {
49
-	if b.buf.Len() <= len(p) {
50
-		sizeToReturn := b.buf.Len()
51
-		copy(p, b.buf.Bytes())
52
-		b.buf.Reset()
53
-		return sizeToReturn, nil
54
-	}
55
-
56
-	return b.buf.Read(p)
57
-}
58
-
59
-func (b *BlockCipher) Write(p []byte) (int, error) {
60
-	if len(p)%aes.BlockSize > 0 {
61
-		return 0, errors.Errorf("Incorrect block size %d", len(p))
62
-	}
63
-
64
-	encrypted := make([]byte, len(p))
65
-	b.encryptor.CryptBlocks(encrypted, p)
66
-
67
-	return b.conn.Write(encrypted)
68
-}
69
-
70
-// Logger returns an instance of the logger for this wrapper.
71
-func (b *BlockCipher) Logger() *zap.SugaredLogger {
72
-	return b.logger
73
-}
74
-
75
-// LocalAddr returns local address of the underlying net.Conn.
76
-func (b *BlockCipher) LocalAddr() *net.TCPAddr {
77
-	return b.conn.LocalAddr()
78
-}
79
-
80
-// RemoteAddr returns remote address of the underlying net.Conn.
81
-func (b *BlockCipher) RemoteAddr() *net.TCPAddr {
82
-	return b.conn.RemoteAddr()
83
-}
84
-
85
-// Close closes underlying net.Conn.
86
-func (b *BlockCipher) Close() error {
87
-	return b.conn.Close()
88
-}
89
-
90
-// NewBlockCipher creates new instance of BlockCipher based on given data.
91
-func NewBlockCipher(conn StreamReadWriteCloser, encryptor, decryptor cipher.BlockMode) StreamReadWriteCloser {
92
-	return &BlockCipher{
93
-		buf:       &bytes.Buffer{},
94
-		conn:      conn,
95
-		logger:    conn.Logger().Named("block-cipher"),
96
-		encryptor: encryptor,
97
-		decryptor: decryptor,
98
-	}
99
-}

+ 0
- 0
wrappers/conn.go Целия файл


Някои файлове не бяха показани, защото твърде много файлове са промени

Loading…
Отказ
Запис