REPOSITORY / ScuroNeko/mtg

Compare commits

DIFF REPOSITORY

Compare commits

...
293 Commits
Author SHA1 Message Date
9seconds 28fe63dbdb Merge remote-tracking branch 'origin/master' into stable 2021-03-03 17:55:00 +03:00
Sergey ArkhipovandGitHub 7718f62477 Merge pull request #179 from 9seconds/golang1.16
Update dockerfile to golang 1.16
2021-03-03 17:54:13 +03:00
9seconds df0994dad3 Update dockerfile to golang 1.16 2021-03-03 17:43:38 +03:00
Sergey ArkhipovandGitHub dca43853fb Merge pull request #178 from 9seconds/ci-arm32
Build for arm 32 platforms
2021-03-03 17:42:59 +03:00
9seconds ea8e31346c Build for arm 32 platforms 2021-03-03 17:30:47 +03:00
9seconds 4aab3b686c Merge remote-tracking branch 'origin/master' into stable 2021-03-03 17:25:34 +03:00
Sergey ArkhipovandGitHub ed495b3800 Merge pull request #177 from 9seconds/faketls-hotfix
Correct rewinding for faketls
2021-03-03 17:24:38 +03:00
9seconds 4025f223c4 Correct rewinding for faketls 2021-03-03 17:18:16 +03:00
9seconds 121200cfad Merge remote-tracking branch 'origin/master' into stable 2021-02-26 08:27:25 +03:00
9seconds 90e0ce4c48 Update dependencies 2021-02-26 08:22:01 +03:00
Sergey ArkhipovandGitHub ac0b442b43 Merge pull request #176 from 9seconds/fix-urls
Fix urls for missed IPs
2021-03-03 15:40:15 +03:00
9seconds af7021fac8 Fix urls for missed IPs 2021-02-26 08:11:34 +03:00
9seconds 5468625212 Use CI on supported branches 2021-02-26 08:04:22 +03:00
9seconds 345c1bf715 Update ignores 2021-02-26 04:41:08 +03:00
Sergey ArkhipovandGitHub 52d47a9786 Create codeql-analysis.yml 2021-03-02 22:01:47 +03:00
Sergey ArkhipovandGitHub 2ba79a8508 Create SECURITY.md 2021-03-02 22:00:18 +03:00
Sergey ArkhipovandGitHub ce7769472d Merge pull request #175 from 9seconds/ci
Integrate with Github actions
2021-03-02 21:43:20 +03:00
Sergey ArkhipovandGitHub eb5eea2625 Merge pull request #174 from 9seconds/linter
Update golangci-linter
2021-03-02 16:18:05 +03:00
Sergey ArkhipovandGitHub d965dc3c07 Merge pull request #173 from 9seconds/simplify-makefile
Simplify Makefile
2021-03-02 15:52:29 +03:00
9seconds 040b3c371c Add github workflow 2021-02-26 04:12:00 +03:00
9seconds c7ba8e54be Remove travis 2021-02-26 03:58:48 +03:00
9seconds 33b1a5426c Update golangci-linter 2021-02-26 00:52:09 +03:00
9seconds 91197c0334 Simplify Makefile
This commit removes GO111MODULE and simplifies internal structure
2021-02-26 00:25:16 +03:00
Sergey ArkhipovandGitHub e075169dd4 Merge pull request #166 from biozz/fix-statsd-docs
Fix docs related to statsd usage
2021-01-06 08:08:02 +03:00
Ivan Elfimov 225444b1b3 Update README.md
- remove MTG_STATSD_PORT, because it is not used in the code
- update MTG_STATSD_ADDR description to reflect all possible use cases
2021-01-02 21:21:23 +03:00
Sergey ArkhipovandGitHub 586ac9dcb9 Merge pull request #160 from a68366/master
Fix t.me QR code. Closes #159
2020-10-19 10:56:39 +03:00
a68366andGitHub c3be5311e8 Fix t.me QR code. Closes #159 2020-10-08 19:32:36 +03:00
9seconds a8c4d0a9c4 Merge remote-tracking branch 'origin/master' into stable 2020-09-21 12:03:19 +03:00
9seconds bfeded67ee Update travis 2020-09-21 11:46:13 +03:00
9seconds dde0d25b77 Update golangci lint to 1.31.0 2020-09-21 11:45:09 +03:00
9seconds 296a34cd6e Update dockerfile to use 1.15 2020-09-21 11:44:06 +03:00
9seconds 44cea68e83 Upgrade dependencies 2020-09-21 11:43:23 +03:00
9seconds 790bf21166 Merge remote-tracking branch 'origin/master' into stable 2020-08-11 13:32:01 +03:00
Sergey ArkhipovandGitHub 5d17e264e6 Merge pull request #156 from 9seconds/no-pools
Get rid of sync pools
2020-08-10 15:20:15 +03:00
9seconds 7ef1e99f66 Add 1.14 to a travis 2020-08-10 15:02:05 +03:00
9seconds 3acbacbdb3 Install lint in a local directory 2020-08-10 15:01:38 +03:00
9seconds 5fd7062972 Update dependencies 2020-08-10 14:41:22 +03:00
9seconds 7a6695f6c4 Update golangci-lint 2020-08-10 14:39:50 +03:00
9seconds 8ac786581b Update golangci lint 2020-08-10 13:30:07 +03:00
9seconds 86a8f69273 Cleanup pools from wrappes 2020-08-10 13:29:08 +03:00
9seconds 95b16cd805 Cleanup pools from tlstypes 2020-08-10 13:27:23 +03:00
9seconds 3996b9a6b8 Remove pools from faketls 2020-08-10 13:21:30 +03:00
Sergey ArkhipovandGitHub e286dea650 Merge pull request #151 from FollowMeDown/master
Fix Keep-Alive Issue for OpenBSD/ArchLinux
2020-08-10 12:49:16 +03:00
DEFACE 5e27e48129 Fix Keep-Alive Issue for OpenBSD/ArchLinux 2020-06-15 21:38:35 +02:00
Sergey ArkhipovandGitHub 95abb54dc8 Merge pull request #146 from 9seconds/fix-docker-image
Updates for dockerfile
2020-05-19 11:31:25 +03:00
9seconds dbd3d3c266 Updates for dockerfile 2020-05-19 11:26:08 +03:00
9seconds e5b9c841cd Merge remote-tracking branch 'origin/master' into stable 2020-03-31 10:09:47 +03:00
9seconds ad7acee64d Update dependencies 2020-03-31 09:51:38 +03:00
Sergey ArkhipovandGitHub 9ce6674070 Merge pull request #144 from 9seconds/tune-buffers
Add possibility to scale middleproxy buffers
2020-03-31 09:51:40 +03:00
9seconds 25edb60f2c Use different formula for calculating proxy buffer 2020-03-31 09:45:59 +03:00
9seconds b5a8d8b804 Add possibility to scale middleproxy buffers 2020-03-30 16:53:14 +03:00
9seconds 8ccf61c23e Merge remote-tracking branch 'origin/master' into stable 2020-03-25 10:24:31 +03:00
9seconds 35f0fca3c5 Update dependencies 2020-03-25 10:24:17 +03:00
Sergey ArkhipovandGitHub cd46a734ea Merge pull request #143 from 9seconds/32kb
Tune buffer limits to 32kb by default
2020-03-25 10:15:47 +03:00
Sergey ArkhipovandGitHub cd92a343aa Merge pull request #142 from 9seconds/keep-alives
Set keep-alive for tcp connections
2020-03-25 10:07:26 +03:00
9seconds 289e8f5945 Tune buffer limits to 32kb by default 2020-03-25 10:07:10 +03:00
9seconds 850bed0dfc Set keep-alive for tcp connections 2020-03-25 10:01:17 +03:00
Sergey ArkhipovandGitHub 8977d77a13 Merge pull request #141 from 9seconds/change-defaults
Decrease defaults
2020-03-24 17:05:25 +03:00
9seconds 91726d461f Decrease defaults 2020-03-24 16:58:30 +03:00
Sergey ArkhipovandGitHub be1a47287a Merge pull request #140 from 9seconds/pools
Add pool support everywhere
2020-03-24 16:53:48 +03:00
9seconds 426483d27b Microoptimization 2020-03-24 16:46:39 +03:00
9seconds bc72717582 Fix a bug with crashing desktop telegram 2020-03-24 16:36:03 +03:00
9seconds 391d901402 Reset bytes buffer before returning back to a pool 2020-03-24 15:54:29 +03:00
9seconds 837d96dc43 Add pool support everywhere 2020-03-24 11:22:59 +03:00
9seconds 9125a29e79 Add pools for packetack 2020-03-23 17:08:13 +03:00
9seconds 6449109b2b Add pooler for mtproto_frame wrapper 2020-03-23 17:00:54 +03:00
9seconds 829669eefd Update dependencies 2020-03-23 15:10:41 +03:00
Sergey ArkhipovandGitHub 2d9f2345dc Merge pull request #139 from 9seconds/memory-direct
Memory optimizations for direct mode
2020-03-23 15:08:37 +03:00
9seconds 6a9069ca72 Fixes 2020-03-23 10:53:58 +03:00
9seconds d72ed03a43 Use pools for obfuscated2 write 2020-03-23 10:29:00 +03:00
9seconds f0d2cd494a Use stack allocation instead of heap one 2020-03-23 09:05:18 +03:00
9seconds b5810c955d Decrease a size of direct pipe buffer 2020-03-23 08:42:02 +03:00
9seconds 4ebae693e0 Memory optimizations for direct mode 2020-03-20 15:54:30 +03:00
9seconds 0e033b59b3 Merge remote-tracking branch 'origin/master' into stable 2020-03-20 14:39:45 +03:00
Sergey ArkhipovandGitHub 1a619e3090 Merge pull request #138 from 9seconds/ntp-servers
Add possibility to define your own ntp servers
2020-03-20 14:37:38 +03:00
9seconds 8e29ff78ba Add possibility to define your own ntp servers 2020-03-20 14:32:23 +03:00
Sergey ArkhipovandGitHub efdac2c0d8 Merge pull request #136 from 9seconds/prefer-options
Option for IP preference
2020-03-20 13:23:16 +03:00
Sergey ArkhipovandGitHub e559388802 Merge pull request #137 from 9seconds/golang-1.14
Use golang 1.14
2020-03-20 13:23:06 +03:00
9seconds cb691453f0 Allow golang 1.14 to fail 2020-03-20 13:17:35 +03:00
9seconds 020065f389 Use golang 1.14 2020-03-20 13:15:08 +03:00
9seconds cf5197cee0 Update golangci lint 2020-03-20 13:10:56 +03:00
9seconds 720158f15c Implement IP preferences 2020-03-20 13:09:30 +03:00
9seconds 07f00a363e Add new option for preferring of new IP 2020-03-20 12:51:56 +03:00
9seconds d32ab34e60 Update dependencies 2020-03-20 12:38:33 +03:00
9seconds cdd93bd25f Merge remote-tracking branch 'origin/master' into stable 2020-02-04 11:23:40 +03:00
9seconds 5a52418394 Update dependencies 2020-02-04 11:18:25 +03:00
Sergey ArkhipovandGitHub cf43ae780b Merge pull request #129 from 9seconds/disable-antireplay
Possibility to disable antireplay cache
2020-02-03 11:32:30 +03:00
9seconds 2407748ab0 Update README 2020-02-03 11:27:12 +03:00
9seconds a47edf08d0 Add possibility to disable antireplay cache 2020-02-03 11:26:37 +03:00
9seconds 566955b8b7 Update dependencies 2019-12-10 14:24:03 +03:00
Sergey ArkhipovandGitHub 5cc07a54a2 Merge pull request #127 from 9seconds/close-connections
Implement connection TTLs for multiplexing
2019-12-10 14:21:34 +03:00
Sergey ArkhipovandGitHub ba2143a6ae Merge pull request #126 from 9seconds/ipv4
Prefer ipv6 protocol on direct connections
2019-12-10 14:21:18 +03:00
9seconds acb02baa2e Implement connection TTLs for multiplexing 2019-12-10 14:13:26 +03:00
9seconds 422ed5b071 Prefer ipv6 protocol on direct connections 2019-12-10 13:59:27 +03:00
Sergey ArkhipovandGitHub 19efcc93fc Merge pull request #123 from 9seconds/panic-on-err
Crash if we can't allocate incoming connection
2019-12-04 10:37:20 +03:00
9seconds b4c47ff4e1 Crash if we can't allocate incoming connection 2019-12-04 10:22:35 +03:00
Sergey ArkhipovandGitHub 8a783d6b4e Merge pull request #122 from 9seconds/close-connections
Make cloaking more robust
2019-12-04 10:17:39 +03:00
9seconds c5029c706c Cap max time in cloak mode 2019-12-03 13:56:37 +03:00
9seconds d7e565e0f5 Update dependencies 2019-12-03 11:31:28 +03:00
9seconds 93686377d5 Rewrite cloaking 2019-12-03 11:22:01 +03:00
9seconds 87de28636a Add new metrics 2019-12-03 10:02:56 +03:00
Sergey ArkhipovandGitHub 7788b169b6 Merge pull request #121 from 9seconds/fix-run-sh
Make run.sh more robust
2019-11-25 10:37:46 +03:00
9seconds 6b9601fc2c Use stable version of mtg for run.sh 2019-11-25 10:31:40 +03:00
9seconds a65cb05d20 Make run.sh more robust 2019-11-25 10:31:19 +03:00
9seconds 31866d5f64 Update dependencies 2019-11-25 10:05:29 +03:00
Sergey ArkhipovandGitHub 0db83a0cc8 Merge pull request #117 from rlex/readme_fix
Fix units in readme
2019-11-20 10:13:17 +03:00
Lex Rivera 40bb59faf5 Fix units in readme 2019-11-19 18:38:30 +03:00
9seconds cac476901d Merge remote-tracking branch 'origin/master' into stable 2019-11-19 18:13:43 +03:00
9seconds 307910fe4a More reasonable version management 2019-11-19 18:13:35 +03:00
9seconds bb21bf814f Merge remote-tracking branch 'origin/master' into stable 2019-11-19 17:58:10 +03:00
9seconds 4c2c468289 Update dependencies 2019-11-19 15:28:53 +03:00
Sergey ArkhipovandGitHub f2f7c2a4de Merge pull request #115 from 9seconds/gogettable
Make mtg go-gettable
2019-11-19 11:55:33 +03:00
Sergey ArkhipovandGitHub 8fdd4292b1 Merge pull request #116 from 9seconds/gcoldconnections
Forcefully cleanup old connections
2019-11-19 11:55:07 +03:00
9seconds 44d339d777 Forcefully cleanup old connections 2019-11-19 11:49:27 +03:00
9seconds c6223e256d Fix prometheus namespace 2019-11-19 11:39:09 +03:00
9seconds 7928c2b323 Rename app from MTG to mtg 2019-11-19 11:37:30 +03:00
9seconds b2e7e1a225 Use buildinfo to get a version 2019-11-19 11:36:14 +03:00
9seconds 843fc4b70f Rename module from mtg to github.com/9seconds/mtg 2019-11-19 11:17:14 +03:00
9seconds 68e1d66017 Merge remote-tracking branch 'origin/master' into stable 2019-11-18 12:56:21 +03:00
9seconds ed9689d813 Make proxy secret block more visible 2019-11-18 11:30:57 +03:00
9seconds 59ddffa911 Print port for run.sh 2019-11-18 10:56:27 +03:00
9seconds 3af9ede1c1 Update dependencies 2019-11-18 10:54:53 +03:00
9seconds 47364e7ba8 Add correct paths to the run script 2019-11-18 10:51:30 +03:00
9seconds b6251608d9 Add script for fast deployment 2019-11-18 10:49:03 +03:00
9seconds 6dce3944e4 More correct information on metrics 2019-11-18 10:04:51 +03:00
Sergey ArkhipovandGitHub 112d3a916c Merge pull request #110 from pexcn/patch-1
fix typo
2019-11-12 16:14:35 +03:00
9seconds 5d3824704c Remove certificate server 2019-11-12 16:13:05 +03:00
pexcnandGitHub 0ff5ca6808 fix typo
according to README.md, read buffer should set to 128KB.
2019-11-12 20:30:01 +08:00
Sergey ArkhipovandGitHub 8e0436e89a Merge pull request #109 from 9seconds/logger-names
Add more granular names to connection loggers
2019-11-12 14:29:26 +03:00
9seconds 646294b6ba Add more granular names to conn loggers 2019-11-12 14:24:27 +03:00
Sergey ArkhipovandGitHub 196ef3d449 Merge pull request #108 from 9seconds/fix-buffers
Fix minor issues with cli
2019-11-12 12:28:36 +03:00
Sergey ArkhipovandGitHub 4f8755879d Merge pull request #107 from 9seconds/statsd
Correct metric types for statsd
2019-11-12 12:21:33 +03:00
9seconds 3451832d13 Fix minor issues with cli 2019-11-12 12:20:25 +03:00
9seconds dfa53af567 Fix for statsd incorrect types 2019-11-12 12:13:00 +03:00
9seconds 27c5646680 Make proxyconn channel response async 2019-11-12 10:36:32 +03:00
Sergey ArkhipovandGitHub 596fafe30d Merge pull request #103 from K900/patch-1
Fix statsd address
2019-11-11 21:19:42 +03:00
K900andGitHub e465817011 Fix statsd address
It used the wrong variable.
2019-11-11 21:14:35 +03:00
Sergey ArkhipovandGitHub ef60877cb0 Merge pull request #102 from 9seconds/next
v1.0
2019-11-11 17:55:06 +03:00
9seconds 8b802819ee We become fat 2019-11-11 17:48:04 +03:00
9seconds 3c75fcca80 Remove run-mtg script 2019-11-11 17:42:49 +03:00
9seconds 1258f07197 Update README 2019-11-11 17:38:26 +03:00
9seconds 24137b6ea2 Some notes on upgrading to 1.0 2019-11-11 17:26:07 +03:00
9seconds 7d1d9007af Update environment variable table 2019-11-11 17:12:54 +03:00
9seconds ae479adeaa Use fastcache instead of ristretto 2019-11-11 16:05:19 +03:00
9seconds 6732e80d06 Use recommended way of generating x25519 curve 2019-11-11 15:22:28 +03:00
9seconds 2bf291bab5 Fix minor lint issue 2019-11-11 15:16:24 +03:00
9seconds 273aa18367 Rename full path in order to favor modules 2019-11-11 15:09:29 +03:00
9seconds b491e03504 Update dependencies 2019-11-11 15:07:46 +03:00
9seconds a7a4da8ee5 Make connection multiplexing configurable 2019-11-11 15:03:59 +03:00
9seconds 34ad883559 Use ristretto instead of bigcache 2019-11-11 14:59:09 +03:00
9seconds 22905b2a25 Correct multiplexing 2019-11-11 14:04:15 +03:00
9seconds 0ff9a58780 Update dockerfile 2019-11-07 14:01:33 +03:00
9seconds ed180ecb3d Remove unnecessary hexify function 2019-11-07 14:01:04 +03:00
9seconds f005592872 Update dependencies 2019-11-07 13:59:20 +03:00
9seconds d4cd779c42 Fix lint issues 2019-11-07 13:58:19 +03:00
9seconds 9e3683ce08 Update golangci-lint 2019-11-07 13:56:19 +03:00
9seconds d8216f776d First version of website cloaking 2019-11-07 13:55:41 +03:00
9seconds 038b2b200d Success path for fake tls is implemented 2019-11-07 13:04:38 +03:00
9seconds fd8506c82a Propagate hostname from the secret 2019-11-05 11:13:18 +03:00
9seconds 559a1a666b Generate tls secrets 2019-11-05 11:01:29 +03:00
9seconds 4c5f1a5636 Propagate cloak_port setting 2019-11-05 10:44:23 +03:00
9seconds fbbf9b1634 Get rid of magic constants 2019-11-04 21:36:59 +03:00
9seconds 1e428e13fc Add base for faketls 2019-11-04 16:07:20 +03:00
9seconds f531d90fc4 Use golang 1.13 in travis 2019-10-11 10:16:17 +03:00
9seconds 5f168d1ee4 Update dependencies 2019-10-11 09:42:04 +03:00
9seconds 5009859a1e linting the code 2019-10-11 09:41:18 +03:00
9seconds 432e2970a0 Merge remote-tracking branch 'origin/master' into next 2019-10-10 21:29:34 +03:00
9seconds 6e154b3f44 Fix for urls 2019-10-10 21:11:30 +03:00
9seconds 56cf90b13d Correct closing of connections 2019-10-10 21:07:55 +03:00
9seconds d10729490d Remove obsolete files 2019-10-10 17:49:16 +03:00
9seconds d459efcf9c bug fixes 2019-10-10 17:48:30 +03:00
9seconds 2eba78b0db Add unregistering on telegram conn close 2019-10-10 16:42:29 +03:00
9seconds 61450ea3db Small logger fix 2019-10-10 16:11:22 +03:00
9seconds c62afbdfd8 Rename prometheus namespace to stats-namespace 2019-10-10 16:06:50 +03:00
9seconds 828627b0d8 Wire things altogether 2019-10-10 14:29:33 +03:00
9seconds c68f2cd50f Add client wrappers 2019-10-10 09:32:50 +03:00
9seconds e81c5970d4 Add client abridged protocol 2019-10-10 07:22:08 +03:00
9seconds 1a7eee444e Use ReadFull in blockcipher 2019-10-09 11:08:34 +03:00
9seconds d9bd07b027 disable gosec in hub connection 2019-10-08 14:09:48 +03:00
9seconds 3ae69183aa Some refactorings for telegram package 2019-10-08 14:08:34 +03:00
9seconds 6c7edfb7db Add new stats metric 2019-10-08 11:55:57 +03:00
9seconds d44474012a Rework antireplay 2019-10-07 17:51:21 +03:00
9seconds 413cafeeb6 reworked hub 2019-10-07 17:26:03 +03:00
9seconds c9743b5675 wip 2019-10-07 12:13:07 +03:00
9seconds 072bce2922 Small corrections for middle proxy connections 2019-09-13 10:49:22 +03:00
9seconds 386522ba22 Correct printable configuration 2019-09-13 10:15:00 +03:00
9seconds 029245cd16 Correct initialization of antireplay cache 2019-09-13 10:14:48 +03:00
9seconds b280f47731 Add mtproto handshake 2019-09-10 16:44:00 +03:00
9seconds 1617866c24 Add required things for middlehandshake 2019-09-10 13:54:14 +03:00
9seconds 691954bd16 Simplify internals 2019-09-09 17:35:53 +03:00
9seconds 9534cb19d0 Move mtproto consts to rpc 2019-09-09 17:11:40 +03:00
9seconds 7827d1255a Add background update for middle proxy 2019-09-09 17:07:59 +03:00
9seconds d431feb0ba Add base wrappers for mtproto 2019-09-09 12:41:53 +03:00
9seconds 3816dbf5b1 Add telegram api 2019-09-09 10:25:38 +03:00
9seconds 2918ed11e5 Remove juju/errors 2019-09-06 16:10:55 +03:00
9seconds 701f62ed4c More reasonable shutdowns 2019-09-04 10:54:38 +03:00
9seconds 299a34252e Move rlimit to utils 2019-09-04 10:30:21 +03:00
9seconds 2492a47d0a Direct proxy works 2019-09-04 10:19:01 +03:00
Sergey ArkhipovandGitHub c2b8e88961 Merge pull request #98 from strangecamelcaselogin/master
Bind 3129 port to 127.0.0.1 in docker examples
2019-08-31 13:35:50 +03:00
strangecamelcaseloginandGitHub c9d3020ea6 Bind 3129 port to 127.0.0.1 in docker examples 2019-08-30 00:08:53 +03:00
Sergey ArkhipovandGitHub 018e5b96f4 Merge pull request #96 from L11R/master
Windows compilation fix; deps update
2019-08-29 22:31:04 +03:00
9seconds 07985cf418 Reworked base 2019-08-29 13:16:08 +03:00
Savely KrasovskyandGitHub 389f6fd4bf Merge pull request #1 from 9seconds/master
Modified antireplay
2019-08-29 10:33:56 +03:00
9seconds 09c7ce45d2 Add new config 2019-08-26 18:23:36 +03:00
9seconds 199eed0f18 Update dependencies 2019-08-20 20:58:44 +03:00
9seconds 82ec3871a4 Modified antireplay 2019-08-20 20:51:09 +03:00
L11R 2748fbd1e5 Typo fix 2019-08-18 20:15:40 +03:00
L11R f14f104bea Windows compilation fix; deps update 2019-08-18 04:05:26 +03:00
9seconds b1074193a8 Update dependencies 2019-06-11 13:16:01 +03:00
Sergey ArkhipovandGitHub 78dea9ae3f Merge pull request #76 from 9seconds/replay
Base prevention of replay attacks on proxy
2019-04-26 16:34:02 +03:00
9seconds da8dba1585 Add some notes to README 2019-04-26 16:24:39 +03:00
9seconds 33852ca481 Base prevention of replay attacks on proxy 2019-04-26 15:53:10 +03:00
9seconds eff53694a0 Update dependencies 2019-04-26 14:56:58 +03:00
9seconds 74a7c505eb Add notes on ansible 2019-03-01 10:30:08 +03:00
9seconds 9983f7fcba Add a note on secure mode 2019-03-01 10:27:33 +03:00
9seconds a90072260e Update docker image to use Go 1.12 2019-03-01 10:24:58 +03:00
9seconds b153240bc3 Update dependencies 2019-03-01 10:20:53 +03:00
9seconds 6dfbd26524 Update golangci-lint version 2019-03-01 10:18:23 +03:00
Sergey ArkhipovandGitHub 39e7663e0c Merge pull request #70 from im-kulikov/fix/deprecation-warning
DualStack is deprecated
2019-03-01 10:12:11 +03:00
Sergey ArkhipovandGitHub 71f7bf6cad Merge pull request #71 from im-kulikov/feature/add-go1.12-to-travis-ci
Add Golang 1.12 to TravisCI
2019-03-01 10:06:59 +03:00
Evgeniy Kulikov fba0e235c1 Add Golang 1.12 to TravisCI 2019-02-26 21:47:29 +03:00
Evgeniy Kulikov 70be70de3c DualStack is deprecated
```
	// DualStack previously enabled RFC 6555 Fast Fallback
	// support, also known as "Happy Eyeballs", in which IPv4 is
	// tried soon if IPv6 appears to be misconfigured and
	// hanging.
	//
	// Deprecated: Fast Fallback is enabled by default. To
	// disable, set FallbackDelay to a negative value.
	DualStack bool
```
2019-02-26 21:44:47 +03:00
Sergey ArkhipovandGitHub b565d83d40 Merge pull request #62 from Eternity-Yarr/patch-1
Update README.md
2019-01-24 08:43:34 +03:00
Sergey ArkhipovandGitHub ebc459440c Merge branch 'master' into patch-1 2019-01-24 08:28:16 +03:00
9seconds 6e6049a73a Remove critic from travis 2019-01-24 08:17:05 +03:00
9seconds 916714a909 Update dependencies 2019-01-24 08:16:47 +03:00
9seconds f68b195306 Update golangci-lint 2019-01-24 08:15:56 +03:00
Dmitry V. RoenkoandGitHub 5f1d1ce883 Update README.md
- little typo in docker command line 
- added more preferred 'secure only' example
2019-01-22 13:03:02 +03:00
9seconds 16558a7c55 Merge remote-tracking branch 'origin/master' into stable 2018-11-02 23:21:06 +03:00
Sergey ArkhipovandGitHub e7958aaf33 Merge pull request #57 from 9seconds/remove-go-generate
Remove generating of version.go
2018-11-02 20:20:18 +00:00
9seconds 7d6b661d97 More correct command for versioning 2018-11-02 19:10:21 +03:00
9seconds 28b3cbe91a Update dependencies 2018-11-02 19:02:39 +03:00
9seconds 6818364231 Update golangci-lint 2018-11-02 19:02:07 +03:00
9seconds ea97bf51c8 Remove autogeneration of version.go 2018-11-02 18:54:28 +03:00
9seconds 1678ddbd53 Reformat README 2018-11-02 18:37:16 +03:00
Sergey ArkhipovandGitHub f81f29cbb4 Merge pull request #55 from 9seconds/secure-mode-readme
Add explanation on secure mode
2018-11-02 15:21:27 +00:00
Sergey ArkhipovandGitHub 2d9259db48 Merge pull request #56 from hdid/patch-1
tiny fix README.md
2018-11-02 15:20:47 +00:00
hdidandGitHub c721c636d9 tiny fix README.md 2018-11-02 13:09:26 +03:30
Sergey ArkhipovandGitHub 60472072aa Add explanation on secure mode
Hopefully, this closes https://github.com/9seconds/mtg/issues/49
2018-11-02 09:02:45 +00:00
9seconds 6721e6fd9f Update dependencies 2018-11-01 18:34:38 +03:00
Sergey ArkhipovandGitHub 47fb5c23cb Merge pull request #53 from 9seconds/prometheus-mtg-only
Use mtg metrics only for prometheus endpoint
2018-11-01 18:32:55 +03:00
Sergey ArkhipovandGitHub fce6118c71 Merge pull request #52 from 9seconds/log-config
Log configuration on proxy start
2018-11-01 18:25:25 +03:00
9seconds d277a2975a Use mtg metrics only for prometheus endpoint 2018-11-01 18:23:36 +03:00
9seconds c3f21a7b0d Log configuration on proxy start
This has to simplify the debugging because usually we do not know all
options which user used to start mtg.
2018-11-01 18:07:16 +03:00
9seconds 61a1024264 Merge remote-tracking branch 'origin/master' into stable 2018-10-17 14:20:36 +03:00
Sergey ArkhipovandGitHub 4695a0c433 Merge pull request #47 from 9seconds/prometheus
Prometheus integration
2018-10-17 13:14:31 +03:00
9seconds a9d0ca1e90 Describe and document prometheus integration 2018-10-17 12:54:14 +03:00
9seconds 9346c11f37 Add support for prometheus 2018-10-17 12:06:46 +03:00
9seconds 5f00363da5 Add goreportcard 2018-10-17 10:52:30 +03:00
9seconds 3aec9657d7 Add comments for exported functions 2018-10-17 10:51:16 +03:00
9seconds 358d42ec61 Fix spelling issue 2018-10-17 10:49:27 +03:00
Sergey ArkhipovandGitHub ac2625209b Merge pull request #45 from 9seconds/stats
Rework stats server
2018-10-16 22:24:01 +03:00
9seconds c74e0ee359 Rework stats server 2018-10-16 22:17:46 +03:00
Sergey ArkhipovandGitHub 7c463851b2 Okay, 3 megabytes. Image grows :( 2018-10-13 11:18:36 +03:00
Sergey ArkhipovandGitHub c9f6b922c3 Merge pull request #44 from 9seconds/goroutines-number
Decrease the number of used goroutines
2018-10-12 10:56:46 +03:00
9seconds 7128e70e50 Minor changes in dockerfile 2018-10-12 10:50:52 +03:00
9seconds cf9bf56d8e Update dependencies 2018-10-12 10:40:54 +03:00
9seconds 76cfbb009a Decrease an amount of goroutines 2018-10-12 10:38:35 +03:00
9seconds cb78169d6a Update dependencies 2018-10-10 13:49:52 +03:00
9seconds d1703873c1 Merge remote-tracking branch 'origin/master' into stable 2018-09-24 18:29:24 +03:00
Sergey ArkhipovandGitHub ac33abbbb1 Merge pull request #40 from 9seconds/secure-only
"Secure only" mode
2018-09-24 18:15:36 +03:00
9seconds a6893c8df7 Update README 2018-09-24 18:08:06 +03:00
9seconds 7182c7bf65 Add new secure-only mode 2018-09-24 18:04:00 +03:00
9seconds 84f4a37d11 Merge remote-tracking branch 'origin/master' into stable 2018-09-21 12:17:12 +03:00
Sergey ArkhipovandGitHub 3cf7f1396b Merge pull request #39 from 9seconds/gofrs-uuid
Switch to maintained UUID fork
2018-09-21 11:35:31 +03:00
9seconds 835ab94470 Switch to maintained UUID fork 2018-09-21 11:29:02 +03:00
Sergey ArkhipovandGitHub 278753d91e Merge pull request #38 from 9seconds/go11modules
Support of Go 1.11 modules
2018-09-21 11:20:08 +03:00
9seconds b81505ec21 Update build files 2018-09-21 11:09:46 +03:00
9seconds 0828bd85ad Add support for GO111MODULE 2018-09-21 10:58:38 +03:00
9seconds 96e80fc0d6 Build images with golang 1.11 2018-09-21 10:34:43 +03:00
Sergey ArkhipovandGitHub 591500de41 Merge pull request #37 from 9seconds/default-dcidx
Support of default DC indexes
2018-09-21 10:22:52 +03:00
9seconds 07ddfa12f7 Install populate vendor on make prepare 2018-09-21 10:13:52 +03:00
9seconds 618d91b15d Pin to golang 1.10 for a moment 2018-09-21 09:56:34 +03:00
9seconds a83d20dd46 Update dependencies 2018-09-21 09:50:11 +03:00
9seconds 1e9bbfae55 Support default dc idx from configs 2018-09-21 09:47:37 +03:00
9seconds 8268620615 Update version of golangci-lint 2018-07-29 17:10:12 +03:00
9seconds 753639beb8 Make session drop even more responsive 2018-07-29 17:08:57 +03:00
9seconds 1ee34fd502 Guarantee of closing sockets after context is done 2018-07-28 15:36:40 +03:00
Sergey ArkhipovandGitHub 243a89a68c Merge pull request #33 from 9seconds/contexts
Use contexts for Conn wrapper
2018-07-28 13:31:53 +03:00
9seconds 9f20e8749a Use contexts for Conn wrapper 2018-07-28 13:21:57 +03:00
Sergey ArkhipovandGitHub b0d86abc74 Small table fix 2018-07-28 12:40:41 +03:00
Sergey ArkhipovandGitHub 6c0030d20e Merge pull request #32 from 9seconds/readme-env-vars
Document environment variables
2018-07-28 12:39:47 +03:00
9seconds 8733f6d191 Optimize dockerfile 2018-07-28 12:30:47 +03:00
9seconds 8906ae0db0 Document environment variables 2018-07-28 12:10:39 +03:00
Sergey ArkhipovandGitHub 2e8c9623de Merge pull request #31 from 9seconds/propagate-buffer-sizes
Propagate buffer sizes
2018-07-27 14:31:43 +03:00
9seconds 0ce325f908 Add possibility to set read/write buffers separately 2018-07-27 14:26:14 +03:00
9seconds b86ad07ba9 Simplify the sources 2018-07-27 13:46:14 +03:00
Sergey ArkhipovandGitHub 6253c21615 Merge pull request #30 from 9seconds/ipv6-fix
Fix ipv6/ipv4 name resolution
2018-07-27 10:10:46 +03:00
9seconds a00408562f Fix ipv6/ipv4 name resolution 2018-07-27 10:04:10 +03:00
9seconds 0d0bdd9fde Reuse buffers for stream cipher 2018-07-20 14:49:22 +03:00
Sergey Arkhipovand9seconds c8da08ecce Allow go master to fail 2018-07-18 17:45:45 +03:00
9seconds c5f99d17b7 Add go-critic 2018-07-18 08:53:04 +03:00
9seconds c1b862c1e9 Switch to golangci-lint 2018-07-18 08:37:37 +03:00
134 changed files with 6309 additions and 3485 deletions
+9
View File
@@ -0,0 +1,9 @@
vendor/
tags
.github/
.bin/
*.md
mtg
.golangci.toml
.gitignore
run.sh
+133
View File
@@ -0,0 +1,133 @@
---
name: CI
on:
push:
tags:
- v*
branches:
- master
- stable
- v1
release:
types:
- published
- released
pull_request:
types:
- opened
- edited
- reopened
- synchronize
- ready_for_review
jobs:
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 5
strategy:
matrix:
go_version:
- ~1.14
- ~1.15
- ^1.16
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: recursive
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go_version }}
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- name: Run tests
run: go test -coverprofile=./coverage.txt -covermode=atomic -v ./...
- name: Collect coverage
uses: codecov/codecov-action@v1
with:
file: ./coverage.txt
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: recursive
- name: Run linter
uses: golangci/golangci-lint-action@v2
with:
version: v1.37.1
docker:
name: Docker
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: recursive
- name: Get Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: nineseconds/mtg,ghcr.io/9seconds/mtg
tag-semver: "{{version}},{{major}},{{major}}.{{minor}}"
- name: Setup QEMU
uses: docker/setup-qemu-action@v1
- name: Setup BuildX
uses: docker/setup-buildx-action@v1
- name: Setup cache
uses: actions/cache@v2
with:
path: /tmp/buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to GHCR.io
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
- name: Build and push
uses: docker/build-push-action@v2
with:
pull: true
context: .
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7,linux/arm/v6
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
cache-from: type=local,src=/tmp/buildx-cache
cache-to: type=local,dest=/tmp/buildx-cache
+69
View File
@@ -0,0 +1,69 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches:
- master
- stable
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '24 20 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+1 -1
View File
@@ -7,5 +7,5 @@
*.out *.out
mtg mtg
vendor/ vendor/
version.go
ccbuilds/ ccbuilds/
.bin/
+13
View File
@@ -0,0 +1,13 @@
[run]
concurrency = 4
deadline = "2m"
tests = true
skip-dirs = ["vendor"]
skip-files = ["version.go"]
[output]
format = "colored-line-number"
[linters]
enable-all = true
disable = ["gochecknoglobals", "gas", "gomnd", "goerr113", "exhaustivestruct"]
-4
View File
@@ -1,4 +0,0 @@
{
"aggregate": true,
"vendor": true
}
-18
View File
@@ -1,18 +0,0 @@
---
language: go
sudo: false
dist: trusty
go:
- "1.9.x"
- "1.10.x"
- master
script:
- make lint
- make test
cache:
directories:
- vendor
+10 -16
View File
@@ -1,7 +1,7 @@
############################################################################### ###############################################################################
# BUILD STAGE # BUILD STAGE
FROM golang:alpine FROM golang:1.16-alpine AS build
RUN set -x \ RUN set -x \
&& apk --no-cache --update add \ && apk --no-cache --update add \
@@ -9,17 +9,13 @@ RUN set -x \
ca-certificates \ ca-certificates \
curl \ curl \
git \ git \
make \ make
upx \
&& update-ca-certificates
ADD . /go/src/github.com/9seconds/mtg COPY . /app
WORKDIR /app
RUN set -x \ RUN set -x \
&& cd /go/src/github.com/9seconds/mtg \ && make -j 4 static
&& make clean \
&& make -j 4 static \
&& upx --ultra-brute -qq ./mtg
############################################################################### ###############################################################################
@@ -27,12 +23,10 @@ RUN set -x \
FROM scratch FROM scratch
ENTRYPOINT ["/usr/local/bin/mtg"] ENTRYPOINT ["/mtg"]
ENV MTG_IP=0.0.0.0 \ ENV MTG_BIND=0.0.0.0:3128 \
MTG_PORT=3128 \ MTG_STATS_BIND=0.0.0.0:3129
MTG_STATS_IP=0.0.0.0 \
MTG_STATS_PORT=3129
EXPOSE 3128 3129 EXPOSE 3128 3129
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=0 /go/src/github.com/9seconds/mtg/mtg /usr/local/bin/mtg COPY --from=build /app/mtg /mtg
Generated
-114
View File
@@ -1,114 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/alecthomas/template"
packages = [
".",
"parse"
]
revision = "a0175ee3bccc567396460bf5acd36800cb10c49c"
[[projects]]
branch = "master"
name = "github.com/alecthomas/units"
packages = ["."]
revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
[[projects]]
name = "github.com/beevik/ntp"
packages = ["."]
revision = "62c80a04de2086884d8296004b6d74ee1846c582"
version = "v0.2.0"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
branch = "master"
name = "github.com/dustin/go-humanize"
packages = ["."]
revision = "9f541cc9db5d55bce703bd99987c9d5cb8eea45e"
[[projects]]
branch = "master"
name = "github.com/juju/errors"
packages = ["."]
revision = "c7d06af17c68cd34c835053720b21f6549d9b0ee"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
name = "github.com/satori/go.uuid"
packages = ["."]
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
version = "v1.2.0"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert"]
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
name = "go.uber.org/atomic"
packages = ["."]
revision = "1ea20fb1cbb1cc08cbd0d913a96dead89aa18289"
version = "v1.3.2"
[[projects]]
name = "go.uber.org/multierr"
packages = ["."]
revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a"
version = "v1.1.0"
[[projects]]
name = "go.uber.org/zap"
packages = [
".",
"buffer",
"internal/bufferpool",
"internal/color",
"internal/exit",
"zapcore"
]
revision = "eeedf312bc6c57391d84767a4cd413f02a917974"
version = "v1.8.0"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = [
"bpf",
"internal/iana",
"internal/socket",
"ipv4"
]
revision = "d0887baf81f4598189d4e12a37c6da86f0bba4d0"
[[projects]]
name = "gopkg.in/alecthomas/kingpin.v2"
packages = ["."]
revision = "947dcec5ba9c011838740e680966fd7087a71d0d"
version = "v2.2.6"
[[projects]]
name = "gopkg.in/alexcesaro/statsd.v2"
packages = ["."]
revision = "7fea3f0d2fab1ad973e641e51dba45443a311a90"
version = "v2.0.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "7fad0f62feb7737b064d85cc4333a1a3e9298faec2afd864b4404f515fc7f17c"
solver-name = "gps-cdcl"
solver-version = 1
-58
View File
@@ -1,58 +0,0 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[prune]
go-tests = true
unused-packages = true
[[constraint]]
name = "gopkg.in/alecthomas/kingpin.v2"
version = "2.2.6"
[[constraint]]
branch = "master"
name = "github.com/juju/errors"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.1"
[[constraint]]
name = "github.com/satori/go.uuid"
version = "1.2.0"
[[constraint]]
branch = "master"
name = "github.com/dustin/go-humanize"
[[constraint]]
name = "github.com/beevik/ntp"
version = "0.2.0"
[[constraint]]
name = "gopkg.in/alexcesaro/statsd.v2"
version = "2.0.0"
+51 -39
View File
@@ -1,80 +1,92 @@
ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
IMAGE_NAME := mtg IMAGE_NAME := mtg
APP_NAME := $(IMAGE_NAME) APP_NAME := $(IMAGE_NAME)
GOMETALINTER := gometalinter
VENDOR_FILES := $(shell find "$(ROOT_DIR)/vendor" 2>/dev/null || echo -n "vendor")
CC_BINARIES := $(shell bash -c "echo -n $(APP_NAME)-{linux,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}") CC_BINARIES := $(shell bash -c "echo -n $(APP_NAME)-{linux,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}")
APP_DEPS := version.go $(VENDOR_FILES)
COMMON_BUILD_FLAGS := -ldflags="-s -w" GOLANGCI_LINT_VERSION := v1.37.1
VERSION_GO := $(shell go version)
VERSION_DATE := $(shell date -Ru)
VERSION_TAG := $(shell git describe --tags --always)
COMMON_BUILD_FLAGS := -mod=readonly -ldflags="-s -w -X 'main.version=$(VERSION_TAG) ($(VERSION_GO)) [$(VERSION_DATE)]'"
GOBIN := $(ROOT_DIR)/.bin
GOTOOL := env "GOBIN=$(GOBIN)" "PATH=$(ROOT_DIR)/.bin:$(PATH)"
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
$(APP_NAME): $(APP_DEPS) .PHONY: all
all: build
.PHONY: build
build:
@go build $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)" @go build $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)"
static-$(APP_NAME): $(APP_DEPS) $(APP_NAME): build
.PHONY: static
static:
@env CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)" @env CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)"
$(APP_NAME)-%: GOOS=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f1 -d-) $(APP_NAME)-%: GOOS=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f1 -d-)
$(APP_NAME)-%: GOARCH=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f2 -d-) $(APP_NAME)-%: GOARCH=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f2 -d-)
$(APP_NAME)-%: $(APP_DEPS) ccbuilds $(APP_NAME)-%: ccbuilds
@env "GOOS=$(GOOS)" "GOARCH=$(GOARCH)" \ @env "GOOS=$(GOOS)" "GOARCH=$(GOARCH)" \
go build \ go build \
$(COMMON_BUILD_FLAGS) \ $(COMMON_BUILD_FLAGS) \
-o "./ccbuilds/$(APP_NAME)-$(GOOS)-$(GOARCH)" -o "./ccbuilds/$(APP_NAME)-$(GOOS)-$(GOARCH)"
.PHONY: ccbuilds
ccbuilds: ccbuilds:
@rm -rf ./ccbuilds && mkdir -p ./ccbuilds @rm -rf ./ccbuilds && mkdir -p ./ccbuilds
version.go: vendor: go.mod go.sum
@go generate main.go @$(MOD_ON) go mod vendor
vendor: Gopkg.lock Gopkg.toml install-cli .PHONY: test
@dep ensure test:
@go test -v ./...
# ----------------------------------------------------------------------------- .PHONY: citest
citest:
.PHONY: all @go test -coverprofile=coverage.txt -covermode=atomic -race -v ./...
all: $(APP_NAME)
.PHONY: static
static: static-$(APP_NAME)
.PHONY: crosscompile .PHONY: crosscompile
crosscompile: $(CC_BINARIES) crosscompile: $(CC_BINARIES)
.PHONY: crosscompile-dir
crosscompile-dir:
@rm -rf "$(CC_DIR)" && mkdir -p "$(CC_DIR)"
.PHONY: test
test: vendor install-cli version.go
@go test -v ./...
.PHONY: lint
lint: vendor install-cli version.go
@$(GOMETALINTER) --deadline=2m ./...
.PHONY: clean .PHONY: clean
clean: clean:
@git clean -xfd && \ @git clean -xfd && \
git reset --hard >/dev/null && \ git reset --hard >/dev/null && \
git submodule foreach --recursive sh -c 'git clean -xfd && git reset --hard' >/dev/null git submodule foreach --recursive sh -c 'git clean -xfd && git reset --hard' >/dev/null
.PHONY: lint
lint:
@$(GOTOOL) golangci-lint run
.PHONY: docker .PHONY: docker
docker: docker:
@docker build --pull -t "$(IMAGE_NAME)" "$(ROOT_DIR)" @docker build --pull -t "$(IMAGE_NAME)" "$(ROOT_DIR)"
.PHONY: install-cli .PHONY: doc
install-cli: install-dep install-lint doc:
@$(GOTOOL) godoc -http 0.0.0.0:10000
.PHONY: install-dep .PHONY: install-tools
install-dep: install-tools: install-tools-lint install-tools-godoc
@go get github.com/golang/dep/cmd/dep
.PHONY: install-lint .PHONY: install-tools-lint
install-lint: install-tools-lint:
@go get github.com/alecthomas/gometalinter && \ @mkdir -p "$(GOBIN)" || true && \
$(GOMETALINTER) --install >/dev/null curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh \
| bash -s -- -b "$(GOBIN)" "$(GOLANGCI_LINT_VERSION)"
.PHONY: install-tools-godoc
install-tools-godoc:
@mkdir -p "$(GOBIN)" || true && \
$(GOTOOL) go get -u golang.org/x/tools/cmd/godoc
.PHONY: update-deps
upgrade-deps:
$go get -u && go mod tidy
+152 -66
View File
@@ -3,8 +3,10 @@
Bullshit-free MTPROTO proxy for Telegram Bullshit-free MTPROTO proxy for Telegram
[![Build Status](https://travis-ci.org/9seconds/mtg.svg?branch=master)](https://travis-ci.org/9seconds/mtg) [![Build Status](https://travis-ci.org/9seconds/mtg.svg?branch=master)](https://travis-ci.org/9seconds/mtg)
[![Go Report Card](https://goreportcard.com/badge/github.com/9seconds/mtg)](https://goreportcard.com/report/github.com/9seconds/mtg)
[![Docker Build Status](https://img.shields.io/docker/build/nineseconds/mtg.svg)](https://hub.docker.com/r/nineseconds/mtg/) [![Docker Build Status](https://img.shields.io/docker/build/nineseconds/mtg.svg)](https://hub.docker.com/r/nineseconds/mtg/)
**Please see a guide on upgrading to 1.0 at the end of this README.**
# Rationale # Rationale
@@ -15,33 +17,33 @@ are the most notable:
* [Python](https://github.com/alexbers/mtprotoproxy) * [Python](https://github.com/alexbers/mtprotoproxy)
* [Erlang](https://github.com/seriyps/mtproto_proxy) * [Erlang](https://github.com/seriyps/mtproto_proxy)
Almost all of them follow the way how official proxy was build. This Almost all of them follow the way how official proxy was built. This
includes support of multiple secrets, support of promoted channels etc. includes support of multiple secrets, support of promoted channels, etc.
mtg is an implementation in golang which is intended to be: mtg is an implementation in golang which is intended to be:
* **Lightweight** * **Lightweight**
It has to consume as less resources as possible but not by losing It has to consume as few resources as possible but not by losing
maintainability. maintainability.
* **Easily deployable** * **Easily deployable**
I strongly believe that Telegram proxies should follow the way of I strongly believe that Telegram proxies should follow the way of
ShadowSocks: promoted channels is a strange way of doing business ShadowSocks: promoted channels is a strange way of doing business
I suppose. I think the only viable way is to have a proxy with I suppose. I think the only viable way is to have a proxy with
minimum configuration which should work everywhere. minimum configuration which should work everywhere.
* **Single secret** * **A single secret**
I think that multiple secrets solves no problems and just complexify I think that multiple secrets solve no problems and just complexify
software. I also believe that in case of throwout proxies, this feature software. I also believe that in the case of throwout proxies, this
is useless luxury. feature is a useless luxury.
* **Minimum docker image size** * **Minimum docker image size**
Official image is less than 2.5 megabytes. Literally. Official image is less than 3.5 megabytes. Literally.
* **No management WebUI** * **No management WebUI**
This is an implementation of simple lightweight proxy. I won't do that. This is an implementation of a simple lightweight proxy. I won't do that.
This proxy supports 2 modes of work: direct connection to Telegram and This proxy supports 2 modes of work: direct connection to Telegram and
promoted channel mode. If you do not need promoted channels, I would promoted channel mode. If you do not need promoted channels, I would
recommend you to go with direct mode: this is way more robust. recommend you to go with direct mode: this way is more robust.
To run proxy in direct mode, all you need to do is just provide a To run a proxy in direct mode, all you need to do is just provide a
secret. If you do not provide ADTag as a second parameter, promoted secret. If you do not provide ADTag as a second parameter, promoted
channels mode won't be activated. channels mode won't be activated.
@@ -60,19 +62,19 @@ There are 2 main branches:
# How to build # How to build
```console ```console
$ make make
``` ```
If you want to build for another platform: If you want to build for another platform:
```console ```console
$ make crosscompile make crosscompile
``` ```
If you want to build Docker image (called `mtg`): If you want to build Docker image (called `mtg`):
```console ```console
$ make docker make docker
``` ```
# Docker image # Docker image
@@ -84,88 +86,155 @@ Docker follows the same policy as the source code organization:
- tags are for tagged releases - tags are for tagged releases
```console ```console
$ docker pull nineseconds/mtg:latest docker pull nineseconds/mtg:latest
``` ```
```console ```console
$ docker pull nineseconds/mtg:stable docker pull nineseconds/mtg:stable
``` ```
```console ```console
$ docker pull nineseconds/mtg:0.10 docker pull nineseconds/mtg:0.10
``` ```
# Ansible role
You can find unofficial Ansible role for mtg here: https://github.com/rlex/ansible-role-mtg
Also, there is another project on Ansible Galaxy: https://galaxy.ansible.com/ivansible/lin_mtproxy
# Configuration # Configuration
Basically, to run this tool you need to configure as less as possible. To run this tool you need to configure as less as possible. Telegram
clients support 3 different secret types:
* Simple - basically, it is just a flow of frames ciphered by AES-CTR stream
cipher.
* Secured - the same stream as simple but with some random noise to prevent
statistical analysis of traffic flow.
* FakeTLS - this mode envelops telegram stream in TLS so it looks (in theory)
the same as any TLS1.3 traffic from DPI point of view.
If you do not have preferences, go with FakeTLS or at least secured.
Simple mode is a little bit naive and traffic flow can be easily
identified as Telegram one.
Unlike the rest of implementation, mtg is quite strict about the
execution mode: if you run a proxy instance with FakeTLS secret, you
can't connect to it with simple or secured clients. You can't connect
to the proxy with secured secret with FakeTLS key. It forces one mode
of working. So, unfortunately, there is no way how to connect to the
deployed proxy with another secret (if you know how to construct and
convert them). But at the same time, old clients can't connect so they
won't expose the type of the service.
First, you need to generate a secret: First, you need to generate a secret:
```console ```console
$ openssl rand -hex 16 $ mtg generate-secret simple
52a493bdfb90eea55739eabff2d92a14
``` ```
or
```console ```console
$ head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' ' $ mtg generate-secret secured
ddf05fb7acb549be047a7c585116581418
``` ```
## Secure mode
If you want to support new secure mode, please prepend `dd` to the
secret. For example, secret `cf18fa8ea0267057e2c61a5f7322a8e7` should
be `ddcf18fa8ea0267057e2c61a5f7322a8e7`. But pay attention that some
old clients won't support this mode. If this is not your case, I would
suggest to go with this mode.
Oneliners to generate such secrets:
```console ```console
$ echo dd$(openssl rand -hex 16) $ mtg generate-secret -c google.com tls
ee852380f362a09343efb4690c4e17862e676f6f676c652e636f6d
``` ```
or Or, if you prefer docker:
```console ```console
$ echo dd$(head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' ') $ docker run --rm nineseconds/mtg generate-secret tls -c bing.com
eedf71035a8ed48a623d8e83e66aec4d0562696e672e636f6d
``` ```
## Antireplay cache
To prevent replay attacks, we have internal storage of first frames
messages for connected clients. These frames are generated randomly
by design and we have the negligible possibility of duplication
(probability is 1/(2^64)) but it could be quite effective to prevent
replays.
It is possible to disable this cache. To do that, please explicitly set
its size to 0.
## FakeTLS
If you run this a proxy in faketls mode, this proxy will try to hide
itself cloaking a host provided as a part of the generated secret. It
means that if you cloak google.com then you can curl this proxy and
you'll get a google.com response back.
mtg proxies L3 traffic. In other words, only TCP, without interfering in
TLS, HTTP or any other high-level protocol.
## Environment variables
It is possible to configure this tool using environment variables. You
can configure any flag but not secret or adtag. Here is the list of
supported environment variables:
| Environment variable | Corresponding flags | Default value | Description |
|-------------------------------|------------------------------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `MTG_DEBUG` | `-d`, `--debug` | `false` | Run in debug mode. Usually, you need to run in this mode only if you develop this tool or its maintainer is asking you to provide logs with such verbosity. |
| `MTG_VERBOSE` | `-v`, `--verbose` | `false` | Run in verbose mode. This is way less chatty than debug mode. |
| `MTG_BIND` | `-b`, `--bind` | `0.0.0.0:3128` | Which host/port pair should we bind to (listen on). |
| `MTG_IPV4` | `-4`, `--public-ipv4` | [Autodetect](https://ifconfig.co) | IPv4 address:port of this proxy. This is required if you NAT your proxy or run it in a docker container. In that case, you absolutely need to specify public IPv4 address of the proxy, otherwise either URLs will be broken or proxy could not access Telegram middle proxies. |
| `MTG_IPV6` | `-6`, `--public-ipv6` | [Autodetect](https://ifconfig.co) | IPv6 address:port of this proxy. This is required if you NAT your proxy or run it in a docker container. In that case, you absolutely need to specify public IPv6 address of the proxy, otherwise either URLs will be broken or proxy could not access Telegram middle proxies. |
| `MTG_STATS_BIND` | `-t`, `--stats-bind` | `127.0.0.1:3129` | Which hist:port should we bind the internal statistics HTTP server (Prometheus). |
| `MTG_STATS_NAMESPACE` | `--stats-namespace` | `mtg` | Which namespace should be used for prometheus metrics. |
| `MTG_STATSD_ADDR` | `--statsd-addr` | | host:port of statsd service. No defaults, by default we do not send anything there. |
| `MTG_STATSD_PREFIX` | `--statsd-prefix` | `mtg` | Which bucket prefix we should use. For example, if you set `mtg`, then metric `traffic.ingress` would be send as `mtg.traffic.ingress`. |
| `MTG_STATSD_TAGS_FORMAT` | `--statsd-tags-format` | | Which tags format we should use. By default, we are using default vanilla statsd tags format but if you want to send directly to InfluxDB or Datadog, please specify it there. Possible options are `influxdb` and `datadog`. |
| `MTG_STATSD_TAGS` | `--statsd-tags` | | Which tags should we send to statsd with our metrics. Please specify them as `key=value` pairs. |
| `MTG_BUFFER_WRITE` | `-w`, `--write-buffer` | `32KB` | The size of TCP write buffer in bytes. Write buffer is the buffer for messages which are going from client to Telegram. |
| `MTG_BUFFER_READ` | `-r`, `--read-buffer` | `32KB` | The size of TCP read buffer in bytes. Read buffer is the buffer for messages from Telegram to client. |
| `MTG_ANTIREPLAY_MAXSIZE` | `--anti-replay-max-size` | `128MB` | Max size of antireplay cache. |
| `MTG_CLOAK_PORT` | `--cloak-port` | `443` | Which port we should use to connect to cloaked host in FakeTLS mode. |
| `MTG_MULTIPLEX_PERCONNECTION` | `--multiplex-per-connection` | `50` | How many client connections can share a single Telegram connection in adtag mode |
| `MTG_NTP_SERVERS` | `--ntp-server` | default pool | A list of NTP servers to use. |
| `MTG_PREFER_DIRECT_IP` | `--prefer-ip` | `ipv6` | Which IP protocol to prefer if possible. Works mostly in direct mode. |
Usually you want to modify only read/write buffer sizes. If you feel
that proxy is slow, try to increase both sizes giving more priority to
read buffer.
Unfortunately, MTPROTO proxy protocol does not allow us to use splice
or any other neat tricks how to eliminate the need of copying data into
userspace.
# How to run the tool # How to run the tool
Now run the tool: Now run the tool:
```console ```console
$ mtg <secret> $ mtg run <secret>
``` ```
How to run the tool with ADTag: How to run the tool with ADTag:
```console ```console
$ mtg <secret> <adtag> $ mtg run <secret> <adtag>
``` ```
This tool will listen on port 3128 by default with the given secret. This tool will listen on port 3128 by default with the given secret.
# One-line runner
# oneliner to run this proxy
Please ensure that docker is installed. After that just execute
```console ```console
$ docker run --name mtg --restart=unless-stopped -p 3128:3128 -p 3129:3129 -d nineseconds/mtg:stable $(openssl rand -hex 16) curl -sfL --compressed https://raw.githubusercontent.com/9seconds/mtg/master/run.sh | bash
``` ```
or in secret mode:
```console
$ docker run --name mtg --restart=unless-stopped -p 3128:3128 -p 3129:3129 -d nineseconds/mtg:stable dd$(openssl rand -hex 16)
```
You will have this tool up and running on port 3128. Now curl
`localhost:3129` to get `tg://` links or do `docker logs mtg`. Also,
port 3129 will show you some statistics if you are interested in.
Also, you can use [run-mtg.sh](https://github.com/9seconds/mtg/blob/master/run-mtg.sh) script
# statsd integration # statsd integration
@@ -178,20 +247,37 @@ and [Datadog](https://docs.datadoghq.com/developers/dogstatsd/).
All metrics are gauges. Here is the list of metrics and their meaning: All metrics are gauges. Here is the list of metrics and their meaning:
| Metric name | Unit | Description | | Metric name | Unit | Description |
|---------------------------------|---------|-----------------------------------------------------------| |------------------------|---------|--------------------------------------------|
| `connections.abridged.ipv4` | number | The number of active abridged IPv4 connections | | `connections` | number | The number of active connections. |
| `connections.abridged.ipv6` | number | The number of active abridged IPv6 connections | | `telegram_connections` | number | The number of active telegram connections. |
| `connections.intermediate.ipv4` | number | The number of active intermediate IPv4 connections | | `crashes` | number | An amount of crashes in client handlers. |
| `connections.intermediate.ipv6` | number | The number of active intermediate IPv6 connections | | `traffic.egress` | bytes | Traffic from the start of application. |
| `connections.secure.ipv4` | number | The number of active secure intermediate IPv4 connections | | `replay_attacks` | number | The number of prevented replay attacks. |
| `connections.secure.ipv6` | number | The number of active secure intermediate IPv6 connections |
| `crashes` | number | An amount of crashes in client handlers |
| `traffic.ingress` | bytes | Ingress traffic from the start of application (incoming) |
| `traffic.egress` | bytes | Egress traffic from the start of application (outgoing) |
| `speed.ingress` | bytes/s | Ingress bandwidth of the latest second (incoming traffic) |
| `speed.egress` | bytes/s | Egress bandwidth of the latest second (outgoing traffic) |
All metrics are prefixed with given prefix. Default prefix is `mtg`. All metrics are prefixed with given prefix. Default prefix is `mtg`.
With such prefix metric name `traffic.ingress`, for example, would be Also, metrics provide tags (ipv4/ipv6, dc indexes etc).
`mtg.traffic.ingress`.
# Prometheus integration
[Prometheus](https://prometheus.io) integration comes out of
the box, you do not need to setup anything special.
# Upgrade to 1.0
Version 1.0 breaks compatibility with previous versions so please read
this chapter carefully:
1. mtg now uses subcommands. Please use `mtg run` instead of just
`mtg` to run a proxy.
2. Options which set host and port separately were removed in a
favor of fused `host:port` options.
3. Own stats server was removed. Prometheus endpoint is moved to
default stats endpoint.
4. It is possible to connect to this proxy only with a secret which
was used to run it. So, no backward compatibility of clients.
5. Multiplexing involves connectivity with middle proxies and involves
the most complex code path of this proxy. To avoid potential bugs,
we still recommend using direct mode.
+24
View File
@@ -0,0 +1,24 @@
# Security Policy
## Supported Versions
We support 2 tracks: 2.x and 1.x
| Version | Supported |
| ------- | ------------------ |
| 2.x | :white_check_mark: |
| 1.x | :interrobang: |
| < 1.0 | :x: |
1.x has adtag support. We do not plan any active development there but we guarantee:
1. Regular dependency updates
2. Updates for Golang versions
3. Security vulnerability fixes
4. Merging small PRs
2.x is in active development and have a full support.
## Reporting a Vulnerability
If you have found a vulnerability, please report about it to nineseconds@yandex.ru, telegram @9seconds or here in issues
+36
View File
@@ -0,0 +1,36 @@
package antireplay
import "github.com/VictoriaMetrics/fastcache"
var (
prefixObfuscated2 = []byte{0x00}
prefixTLS = []byte{0x01}
)
type cache struct {
data *fastcache.Cache
}
func (c cache) AddObfuscated2(data []byte) {
c.data.Set(keyObfuscated2(data), nil)
}
func (c cache) AddTLS(data []byte) {
c.data.Set(keyTLS(data), nil)
}
func (c cache) HasObfuscated2(data []byte) bool {
return c.data.Has(keyObfuscated2(data))
}
func (c cache) HasTLS(data []byte) bool {
return c.data.Has(keyTLS(data))
}
func keyObfuscated2(data []byte) []byte {
return append(prefixObfuscated2, data...)
}
func keyTLS(data []byte) []byte {
return append(prefixTLS, data...)
}
+30
View File
@@ -0,0 +1,30 @@
package antireplay
import (
"sync"
"github.com/9seconds/mtg/config"
"github.com/VictoriaMetrics/fastcache"
)
type CacheInterface interface {
AddObfuscated2([]byte)
AddTLS([]byte)
HasObfuscated2([]byte) bool
HasTLS([]byte) bool
}
var (
Cache CacheInterface
initOnce sync.Once
)
func Init() {
initOnce.Do(func() {
if config.C.AntiReplayMaxSize == 0 {
Cache = nilCache{}
} else {
Cache = cache{fastcache.New(config.C.AntiReplayMaxSize)}
}
})
}
+8
View File
@@ -0,0 +1,8 @@
package antireplay
type nilCache struct{}
func (n nilCache) AddObfuscated2(_ []byte) {}
func (n nilCache) AddTLS(_ []byte) {}
func (n nilCache) HasObfuscated2(_ []byte) bool { return false }
func (n nilCache) HasTLS(_ []byte) bool { return false }
+26
View File
@@ -0,0 +1,26 @@
package cli
import (
"crypto/rand"
"encoding/hex"
"github.com/9seconds/mtg/config"
)
func Generate(secretType, hostname string) {
data := make([]byte, config.SimpleSecretLength)
if _, err := rand.Read(data); err != nil {
panic(err)
}
secret := hex.EncodeToString(data)
switch secretType {
case "simple":
PrintStdout(secret)
case "secured":
PrintStdout("dd" + secret)
default:
PrintStdout("ee" + secret + hex.EncodeToString([]byte(hostname)))
}
}
+101
View File
@@ -0,0 +1,101 @@
package cli
import (
"net"
"os"
"time"
"github.com/9seconds/mtg/antireplay"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/faketls"
"github.com/9seconds/mtg/hub"
"github.com/9seconds/mtg/ntp"
"github.com/9seconds/mtg/obfuscated2"
"github.com/9seconds/mtg/proxy"
"github.com/9seconds/mtg/stats"
"github.com/9seconds/mtg/telegram"
"github.com/9seconds/mtg/utils"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func Proxy() error { // nolint: funlen,cyclop
ctx := utils.GetSignalContext()
atom := zap.NewAtomicLevel()
switch {
case config.C.Debug:
atom.SetLevel(zapcore.DebugLevel)
case config.C.Verbose:
atom.SetLevel(zapcore.InfoLevel)
default:
atom.SetLevel(zapcore.ErrorLevel)
}
encoderCfg := zap.NewProductionEncoderConfig()
logger := zap.New(zapcore.NewCore(
zapcore.NewJSONEncoder(encoderCfg),
zapcore.Lock(os.Stderr),
atom,
))
zap.ReplaceGlobals(logger)
defer logger.Sync() // nolint: errcheck
if err := config.InitPublicAddress(ctx); err != nil {
Fatal(err)
}
zap.S().Debugw("Configuration", "config", config.Printable())
if config.C.MiddleProxyMode() {
zap.S().Infow("Use middle proxy connection to Telegram")
diff, err := ntp.Fetch()
if err != nil {
Fatal("Cannot fetch time data from NTP")
}
if diff > time.Second {
Fatal("Your local time is skewed and drift is bigger than a second. Please sync your time.")
}
go ntp.AutoUpdate()
} else {
zap.S().Infow("Use direct connection to Telegram")
}
PrintJSONStdout(config.GetURLs())
if err := stats.Init(ctx); err != nil {
Fatal(err)
}
antireplay.Init()
telegram.Init()
hub.Init(ctx)
proxyListener, err := net.Listen("tcp", config.C.Bind.String())
if err != nil {
Fatal(err)
}
go func() {
<-ctx.Done()
proxyListener.Close()
}()
app := &proxy.Proxy{
Logger: zap.S().Named("proxy"),
Context: ctx,
ClientProtocolMaker: obfuscated2.MakeClientProtocol,
}
if config.C.SecretMode == config.SecretModeTLS {
app.ClientProtocolMaker = faketls.MakeClientProtocol
}
app.Serve(proxyListener)
return nil
}
+43
View File
@@ -0,0 +1,43 @@
package cli
import (
"encoding/json"
"fmt"
"io"
"os"
)
func Fatal(arg interface{}) {
if value, ok := arg.(error); ok {
arg = fmt.Errorf("fatal error: %+v", value) // nolint: errorlint
}
PrintStderr(arg)
os.Exit(1)
}
func PrintStderr(args ...interface{}) {
fmt.Fprintln(os.Stderr, args...)
}
func PrintStdout(args ...interface{}) {
fmt.Println(args...) // nolint: forbidigo
}
func PrintJSONStderr(data interface{}) {
printJSON(os.Stderr, data)
}
func PrintJSONStdout(data interface{}) {
printJSON(os.Stdout, data)
}
func printJSON(writer io.Writer, data interface{}) {
encoder := json.NewEncoder(writer)
encoder.SetEscapeHTML(false)
encoder.SetIndent("", " ")
if err := encoder.Encode(data); err != nil {
panic(err)
}
}
-12
View File
@@ -1,12 +0,0 @@
package client
import (
"net"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/mtproto"
"github.com/9seconds/mtg/wrappers"
)
// Init defines common method for initializing client connections.
type Init func(net.Conn, string, *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error)
-55
View File
@@ -1,55 +0,0 @@
package client
import (
"net"
"time"
"github.com/juju/errors"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/mtproto"
"github.com/9seconds/mtg/obfuscated2"
"github.com/9seconds/mtg/wrappers"
)
const (
handshakeTimeout = 10 * time.Second
readBufferSize = 64 * 1024
writeBufferSize = 64 * 1024
)
// DirectInit initializes client connection for proxy which connects to
// Telegram directly.
func DirectInit(socket net.Conn, connID string, conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) {
tcpSocket := socket.(*net.TCPConn)
if err := tcpSocket.SetNoDelay(false); err != nil {
return nil, nil, errors.Annotate(err, "Cannot disable NO_DELAY to client socket")
}
if err := tcpSocket.SetReadBuffer(readBufferSize); err != nil {
return nil, nil, errors.Annotate(err, "Cannot set read buffer size of client socket")
}
if err := tcpSocket.SetWriteBuffer(writeBufferSize); err != nil {
return nil, nil, errors.Annotate(err, "Cannot set write buffer size of client socket")
}
socket.SetReadDeadline(time.Now().Add(handshakeTimeout)) // nolint: errcheck
frame, err := obfuscated2.ExtractFrame(socket)
if err != nil {
return nil, nil, errors.Annotate(err, "Cannot extract frame")
}
socket.SetReadDeadline(time.Time{}) // nolint: errcheck
conn := wrappers.NewConn(socket, connID, wrappers.ConnPurposeClient, conf.PublicIPv4, conf.PublicIPv6)
obfs2, connOpts, err := obfuscated2.ParseObfuscated2ClientFrame(conf.Secret, frame)
if err != nil {
return nil, nil, errors.Annotate(err, "Cannot parse obfuscated frame")
}
connOpts.ConnectionProto = mtproto.ConnectionProtocolAny
connOpts.ClientAddr = conn.RemoteAddr()
conn = wrappers.NewStreamCipher(conn, obfs2.Encryptor, obfs2.Decryptor)
conn.Logger().Infow("Client connection initialized")
return conn, connOpts, nil
}
-38
View File
@@ -1,38 +0,0 @@
package client
import (
"net"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/mtproto"
"github.com/9seconds/mtg/wrappers"
)
// MiddleInit initializes client connection for proxy which has to
// support promoted channels, connect to Telegram middle proxies etc.
func MiddleInit(socket net.Conn, connID string, conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) {
conn, opts, err := DirectInit(socket, connID, conf)
if err != nil {
return nil, nil, err
}
connStream := conn.(wrappers.StreamReadWriteCloser)
var newConn wrappers.PacketReadWriteCloser
switch opts.ConnectionType {
case mtproto.ConnectionTypeAbridged:
newConn = wrappers.NewMTProtoAbridged(connStream, opts)
case mtproto.ConnectionTypeIntermediate:
newConn = wrappers.NewMTProtoIntermediate(connStream, opts)
case mtproto.ConnectionTypeSecure:
newConn = wrappers.NewMTProtoIntermediateSecure(connStream, opts)
default:
panic("Unknown connection type")
}
opts.ConnectionProto = mtproto.ConnectionProtocolIPv4
if socket.LocalAddr().(*net.TCPAddr).IP.To4() == nil {
opts.ConnectionProto = mtproto.ConnectionProtocolIPv6
}
return newConn, opts, err
}
+272 -180
View File
@@ -1,220 +1,312 @@
package config package config
import ( import (
"encoding/hex" "bytes"
"context"
"encoding/json"
"errors"
"fmt" "fmt"
"math"
"net" "net"
"strconv"
"strings"
"github.com/juju/errors" "github.com/alecthomas/units"
statsd "gopkg.in/alexcesaro/statsd.v2" statsd "github.com/smira/go-statsd"
"go.uber.org/zap"
) )
// Buffer sizes define internal socket buffer sizes. type SecretMode uint8
const (
BufferWriteSize = 32 * 1024
BufferReadSize = 32 * 1024
)
// Config represents common configuration of mtg. func (s SecretMode) String() string {
type Config struct { switch s {
Debug bool case SecretModeSimple:
Verbose bool return "simple"
SecureMode bool case SecretModeSecured:
return "secured"
BindPort uint16 case SecretModeTLS:
PublicIPv4Port uint16 return "tls"
PublicIPv6Port uint16
StatsPort uint16
BindIP net.IP
PublicIPv4 net.IP
PublicIPv6 net.IP
StatsIP net.IP
StatsD struct {
Addr net.Addr
Prefix string
Tags map[string]string
TagsFormat statsd.TagFormat
Enabled bool
} }
Secret []byte return "tls"
AdTag []byte
} }
// URLs contains links to the proxy (tg://, t.me) and their QR codes. const (
type URLs struct { SecretModeSimple SecretMode = iota
TG string `json:"tg_url"` SecretModeSecured
TMe string `json:"tme_url"` SecretModeTLS
TGQRCode string `json:"tg_qrcode"` )
TMeQRCode string `json:"tme_qrcode"`
type PreferIP uint8
const (
PreferIPv4 PreferIP = iota
PreferIPv6
)
const SimpleSecretLength = 16
type OptionType uint8
const (
OptionTypeDebug OptionType = iota
OptionTypeVerbose
OptionTypePreferIP
OptionTypeBind
OptionTypePublicIPv4
OptionTypePublicIPv6
OptionTypeStatsBind
OptionTypeStatsNamespace
OptionTypeStatsdAddress
OptionTypeStatsdTagsFormat
OptionTypeStatsdTags
OptionTypeWriteBufferSize
OptionTypeReadBufferSize
OptionTypeCloakPort
OptionTypeAntiReplayMaxSize
OptionTypeMultiplexPerConnection
OptionTypeNTPServers
OptionTypeSecret
OptionTypeAdtag
)
type Config struct {
Bind *net.TCPAddr `json:"bind"`
PublicIPv4 *net.TCPAddr `json:"public_ipv4"`
PublicIPv6 *net.TCPAddr `json:"public_ipv6"`
StatsBind *net.TCPAddr `json:"stats_bind"`
StatsdAddr *net.TCPAddr `json:"stats_addr"`
StatsdTagsFormat *statsd.TagFormat `json:"statsd_tags_format"`
StatsNamespace string `json:"stats_namespace"`
CloakHost string `json:"cloak_host"`
StatsdTags map[string]string `json:"statsd_tags"`
WriteBuffer int `json:"write_buffer"`
ReadBuffer int `json:"read_buffer"`
CloakPort int `json:"cloak_port"`
AntiReplayMaxSize int `json:"anti_replay_max_size"`
MultiplexPerConnection int `json:"multiplex_per_connection"`
Debug bool `json:"debug"`
Verbose bool `json:"verbose"`
SecretMode SecretMode `json:"secret_mode"`
PreferIP PreferIP `json:"prefer_ip"`
NTPServers []string `json:"ntp_servers"`
Secret []byte `json:"secret"`
AdTag []byte `json:"adtag"`
} }
// IPURLs contains links to both ipv4 and ipv6 of the proxy. func (c *Config) ClientReadBuffer() int {
type IPURLs struct { return c.ReadBuffer
IPv4 URLs `json:"ipv4"`
IPv6 URLs `json:"ipv6"`
BotSecret string `json:"secret_for_mtproxybot"`
} }
// BindAddr returns connection for this server to bind to. func (c *Config) ClientWriteBuffer() int {
func (c *Config) BindAddr() string { return c.WriteBuffer
return getAddr(c.BindIP, c.BindPort)
} }
// StatAddr returns connection string to the stats API. func (c *Config) MiddleProxyMode() bool {
func (c *Config) StatAddr() string {
return getAddr(c.StatsIP, c.StatsPort)
}
// UseMiddleProxy defines if this proxy has to connect middle proxies
// which supports promoted channels or directly access Telegram.
func (c *Config) UseMiddleProxy() bool {
return len(c.AdTag) > 0 return len(c.AdTag) > 0
} }
// BotSecretString returns secret string which should work with MTProxybot. func (c *Config) ProxyReadBuffer() int {
func (c *Config) BotSecretString() string { value := c.ReadBuffer
return hex.EncodeToString(c.Secret)
if c.MiddleProxyMode() {
value = c.adjustProxyValue(value)
}
return value
} }
// SecretString returns a secret in a form entered on the start of the func (c *Config) ProxyWriteBuffer() int {
// application. value := c.WriteBuffer
func (c *Config) SecretString() string {
secret := c.BotSecretString() if c.MiddleProxyMode() {
if c.SecureMode { value = c.adjustProxyValue(value)
return "dd" + secret
} }
return secret
return value
} }
// GetURLs returns configured IPURLs instance with links to this server. func (c *Config) adjustProxyValue(value int) int {
func (c *Config) GetURLs() IPURLs { if c.MultiplexPerConnection == 0 {
urls := IPURLs{} return value
secret := c.SecretString()
if c.PublicIPv4 != nil {
urls.IPv4 = getURLs(c.PublicIPv4, c.PublicIPv4Port, secret)
} }
if c.PublicIPv6 != nil {
urls.IPv6 = getURLs(c.PublicIPv6, c.PublicIPv6Port, secret)
}
urls.BotSecret = c.BotSecretString()
return urls fvalue := float64(value)
newValue := fvalue * 2 * math.Log(float64(c.MultiplexPerConnection))
newValue = math.Ceil(newValue)
newValue = math.Max(fvalue, newValue)
return int(newValue)
} }
func getAddr(host fmt.Stringer, port uint16) string { type Opt struct {
return net.JoinHostPort(host.String(), strconv.Itoa(int(port))) Option OptionType
Value interface{}
} }
// NewConfig returns new configuration. If required, it manages and var C = Config{}
// fetches data from external sources. Parameters passed to this
// function, should come from command line arguments. func Init(options ...Opt) error { // nolint: gocyclo, funlen, cyclop
func NewConfig(debug, verbose bool, // nolint: gocyclo for _, opt := range options {
bindIP net.IP, bindPort uint16, switch opt.Option {
publicIPv4 net.IP, PublicIPv4Port uint16, case OptionTypeDebug:
publicIPv6 net.IP, publicIPv6Port uint16, C.Debug = opt.Value.(bool)
statsIP net.IP, statsPort uint16, case OptionTypeVerbose:
secret, adtag string, C.Verbose = opt.Value.(bool)
statsdIP string, statsdPort uint16, statsdNetwork string, statsdPrefix string, case OptionTypePreferIP:
statsdTagsFormat string, statsdTags map[string]string) (*Config, error) { value := opt.Value.(string)
secureMode := false switch value {
if strings.HasPrefix(secret, "dd") && len(secret) == 34 { case "ipv4":
secureMode = true C.PreferIP = PreferIPv4
secret = strings.TrimPrefix(secret, "dd") case "ipv6":
} else if len(secret) != 32 { C.PreferIP = PreferIPv6
return nil, errors.New("Telegram demands secret of length 32") default:
return fmt.Errorf("incorrect direct IP mode %s", value)
}
case OptionTypeBind:
C.Bind = opt.Value.(*net.TCPAddr)
case OptionTypePublicIPv4:
C.PublicIPv4 = opt.Value.(*net.TCPAddr)
if C.PublicIPv4 == nil {
C.PublicIPv4 = &net.TCPAddr{}
}
case OptionTypePublicIPv6:
C.PublicIPv6 = opt.Value.(*net.TCPAddr)
if C.PublicIPv6 == nil {
C.PublicIPv6 = &net.TCPAddr{}
}
case OptionTypeStatsBind:
C.StatsBind = opt.Value.(*net.TCPAddr)
case OptionTypeStatsNamespace:
C.StatsNamespace = opt.Value.(string)
case OptionTypeStatsdAddress:
C.StatsdAddr = opt.Value.(*net.TCPAddr)
case OptionTypeStatsdTagsFormat:
value := opt.Value.(string)
switch value {
case "datadog":
C.StatsdTagsFormat = statsd.TagFormatDatadog
case "influxdb":
C.StatsdTagsFormat = statsd.TagFormatInfluxDB
default:
return fmt.Errorf("incorrect statsd tag %s", value)
}
case OptionTypeStatsdTags:
C.StatsdTags = opt.Value.(map[string]string)
case OptionTypeWriteBufferSize:
C.WriteBuffer = int(opt.Value.(units.Base2Bytes))
case OptionTypeReadBufferSize:
C.ReadBuffer = int(opt.Value.(units.Base2Bytes))
case OptionTypeCloakPort:
C.CloakPort = int(opt.Value.(uint16))
case OptionTypeAntiReplayMaxSize:
C.AntiReplayMaxSize = int(opt.Value.(units.Base2Bytes))
case OptionTypeMultiplexPerConnection:
C.MultiplexPerConnection = int(opt.Value.(uint))
case OptionTypeNTPServers:
C.NTPServers = opt.Value.([]string)
if len(C.NTPServers) == 0 {
return errors.New("ntp server list is empty")
}
case OptionTypeSecret:
C.Secret = opt.Value.([]byte)
case OptionTypeAdtag:
C.AdTag = opt.Value.([]byte)
default:
return fmt.Errorf("unknown tag %v", opt.Option)
}
} }
secretBytes, err := hex.DecodeString(secret)
switch {
case len(C.Secret) == 1+SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xdd}):
C.SecretMode = SecretModeSecured
C.Secret = bytes.TrimPrefix(C.Secret, []byte{0xdd})
case len(C.Secret) > SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xee}):
C.SecretMode = SecretModeTLS
secret := bytes.TrimPrefix(C.Secret, []byte{0xee})
C.Secret = secret[:SimpleSecretLength]
C.CloakHost = string(secret[SimpleSecretLength:])
case len(C.Secret) == SimpleSecretLength:
C.SecretMode = SecretModeSimple
default:
return errors.New("incorrect secret")
}
if C.MultiplexPerConnection == 0 {
return errors.New("cannot use 0 clients per connection for multiplexing")
}
if C.CloakHost != "" {
if _, err := net.LookupHost(C.CloakHost); err != nil {
zap.S().Warnw("Cannot resolve address of host", "hostname", C.CloakHost, "error", err)
}
}
return nil
}
func InitPublicAddress(ctx context.Context) error {
if C.PublicIPv4.Port == 0 {
C.PublicIPv4.Port = C.Bind.Port
}
if C.PublicIPv6.Port == 0 {
C.PublicIPv6.Port = C.Bind.Port
}
foundAddress := C.PublicIPv4.IP != nil || C.PublicIPv6.IP != nil
if C.PublicIPv4.IP == nil {
ip, err := getGlobalIPv4(ctx)
if err != nil {
zap.S().Warnw("Cannot resolve public address", "error", err)
} else {
C.PublicIPv4.IP = ip
foundAddress = true
}
}
if C.PublicIPv6.IP == nil {
ip, err := getGlobalIPv6(ctx)
if err != nil {
zap.S().Warnw("Cannot resolve public address", "error", err)
} else {
C.PublicIPv6.IP = ip
foundAddress = true
}
}
if !foundAddress {
return errors.New("cannot resolve any public address")
}
return nil
}
func Printable() interface{} {
data, err := json.Marshal(C)
if err != nil { if err != nil {
return nil, errors.Annotate(err, "Cannot create config") panic(err)
} }
var adTagBytes []byte rv := map[string]interface{}{}
if len(adtag) != 0 { if err := json.Unmarshal(data, &rv); err != nil {
adTagBytes, err = hex.DecodeString(adtag) panic(err)
if err != nil {
return nil, errors.Annotate(err, "Cannot create config")
}
} }
if publicIPv4 == nil { return rv
publicIPv4, err = getGlobalIPv4()
if err != nil {
publicIPv4 = nil
} else if publicIPv4.To4() == nil {
return nil, errors.Errorf("IP %s is not IPv4", publicIPv4.String())
}
}
if PublicIPv4Port == 0 {
PublicIPv4Port = bindPort
}
if publicIPv6 == nil {
publicIPv6, err = getGlobalIPv6()
if err != nil {
publicIPv6 = nil
} else if publicIPv6.To4() != nil {
return nil, errors.Errorf("IP %s is not IPv6", publicIPv6.String())
}
}
if publicIPv6Port == 0 {
publicIPv6Port = bindPort
}
if statsIP == nil {
statsIP = publicIPv4
}
conf := &Config{
Debug: debug,
Verbose: verbose,
BindIP: bindIP,
BindPort: bindPort,
PublicIPv4: publicIPv4,
PublicIPv4Port: PublicIPv4Port,
PublicIPv6: publicIPv6,
PublicIPv6Port: publicIPv6Port,
StatsIP: statsIP,
StatsPort: statsPort,
Secret: secretBytes,
AdTag: adTagBytes,
SecureMode: secureMode,
}
if statsdIP != "" {
conf.StatsD.Enabled = true
conf.StatsD.Prefix = statsdPrefix
conf.StatsD.Tags = statsdTags
var addr net.Addr
hostPort := net.JoinHostPort(statsdIP, strconv.Itoa(int(statsdPort)))
switch statsdNetwork {
case "tcp":
addr, err = net.ResolveTCPAddr("tcp", hostPort)
case "udp":
addr, err = net.ResolveUDPAddr("udp", hostPort)
default:
err = errors.Errorf("Unknown network %s", statsdNetwork)
}
if err != nil {
return nil, errors.Annotate(err, "Cannot resolve statsd address")
}
conf.StatsD.Addr = addr
switch statsdTagsFormat {
case "datadog":
conf.StatsD.TagsFormat = statsd.Datadog
case "influxdb":
conf.StatsD.TagsFormat = statsd.InfluxDB
case "":
default:
return nil, errors.Errorf("Unknown tags format %s", statsdTagsFormat)
}
}
return conf, nil
} }
+52 -13
View File
@@ -1,38 +1,77 @@
package config package config
import ( import (
"context"
"fmt"
"io"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
"strings" "strings"
"time"
"github.com/juju/errors"
) )
func getGlobalIPv4() (net.IP, error) { const (
return fetchIP("https://v4.ifconfig.co/ip") ifconfigAddress = "https://ifconfig.co/ip"
} ifconfigTimeout = 10 * time.Second
)
func getGlobalIPv6() (net.IP, error) { func getGlobalIPv4(ctx context.Context) (net.IP, error) {
return fetchIP("https://v6.ifconfig.co/ip") ip, err := fetchIP(ctx, "tcp4")
if err != nil || ip.To4() == nil {
return nil, fmt.Errorf("cannot find public ipv4 address: %w", err)
}
return ip, nil
} }
func getGlobalIPv6(ctx context.Context) (net.IP, error) {
ip, err := fetchIP(ctx, "tcp6")
if err != nil || ip.To4() != nil {
return nil, fmt.Errorf("cannot find public ipv6 address: %w", err)
}
return ip, nil
}
func fetchIP(ctx context.Context, network string) (net.IP, error) {
dialer := &net.Dialer{FallbackDelay: -1}
client := &http.Client{
Jar: nil,
Timeout: ifconfigTimeout,
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, network, addr)
},
},
}
func fetchIP(url string) (net.IP, error) { req, err := http.NewRequest("GET", ifconfigAddress, nil)
resp, err := http.Get(url)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("cannot create a request: %w", err)
} }
defer resp.Body.Close() // nolint: errcheck
resp, err := client.Do(req.WithContext(ctx))
if err != nil {
if resp != nil {
io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck
}
return nil, fmt.Errorf("cannot perform a request: %w", err)
}
defer resp.Body.Close()
respDataBytes, err := ioutil.ReadAll(resp.Body) respDataBytes, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("cannot read response body: %w", err)
} }
respData := strings.TrimSpace(string(respDataBytes)) respData := strings.TrimSpace(string(respDataBytes))
ip := net.ParseIP(respData) ip := net.ParseIP(respData)
if ip == nil { if ip == nil {
return nil, errors.Errorf("ifconfig.co returns incorrect IP %s", respData) return nil, fmt.Errorf("ifconfig.co returns incorrect IP %s", respData)
} }
return ip, nil return ip, nil
+53 -12
View File
@@ -1,23 +1,64 @@
package config package config
import ( import (
"encoding/hex"
"net" "net"
"net/url" "net/url"
"strconv" "strconv"
) )
func getURLs(addr net.IP, port uint16, secret string) (urls URLs) { type URLs struct {
TG string `json:"tg_url"`
TMe string `json:"tme_url"`
TGQRCode string `json:"tg_qrcode"`
TMeQRCode string `json:"tme_qrcode"`
}
type IPURLs struct {
IPv4 *URLs `json:"ipv4,omitempty"`
IPv6 *URLs `json:"ipv6,omitempty"`
BotSecret string `json:"secret_for_mtproxybot"`
}
func GetURLs() (urls IPURLs) {
secret := ""
switch C.SecretMode {
case SecretModeSimple:
secret = hex.EncodeToString(C.Secret)
case SecretModeSecured:
secret = "dd" + hex.EncodeToString(C.Secret)
case SecretModeTLS:
secret = "ee" + hex.EncodeToString(C.Secret) + hex.EncodeToString([]byte(C.CloakHost))
}
if C.PublicIPv4.IP != nil {
urls.IPv4 = makeURLs(C.PublicIPv4, secret)
}
if C.PublicIPv6.IP != nil {
urls.IPv6 = makeURLs(C.PublicIPv6, secret)
}
urls.BotSecret = hex.EncodeToString(C.Secret)
return urls
}
func makeURLs(addr *net.TCPAddr, secret string) *URLs {
urls := &URLs{}
values := url.Values{} values := url.Values{}
values.Set("server", addr.String()) values.Set("server", addr.IP.String())
values.Set("port", strconv.Itoa(int(port))) values.Set("port", strconv.Itoa(addr.Port))
values.Set("secret", secret) values.Set("secret", secret)
urls.TG = makeTGURL(values) return &URLs{
urls.TMe = makeTMeURL(values) TG: makeTGURL(values),
urls.TGQRCode = makeQRCodeURL(urls.TG) TMe: makeTMeURL(values),
urls.TMeQRCode = makeQRCodeURL(urls.TG) TGQRCode: makeQRCodeURL(urls.TG),
TMeQRCode: makeQRCodeURL(urls.TMe),
return }
} }
func makeTGURL(values url.Values) string { func makeTGURL(values url.Values) string {
@@ -42,7 +83,7 @@ func makeTMeURL(values url.Values) string {
} }
func makeQRCodeURL(data string) string { func makeQRCodeURL(data string) string {
QRURL := url.URL{ qr := url.URL{
Scheme: "https", Scheme: "https",
Host: "api.qrserver.com", Host: "api.qrserver.com",
Path: "v1/create-qr-code", Path: "v1/create-qr-code",
@@ -52,7 +93,7 @@ func makeQRCodeURL(data string) string {
values.Set("qzone", "4") values.Set("qzone", "4")
values.Set("format", "svg") values.Set("format", "svg")
values.Set("data", data) values.Set("data", data)
QRURL.RawQuery = values.Encode() qr.RawQuery = values.Encode()
return QRURL.String() return qr.String()
} }
+6
View File
@@ -0,0 +1,6 @@
package conntypes
type ConnectionAcks struct {
Simple bool
Quick bool
}
+5
View File
@@ -0,0 +1,5 @@
package conntypes
type DC int16
const DCDefaultIdx DC = 1
+24
View File
@@ -0,0 +1,24 @@
package conntypes
import (
"crypto/rand"
"encoding/hex"
)
const ConnIDLength = 8
type ConnID [ConnIDLength]byte
func (c ConnID) String() string {
return hex.EncodeToString(c[:])
}
func NewConnID() ConnID {
var id ConnID
if _, err := rand.Read(id[:]); err != nil {
panic(err)
}
return id
}
+3
View File
@@ -0,0 +1,3 @@
package conntypes
type Packet []byte
+22
View File
@@ -0,0 +1,22 @@
package conntypes
type ConnectionProtocol uint8
func (c ConnectionProtocol) String() string {
switch c {
case ConnectionProtocolAny:
return "any"
case ConnectionProtocolIPv4:
return "ipv4"
case ConnectionProtocolIPv6:
return "ipv6"
}
return "ipv6"
}
const (
ConnectionProtocolIPv4 ConnectionProtocol = 1
ConnectionProtocolIPv6 = ConnectionProtocolIPv4 << 1
ConnectionProtocolAny = ConnectionProtocolIPv4 | ConnectionProtocolIPv6
)
+29
View File
@@ -0,0 +1,29 @@
package conntypes
type ConnectionType uint8
const (
ConnectionTypeUnknown ConnectionType = iota
ConnectionTypeAbridged
ConnectionTypeIntermediate
ConnectionTypeSecure
)
var (
ConnectionTagAbridged = []byte{0xef, 0xef, 0xef, 0xef}
ConnectionTagIntermediate = []byte{0xee, 0xee, 0xee, 0xee}
ConnectionTagSecure = []byte{0xdd, 0xdd, 0xdd, 0xdd}
)
func (t ConnectionType) Tag() []byte {
switch t {
case ConnectionTypeAbridged:
return ConnectionTagAbridged
case ConnectionTypeIntermediate:
return ConnectionTagIntermediate
case ConnectionTypeSecure, ConnectionTypeUnknown:
return ConnectionTagSecure
}
return ConnectionTagSecure
}
+14
View File
@@ -0,0 +1,14 @@
package conntypes
import (
"net"
"go.uber.org/zap"
)
type Wrap interface {
Conn() net.Conn
Logger() *zap.SugaredLogger
LocalAddr() *net.TCPAddr
RemoteAddr() *net.TCPAddr
}
+41
View File
@@ -0,0 +1,41 @@
package conntypes
import "io"
type PacketAckReader interface {
Read(*ConnectionAcks) (Packet, error)
}
type PacketAckWriter interface {
Write(Packet, *ConnectionAcks) error
}
type PacketAckCloser interface {
io.Closer
}
type PacketAckReadCloser interface {
PacketAckReader
PacketAckCloser
}
type PacketAckWriteCloser interface {
PacketAckWriter
PacketAckCloser
}
type PacketAckReadWriter interface {
PacketAckReader
PacketAckWriter
}
type PacketAckReadWriteCloser interface {
PacketAckReader
PacketAckWriter
PacketAckCloser
}
type PacketAckFullReadWriteCloser interface {
Wrap
PacketAckReadWriteCloser
}
+51
View File
@@ -0,0 +1,51 @@
package conntypes
import "io"
type BasePacketReader interface {
Read() (Packet, error)
}
type BasePacketWriter interface {
Write(Packet) error
}
type PacketReader interface {
Wrap
BasePacketReader
}
type PacketWriter interface {
Wrap
BasePacketWriter
}
type PacketCloser interface {
Wrap
io.Closer
}
type PacketReadCloser interface {
Wrap
BasePacketReader
io.Closer
}
type PacketWriteCloser interface {
Wrap
BasePacketWriter
io.Closer
}
type PacketReadWriter interface {
Wrap
BasePacketWriter
BasePacketReader
}
type PacketReadWriteCloser interface {
Wrap
BasePacketWriter
BasePacketReader
io.Closer
}
+56
View File
@@ -0,0 +1,56 @@
package conntypes
import (
"io"
"time"
)
type BaseStreamReaderWithTimeout interface {
ReadTimeout([]byte, time.Duration) (int, error)
}
type BaseStreamWriterWithTimeout interface {
WriteTimeout([]byte, time.Duration) (int, error)
}
type StreamReader interface {
Wrap
io.Reader
BaseStreamReaderWithTimeout
}
type StreamWriter interface {
Wrap
io.Writer
BaseStreamWriterWithTimeout
}
type StreamCloser interface {
Wrap
io.Closer
}
type StreamReadCloser interface {
Wrap
io.ReadCloser
BaseStreamReaderWithTimeout
}
type StreamWriteCloser interface {
Wrap
io.WriteCloser
BaseStreamWriterWithTimeout
}
type StreamReadWriter interface {
Wrap
io.ReadWriter
BaseStreamReaderWithTimeout
}
type StreamReadWriteCloser interface {
Wrap
io.ReadWriteCloser
BaseStreamReaderWithTimeout
BaseStreamWriterWithTimeout
}
+122
View File
@@ -0,0 +1,122 @@
package faketls
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"strconv"
"time"
"github.com/9seconds/mtg/antireplay"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/obfuscated2"
"github.com/9seconds/mtg/protocol"
"github.com/9seconds/mtg/stats"
"github.com/9seconds/mtg/tlstypes"
"github.com/9seconds/mtg/wrappers/stream"
)
type ClientProtocol struct {
obfuscated2.ClientProtocol
}
func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error) {
rewinded := stream.NewRewind(socket)
bufferedReader := bufio.NewReader(rewinded)
for _, expected := range faketlsStartBytes {
if actual, err := bufferedReader.ReadByte(); err != nil || actual != expected {
rewinded.Rewind()
c.cloakHost(rewinded)
return nil, errors.New("failed first bytes of tls handshake")
}
}
rewinded.Rewind()
rewinded = stream.NewRewind(rewinded)
if err := c.tlsHandshake(rewinded); err != nil {
rewinded.Rewind()
c.cloakHost(rewinded)
return nil, fmt.Errorf("failed tls handshake: %w", err)
}
conn := stream.NewFakeTLS(socket)
conn, err := c.ClientProtocol.Handshake(conn)
if err != nil {
return nil, err // nolint: wrapcheck
}
return conn, err // nolint: wrapcheck
}
func (c *ClientProtocol) tlsHandshake(conn io.ReadWriter) error {
helloRecord, err := tlstypes.ReadRecord(conn)
if err != nil {
return fmt.Errorf("cannot read initial record: %w", err)
}
buf := &bytes.Buffer{}
helloRecord.Data.WriteBytes(buf)
clientHello, err := tlstypes.ParseClientHello(buf.Bytes())
if err != nil {
return fmt.Errorf("cannot parse client hello: %w", err)
}
digest := clientHello.Digest()
for i := 0; i < len(digest)-4; i++ {
if digest[i] != 0 {
return errBadDigest
}
}
timestamp := int64(binary.LittleEndian.Uint32(digest[len(digest)-4:]))
createdAt := time.Unix(timestamp, 0)
timeDiff := time.Since(createdAt)
if (timeDiff > TimeSkew || timeDiff < -TimeSkew) && timestamp > TimeFromBoot {
return errBadTime
}
if antireplay.Cache.HasTLS(clientHello.Random[:]) {
stats.Stats.ReplayDetected()
return errors.New("replay attack is detected")
}
antireplay.Cache.AddTLS(clientHello.Random[:])
serverHello := tlstypes.NewServerHello(clientHello)
serverHelloPacket := serverHello.WelcomePacket()
if _, err := conn.Write(serverHelloPacket); err != nil {
return fmt.Errorf("cannot send welcome packet: %w", err)
}
return nil
}
func (c *ClientProtocol) cloakHost(clientConn io.ReadWriteCloser) {
stats.Stats.CloakedRequest()
addr := net.JoinHostPort(config.C.CloakHost, strconv.Itoa(config.C.CloakPort))
hostConn, err := net.Dial("tcp", addr)
if err != nil {
return
}
cloak(clientConn, hostConn)
}
func MakeClientProtocol() protocol.ClientProtocol {
return &ClientProtocol{}
}
+73
View File
@@ -0,0 +1,73 @@
package faketls
import (
"context"
"io"
"sync"
"time"
"github.com/9seconds/mtg/wrappers/rwc"
)
const (
cloakLastActivityTimeout = 5 * time.Second
cloakMaxTimeout = 30 * time.Second
)
func cloak(one, another io.ReadWriteCloser) {
defer func() {
one.Close()
another.Close()
}()
channelPing := make(chan struct{}, 1)
ctx, cancel := context.WithCancel(context.Background())
one = rwc.NewPing(ctx, one, channelPing)
another = rwc.NewPing(ctx, another, channelPing)
wg := &sync.WaitGroup{}
wg.Add(2)
go cloakPipe(one, another, wg)
go cloakPipe(another, one, wg)
go func() {
wg.Wait()
cancel()
}()
go func() {
lastActivityTimer := time.NewTimer(cloakLastActivityTimeout)
defer lastActivityTimer.Stop()
maxTimer := time.NewTimer(cloakMaxTimeout)
defer maxTimer.Stop()
for {
select {
case <-channelPing:
lastActivityTimer.Stop()
lastActivityTimer = time.NewTimer(cloakLastActivityTimeout)
case <-ctx.Done():
return
case <-lastActivityTimer.C:
cancel()
return
case <-maxTimer.C:
cancel()
return
}
}
}()
<-ctx.Done()
}
func cloakPipe(one io.Writer, another io.Reader, wg *sync.WaitGroup) {
defer wg.Done()
io.Copy(one, another) // nolint: errcheck
}
+30
View File
@@ -0,0 +1,30 @@
package faketls
import (
"errors"
"time"
)
const (
TimeSkew = 5 * time.Second
TimeFromBoot = 24 * 60 * 60
)
var (
errBadDigest = errors.New("bad digest")
errBadTime = errors.New("bad time")
faketlsStartBytes = [...]byte{
0x16,
0x03,
0x01,
0x02,
0x00,
0x01,
0x00,
0x01,
0xfc,
0x03,
0x03,
}
)
+25
View File
@@ -0,0 +1,25 @@
module github.com/9seconds/mtg
go 1.13
require (
github.com/VictoriaMetrics/fastcache v1.5.7
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15
github.com/beevik/ntp v0.3.0
github.com/golang/snappy v0.0.3 // indirect
github.com/prometheus/client_golang v1.9.0
github.com/prometheus/common v0.18.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/smira/go-statsd v1.3.2
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
golang.org/x/mod v0.4.1 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04
golang.org/x/tools v0.1.0 // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6
honnef.co/go/tools v0.0.1-2020.1.3 // indirect
)
+472
View File
@@ -0,0 +1,472 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw=
github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4=
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU=
github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y=
github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smira/go-statsd v1.3.2 h1:1EeuzxNZ/TD9apbTOFSM9nulqfcsQFmT4u1A2DREabI=
github.com/smira/go-statsd v1.3.2/go.mod h1:1srXJ9/pbnN04G8f4F1jUzsGOnwkPKXciyqpewGlkC4=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04 h1:cEhElsAv9LUt9ZUUocxzWe05oFLVd+AA2nstydTeI8g=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
+172
View File
@@ -0,0 +1,172 @@
package hub
import (
"fmt"
"math/rand"
"sync"
"time"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/mtproto"
"github.com/9seconds/mtg/mtproto/rpc"
"github.com/9seconds/mtg/protocol"
"go.uber.org/zap"
)
const connectionTTL = time.Hour
type connection struct {
conn conntypes.PacketReadWriteCloser
proxyConns map[string]*ProxyConn
closeOnce sync.Once
proxyConnsMutex sync.RWMutex
id int
logger *zap.SugaredLogger
channelDone chan struct{}
channelWrite chan conntypes.Packet
channelRead chan *rpc.ProxyResponse
channelConnAttach chan *ProxyConn
channelConnDetach chan conntypes.ConnID
}
func (c *connection) run() { // nolint: cyclop
defer c.Close()
ttl := time.NewTimer(connectionTTL)
defer ttl.Stop()
for {
select {
case <-c.channelDone:
for _, v := range c.proxyConns {
v.Close()
}
return
case <-ttl.C:
c.logger.Debugw("Closing connection by TTL")
c.Close()
case resp := <-c.channelRead:
if channel, ok := c.proxyConns[string(resp.ConnID[:])]; ok {
if resp.Type == rpc.ProxyResponseTypeCloseExt {
channel.Close()
} else {
channel.put(resp)
}
}
case packet := <-c.channelWrite:
if err := c.conn.Write(packet); err != nil {
c.logger.Debugw("Cannot write packet", "error", err)
c.Close()
}
case conn := <-c.channelConnAttach:
c.proxyConnsMutex.Lock()
c.proxyConns[string(conn.req.ConnID[:])] = conn
c.proxyConnsMutex.Unlock()
conn.channelWrite = c.channelWrite
case connID := <-c.channelConnDetach:
if conn, ok := c.proxyConns[string(connID[:])]; ok {
c.proxyConnsMutex.Lock()
delete(c.proxyConns, string(connID[:]))
c.proxyConnsMutex.Unlock()
conn.Close()
}
}
}
}
func (c *connection) readLoop() {
for {
packet, err := c.conn.Read()
if err != nil {
c.logger.Debugw("Cannot read packet", "error", err)
c.Close()
return
}
response, err := rpc.ParseProxyResponse(packet)
if err != nil {
c.logger.Debugw("Failed response", "error", err)
continue
}
select {
case <-c.channelDone:
return
case c.channelRead <- response:
}
}
}
func (c *connection) Close() {
c.closeOnce.Do(func() {
c.logger.Debugw("Closing connection")
close(c.channelDone)
c.conn.Close()
})
}
func (c *connection) Done() bool {
select {
case <-c.channelDone:
return true
default:
return c.Len() == 0
}
}
func (c *connection) Len() int {
c.proxyConnsMutex.RLock()
defer c.proxyConnsMutex.RUnlock()
return len(c.proxyConns)
}
func (c *connection) Attach(conn *ProxyConn) error {
select {
case <-c.channelDone:
return ErrClosed
case c.channelConnAttach <- conn:
return nil
}
}
func (c *connection) Detach(connID conntypes.ConnID) {
select {
case <-c.channelDone:
case c.channelConnDetach <- connID:
}
}
func newConnection(req *protocol.TelegramRequest) (*connection, error) {
conn, err := mtproto.TelegramProtocol(req)
if err != nil {
return nil, fmt.Errorf("cannot create a new connection: %w", err)
}
id := rand.Int() // nolint: gosec
rv := &connection{
conn: conn,
id: id,
logger: zap.S().Named("hub-connection").With("id", id,
"dc", req.ClientProtocol.DC(),
"protocol", req.ClientProtocol.ConnectionProtocol()),
proxyConns: make(map[string]*ProxyConn),
channelRead: make(chan *rpc.ProxyResponse, 1),
channelDone: make(chan struct{}),
channelWrite: make(chan conntypes.Packet),
channelConnAttach: make(chan *ProxyConn),
channelConnDetach: make(chan conntypes.ConnID),
}
go rv.readLoop()
go rv.run()
return rv, nil
}
+70
View File
@@ -0,0 +1,70 @@
package hub
import (
"fmt"
"sort"
"github.com/9seconds/mtg/config"
)
type connectionList struct {
connections []*connection
}
func (c *connectionList) get(conn *ProxyConn) (*connection, error) {
if len(c.connections) > 0 && c.connections[0].Len() < config.C.MultiplexPerConnection {
if err := c.connections[0].Attach(conn); err == nil {
return c.connections[0], nil
}
}
newConn, err := newConnection(conn.req)
if err != nil {
return nil, fmt.Errorf("cannot allocate a new connection: %w", err)
}
if err = newConn.Attach(conn); err != nil {
newConn.Close()
return nil, fmt.Errorf("cannot attach to the newly created connection: %w", err)
}
c.connections = append(c.connections, newConn)
lastIndex := len(c.connections) - 1
c.connections[0], c.connections[lastIndex] = c.connections[lastIndex], c.connections[0]
return newConn, nil
}
func (c *connectionList) gc() {
prevLen := len(c.connections)
if prevLen == 0 {
return
}
for i := len(c.connections) - 1; i >= 0; i-- {
lastIndex := len(c.connections) - 1
if c.connections[i].Done() {
c.connections[i].Close()
if len(c.connections)-1 == i {
c.connections = c.connections[:lastIndex]
} else {
c.connections[i], c.connections[lastIndex] = c.connections[lastIndex], c.connections[i]
}
}
}
if prevLen != len(c.connections) {
c.sort()
}
}
func (c *connectionList) sort() {
if len(c.connections) > 1 {
sort.Slice(c.connections, func(i, j int) bool {
return c.connections[i].Len() < c.connections[j].Len()
})
}
}
+40
View File
@@ -0,0 +1,40 @@
package hub
import (
"context"
"sync"
"github.com/9seconds/mtg/protocol"
)
type hub struct {
muxes map[int32]*mux
mutex sync.RWMutex
ctx context.Context
}
func (h *hub) Register(req *protocol.TelegramRequest) (*ProxyConn, error) {
return h.getMux(req).Get(req)
}
func (h *hub) getMux(req *protocol.TelegramRequest) *mux {
var key int32 = 32767 + int32(req.ClientProtocol.DC()) + 100000*int32(req.ClientProtocol.ConnectionProtocol())
h.mutex.RLock()
m, ok := h.muxes[key]
h.mutex.RUnlock()
if !ok {
h.mutex.Lock()
m, ok = h.muxes[key]
if !ok {
m = newMux(h.ctx)
h.muxes[key] = m
}
h.mutex.Unlock()
}
return m
}
+24
View File
@@ -0,0 +1,24 @@
package hub
import (
"context"
"errors"
"sync"
)
var (
ErrTimeout = errors.New("timeout")
ErrClosed = errors.New("context is closed")
Hub Interface
initOnce sync.Once
)
func Init(ctx context.Context) {
initOnce.Do(func() {
Hub = &hub{
muxes: make(map[int32]*mux),
ctx: ctx,
}
})
}
+7
View File
@@ -0,0 +1,7 @@
package hub
import "github.com/9seconds/mtg/protocol"
type Interface interface {
Register(*protocol.TelegramRequest) (*ProxyConn, error)
}
+90
View File
@@ -0,0 +1,90 @@
package hub
import (
"context"
"time"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/protocol"
)
const muxGCEvery = time.Minute
type muxNewRequest struct {
req *protocol.TelegramRequest
resp chan<- muxNewResponse
}
type muxNewResponse struct {
conn *ProxyConn
err error
}
type mux struct {
connections connectionList
clients map[string]*connection
ctx context.Context
channelClosed chan conntypes.ConnID
channelNew chan muxNewRequest
}
func (m *mux) run() {
gcTicker := time.NewTicker(muxGCEvery)
defer gcTicker.Stop()
for {
select {
case <-m.ctx.Done():
for _, v := range m.clients {
v.Close()
}
return
case <-gcTicker.C:
m.connections.gc()
case req := <-m.channelNew:
m.connections.gc()
proxyConn := newProxyConn(req.req, m.channelClosed)
conn, err := m.connections.get(proxyConn)
if err == nil {
m.clients[string(req.req.ConnID[:])] = conn
}
req.resp <- muxNewResponse{
conn: proxyConn,
err: err,
}
close(req.resp)
case connID := <-m.channelClosed:
if conn, ok := m.clients[string(connID[:])]; ok {
conn.Detach(connID)
delete(m.clients, string(connID[:]))
}
}
}
}
func (m *mux) Get(req *protocol.TelegramRequest) (*ProxyConn, error) {
resp := make(chan muxNewResponse)
m.channelNew <- muxNewRequest{
req: req,
resp: resp,
}
rv := <-resp
return rv.conn, rv.err
}
func newMux(ctx context.Context) *mux {
m := &mux{
ctx: ctx,
clients: make(map[string]*connection),
channelClosed: make(chan conntypes.ConnID, 1),
channelNew: make(chan muxNewRequest),
}
go m.run()
return m
}
+79
View File
@@ -0,0 +1,79 @@
package hub
import (
"sync"
"time"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/mtproto/rpc"
"github.com/9seconds/mtg/protocol"
)
const (
proxyConnWriteTimeout = 2 * time.Minute
proxyConnReadTimeout = 2 * time.Minute
proxyConnBackpressureAfter = 10
)
type ProxyConn struct {
closeOnce sync.Once
req *protocol.TelegramRequest
channelResponse chan *rpc.ProxyResponse
channelClosed chan<- conntypes.ConnID
channelWrite chan<- conntypes.Packet
channelDone chan struct{}
}
func (p *ProxyConn) Read() (*rpc.ProxyResponse, error) {
timer := time.NewTimer(proxyConnReadTimeout)
defer timer.Stop()
select {
case <-timer.C:
return nil, ErrTimeout
case <-p.channelDone:
return nil, ErrClosed
case packet := <-p.channelResponse:
return packet, nil
}
}
func (p *ProxyConn) Write(packet conntypes.Packet) error {
timer := time.NewTimer(proxyConnWriteTimeout)
defer timer.Stop()
select {
case <-timer.C:
return ErrTimeout
case <-p.channelDone:
return ErrClosed
case p.channelWrite <- packet:
return nil
}
}
func (p *ProxyConn) put(response *rpc.ProxyResponse) {
select {
case <-p.channelDone:
case p.channelResponse <- response:
}
}
func (p *ProxyConn) Close() {
p.closeOnce.Do(func() {
close(p.channelDone)
go func() {
p.channelClosed <- p.req.ConnID
}()
})
}
func newProxyConn(req *protocol.TelegramRequest, channelClosed chan<- conntypes.ConnID) *ProxyConn {
return &ProxyConn{
channelResponse: make(chan *rpc.ProxyResponse, proxyConnBackpressureAfter),
channelDone: make(chan struct{}),
channelClosed: channelClosed,
req: req,
}
}
+153 -165
View File
@@ -1,202 +1,190 @@
package main package main
//go:generate scripts/generate_version.sh
import ( import (
"encoding/json"
"fmt"
"io"
"math/rand" "math/rand"
"os" "os"
"syscall" "runtime/debug"
"strings"
"time" "time"
"github.com/juju/errors" "github.com/9seconds/mtg/cli"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
kingpin "gopkg.in/alecthomas/kingpin.v2"
"github.com/9seconds/mtg/config" "github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/ntp" "github.com/9seconds/mtg/utils"
"github.com/9seconds/mtg/proxy" kingpin "gopkg.in/alecthomas/kingpin.v2"
"github.com/9seconds/mtg/stats"
) )
var version = "dev" // has to be set by ldflags
var ( var (
app = kingpin.New("mtg", "Simple MTPROTO proxy.") app = kingpin.New("mtg", "Simple MTPROTO proxy.")
debug = app.Flag("debug", "Run in debug mode."). generateSecretCommand = app.Command("generate-secret",
"Generate new secret")
generateCloakHost = generateSecretCommand.Flag("cloak-host",
"A host to use for TLS cloaking.").
Short('c').
Default("storage.googleapis.com").
String()
generateSecretType = generateSecretCommand.Arg("type",
"A type of secret to generate. Valid options are 'simple', 'secured' and 'tls'").
Required().
Enum("simple", "secured", "tls")
runCommand = app.Command("run",
"Run new proxy instance")
runDebug = runCommand.Flag("debug",
"Run in debug mode.").
Short('d'). Short('d').
Envar("MTG_DEBUG"). Envar("MTG_DEBUG").
Bool() Bool()
verbose = app.Flag("verbose", "Run in verbose mode."). runVerbose = runCommand.Flag("verbose",
"Run in verbose mode.").
Short('v'). Short('v').
Envar("MTG_VERBOSE"). Envar("MTG_VERBOSE").
Bool() Bool()
runPreferIP = runCommand.Flag("prefer-ip",
bindIP = app.Flag("bind-ip", "Which IP to bind to."). "Prefer this IP protocol if possible. Valid options are 'ipv4' and 'ipv6'").
Envar("MTG_PREFER_DIRECT_IP").
Default("ipv6").
Enum("ipv4", "ipv6")
runBind = runCommand.Flag("bind",
"Host:Port to bind proxy to.").
Short('b'). Short('b').
Envar("MTG_IP"). Envar("MTG_BIND").
Default("127.0.0.1"). Default("0.0.0.0:3128").
IP() TCP()
bindPort = app.Flag("bind-port", "Which port to bind to."). runPublicIPv4 = runCommand.Flag("public-ipv4",
Short('p'). "Which IPv4 host:port to use.").
Envar("MTG_PORT"). Short('4').
Default("3128"). Envar("MTG_IPV4").
Uint16() TCP()
runPublicIPv6 = runCommand.Flag("public-ipv6",
publicIPv4 = app.Flag("public-ipv4", "Which IPv4 address is public."). "Which IPv6 host:port to use.").
Short('4'). Short('6').
Envar("MTG_IPV4"). Envar("MTG_IPV6").
IP() TCP()
publicIPv4Port = app.Flag("public-ipv4-port", "Which IPv4 port is public. Default is 'bind-port' value."). runStatsBind = runCommand.Flag("stats-bind",
Envar("MTG_IPV4_PORT"). "Which Host:Port to bind stats server to.").
Uint16()
publicIPv6 = app.Flag("public-ipv6", "Which IPv6 address is public.").
Short('6').
Envar("MTG_IPV6").
IP()
publicIPv6Port = app.Flag("public-ipv6-port", "Which IPv6 port is public. Default is 'bind-port' value.").
Envar("MTG_IPV6_PORT").
Uint16()
statsIP = app.Flag("stats-ip", "Which IP bind stats server to.").
Short('t'). Short('t').
Envar("MTG_STATS_IP"). Envar("MTG_STATS_BIND").
Default("127.0.0.1"). Default("127.0.0.1:3129").
IP() TCP()
statsPort = app.Flag("stats-port", "Which port bind stats to."). runStatsNamespace = runCommand.Flag("stats-namespace",
Short('q'). "Which namespace to use for Prometheus.").
Envar("MTG_STATS_PORT"). Envar("MTG_STATS_NAMESPACE").
Default("3129"). Default("mtg").
Uint16() String()
runStatsdAddress = runCommand.Flag("statsd-addr",
statsdIP = app.Flag("statsd-ip", "Which IP should we use for working with statsd."). "Host:port of statsd server").
Envar("MTG_STATSD_IP"). Envar("MTG_STATSD_ADDR").
String() TCP()
statsdPort = app.Flag("statsd-port", "Which port should we use for working with statsd."). runStatsdTagsFormat = runCommand.Flag("statsd-tags-format",
Envar("MTG_STATSD_PORT"). "Which tag format should we use to send stats metrics. Valid options are 'datadog' and 'influxdb'.").
Default("8125"). Envar("MTG_STATSD_TAGS_FORMAT").
Uint16() Default("influxdb").
statsdNetwork = app.Flag("statsd-network", "Which network is used to work with statsd. Only 'tcp' and 'udp' are supported."). Enum("datadog", "influxdb")
Envar("MTG_STATSD_NETWORK"). runStatsdTags = runCommand.Flag("statsd-tags",
Default("udp"). "Tags to use for working with statsd (specified as 'key=value').").
String() Envar("MTG_STATSD_TAGS").
statsdPrefix = app.Flag("statsd-prefix", "Which bucket prefix should we use for sending stats to statsd."). StringMap()
Envar("MTG_STATSD_PREFIX"). runWriteBufferSize = runCommand.Flag("write-buffer",
Default("mtg"). "Write buffer size. You can think about it as a buffer from client to Telegram.").
String() Short('w').
statsdTagsFormat = app.Flag("statsd-tags-format", "Which tag format should we use to send stats metrics. Valid options are 'datadog' and 'influxdb'."). Envar("MTG_BUFFER_WRITE").
Envar("MTG_STATSD_TAGS_FORMAT"). Default("32KB").
String() Bytes()
statsdTags = app.Flag("statsd-tags", "Tags to use for working with statsd (specified as 'key=value')."). runReadBufferSize = runCommand.Flag("read-buffer",
Envar("MTG_STATSD_TAGS"). "Read buffer size. You can think about it as a buffer from Telegram to client.").
StringMap() Short('r').
Envar("MTG_BUFFER_READ").
secret = app.Arg("secret", "Secret of this proxy.").Required().String() Default("32KB").
adtag = app.Arg("adtag", "ADTag of the proxy.").String() Bytes()
runTLSCloakPort = runCommand.Flag("cloak-port",
"Port which should be used for host cloaking.").
Envar("MTG_CLOAK_PORT").
Default("443").
Uint16()
runAntiReplayMaxSize = runCommand.Flag("anti-replay-max-size",
"Max size of antireplay cache.").
Envar("MTG_ANTIREPLAY_MAXSIZE").
Default("128MB").
Bytes()
runMultiplexPerConnection = runCommand.Flag("multiplex-per-connection",
"How many clients can share a single connection to Telegram.").
Envar("MTG_MULTIPLEX_PERCONNECTION").
Default("50").
Uint()
runNTPServers = runCommand.Flag("ntp-server",
"A list of NTP servers to use.").
Envar("MTG_NTP_SERVERS").
Default("0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org", "3.pool.ntp.org").
Strings()
runSecret = runCommand.Arg("secret", "Secret of this proxy.").Required().HexBytes()
runAdtag = runCommand.Arg("adtag", "ADTag of the proxy.").HexBytes()
) )
func init() {
rand.Seed(time.Now().UTC().UnixNano())
app.Version(version)
}
func main() { func main() {
kingpin.MustParse(app.Parse(os.Args[1:])) rand.Seed(time.Now().UTC().UnixNano())
app.Version(getVersion())
app.HelpFlag.Short('h')
err := setRLimit() if err := utils.SetLimits(); err != nil {
if err != nil { cli.Fatal(err)
usage(err.Error())
} }
conf, err := config.NewConfig(*debug, *verbose, switch kingpin.MustParse(app.Parse(os.Args[1:])) {
*bindIP, *bindPort, case generateSecretCommand.FullCommand():
*publicIPv4, *publicIPv4Port, cli.Generate(*generateSecretType, *generateCloakHost)
*publicIPv6, *publicIPv6Port, case runCommand.FullCommand():
*statsIP, *statsPort, err := config.Init(
*secret, *adtag, config.Opt{Option: config.OptionTypeDebug, Value: *runDebug},
*statsdIP, *statsdPort, *statsdNetwork, *statsdPrefix, config.Opt{Option: config.OptionTypeVerbose, Value: *runVerbose},
*statsdTagsFormat, *statsdTags, config.Opt{Option: config.OptionTypePreferIP, Value: *runPreferIP},
) config.Opt{Option: config.OptionTypeBind, Value: *runBind},
if err != nil { config.Opt{Option: config.OptionTypePublicIPv4, Value: *runPublicIPv4},
usage(err.Error()) config.Opt{Option: config.OptionTypePublicIPv6, Value: *runPublicIPv6},
} config.Opt{Option: config.OptionTypeStatsBind, Value: *runStatsBind},
config.Opt{Option: config.OptionTypeStatsNamespace, Value: *runStatsNamespace},
atom := zap.NewAtomicLevel() config.Opt{Option: config.OptionTypeStatsdAddress, Value: *runStatsdAddress},
if conf.Debug { config.Opt{Option: config.OptionTypeStatsdTagsFormat, Value: *runStatsdTagsFormat},
atom.SetLevel(zapcore.DebugLevel) config.Opt{Option: config.OptionTypeStatsdTags, Value: *runStatsdTags},
} else if conf.Verbose { config.Opt{Option: config.OptionTypeWriteBufferSize, Value: *runWriteBufferSize},
atom.SetLevel(zapcore.InfoLevel) config.Opt{Option: config.OptionTypeReadBufferSize, Value: *runReadBufferSize},
} else { config.Opt{Option: config.OptionTypeCloakPort, Value: *runTLSCloakPort},
atom.SetLevel(zapcore.ErrorLevel) config.Opt{Option: config.OptionTypeAntiReplayMaxSize, Value: *runAntiReplayMaxSize},
} config.Opt{Option: config.OptionTypeMultiplexPerConnection, Value: *runMultiplexPerConnection},
encoderCfg := zap.NewProductionEncoderConfig() config.Opt{Option: config.OptionTypeNTPServers, Value: *runNTPServers},
logger := zap.New(zapcore.NewCore( config.Opt{Option: config.OptionTypeSecret, Value: *runSecret},
zapcore.NewJSONEncoder(encoderCfg), config.Opt{Option: config.OptionTypeAdtag, Value: *runAdtag},
zapcore.Lock(os.Stderr), )
atom, if err != nil {
)) cli.Fatal(err)
zap.ReplaceGlobals(logger)
defer logger.Sync() // nolint: errcheck
printURLs(conf.GetURLs())
if conf.UseMiddleProxy() {
zap.S().Infow("Use middle proxy connection to Telegram")
if diff, err := ntp.Fetch(); err != nil {
zap.S().Warnw("Could not fetch time data from NTP")
} else {
if diff >= time.Second {
usage(fmt.Sprintf("You choose to use middle proxy but your clock drift (%s) is bigger than 1 second. Please, sync your time", diff))
}
go ntp.AutoUpdate()
} }
} else {
zap.S().Infow("Use direct connection to Telegram")
}
if err := stats.Start(conf); err != nil { if err := cli.Proxy(); err != nil {
panic(err) cli.Fatal(err)
} }
server := proxy.NewProxy(conf)
if err := server.Serve(); err != nil {
zap.S().Fatalw("Server stopped", "error", err)
} }
} }
func setRLimit() (err error) { func getVersion() string {
rLimit := syscall.Rlimit{} if version != "dev" {
err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) return version
if err != nil {
err = errors.Annotate(err, "Cannot get rlimit")
return
}
rLimit.Cur = rLimit.Max
err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
if err != nil {
err = errors.Annotate(err, "Cannot set rlimit")
} }
return info, ok := debug.ReadBuildInfo()
} if !ok {
return version
func printURLs(data interface{}) {
encoder := json.NewEncoder(os.Stdout)
encoder.SetEscapeHTML(false)
encoder.SetIndent("", " ")
err := encoder.Encode(data)
if err != nil {
panic(err)
} }
}
func usage(msg string) { builder := strings.Builder{}
io.WriteString(os.Stderr, msg+"\n") // nolint: errcheck builder.WriteString(info.Main.Version)
os.Exit(1)
if info.Main.Sum != "" {
builder.WriteString(" (checksum: ")
builder.WriteString(info.Main.Sum)
builder.WriteRune(')')
}
return builder.String()
} }
-87
View File
@@ -1,87 +0,0 @@
package mtproto
import (
"bytes"
"net"
"github.com/juju/errors"
)
// ConnectionType is a type of obfuscated2/mtproto connection requested
// by the user.
type ConnectionType uint8
// ConnectionProtocol is a type of IP protocol to use.
type ConnectionProtocol uint8
// Hacks is a simple structure to store flags for packet transmission.
type Hacks struct {
SimpleAck bool
QuickAck bool
}
// ConnectionOpts presents an options, metadata on connection requested
// by the user on handshake.
type ConnectionOpts struct {
DC int16
ConnectionType ConnectionType
ConnectionProto ConnectionProtocol
// Read and Write means direction related to the client.
// ReadHacks are meant to be flushed on client read
// WriteHacks are meant to be flushed on client write.
ReadHacks Hacks
WriteHacks Hacks
ClientAddr *net.TCPAddr
}
// Different connection types which user requests from Telegram.
const (
ConnectionTypeUnknown ConnectionType = iota
ConnectionTypeAbridged
ConnectionTypeIntermediate
ConnectionTypeSecure
)
// ConnectionProtocol* define which connection protocols to use.
// ConnectionProtocolAny means that any is suitable.
const (
ConnectionProtocolIPv4 ConnectionProtocol = 1
ConnectionProtocolIPv6 = ConnectionProtocolIPv4 << 1
ConnectionProtocolAny = ConnectionProtocolIPv4 | ConnectionProtocolIPv6
)
// Connection tags for mtproto handshakes.
var (
ConnectionTagAbridged = []byte{0xef, 0xef, 0xef, 0xef}
ConnectionTagIntermediate = []byte{0xee, 0xee, 0xee, 0xee}
ConnectionTagSecure = []byte{0xdd, 0xdd, 0xdd, 0xdd}
)
// Tag maps connection type to the corresponding handshake tag.
func (t ConnectionType) Tag() ([]byte, error) {
switch t {
case ConnectionTypeAbridged:
return ConnectionTagAbridged, nil
case ConnectionTypeIntermediate:
return ConnectionTagIntermediate, nil
case ConnectionTypeSecure:
return ConnectionTagSecure, nil
default:
return nil, errors.Errorf("Unknown connection type %d", t)
}
}
// ConnectionTagFromHandshake maps magic bytes to the connection type.
func ConnectionTagFromHandshake(magic []byte) (ConnectionType, error) {
if bytes.Equal(magic, ConnectionTagIntermediate) {
return ConnectionTypeIntermediate, nil
}
if bytes.Equal(magic, ConnectionTagAbridged) {
return ConnectionTypeAbridged, nil
}
if bytes.Equal(magic, ConnectionTagSecure) {
return ConnectionTypeSecure, nil
}
return ConnectionTypeUnknown, errors.New("Unknown handshake protocol")
}
+102
View File
@@ -0,0 +1,102 @@
package mtproto
import (
"fmt"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/mtproto/rpc"
"github.com/9seconds/mtg/protocol"
"github.com/9seconds/mtg/telegram"
"github.com/9seconds/mtg/wrappers/packet"
"github.com/9seconds/mtg/wrappers/stream"
)
func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.PacketReadWriteCloser, error) {
conn, err := telegram.Middle.Dial(req.ClientProtocol.DC(),
req.ClientProtocol.ConnectionProtocol())
if err != nil {
return nil, fmt.Errorf("cannot connect to telegram: %w", err)
}
rpcNonceConn := packet.NewMtprotoFrame(conn, rpc.SeqNoNonce)
rpcNonceReq, err := doRPCNonceRequest(rpcNonceConn)
if err != nil {
return nil, fmt.Errorf("cannot do nonce request: %w", err)
}
rpcNonceResp, err := getRPCNonceResponse(rpcNonceConn, rpcNonceReq)
if err != nil {
return nil, fmt.Errorf("cannot get nonce response: %w", err)
}
secureConn := stream.NewMiddleProxyCipher(conn, rpcNonceReq, rpcNonceResp, telegram.Middle.Secret())
frameConn := packet.NewMtprotoFrame(secureConn, rpc.SeqNoHandshake)
if err := doRPCHandshakeRequest(frameConn); err != nil {
return nil, fmt.Errorf("cannot do handshake request: %w", err)
}
if err := getRPCHandshakeResponse(frameConn); err != nil {
return nil, fmt.Errorf("cannot get handshake response: %w", err)
}
return frameConn, nil
}
func doRPCNonceRequest(conn conntypes.BasePacketWriter) (*rpc.NonceRequest, error) {
rpcNonceReq, err := rpc.NewNonceRequest(telegram.Middle.Secret())
if err != nil {
panic(err)
}
if err := conn.Write(rpcNonceReq.Bytes()); err != nil {
return nil, err // nolint: wrapcheck
}
return rpcNonceReq, nil
}
func getRPCNonceResponse(conn conntypes.BasePacketReader, req *rpc.NonceRequest) (*rpc.NonceResponse, error) {
packet, err := conn.Read()
if err != nil {
return nil, fmt.Errorf("cannot read from connection: %w", err)
}
resp, err := rpc.NewNonceResponse(packet)
if err != nil {
return nil, fmt.Errorf("cannot build rpc nonce response: %w", err)
}
if err = resp.Valid(req); err != nil {
return nil, fmt.Errorf("invalid nonce response: %w", err)
}
return resp, nil
}
func doRPCHandshakeRequest(conn conntypes.BasePacketWriter) error {
if err := conn.Write(rpc.HandshakeRequest); err != nil {
return fmt.Errorf("cannot make a request: %w", err)
}
return nil
}
func getRPCHandshakeResponse(conn conntypes.BasePacketReader) error {
packet, err := conn.Read()
if err != nil {
return fmt.Errorf("cannot read a response: %w", err)
}
resp, err := rpc.NewHandshakeResponse(packet)
if err != nil {
return fmt.Errorf("cannot build a handshake response: %w", err)
}
if err := resp.Valid(); err != nil {
return fmt.Errorf("invalid handshake response: %w", err)
}
return nil
}
+3 -8
View File
@@ -7,7 +7,7 @@ const (
SeqNoHandshake = -1 SeqNoHandshake = -1
) )
// Different constants for RPC protocol // Different constants for RPC protocol.
var ( var (
TagCloseExt = []byte{0xa2, 0x34, 0xb6, 0x5e} TagCloseExt = []byte{0xa2, 0x34, 0xb6, 0x5e}
TagProxyAns = []byte{0x0d, 0xda, 0x03, 0x44} TagProxyAns = []byte{0x0d, 0xda, 0x03, 0x44}
@@ -23,11 +23,6 @@ var (
ProxyRequestExtraSize = []byte{0x18, 0x00, 0x00, 0x00} ProxyRequestExtraSize = []byte{0x18, 0x00, 0x00, 0x00}
ProxyRequestProxyTag = []byte{0xae, 0x26, 0x1e, 0xdb} ProxyRequestProxyTag = []byte{0xae, 0x26, 0x1e, 0xdb}
HandshakeSenderPID []byte
HandshakePeerPID []byte
)
func init() {
HandshakeSenderPID = []byte("IPIPPRPDTIME") HandshakeSenderPID = []byte("IPIPPRPDTIME")
HandshakePeerPID = []byte("IPIPPRPDTIME") HandshakePeerPID = []byte("IPIPPRPDTIME")
} )
+3 -24
View File
@@ -1,26 +1,5 @@
package rpc package rpc
import "bytes" var HandshakeRequest = append(TagHandshake,
append(HandshakeFlags,
// HandshakeRequest is the data type which is responsible for append(HandshakeSenderPID, HandshakePeerPID...)...)...)
// constructing of correct handshake request.
type HandshakeRequest struct {
}
// Bytes returns serialized handshake request.
func (r *HandshakeRequest) Bytes() []byte {
buf := &bytes.Buffer{}
buf.Grow(len(TagHandshake) + len(HandshakeFlags) + len(HandshakeSenderPID) + len(HandshakePeerPID))
buf.Write(TagHandshake)
buf.Write(HandshakeFlags)
buf.Write(HandshakeSenderPID)
buf.Write(HandshakePeerPID)
return buf.Bytes()
}
// NewHandshakeRequest creates new HandshakeRequest instance.
func NewHandshakeRequest() *HandshakeRequest {
return &HandshakeRequest{}
}
+11 -12
View File
@@ -2,12 +2,10 @@ package rpc
import ( import (
"bytes" "bytes"
"errors"
"github.com/juju/errors" "fmt"
) )
// HandshakeResponse defines data structure which is used for storage of
// handshake response.
type HandshakeResponse struct { type HandshakeResponse struct {
Type []byte Type []byte
Flags []byte Flags []byte
@@ -19,21 +17,22 @@ type HandshakeResponse struct {
func (r *HandshakeResponse) Bytes() []byte { func (r *HandshakeResponse) Bytes() []byte {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
buf.Write(r.Type[:]) buf.Write(r.Type)
buf.Write(r.Flags[:]) buf.Write(r.Flags)
buf.Write(r.SenderPID[:]) buf.Write(r.SenderPID)
buf.Write(r.PeerPID[:]) buf.Write(r.PeerPID)
return buf.Bytes() return buf.Bytes()
} }
// Valid checks that handshake response compliments request. // Valid checks that handshake response compliments request.
func (r *HandshakeResponse) Valid(req *HandshakeRequest) error { func (r *HandshakeResponse) Valid() error {
if !bytes.Equal(r.Type, TagHandshake) { if !bytes.Equal(r.Type, TagHandshake) {
return errors.New("Unexpected handshake tag") return errors.New("unexpected handshake tag")
} }
if !bytes.Equal(r.PeerPID, HandshakeSenderPID) { if !bytes.Equal(r.PeerPID, HandshakeSenderPID) {
return errors.New("Incorrect sender PID") return errors.New("incorrect sender PID")
} }
return nil return nil
@@ -43,7 +42,7 @@ func (r *HandshakeResponse) Valid(req *HandshakeRequest) error {
// data. // data.
func NewHandshakeResponse(data []byte) (*HandshakeResponse, error) { func NewHandshakeResponse(data []byte) (*HandshakeResponse, error) {
if len(data) != 32 { if len(data) != 32 {
return nil, errors.New("Incorrect handshake response length") return nil, fmt.Errorf("incorrect handshake response length %d", len(data))
} }
return &HandshakeResponse{ return &HandshakeResponse{
+3 -5
View File
@@ -4,13 +4,10 @@ import (
"bytes" "bytes"
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"fmt"
"time" "time"
"github.com/juju/errors"
) )
// NonceRequest is the data type which contains all the data for correct
// nonce request.
type NonceRequest struct { type NonceRequest struct {
KeySelector []byte KeySelector []byte
CryptoTS []byte CryptoTS []byte
@@ -37,8 +34,9 @@ func NewNonceRequest(proxySecret []byte) (*NonceRequest, error) {
cryptoTS := make([]byte, 4) cryptoTS := make([]byte, 4)
if _, err := rand.Read(nonce); err != nil { if _, err := rand.Read(nonce); err != nil {
return nil, errors.Annotate(err, "Cannot generate nonce") return nil, fmt.Errorf("cannot generate nonce: %w", err)
} }
copy(keySelector, proxySecret) copy(keySelector, proxySecret)
timestamp := time.Now().Truncate(time.Second).Unix() % 4294967296 // 256 ^ 4 - do not know how to name timestamp := time.Now().Truncate(time.Second).Unix() % 4294967296 // 256 ^ 4 - do not know how to name
+9 -9
View File
@@ -2,11 +2,10 @@ package rpc
import ( import (
"bytes" "bytes"
"errors"
"github.com/juju/errors" "fmt"
) )
// NonceResponse is the data type which contains data of nonce response.
type NonceResponse struct { type NonceResponse struct {
NonceRequest NonceRequest
@@ -16,7 +15,7 @@ type NonceResponse struct {
// Bytes returns serialized form of the nonce response. // Bytes returns serialized form of the nonce response.
func (r *NonceResponse) Bytes() []byte { func (r *NonceResponse) Bytes() []byte {
buf := &bytes.Buffer{} buf := bytes.Buffer{}
buf.Write(r.Type) buf.Write(r.Type)
buf.Write(r.KeySelector) buf.Write(r.KeySelector)
@@ -27,16 +26,17 @@ func (r *NonceResponse) Bytes() []byte {
return buf.Bytes() return buf.Bytes()
} }
// Valid checks that nonce response compliments nonce request.
func (r *NonceResponse) Valid(req *NonceRequest) error { func (r *NonceResponse) Valid(req *NonceRequest) error {
if !bytes.Equal(r.Type, TagNonce) { if !bytes.Equal(r.Type, TagNonce) {
return errors.New("Unexpected RPC type") return errors.New("unexpected RPC type")
} }
if !bytes.Equal(r.Crypto, NonceCryptoAES) { if !bytes.Equal(r.Crypto, NonceCryptoAES) {
return errors.New("Unexpected crypto type") return errors.New("unexpected crypto type")
} }
if !bytes.Equal(r.KeySelector, req.KeySelector) { if !bytes.Equal(r.KeySelector, req.KeySelector) {
return errors.New("Unexpected key selector") return errors.New("unexpected key selector")
} }
return nil return nil
@@ -45,7 +45,7 @@ func (r *NonceResponse) Valid(req *NonceRequest) error {
// NewNonceResponse build new nonce response based on the given data. // NewNonceResponse build new nonce response based on the given data.
func NewNonceResponse(data []byte) (*NonceResponse, error) { func NewNonceResponse(data []byte) (*NonceResponse, error) {
if len(data) != 32 { if len(data) != 32 {
return nil, errors.New("Unexpected message length") return nil, fmt.Errorf("unexpected message length %d", len(data))
} }
return &NonceResponse{ return &NonceResponse{
+27 -20
View File
@@ -5,53 +5,60 @@ import (
"strings" "strings"
) )
type proxyRequestFlags uint32 type ProxyRequestFlags uint32
const ( const (
proxyRequestFlagsHasAdTag proxyRequestFlags = 0x8 ProxyRequestFlagsHasAdTag ProxyRequestFlags = 0x8
proxyRequestFlagsEncrypted = 0x2 ProxyRequestFlagsEncrypted ProxyRequestFlags = 0x2
proxyRequestFlagsMagic = 0x1000 ProxyRequestFlagsMagic ProxyRequestFlags = 0x1000
proxyRequestFlagsExtMode2 = 0x20000 ProxyRequestFlagsExtMode2 ProxyRequestFlags = 0x20000
proxyRequestFlagsIntermediate = 0x20000000 ProxyRequestFlagsIntermediate ProxyRequestFlags = 0x20000000
proxyRequestFlagsAbdridged = 0x40000000 ProxyRequestFlagsAbdridged ProxyRequestFlags = 0x40000000
proxyRequestFlagsQuickAck = 0x80000000 ProxyRequestFlagsQuickAck ProxyRequestFlags = 0x80000000
proxyRequestFlagsPad = 0x8000000 ProxyRequestFlagsPad ProxyRequestFlags = 0x8000000
) )
var proxyRequestFlagsEncryptedPrefix [8]byte var ProxyRequestFlagsEncryptedPrefix [8]byte
func (r proxyRequestFlags) Bytes() []byte { func (r ProxyRequestFlags) Bytes() []byte {
converted := make([]byte, 4) converted := make([]byte, 4)
binary.LittleEndian.PutUint32(converted, uint32(r)) binary.LittleEndian.PutUint32(converted, uint32(r))
return converted return converted
} }
func (r proxyRequestFlags) String() string { func (r ProxyRequestFlags) String() string {
flags := make([]string, 0, 7) flags := make([]string, 0, 7)
if r&proxyRequestFlagsHasAdTag != 0 { if r&ProxyRequestFlagsHasAdTag != 0 {
flags = append(flags, "HAS_AD_TAG") flags = append(flags, "HAS_AD_TAG")
} }
if r&proxyRequestFlagsEncrypted != 0 {
if r&ProxyRequestFlagsEncrypted != 0 {
flags = append(flags, "ENCRYPTED") flags = append(flags, "ENCRYPTED")
} }
if r&proxyRequestFlagsMagic != 0 {
if r&ProxyRequestFlagsMagic != 0 {
flags = append(flags, "MAGIC") flags = append(flags, "MAGIC")
} }
if r&proxyRequestFlagsExtMode2 != 0 {
if r&ProxyRequestFlagsExtMode2 != 0 {
flags = append(flags, "EXT_MODE_2") flags = append(flags, "EXT_MODE_2")
} }
if r&proxyRequestFlagsIntermediate != 0 {
if r&ProxyRequestFlagsIntermediate != 0 {
flags = append(flags, "INTERMEDIATE") flags = append(flags, "INTERMEDIATE")
} }
if r&proxyRequestFlagsAbdridged != 0 {
if r&ProxyRequestFlagsAbdridged != 0 {
flags = append(flags, "ABRIDGED") flags = append(flags, "ABRIDGED")
} }
if r&proxyRequestFlagsQuickAck != 0 {
if r&ProxyRequestFlagsQuickAck != 0 {
flags = append(flags, "QUICK_ACK") flags = append(flags, "QUICK_ACK")
} }
if r&proxyRequestFlagsPad != 0 {
if r&ProxyRequestFlagsPad != 0 {
flags = append(flags, "PAD") flags = append(flags, "PAD")
} }
-104
View File
@@ -1,104 +0,0 @@
package rpc
import (
"bytes"
"crypto/rand"
"encoding/binary"
"fmt"
"net"
"github.com/juju/errors"
"github.com/9seconds/mtg/mtproto"
)
// ProxyRequest is the data type for storing data required to compose
// RPC_PROXY_REQ request.
type ProxyRequest struct {
Flags proxyRequestFlags
ConnectionID []byte
OurIPPort []byte
ClientIPPort []byte
ADTag []byte
Options *mtproto.ConnectionOpts
}
// MakeHeader makes RPC_PROXY_REQ header. We need only to append the
// data for it.
func (r *ProxyRequest) MakeHeader(message []byte) (*bytes.Buffer, fmt.Stringer) {
bufferLength := len(TagProxyRequest) +
4 + // len(flags)
len(r.ConnectionID) +
len(r.ClientIPPort) +
len(r.OurIPPort) +
len(ProxyRequestExtraSize) +
len(ProxyRequestProxyTag) +
1 + // len(AdTag)
len(r.ADTag)
bufferLength += bufferLength % 4
buf := &bytes.Buffer{}
buf.Grow(bufferLength + len(message))
flags := r.Flags
if r.Options.ReadHacks.QuickAck {
flags |= proxyRequestFlagsQuickAck
}
if bytes.HasPrefix(message, proxyRequestFlagsEncryptedPrefix[:]) {
flags |= proxyRequestFlagsEncrypted
}
buf.Write(TagProxyRequest)
buf.Write(flags.Bytes())
buf.Write(r.ConnectionID)
buf.Write(r.ClientIPPort)
buf.Write(r.OurIPPort)
buf.Write(ProxyRequestExtraSize)
buf.Write(ProxyRequestProxyTag)
buf.WriteByte(byte(len(r.ADTag)))
buf.Write(r.ADTag)
buf.Write(make([]byte, (4-buf.Len()%4)%4))
return buf, flags
}
// NewProxyRequest build new ProxyRequest data structure.
func NewProxyRequest(clientAddr, ownAddr *net.TCPAddr, opts *mtproto.ConnectionOpts, adTag []byte) (*ProxyRequest, error) {
flags := proxyRequestFlagsHasAdTag | proxyRequestFlagsMagic | proxyRequestFlagsExtMode2
switch opts.ConnectionType {
case mtproto.ConnectionTypeAbridged:
flags |= proxyRequestFlagsAbdridged
case mtproto.ConnectionTypeIntermediate:
flags |= proxyRequestFlagsIntermediate
case mtproto.ConnectionTypeSecure:
flags |= proxyRequestFlagsIntermediate | proxyRequestFlagsPad
default:
panic("Unknown connection type")
}
request := &ProxyRequest{
Flags: flags,
ADTag: adTag,
Options: opts,
ConnectionID: make([]byte, 8),
ClientIPPort: make([]byte, 16+4),
OurIPPort: make([]byte, 16+4),
}
if _, err := rand.Read(request.ConnectionID); err != nil {
return nil, errors.Annotate(err, "Cannot generate connection ID")
}
port := [4]byte{}
copy(request.ClientIPPort[:16], clientAddr.IP.To16())
binary.LittleEndian.PutUint32(port[:], uint32(clientAddr.Port))
copy(request.ClientIPPort[16:], port[:])
copy(request.OurIPPort[:16], ownAddr.IP.To16())
binary.LittleEndian.PutUint32(port[:], uint32(ownAddr.Port))
copy(request.OurIPPort[16:], port[:])
return request, nil
}
+53
View File
@@ -0,0 +1,53 @@
package rpc
import (
"bytes"
"fmt"
"github.com/9seconds/mtg/conntypes"
)
type ProxyResponseType uint8
const (
ProxyResponseTypeAns ProxyResponseType = iota
ProxyResponseTypeSimpleAck
ProxyResponseTypeCloseExt
)
type ProxyResponse struct {
Type ProxyResponseType
ConnID conntypes.ConnID
Payload conntypes.Packet
}
func ParseProxyResponse(packet conntypes.Packet) (*ProxyResponse, error) {
var response ProxyResponse
if len(packet) < 4 {
return nil, fmt.Errorf("incorrect packet length: %d", len(packet))
}
tag := packet[:4]
switch {
case bytes.Equal(tag, TagProxyAns):
response.Type = ProxyResponseTypeAns
copy(response.ConnID[:], packet[8:16])
response.Payload = packet[16:]
return &response, nil
case bytes.Equal(tag, TagSimpleAck):
response.Type = ProxyResponseTypeSimpleAck
copy(response.ConnID[:], packet[4:12])
response.Payload = packet[12:]
return &response, nil
case bytes.Equal(tag, TagCloseExt):
response.Type = ProxyResponseTypeCloseExt
return &response, nil
}
return nil, fmt.Errorf("unknown response type %x", tag)
}
+7 -10
View File
@@ -1,35 +1,31 @@
package ntp package ntp
import ( import (
"fmt"
"math/rand" "math/rand"
"time" "time"
"github.com/9seconds/mtg/config"
"github.com/beevik/ntp" "github.com/beevik/ntp"
"github.com/juju/errors"
"go.uber.org/zap" "go.uber.org/zap"
) )
const autoUpdatePeriod = time.Minute const autoUpdatePeriod = time.Minute
var ntpEndpoints = []string{
"0.pool.ntp.org",
"1.pool.ntp.org",
"2.pool.ntp.org",
"3.pool.ntp.org",
}
// Fetch fetches the data on time drift. // Fetch fetches the data on time drift.
func Fetch() (time.Duration, error) { func Fetch() (time.Duration, error) {
url := ntpEndpoints[rand.Intn(len(ntpEndpoints))] url := config.C.NTPServers[rand.Intn(len(config.C.NTPServers))] // nolint: gosec
resp, err := ntp.Query(url) resp, err := ntp.Query(url)
if err != nil { if err != nil {
return 0, errors.Annotatef(err, "Cannot fetch NTP server %s", url) return 0, fmt.Errorf("cannot fetch NTP server %s: %w", url, err)
} }
offsetInt := int64(resp.ClockOffset) offsetInt := int64(resp.ClockOffset)
if offsetInt < 0 { if offsetInt < 0 {
offsetInt = -offsetInt offsetInt = -offsetInt
} }
offset := time.Duration(offsetInt) offset := time.Duration(offsetInt)
return offset, nil return offset, nil
@@ -43,6 +39,7 @@ func AutoUpdate() {
diff, err := Fetch() diff, err := Fetch()
if err != nil { if err != nil {
logger.Debugw("Cannot fetch time from NTP", "error", err) logger.Debugw("Cannot fetch time from NTP", "error", err)
continue continue
} }
+114
View File
@@ -0,0 +1,114 @@
package obfuscated2
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"io"
"time"
"github.com/9seconds/mtg/antireplay"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/protocol"
"github.com/9seconds/mtg/stats"
"github.com/9seconds/mtg/utils"
"github.com/9seconds/mtg/wrappers/stream"
)
const clientProtocolHandshakeTimeout = 10 * time.Second
type ClientProtocol struct {
connectionType conntypes.ConnectionType
connectionProtocol conntypes.ConnectionProtocol
dc conntypes.DC
}
func (c *ClientProtocol) ConnectionType() conntypes.ConnectionType {
return c.connectionType
}
func (c *ClientProtocol) ConnectionProtocol() conntypes.ConnectionProtocol {
return c.connectionProtocol
}
func (c *ClientProtocol) DC() conntypes.DC {
return c.dc
}
func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error) {
fm, err := c.ReadFrame(socket)
if err != nil {
return nil, fmt.Errorf("cannot make a client handshake: %w", err)
}
decHasher := sha256.New()
decHasher.Write(fm.Key()) // nolint: errcheck
decHasher.Write(config.C.Secret) // nolint: errcheck
decryptor := utils.MakeStreamCipher(decHasher.Sum(nil), fm.IV())
invertedFrame := fm.Invert()
encHasher := sha256.New()
encHasher.Write(invertedFrame.Key()) // nolint: errcheck
encHasher.Write(config.C.Secret) // nolint: errcheck
encryptor := utils.MakeStreamCipher(encHasher.Sum(nil), invertedFrame.IV())
decryptedFrame := Frame{}
decryptor.XORKeyStream(decryptedFrame.Bytes(), fm.Bytes())
magic := decryptedFrame.Magic()
switch {
case bytes.Equal(magic, conntypes.ConnectionTagAbridged):
c.connectionType = conntypes.ConnectionTypeAbridged
case bytes.Equal(magic, conntypes.ConnectionTagIntermediate):
c.connectionType = conntypes.ConnectionTypeIntermediate
case bytes.Equal(magic, conntypes.ConnectionTagSecure):
c.connectionType = conntypes.ConnectionTypeSecure
default:
return nil, errors.New("unknown connection type")
}
c.connectionProtocol = conntypes.ConnectionProtocolIPv4
if socket.LocalAddr().IP.To4() == nil {
c.connectionProtocol = conntypes.ConnectionProtocolIPv6
}
buf := bytes.NewReader(decryptedFrame.DC())
if err := binary.Read(buf, binary.LittleEndian, &c.dc); err != nil {
c.dc = conntypes.DCDefaultIdx
}
replayKey := decryptedFrame.Unique()
if antireplay.Cache.HasObfuscated2(replayKey) {
stats.Stats.ReplayDetected()
return nil, errors.New("replay attack is detected")
}
antireplay.Cache.AddObfuscated2(replayKey)
return stream.NewObfuscated2(socket, encryptor, decryptor), nil
}
func (c *ClientProtocol) ReadFrame(socket conntypes.StreamReader) (fm Frame, err error) {
if _, err = io.ReadFull(handshakeReader{socket}, fm.Bytes()); err != nil {
err = fmt.Errorf("cannot extract obfuscated2 frame: %w", err)
}
return
}
type handshakeReader struct {
parent conntypes.StreamReader
}
func (h handshakeReader) Read(p []byte) (int, error) {
return h.parent.ReadTimeout(p, clientProtocolHandshakeTimeout)
}
func MakeClientProtocol() protocol.ClientProtocol {
return &ClientProtocol{}
}
+28 -95
View File
@@ -1,17 +1,5 @@
package obfuscated2 package obfuscated2
import (
"bytes"
"crypto/rand"
"encoding/binary"
"io"
"github.com/juju/errors"
"github.com/9seconds/mtg/mtproto"
)
// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd]
const ( const (
frameLenKey = 32 frameLenKey = 32
frameLenIV = 16 frameLenIV = 16
@@ -24,98 +12,43 @@ const (
frameOffsetMagic = frameOffsetIV + frameLenMagic frameOffsetMagic = frameOffsetIV + frameLenMagic
frameOffsetDC = frameOffsetMagic + frameLenDC frameOffsetDC = frameOffsetMagic + frameLenDC
FrameLen = 64 frameLen = 64
) )
// Frame represents handshake frame. Telegram sends 64 bytes of obfuscated2 // [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd].
// initialization data first. type Frame struct {
// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/ data [frameLen]byte
type Frame []byte
// Key returns AES encryption key.
func (f Frame) Key() []byte {
return f[frameOffsetFirst:frameOffsetKey]
} }
// IV returns AES encryption initialization vector func (f *Frame) Bytes() []byte {
func (f Frame) IV() []byte { return f.data[:]
return f[frameOffsetKey:frameOffsetIV]
} }
// Magic returns magic bytes from last 8 bytes of frame. Telegram checks func (f *Frame) Key() []byte {
// for values there. If after decryption magic is not as expected, return f.data[frameOffsetFirst:frameOffsetKey]
// connection considered as failed.
func (f Frame) Magic() []byte {
return f[frameOffsetIV:frameOffsetMagic]
} }
// DC returns number of datacenter IP client wants to use. func (f *Frame) IV() []byte {
func (f Frame) DC() (n int16) { return f.data[frameOffsetKey:frameOffsetIV]
buf := bytes.NewReader(f[frameOffsetMagic:frameOffsetDC]) }
if err := binary.Read(buf, binary.LittleEndian, &n); err != nil {
n = 1 func (f *Frame) Magic() []byte {
return f.data[frameOffsetIV:frameOffsetMagic]
}
func (f *Frame) DC() []byte {
return f.data[frameOffsetMagic:frameOffsetDC]
}
func (f *Frame) Unique() []byte {
return f.data[frameOffsetFirst:frameOffsetDC]
}
func (f *Frame) Invert() (nf Frame) {
nf = *f
for i := 0; i < frameLenKey+frameLenIV; i++ {
nf.data[frameOffsetFirst+i] = f.data[frameOffsetIV-1-i]
} }
return return
} }
// ConnectionType identifies connection type of the handshake frame.
func (f Frame) ConnectionType() (mtproto.ConnectionType, error) {
return mtproto.ConnectionTagFromHandshake(f.Magic())
}
// Invert inverts frame for extracting encryption keys. Pkease check that link:
// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
func (f Frame) Invert() Frame {
reversed := make(Frame, FrameLen)
copy(reversed, f)
for i := 0; i < frameLenKey+frameLenIV; i++ {
reversed[frameOffsetFirst+i] = f[frameOffsetIV-1-i]
}
return reversed
}
// ExtractFrame extracts exact obfuscated2 handshake frame from given reader.
func ExtractFrame(conn io.Reader) (Frame, error) {
frame := make(Frame, FrameLen)
buf := bytes.NewBuffer(frame)
buf.Reset()
if _, err := io.CopyN(buf, conn, FrameLen); err != nil {
return nil, errors.Annotate(err, "Cannot extract obfuscated header")
}
copy(frame, buf.Bytes())
return frame, nil
}
func generateFrame(connectionType mtproto.ConnectionType) Frame {
frame := make(Frame, FrameLen)
for {
if _, err := rand.Read(frame); err != nil {
continue
}
if frame[0] == 0xef {
continue
}
val := (uint32(frame[3]) << 24) | (uint32(frame[2]) << 16) | (uint32(frame[1]) << 8) | uint32(frame[0])
if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee {
continue
}
val = (uint32(frame[7]) << 24) | (uint32(frame[6]) << 16) | (uint32(frame[5]) << 8) | uint32(frame[4])
if val == 0x00000000 {
continue
}
// error has to be checked before calling this function
tag, _ := connectionType.Tag() // nolint: errcheck
copy(frame.Magic(), tag)
return frame
}
}
-106
View File
@@ -1,106 +0,0 @@
package obfuscated2
import (
"bytes"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/9seconds/mtg/mtproto"
)
func TestFrameKey(t *testing.T) {
toCompare := make([]byte, 32)
for i := 0; i < 32; i++ {
toCompare[i] = byte(1)
}
assert.Equal(t, toCompare, makeFrame().Key())
}
func TestFrameIV(t *testing.T) {
toCompare := make([]byte, 16)
for i := 0; i < 16; i++ {
toCompare[i] = byte(2)
}
assert.Equal(t, toCompare, makeFrame().IV())
}
func TestFrameMagic(t *testing.T) {
toCompare := make([]byte, 4)
for i := 0; i < 4; i++ {
toCompare[i] = 0xee
}
assert.Equal(t, toCompare, makeFrame().Magic())
}
func TestFrameDC(t *testing.T) {
assert.Equal(t, int16(771), makeFrame().DC())
}
func TestFrameValid(t *testing.T) {
frame := makeFrame()
connType, err := frame.ConnectionType()
assert.Nil(t, err)
assert.Equal(t, connType, mtproto.ConnectionTypeIntermediate)
frame[8+32+16+2] = byte(3)
_, err = frame.ConnectionType()
assert.NotNil(t, err)
}
func TestFrameDoubleInvert(t *testing.T) {
frame := makeFrame()
assert.True(t, bytes.Equal(frame, frame.Invert().Invert()))
}
func TestFrameInvert(t *testing.T) {
frame := makeFrame()
reversed := frame.Invert()
assert.Exactly(t, frame[:8], reversed[:8])
assert.Exactly(t, frame[56:], reversed[56:])
toCompare := make([]byte, 48)
for i := 0; i < 48; i++ {
toCompare[i] = frame[55-i]
}
assert.Equal(t, []byte(reversed[8:56]), toCompare)
}
func TestFrameGenerateValid(t *testing.T) {
validTests := []mtproto.ConnectionType{
mtproto.ConnectionTypeIntermediate,
mtproto.ConnectionTypeAbridged,
}
for _, test := range validTests {
t.Run(strconv.Itoa(int(test)), func(tt *testing.T) {
frame := generateFrame(test)
conType, err := frame.ConnectionType()
assert.Nil(t, err)
assert.Equal(t, conType, test)
})
}
}
func makeFrame() Frame {
f := make(Frame, FrameLen)
for i := 8; i < (8 + 32); i++ {
f[i] = byte(1)
}
for i := (8 + 32); i < (8 + 32 + 16); i++ {
f[i] = byte(2)
}
for i := (8 + 32 + 16); i < (8 + 32 + 16 + 4); i++ {
f[i] = 0xee
}
for i := (8 + 32 + 16 + 4); i < (8 + 32 + 16 + 4 + 2); i++ {
f[i] = byte(3)
}
return f
}
-81
View File
@@ -1,81 +0,0 @@
package obfuscated2
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"github.com/juju/errors"
"github.com/9seconds/mtg/mtproto"
)
// Obfuscated2 contains AES CTR encryption and decryption streams
// for telegram connection.
type Obfuscated2 struct {
Decryptor cipher.Stream
Encryptor cipher.Stream
}
// ParseObfuscated2ClientFrame parses client frame. Please check this link for
// details: http://telegra.ph/telegram-blocks-wtf-05-26
//
// Beware, link above is in russian.
func ParseObfuscated2ClientFrame(secret []byte, frame Frame) (*Obfuscated2, *mtproto.ConnectionOpts, error) {
decHasher := sha256.New()
decHasher.Write(frame.Key()) // nolint: errcheck
decHasher.Write(secret) // nolint: errcheck
decryptor := makeStreamCipher(decHasher.Sum(nil), frame.IV())
invertedFrame := frame.Invert()
encHasher := sha256.New()
encHasher.Write(invertedFrame.Key()) // nolint: errcheck
encHasher.Write(secret) // nolint: errcheck
encryptor := makeStreamCipher(encHasher.Sum(nil), invertedFrame.IV())
decryptedFrame := make(Frame, FrameLen)
decryptor.XORKeyStream(decryptedFrame, frame)
connType, err := decryptedFrame.ConnectionType()
if err != nil {
return nil, nil, errors.Annotate(err, "Unknown protocol")
}
obfs := &Obfuscated2{
Decryptor: decryptor,
Encryptor: encryptor,
}
connOpts := &mtproto.ConnectionOpts{
DC: decryptedFrame.DC(),
ConnectionType: connType,
}
return obfs, connOpts, nil
}
// MakeTelegramObfuscated2Frame creates new handshake frame to send to
// Telegram.
// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
func MakeTelegramObfuscated2Frame(opts *mtproto.ConnectionOpts) (*Obfuscated2, Frame) {
frame := generateFrame(opts.ConnectionType)
encryptor := makeStreamCipher(frame.Key(), frame.IV())
decryptorFrame := frame.Invert()
decryptor := makeStreamCipher(decryptorFrame.Key(), decryptorFrame.IV())
copyFrame := make(Frame, FrameLen)
copy(copyFrame[:frameOffsetIV], frame[:frameOffsetIV])
encryptor.XORKeyStream(frame, frame)
copy(frame[:frameOffsetIV], copyFrame[:frameOffsetIV])
obfs := &Obfuscated2{
Decryptor: decryptor,
Encryptor: encryptor,
}
return obfs, frame
}
func makeStreamCipher(key, iv []byte) cipher.Stream {
block, _ := aes.NewCipher(key)
return cipher.NewCTR(block, iv)
}
-97
View File
@@ -1,97 +0,0 @@
package obfuscated2
import (
"crypto/sha256"
"testing"
"github.com/stretchr/testify/assert"
"github.com/9seconds/mtg/mtproto"
)
func TestObfs2TelegramFrameDecrypt(t *testing.T) {
connOpts := &mtproto.ConnectionOpts{
DC: 1,
ConnectionType: mtproto.ConnectionTypeIntermediate,
}
_, frame := MakeTelegramObfuscated2Frame(connOpts)
decryptor := makeStreamCipher(frame.Key(), frame.IV())
decrypted := make(Frame, FrameLen)
decryptor.XORKeyStream(decrypted, frame)
_, err := decrypted.ConnectionType()
assert.Nil(t, err)
}
func TestObfs2TelegramDecryptEncryptDecrypt(t *testing.T) {
connOpts := &mtproto.ConnectionOpts{
DC: 1,
ConnectionType: mtproto.ConnectionTypeIntermediate,
}
obfs2, frame := MakeTelegramObfuscated2Frame(connOpts)
inverted := frame.Invert()
encryptor := makeStreamCipher(inverted.Key(), inverted.IV())
data := []byte{1, 2, 3}
encrypted := make([]byte, 3)
encryptor.XORKeyStream(encrypted, data)
decrypted := make([]byte, 3)
obfs2.Decryptor.XORKeyStream(decrypted, encrypted)
assert.Equal(t, data, decrypted)
}
func TestObfs2Full(t *testing.T) {
secret := []byte{1, 2, 3, 4, 5}
clientFrame := generateFrame(mtproto.ConnectionTypeIntermediate)
clientHasher := sha256.New()
clientHasher.Write(clientFrame.Key())
clientHasher.Write(secret)
clientKey := clientHasher.Sum(nil)
encryptor := makeStreamCipher(clientKey, clientFrame.IV())
encrypted := make(Frame, FrameLen)
encryptor.XORKeyStream(encrypted, clientFrame)
copy(encrypted[:56], clientFrame[:56])
invertedClientFrame := clientFrame.Invert()
clientHasher = sha256.New()
clientHasher.Write(invertedClientFrame.Key())
clientHasher.Write(secret)
invertedClientKey := clientHasher.Sum(nil)
clientDecryptor := makeStreamCipher(invertedClientKey, invertedClientFrame.IV())
clientObfs, _, err := ParseObfuscated2ClientFrame(secret, encrypted)
assert.Nil(t, err)
connOpts := &mtproto.ConnectionOpts{
DC: 1,
ConnectionType: mtproto.ConnectionTypeIntermediate,
}
tgObfs, tgFrame := MakeTelegramObfuscated2Frame(connOpts)
tgDecryptor := makeStreamCipher(tgFrame.Key(), tgFrame.IV())
decrypted := make(Frame, FrameLen)
tgDecryptor.XORKeyStream(decrypted, tgFrame)
_, err = decrypted.ConnectionType()
assert.Nil(t, err)
tgInvertedFrame := tgFrame.Invert()
tgEncryptor := makeStreamCipher(tgInvertedFrame.Key(), tgInvertedFrame.IV())
message := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}
tgEncryptedMessage := make([]byte, len(message))
tgEncryptor.XORKeyStream(tgEncryptedMessage, message)
tgEncDecryptedMessage := make([]byte, len(tgEncryptedMessage))
tgObfs.Decryptor.XORKeyStream(tgEncDecryptedMessage, tgEncryptedMessage)
assert.Equal(t, message, tgEncDecryptedMessage)
clientEncryptedMessage := make([]byte, len(tgEncDecryptedMessage))
clientObfs.Encryptor.XORKeyStream(clientEncryptedMessage, tgEncDecryptedMessage)
finalMessage := make([]byte, len(clientEncryptedMessage))
clientDecryptor.XORKeyStream(finalMessage, clientEncryptedMessage)
assert.Equal(t, finalMessage, message)
}
+68
View File
@@ -0,0 +1,68 @@
package obfuscated2
import (
"crypto/rand"
"fmt"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/protocol"
"github.com/9seconds/mtg/telegram"
"github.com/9seconds/mtg/utils"
"github.com/9seconds/mtg/wrappers/stream"
)
func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteCloser, error) {
conn, err := telegram.Direct.Dial(req.ClientProtocol.DC(),
req.ClientProtocol.ConnectionProtocol())
if err != nil {
return nil, fmt.Errorf("cannot dial to telegram: %w", err)
}
conn = stream.NewTimeout(conn)
conn = stream.NewCtx(req.Ctx, req.Cancel, conn)
fm := generateFrame(req.ClientProtocol)
data := fm.Bytes()
encryptor := utils.MakeStreamCipher(fm.Key(), fm.IV())
decryptedFrame := fm.Invert()
decryptor := utils.MakeStreamCipher(decryptedFrame.Key(), decryptedFrame.IV())
copyFrame := make([]byte, frameLen)
copy(copyFrame[:frameOffsetIV], data[:frameOffsetIV])
encryptor.XORKeyStream(data, data)
copy(data[:frameOffsetIV], copyFrame[:frameOffsetIV])
if _, err := conn.Write(data); err != nil {
return nil, fmt.Errorf("cannot write handshake frame to telegram: %w", err)
}
return stream.NewObfuscated2(conn, encryptor, decryptor), nil
}
func generateFrame(cp protocol.ClientProtocol) (fm Frame) {
data := fm.Bytes()
for {
if _, err := rand.Read(data); err != nil {
continue
}
if data[0] == 0xef {
continue
}
val := (uint32(data[3]) << 24) | (uint32(data[2]) << 16) | (uint32(data[1]) << 8) | uint32(data[0])
if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee {
continue
}
val = (uint32(data[7]) << 24) | (uint32(data[6]) << 16) | (uint32(data[5]) << 8) | uint32(data[4])
if val == 0x00000000 {
continue
}
copy(fm.Magic(), cp.ConnectionType().Tag())
return
}
}
+12
View File
@@ -0,0 +1,12 @@
package protocol
import "github.com/9seconds/mtg/conntypes"
type ClientProtocol interface {
Handshake(conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error)
ConnectionType() conntypes.ConnectionType
ConnectionProtocol() conntypes.ConnectionProtocol
DC() conntypes.DC
}
type ClientProtocolMaker func() ClientProtocol
+17
View File
@@ -0,0 +1,17 @@
package protocol
import (
"context"
"github.com/9seconds/mtg/conntypes"
"go.uber.org/zap"
)
type TelegramRequest struct {
Logger *zap.SugaredLogger
ClientConn conntypes.StreamReadWriteCloser
ConnID conntypes.ConnID
Ctx context.Context
Cancel context.CancelFunc
ClientProtocol ClientProtocol
}
+49
View File
@@ -0,0 +1,49 @@
package proxy
import (
"io"
"sync"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/obfuscated2"
"github.com/9seconds/mtg/protocol"
"go.uber.org/zap"
)
const directPipeBufferSize = 1024
func directConnection(request *protocol.TelegramRequest) error {
telegramConnRaw, err := obfuscated2.TelegramProtocol(request)
if err != nil {
return err // nolint: wrapcheck
}
telegramConn := telegramConnRaw.(conntypes.StreamReadWriteCloser)
defer telegramConn.Close()
wg := &sync.WaitGroup{}
wg.Add(2)
go directPipe(telegramConn, request.ClientConn, wg, request.Logger)
go directPipe(request.ClientConn, telegramConn, wg, request.Logger)
wg.Wait()
return nil
}
func directPipe(dst io.WriteCloser, src io.ReadCloser, wg *sync.WaitGroup, logger *zap.SugaredLogger) {
defer func() {
dst.Close()
src.Close()
wg.Done()
}()
buf := [directPipeBufferSize]byte{}
if _, err := io.CopyBuffer(dst, src, buf[:]); err != nil {
logger.Debugw("Cannot pump sockets", "error", err)
}
}
+70
View File
@@ -0,0 +1,70 @@
package proxy
import (
"sync"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/protocol"
"github.com/9seconds/mtg/wrappers/packetack"
"go.uber.org/zap"
)
func middleConnection(request *protocol.TelegramRequest) {
telegramConn, err := packetack.NewProxy(request)
if err != nil {
request.Logger.Debugw("Cannot dial to Telegram", "error", err)
return
}
defer telegramConn.Close()
var clientConn conntypes.PacketAckFullReadWriteCloser
switch request.ClientProtocol.ConnectionType() {
case conntypes.ConnectionTypeAbridged:
clientConn = packetack.NewClientAbridged(request.ClientConn)
case conntypes.ConnectionTypeIntermediate:
clientConn = packetack.NewClientIntermediate(request.ClientConn)
case conntypes.ConnectionTypeSecure:
clientConn = packetack.NewClientIntermediateSecure(request.ClientConn)
case conntypes.ConnectionTypeUnknown:
panic("unknown connection type")
}
wg := &sync.WaitGroup{}
wg.Add(2)
go middlePipe(telegramConn, clientConn, wg, request.Logger)
go middlePipe(clientConn, telegramConn, wg, request.Logger)
wg.Wait()
}
func middlePipe(dst conntypes.PacketAckWriteCloser,
src conntypes.PacketAckReadCloser,
wg *sync.WaitGroup,
logger *zap.SugaredLogger) {
defer func() {
dst.Close()
src.Close()
wg.Done()
}()
for {
acks := conntypes.ConnectionAcks{}
packet, err := src.Read(&acks)
if err != nil {
logger.Debugw("Cannot read packet", "error", err)
return
}
if err = dst.Write(packet, &acks); err != nil {
logger.Debugw("Cannot send packet", "error", err)
return
}
}
}
+61 -123
View File
@@ -1,162 +1,100 @@
package proxy package proxy
import ( import (
"io" "context"
"net" "net"
"sync"
"github.com/juju/errors"
uuid "github.com/satori/go.uuid"
"go.uber.org/zap"
"github.com/9seconds/mtg/client"
"github.com/9seconds/mtg/config" "github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/mtproto" "github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/protocol"
"github.com/9seconds/mtg/stats" "github.com/9seconds/mtg/stats"
"github.com/9seconds/mtg/telegram" "github.com/9seconds/mtg/utils"
"github.com/9seconds/mtg/wrappers" "github.com/9seconds/mtg/wrappers/stream"
"go.uber.org/zap"
) )
// Proxy is a core of this program.
type Proxy struct { type Proxy struct {
clientInit client.Init Logger *zap.SugaredLogger
tg telegram.Telegram Context context.Context
conf *config.Config ClientProtocolMaker protocol.ClientProtocolMaker
} }
// Serve runs TCP proxy server. func (p *Proxy) Serve(listener net.Listener) {
func (p *Proxy) Serve() error { doneChan := p.Context.Done()
lsock, err := net.Listen("tcp", p.conf.BindAddr())
if err != nil {
return errors.Annotate(err, "Cannot create listen socket")
}
for { for {
if conn, err := lsock.Accept(); err != nil { conn, err := listener.Accept()
zap.S().Errorw("Cannot allocate incoming connection", "error", err) if err != nil {
} else { select {
go p.accept(conn) case <-doneChan:
return
default:
p.Logger.Fatalw("Cannot allocate incoming connection", "error", err)
}
} }
go p.accept(conn)
} }
} }
func (p *Proxy) accept(conn net.Conn) { func (p *Proxy) accept(conn net.Conn) {
connID := uuid.NewV4().String()
log := zap.S().With("connection_id", connID).Named("main")
defer func() { defer func() {
conn.Close() // nolint: errcheck conn.Close()
if err := recover(); err != nil { if err := recover(); err != nil {
stats.NewCrash() stats.Stats.Crash()
log.Errorw("Crash of accept handler", "error", err) p.Logger.Errorw("Crash of accept handler", "error", err)
} }
}() }()
log.Infow("Client connected", "addr", conn.RemoteAddr()) connID := conntypes.NewConnID()
logger := p.Logger.With("connection_id", connID)
if err := utils.InitTCP(conn, config.C.ClientReadBuffer(), config.C.ClientWriteBuffer()); err != nil {
logger.Errorw("Cannot initialize client TCP connection", "error", err)
clientConn, opts, err := p.clientInit(conn, connID, p.conf)
if err != nil {
log.Errorw("Cannot initialize client connection", "error", err)
return return
} }
defer clientConn.(io.Closer).Close() // nolint: errcheck
stats.ClientConnected(opts.ConnectionType, clientConn.RemoteAddr()) ctx, cancel := context.WithCancel(p.Context)
defer stats.ClientDisconnected(opts.ConnectionType, clientConn.RemoteAddr()) defer cancel()
serverConn, err := p.getTelegramConn(opts, connID) clientConn := stream.NewClientConn(conn, connID)
clientConn = stream.NewCtx(ctx, cancel, clientConn)
clientConn = stream.NewTimeout(clientConn)
defer clientConn.Close()
clientProtocol := p.ClientProtocolMaker()
clientConn, err := clientProtocol.Handshake(clientConn)
if err != nil { if err != nil {
log.Errorw("Cannot initialize server connection", "error", err) stats.Stats.AuthenticationFailed()
logger.Warnw("Cannot perform client handshake", "error", err)
return return
} }
defer serverConn.(io.Closer).Close() // nolint: errcheck
wait := &sync.WaitGroup{} stats.Stats.ClientConnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr())
wait.Add(2) defer stats.Stats.ClientDisconnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr())
logger.Infow("Client connected", "addr", conn.RemoteAddr())
if p.conf.UseMiddleProxy() { req := &protocol.TelegramRequest{
clientPacket := clientConn.(wrappers.PacketReadWriteCloser) Logger: logger,
serverPacket := serverConn.(wrappers.PacketReadWriteCloser) ClientConn: clientConn,
go p.middlePipe(clientPacket, serverPacket, wait, &opts.ReadHacks) ConnID: connID,
go p.middlePipe(serverPacket, clientPacket, wait, &opts.WriteHacks) Ctx: ctx,
Cancel: cancel,
ClientProtocol: clientProtocol,
}
err = nil
if config.C.MiddleProxyMode() {
middleConnection(req)
} else { } else {
clientStream := clientConn.(wrappers.StreamReadWriteCloser) err = directConnection(req)
serverStream := serverConn.(wrappers.StreamReadWriteCloser)
go p.directPipe(clientStream, serverStream, wait)
go p.directPipe(serverStream, clientStream, wait)
} }
wait.Wait() logger.Infow("Client disconnected", "error", err, "addr", conn.RemoteAddr())
log.Infow("Client disconnected", "addr", conn.RemoteAddr())
}
func (p *Proxy) getTelegramConn(opts *mtproto.ConnectionOpts, connID string) (wrappers.Wrap, error) {
streamConn, err := p.tg.Dial(connID, opts)
if err != nil {
return nil, errors.Annotate(err, "Cannot dial to Telegram")
}
packetConn, err := p.tg.Init(opts, streamConn)
if err != nil {
return nil, errors.Annotate(err, "Cannot handshake telegram")
}
return packetConn, nil
}
func (p *Proxy) middlePipe(src wrappers.PacketReadCloser, dst io.WriteCloser, wait *sync.WaitGroup, hacks *mtproto.Hacks) {
defer func() {
src.Close() // nolint: errcheck
dst.Close() // nolint: errcheck
wait.Done()
}()
for {
hacks.SimpleAck = false
hacks.QuickAck = false
packet, err := src.Read()
if err != nil {
src.Logger().Warnw("Cannot read packet", "error", err)
return
}
if _, err = dst.Write(packet); err != nil {
src.Logger().Warnw("Cannot write packet", "error", err)
return
}
}
}
func (p *Proxy) directPipe(src wrappers.StreamReadCloser, dst io.WriteCloser, wait *sync.WaitGroup) {
defer func() {
src.Close() // nolint: errcheck
dst.Close() // nolint: errcheck
wait.Done()
}()
if _, err := io.Copy(dst, src); err != nil {
src.Logger().Warnw("Cannot pump sockets", "error", err)
}
}
// NewProxy returns new proxy instance.
func NewProxy(conf *config.Config) *Proxy {
var clientInit client.Init
var tg telegram.Telegram
if conf.UseMiddleProxy() {
clientInit = client.MiddleInit
tg = telegram.NewMiddleTelegram(conf)
} else {
clientInit = client.DirectInit
tg = telegram.NewDirectTelegram(conf)
}
return &Proxy{
conf: conf,
clientInit: clientInit,
tg: tg,
}
} }
-35
View File
@@ -1,35 +0,0 @@
#!/bin/bash
set -eu -o pipefail
IMAGE_NAME="nineseconds/mtg"
CONTAINER_NAME="mtg"
SECRET_PATH="$HOME/.mtg.secret"
PROXY_PORT=444
STAT_PORT=3129
[[ -e "$SECRET_PATH" ]] || (
openssl rand -hex 16 > "$SECRET_PATH"
chmod 0400 "$SECRET_PATH"
)
docker pull "$IMAGE_NAME"
docker ps --filter "Name=$CONTAINER_NAME" -aq | xargs -r docker rm -fv
docker run \
-d \
--name "$CONTAINER_NAME" \
--sysctl 'net.ipv4.ip_local_port_range=10000 65000' \
--sysctl net.ipv4.tcp_congestion_control=bbr \
--sysctl net.ipv4.tcp_fastopen=3 \
--sysctl net.ipv4.tcp_fin_timeout=30 \
--sysctl net.ipv4.tcp_max_syn_backlog=4096 \
--sysctl net.ipv4.tcp_max_tw_buckets=5000 \
--sysctl net.ipv4.tcp_mtu_probing=1 \
--sysctl 'net.ipv4.tcp_rmem=4096 87380 67108864' \
--sysctl net.ipv4.tcp_syncookies=1 \
--sysctl net.ipv4.tcp_tw_reuse=1 \
--sysctl 'net.ipv4.tcp_wmem=4096 65536 67108864' \
--ulimit nofile=51200:51200 \
--restart=unless-stopped \
-p $PROXY_PORT:3128 \
-p $STAT_PORT:3129 \
"$IMAGE_NAME" "$(cat "$SECRET_PATH")"
Executable
+68
View File
@@ -0,0 +1,68 @@
#!/bin/bash
#
# Configuration options (set by environment variables during script execution)
# - MTG_CONFIG - directory where mtg stores its configuration
# - MTG_IMAGENAME - a name of the docker image to use
# - MTG_PORT - which port of the host system should be used
# - MTG_CONTAINER - a name of the container to use
#
# Example:
# export MTG_CONFIG="$HOME/mtg_config"
# export MTG_IMAGENAME="nineseconds/mtg:latest"
# curl -sfL --compressed https://raw.githubusercontent.com/9seconds/mtg/master/run.sh | bash
set -eu
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
export MTG_CONFIG="${MTG_CONFIG:-$XDG_CONFIG_HOME/mtg}"
if ! [ -x "$(command -v docker)" ]; then
echo 'Error: docker is not installed.' >&2
exit 1
fi
id -Gn "$USER" | grep -qw 'docker' > /dev/null
if [ $? -eq 0 ] || [ "$(id -u)" -eq '0' ]; then
DOCKER_CMD="$(command -v docker)"
else
DOCKER_CMD="sudo $(command -v docker)"
fi
mkdir -p "$MTG_CONFIG" || true
MTG_SECRET="$MTG_CONFIG/secret"
MTG_ENV="$MTG_CONFIG/env"
if [ ! -f "$MTG_ENV" ]; then
MTG_IMAGENAME="${MTG_IMAGENAME:-nineseconds/mtg:stable}"
MTG_PORT="${MTG_PORT:-3128}"
MTG_CONTAINER="${MTG_CONTAINER:-mtg}"
echo "MTG_IMAGENAME=$MTG_IMAGENAME" > "$MTG_ENV"
echo "MTG_PORT=$MTG_PORT" >> "$MTG_ENV"
echo "MTG_CONTAINER=$MTG_CONTAINER" >> "$MTG_ENV"
fi
set -a
source "$MTG_ENV"
set +a
$DOCKER_CMD pull "$MTG_IMAGENAME" > /dev/null
if [ ! -f "$MTG_SECRET" ]; then
$DOCKER_CMD run \
--rm \
"$MTG_IMAGENAME" \
generate-secret tls -c "$(openssl rand -hex 16).com" \
> "$MTG_SECRET"
fi
echo "Proxy secret is $(cat "$MTG_SECRET"). Port is $MTG_PORT."
$DOCKER_CMD ps --filter "Name=$MTG_CONTAINER" -aq | xargs -r $DOCKER_CMD rm -fv > /dev/null
$DOCKER_CMD run \
-d \
--restart=unless-stopped \
--name "$MTG_CONTAINER" \
--ulimit nofile=51200:51200 \
-p "$MTG_PORT:3128" \
"$MTG_IMAGENAME" run "$(cat "$MTG_SECRET")" > /dev/null
-12
View File
@@ -1,12 +0,0 @@
#!/bin/sh
set -eu
PROJECT_DIR="$(git rev-parse --show-toplevel)"
OUTPUT_FILE="${PROJECT_DIR}/version.go"
cat > "$OUTPUT_FILE" <<EOF
package main
// autogenerated by $(basename "$0") on $(date -Ru)
const version = "$(git describe --long --always) ($(go version)) [$(date -Ru)]"
EOF
-145
View File
@@ -1,145 +0,0 @@
package stats
import (
"net"
"time"
"github.com/9seconds/mtg/mtproto"
)
const (
crashesChanLength = 1
connectionsChanLength = 20
trafficChanLength = 5000
)
var (
crashesChan = make(chan struct{}, crashesChanLength)
connectionsChan = make(chan *connectionData, connectionsChanLength)
trafficChan = make(chan *trafficData, trafficChanLength)
)
type connectionData struct {
connectionType mtproto.ConnectionType
connected bool
addr *net.TCPAddr
}
type trafficData struct {
traffic int
ingress bool
}
func crashManager() {
for range crashesChan {
instance.mutex.RLock()
instance.Crashes++
instance.mutex.RUnlock()
}
}
func connectionManager() {
for event := range connectionsChan {
instance.mutex.RLock()
isIPv4 := event.addr.IP.To4() != nil
var inc uint32 = 1
if !event.connected {
inc = ^uint32(0)
}
switch event.connectionType {
case mtproto.ConnectionTypeAbridged:
if isIPv4 {
instance.Connections.Abridged.IPv4 += inc
} else {
instance.Connections.Abridged.IPv6 += inc
}
case mtproto.ConnectionTypeSecure:
if isIPv4 {
instance.Connections.Secure.IPv4 += inc
} else {
instance.Connections.Secure.IPv6 += inc
}
default:
if isIPv4 {
instance.Connections.Intermediate.IPv4 += inc
} else {
instance.Connections.Intermediate.IPv6 += inc
}
}
instance.mutex.RUnlock()
}
}
func trafficManager() {
speedChan := time.Tick(time.Second)
for {
select {
case event := <-trafficChan:
instance.mutex.RLock()
if event.ingress {
instance.Traffic.Ingress += trafficValue(event.traffic)
instance.speedCurrent.Ingress += trafficSpeedValue(event.traffic)
} else {
instance.Traffic.Egress += trafficValue(event.traffic)
instance.speedCurrent.Egress += trafficSpeedValue(event.traffic)
}
instance.mutex.RUnlock()
case <-speedChan:
instance.mutex.RLock()
instance.Speed.Ingress = instance.speedCurrent.Ingress
instance.Speed.Egress = instance.speedCurrent.Egress
instance.speedCurrent.Ingress = trafficSpeedValue(0)
instance.speedCurrent.Egress = trafficSpeedValue(0)
instance.mutex.RUnlock()
}
}
}
// NewCrash indicates new crash.
func NewCrash() {
crashesChan <- struct{}{}
}
// ClientConnected indicates that new client was connected.
func ClientConnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr) {
connectionsChan <- &connectionData{
connectionType: connectionType,
addr: addr,
connected: true,
}
}
// ClientDisconnected indicates that client was disconnected.
func ClientDisconnected(connectionType mtproto.ConnectionType, addr *net.TCPAddr) {
connectionsChan <- &connectionData{
connectionType: connectionType,
addr: addr,
connected: false,
}
}
// IngressTraffic accounts new ingress traffic.
func IngressTraffic(traffic int) {
trafficChan <- &trafficData{
traffic: traffic,
ingress: true,
}
}
// EgressTraffic accounts new ingress traffic.
func EgressTraffic(traffic int) {
trafficChan <- &trafficData{
traffic: traffic,
ingress: false,
}
}
+60
View File
@@ -0,0 +1,60 @@
package stats
import (
"net"
"github.com/9seconds/mtg/conntypes"
)
type IngressTrafficInterface interface {
IngressTraffic(int)
}
type EgressTrafficInterface interface {
EgressTraffic(int)
}
type ClientConnectedInterface interface {
ClientConnected(conntypes.ConnectionType, *net.TCPAddr)
}
type ClientDisconnectedInterface interface {
ClientDisconnected(conntypes.ConnectionType, *net.TCPAddr)
}
type TelegramConnectedInterface interface {
TelegramConnected(conntypes.DC, *net.TCPAddr)
}
type TelegramDisconnectedInterface interface {
TelegramDisconnected(conntypes.DC, *net.TCPAddr)
}
type CrashInterface interface {
Crash()
}
type ReplayDetectedInterface interface {
ReplayDetected()
}
type AuthenticationFailedInterface interface {
AuthenticationFailed()
}
type CloakedRequestInterface interface {
CloakedRequest()
}
type Interface interface {
IngressTrafficInterface
EgressTrafficInterface
ClientConnectedInterface
ClientDisconnectedInterface
TelegramConnectedInterface
TelegramDisconnectedInterface
CrashInterface
ReplayDetectedInterface
AuthenticationFailedInterface
CloakedRequestInterface
}
+69
View File
@@ -0,0 +1,69 @@
package stats
import (
"net"
"github.com/9seconds/mtg/conntypes"
)
type multiStats []Interface
func (m multiStats) IngressTraffic(traffic int) {
for i := range m {
go m[i].IngressTraffic(traffic)
}
}
func (m multiStats) EgressTraffic(traffic int) {
for i := range m {
go m[i].EgressTraffic(traffic)
}
}
func (m multiStats) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
for i := range m {
go m[i].ClientConnected(connectionType, addr)
}
}
func (m multiStats) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
for i := range m {
go m[i].ClientDisconnected(connectionType, addr)
}
}
func (m multiStats) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) {
for i := range m {
go m[i].TelegramConnected(dc, addr)
}
}
func (m multiStats) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) {
for i := range m {
go m[i].TelegramDisconnected(dc, addr)
}
}
func (m multiStats) Crash() {
for i := range m {
go m[i].Crash()
}
}
func (m multiStats) ReplayDetected() {
for i := range m {
go m[i].ReplayDetected()
}
}
func (m multiStats) AuthenticationFailed() {
for i := range m {
go m[i].AuthenticationFailed()
}
}
func (m multiStats) CloakedRequest() {
for i := range m {
go m[i].CloakedRequest()
}
}
-69
View File
@@ -1,69 +0,0 @@
package stats
import (
"encoding/json"
"net/http"
"sync"
"time"
"go.uber.org/zap"
"github.com/9seconds/mtg/config"
)
var instance *stats
// Start starts new statisitcs server.
func Start(conf *config.Config) error {
log := zap.S().Named("stats")
instance = &stats{
URLs: conf.GetURLs(),
Uptime: uptime(time.Now()),
mutex: &sync.RWMutex{},
}
if conf.StatsD.Enabled {
client, err := newStatsd(conf)
if err != nil {
return err
}
go client.run()
}
go crashManager()
go connectionManager()
go trafficManager()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
instance.mutex.Lock()
first, err := json.Marshal(instance)
instance.mutex.Unlock()
if err != nil {
log.Errorw("Cannot encode json", "error", err)
http.Error(w, "Internal server error", 500)
return
}
interm := map[string]interface{}{}
json.Unmarshal(first, &interm) // nolint: errcheck
encoder := json.NewEncoder(w)
encoder.SetEscapeHTML(false)
encoder.SetIndent("", " ")
if err = encoder.Encode(interm); err != nil {
log.Errorw("Cannot encode json", "error", err)
}
})
go func() {
if err := http.ListenAndServe(conf.StatAddr(), nil); err != nil {
log.Fatalw("Stats server has been stopped", "error", err)
}
}()
return nil
}
+22 -83
View File
@@ -1,102 +1,41 @@
package stats package stats
import ( import (
"encoding/json" "context"
"fmt" "fmt"
"strconv" "net"
"sync" "net/http"
"time"
humanize "github.com/dustin/go-humanize"
"github.com/9seconds/mtg/config" "github.com/9seconds/mtg/config"
) )
type uptime time.Time var Stats Interface
func (u uptime) MarshalJSON() ([]byte, error) { func Init(ctx context.Context) error {
duration := time.Since(time.Time(u)) mux := http.NewServeMux()
value := map[string]string{
"seconds": strconv.Itoa(int(duration.Seconds())), stats := []Interface{newStatsPrometheus(mux)}
"human": humanize.Time(time.Time(u)), if config.C.StatsdAddr != nil {
stats = append(stats, newStatsStatsd())
} }
return json.Marshal(value) listener, err := net.Listen("tcp", config.C.StatsBind.String())
} if err != nil {
return fmt.Errorf("cannot initialize stats server: %w", err)
type trafficValue uint64
func (t trafficValue) MarshalJSON() ([]byte, error) {
tv := uint64(t)
value := map[string]interface{}{
"bytes": tv,
"human": humanize.Bytes(tv),
} }
return json.Marshal(value) srv := http.Server{
} Handler: mux,
type trafficSpeedValue uint64
func (t trafficSpeedValue) MarshalJSON() ([]byte, error) {
speed := uint64(t)
value := map[string]interface{}{
"bytes/s": speed,
"human": fmt.Sprintf("%s/S", humanize.Bytes(speed)),
} }
return json.Marshal(value) go srv.Serve(listener) // nolint: errcheck
}
type connections struct { go func() {
All connectionType `json:"all"` <-ctx.Done()
Abridged connectionType `json:"abridged"` srv.Shutdown(context.Background()) // nolint: errcheck
Intermediate connectionType `json:"intermediate"` }()
Secure connectionType `json:"secure"`
}
func (c connections) MarshalJSON() ([]byte, error) { Stats = multiStats(stats)
c.All.IPv4 = c.Abridged.IPv4 + c.Intermediate.IPv4 + c.Secure.IPv4
c.All.IPv6 = c.Abridged.IPv6 + c.Intermediate.IPv6 + c.Secure.IPv6
value := struct { return nil
All connectionType `json:"all"`
Abridged connectionType `json:"abridged"`
Intermediate connectionType `json:"intermediate"`
Secure connectionType `json:"secure"`
}{
All: c.All,
Abridged: c.Abridged,
Intermediate: c.Intermediate,
Secure: c.Secure,
}
return json.Marshal(value)
}
type connectionType struct {
IPv6 uint32 `json:"ipv6"`
IPv4 uint32 `json:"ipv4"`
}
type traffic struct {
Ingress trafficValue `json:"ingress"`
Egress trafficValue `json:"egress"`
}
type speed struct {
Ingress trafficSpeedValue `json:"ingress"`
Egress trafficSpeedValue `json:"egress"`
}
type stats struct {
URLs config.IPURLs `json:"urls"`
Connections connections `json:"connections"`
Traffic traffic `json:"traffic"`
Speed speed `json:"speed"`
Uptime uptime `json:"uptime"`
Crashes uint32 `json:"crashes"`
speedCurrent speed
mutex *sync.RWMutex
} }
+156
View File
@@ -0,0 +1,156 @@
package stats
import (
"net"
"net/http"
"strconv"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/conntypes"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type statsPrometheus struct {
connections *prometheus.GaugeVec
telegramConnections *prometheus.GaugeVec
traffic *prometheus.GaugeVec
crashes prometheus.Counter
replayAttacks prometheus.Counter
authenticationFailed prometheus.Counter
cloakedRequests prometheus.Counter
}
func (s *statsPrometheus) IngressTraffic(traffic int) {
s.traffic.WithLabelValues("ingress").Add(float64(traffic))
}
func (s *statsPrometheus) EgressTraffic(traffic int) {
s.traffic.WithLabelValues("egress").Add(float64(traffic))
}
func (s *statsPrometheus) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
s.changeConnections(connectionType, addr, 1.0)
}
func (s *statsPrometheus) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
s.changeConnections(connectionType, addr, -1.0)
}
func (s *statsPrometheus) changeConnections(connectionType conntypes.ConnectionType,
addr *net.TCPAddr,
increment float64) {
labels := [...]string{
"intermediate",
"ipv4",
}
switch connectionType {
case conntypes.ConnectionTypeAbridged:
labels[0] = "abridged"
case conntypes.ConnectionTypeSecure:
labels[0] = "secured"
case conntypes.ConnectionTypeIntermediate:
labels[0] = "intermediate"
case conntypes.ConnectionTypeUnknown:
panic("unknown connection type")
}
if addr.IP.To4() == nil {
labels[1] = "ipv6"
}
s.connections.WithLabelValues(labels[:]...).Add(increment)
}
func (s *statsPrometheus) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) {
s.changeTelegramConnections(dc, addr, 1.0)
}
func (s *statsPrometheus) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) {
s.changeTelegramConnections(dc, addr, -1.0)
}
func (s *statsPrometheus) changeTelegramConnections(dc conntypes.DC, addr *net.TCPAddr, increment float64) {
labels := [...]string{
strconv.Itoa(int(dc)),
"ipv4",
}
if addr.IP.To4() == nil {
labels[1] = "ipv6"
}
s.telegramConnections.WithLabelValues(labels[:]...).Add(increment)
}
func (s *statsPrometheus) Crash() {
s.crashes.Inc()
}
func (s *statsPrometheus) ReplayDetected() {
s.replayAttacks.Inc()
}
func (s *statsPrometheus) AuthenticationFailed() {
s.authenticationFailed.Inc()
}
func (s *statsPrometheus) CloakedRequest() {
s.cloakedRequests.Inc()
}
func newStatsPrometheus(mux *http.ServeMux) Interface {
registry := prometheus.NewPedanticRegistry()
instance := &statsPrometheus{
connections: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: config.C.StatsNamespace,
Name: "connections",
Help: "Current number of client connections to the proxy.",
}, []string{"type", "protocol"}),
telegramConnections: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: config.C.StatsNamespace,
Name: "telegram_connections",
Help: "Current number of telegram connections established by this proxy.",
}, []string{"dc", "protocol"}),
traffic: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: config.C.StatsNamespace,
Name: "traffic",
Help: "Traffic passed through the proxy in bytes.",
}, []string{"direction"}),
crashes: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: config.C.StatsNamespace,
Name: "crashes",
Help: "How many crashes happened.",
}),
replayAttacks: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: config.C.StatsNamespace,
Name: "replay_attacks",
Help: "How many replay attacks were prevented.",
}),
authenticationFailed: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: config.C.StatsNamespace,
Name: "authentication_failed",
Help: "How many authentication failed events we've seen.",
}),
cloakedRequests: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: config.C.StatsNamespace,
Name: "cloaked_requests",
Help: "How many requests were proxified during cloaking.",
}),
}
registry.MustRegister(instance.connections)
registry.MustRegister(instance.telegramConnections)
registry.MustRegister(instance.traffic)
registry.MustRegister(instance.crashes)
registry.MustRegister(instance.replayAttacks)
registry.MustRegister(instance.authenticationFailed)
registry.MustRegister(instance.cloakedRequests)
handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
mux.Handle("/", handler)
return instance
}
+204
View File
@@ -0,0 +1,204 @@
package stats
import (
"fmt"
"net"
"strconv"
"strings"
"sync"
"time"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/conntypes"
statsd "github.com/smira/go-statsd"
"go.uber.org/zap"
)
var (
tagTrafficIngress = &statsStatsdTag{
name: "ingress",
tag: statsd.StringTag("type", "ingress"),
}
tagTrafficEgress = &statsStatsdTag{
name: "egress",
tag: statsd.StringTag("type", "egress"),
}
tagConnectionTypeAbridged = &statsStatsdTag{
name: "abridged",
tag: statsd.StringTag("type", "abridged"),
}
tagConnectionTypeIntermediate = &statsStatsdTag{
name: "intermediate",
tag: statsd.StringTag("type", "intermediate"),
}
tagConnectionTypeSecured = &statsStatsdTag{
name: "secured",
tag: statsd.StringTag("type", "secured"),
}
tagConnectionProtocol4 = &statsStatsdTag{
name: "ipv4",
tag: statsd.StringTag("protocol", "ipv4"),
}
tagConnectionProtocol6 = &statsStatsdTag{
name: "ipv6",
tag: statsd.StringTag("protocol", "ipv6"),
}
)
type statsStatsdTag struct {
tag statsd.Tag
name string
}
type statsStatsdLogger struct {
log *zap.SugaredLogger
}
func (s statsStatsdLogger) Printf(msg string, args ...interface{}) {
s.log.Debugw(fmt.Sprintf(msg, args...))
}
type statsStatsd struct {
seen map[string]struct{}
seenMutex sync.RWMutex
client *statsd.Client
}
func (s *statsStatsd) IngressTraffic(traffic int) {
s.gauge("traffic", int64(traffic), tagTrafficIngress)
}
func (s *statsStatsd) EgressTraffic(traffic int) {
s.gauge("traffic", int64(traffic), tagTrafficEgress)
}
func (s *statsStatsd) ClientConnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
s.changeConnections(connectionType, addr, 1)
}
func (s *statsStatsd) ClientDisconnected(connectionType conntypes.ConnectionType, addr *net.TCPAddr) {
s.changeConnections(connectionType, addr, -1)
}
func (s *statsStatsd) changeConnections(connectionType conntypes.ConnectionType, addr *net.TCPAddr, increment int64) {
tags := make([]*statsStatsdTag, 0, 2)
switch connectionType {
case conntypes.ConnectionTypeAbridged:
tags = append(tags, tagConnectionTypeAbridged)
case conntypes.ConnectionTypeIntermediate:
tags = append(tags, tagConnectionTypeIntermediate)
case conntypes.ConnectionTypeSecure:
tags = append(tags, tagConnectionTypeSecured)
case conntypes.ConnectionTypeUnknown:
panic("Unknown connection type")
}
if addr.IP.To4() == nil {
tags = append(tags, tagConnectionProtocol6)
} else {
tags = append(tags, tagConnectionProtocol4)
}
s.gauge("connections", increment, tags...)
}
func (s *statsStatsd) TelegramConnected(dc conntypes.DC, addr *net.TCPAddr) {
s.changeTelegramConnections(dc, addr, 1)
}
func (s *statsStatsd) TelegramDisconnected(dc conntypes.DC, addr *net.TCPAddr) {
s.changeTelegramConnections(dc, addr, -1)
}
func (s *statsStatsd) changeTelegramConnections(dc conntypes.DC, addr *net.TCPAddr, increment int64) {
tags := []*statsStatsdTag{
{
name: "dc" + strconv.Itoa(int(dc)),
tag: statsd.IntTag("dc", int(dc)),
},
}
if addr.IP.To4() == nil {
tags = append(tags, tagConnectionProtocol6)
} else {
tags = append(tags, tagConnectionProtocol4)
}
s.gauge("telegram_connections", increment, tags...)
}
func (s *statsStatsd) Crash() {
s.gauge("crashes", 1)
}
func (s *statsStatsd) ReplayDetected() {
s.gauge("replay_attacks", 1)
}
func (s *statsStatsd) AuthenticationFailed() {
s.gauge("authentication_failed", 1)
}
func (s *statsStatsd) CloakedRequest() {
s.gauge("cloaked_requests", 1)
}
func (s *statsStatsd) gauge(metric string, value int64, tags ...*statsStatsdTag) {
key, tagList := s.prepareVals(metric, tags)
s.initGauge(metric, key, tagList)
s.client.GaugeDelta(metric, value, tagList...)
}
func (s *statsStatsd) prepareVals(metric string, tags []*statsStatsdTag) (string, []statsd.Tag) {
tagList := make([]statsd.Tag, len(tags))
builder := strings.Builder{}
builder.WriteString(metric)
for i, v := range tags {
builder.WriteRune('.')
builder.WriteString(v.name)
tagList[i] = v.tag
}
return builder.String(), tagList
}
func (s *statsStatsd) initGauge(metric, key string, tags []statsd.Tag) {
s.seenMutex.RLock()
if _, ok := s.seen[key]; ok {
s.seenMutex.RUnlock()
return
} else { // nolint: golint,revive
s.seenMutex.RUnlock()
}
s.seenMutex.Lock()
defer s.seenMutex.Unlock()
if _, ok := s.seen[key]; !ok {
s.seen[key] = struct{}{}
s.client.Gauge(metric, 0, tags...)
}
}
func newStatsStatsd() Interface {
prefix := strings.TrimSuffix(config.C.StatsNamespace, ".") + "."
logger := statsStatsdLogger{
log: zap.S().Named("stats").Named("statsd"),
}
return &statsStatsd{
seen: make(map[string]struct{}),
client: statsd.NewClient(config.C.StatsdAddr.String(),
statsd.SendLoopCount(2),
statsd.ReconnectInterval(10*time.Second),
statsd.Logger(logger),
statsd.MetricPrefix(prefix),
statsd.TagStyle(config.C.StatsdTagsFormat),
),
}
}
-79
View File
@@ -1,79 +0,0 @@
package stats
import (
"time"
"github.com/juju/errors"
statsd "gopkg.in/alexcesaro/statsd.v2"
"github.com/9seconds/mtg/config"
)
const (
statsdConnectionsAbridgedV4 = "connections.abridged.ipv4"
statsdConnectionsAbridgedV6 = "connections.abridged.ipv6"
statsdConnectionsIntermediateV4 = "connections.intermediate.ipv4"
statsdConnectionsIntermediateV6 = "connections.intermediate.ipv6"
statsdConnectionsSecureV4 = "connections.secure.ipv4"
statsdConnectionsSecureV6 = "connections.secure.ipv6"
statsdTrafficIngress = "traffic.ingress"
statsdTrafficEgress = "traffic.egress"
statsdSpeedIngress = "speed.ingress"
statsdSpeedEgress = "speed.egress"
statsdCrashes = "crashes"
)
const statsdPollTime = time.Second
type statsdExporter struct {
client *statsd.Client
}
func (s *statsdExporter) run() {
for range time.Tick(statsdPollTime) {
instance.mutex.Lock()
s.client.Gauge(statsdConnectionsAbridgedV4, instance.Connections.Abridged.IPv4)
s.client.Gauge(statsdConnectionsAbridgedV6, instance.Connections.Abridged.IPv6)
s.client.Gauge(statsdConnectionsIntermediateV4, instance.Connections.Intermediate.IPv4)
s.client.Gauge(statsdConnectionsIntermediateV6, instance.Connections.Intermediate.IPv6)
s.client.Gauge(statsdConnectionsSecureV4, instance.Connections.Secure.IPv4)
s.client.Gauge(statsdConnectionsSecureV6, instance.Connections.Secure.IPv6)
s.client.Gauge(statsdTrafficIngress, uint64(instance.Traffic.Ingress))
s.client.Gauge(statsdTrafficEgress, uint64(instance.Traffic.Egress))
s.client.Gauge(statsdSpeedIngress, uint64(instance.Speed.Ingress))
s.client.Gauge(statsdSpeedEgress, uint64(instance.Speed.Egress))
s.client.Gauge(statsdCrashes, instance.Crashes)
instance.mutex.Unlock()
}
}
func newStatsd(conf *config.Config) (*statsdExporter, error) {
options := []statsd.Option{
statsd.Network(conf.StatsD.Addr.Network()),
statsd.Address(conf.StatsD.Addr.String()),
statsd.Prefix(conf.StatsD.Prefix),
}
if conf.StatsD.TagsFormat > 0 {
options = append(options, statsd.TagsFormat(conf.StatsD.TagsFormat))
tags := make([]string, len(conf.StatsD.Tags)*2)
for k, v := range conf.StatsD.Tags {
tags = append(tags, k, v)
}
options = append(options, statsd.Tags(tags...))
}
client, err := statsd.New(options...)
if err != nil {
return nil, errors.Annotate(err, "Cannot create statsd client")
}
return &statsdExporter{client: client}, nil
}
+109
View File
@@ -0,0 +1,109 @@
package api
import (
"bufio"
"fmt"
"net"
"regexp"
"strconv"
"strings"
"github.com/9seconds/mtg/conntypes"
)
const (
addressesURLV4 = "https://core.telegram.org/getProxyConfig" // nolint: gas
addressesURLV6 = "https://core.telegram.org/getProxyConfigV6" // nolint: gas
)
var addressesProxyForSplitter = regexp.MustCompile(`\s+`)
func AddressesV4() (map[conntypes.DC][]string, conntypes.DC, error) {
return getAddresses(addressesURLV4)
}
func AddressesV6() (map[conntypes.DC][]string, conntypes.DC, error) {
return getAddresses(addressesURLV6)
}
func getAddresses(url string) (map[conntypes.DC][]string, conntypes.DC, error) {
resp, err := request(url)
if err != nil {
return nil, 0, fmt.Errorf("cannot get http response: %w", err)
}
defer resp.Close()
scanner := bufio.NewScanner(resp)
data := map[conntypes.DC][]string{}
defaultDC := conntypes.DCDefaultIdx
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
switch {
case strings.HasPrefix(text, "#"):
continue
case strings.HasPrefix(text, "proxy_for"):
addr, idx, err := addressesParseProxyFor(text)
if err != nil {
return nil, 0, fmt.Errorf("cannot parse 'proxy_for' section: %w", err)
}
if addresses, ok := data[idx]; ok {
data[idx] = append(addresses, addr)
} else {
data[idx] = []string{addr}
}
case strings.HasPrefix(text, "default"):
idx, err := addressesParseDefault(text)
if err != nil {
return nil, 0, fmt.Errorf("cannot parse 'default' section: %w", err)
}
defaultDC = idx
}
}
err = scanner.Err()
if err != nil {
return nil, 0, fmt.Errorf("cannot parse http response: %w", err)
}
return data, defaultDC, nil
}
func addressesParseProxyFor(text string) (string, conntypes.DC, error) {
chunks := addressesProxyForSplitter.Split(text, 3)
if len(chunks) != 3 || chunks[0] != "proxy_for" {
return "", 0, fmt.Errorf("incorrect config %s", text)
}
dc, err := strconv.ParseInt(chunks[1], 10, 16)
if err != nil {
return "", 0, fmt.Errorf("incorrect config '%s': %w", text, err)
}
addr := strings.TrimRight(chunks[2], ";")
if _, _, err = net.SplitHostPort(addr); err != nil {
return "", 0, fmt.Errorf("incorrect config '%s': %w", text, err)
}
return addr, conntypes.DC(dc), nil
}
func addressesParseDefault(text string) (conntypes.DC, error) {
chunks := addressesProxyForSplitter.Split(text, 2)
if len(chunks) != 2 || chunks[0] != "default" {
return 0, fmt.Errorf("incorrect config '%s'", text)
}
dcString := strings.TrimRight(chunks[1], ";")
dc, err := strconv.ParseInt(dcString, 10, 16)
if err != nil {
return 0, fmt.Errorf("incorrect config '%s': %w", text, err)
}
return conntypes.DC(dc), nil
}
+44
View File
@@ -0,0 +1,44 @@
package api
import (
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
"time"
)
const (
apiUserAgent = "github.com/9seconds/mtg"
apiHTTPTimeout = 30 * time.Second
)
var httpClient = http.Client{
Timeout: apiHTTPTimeout,
}
func request(url string) (io.ReadCloser, error) {
ctx, cancel := context.WithTimeout(context.Background(), apiHTTPTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
panic(err)
}
req.Header.Set("Accept", "text/plan")
req.Header.Set("User-Agent", apiUserAgent)
resp, err := httpClient.Do(req)
if err != nil {
if resp != nil {
io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck
resp.Body.Close()
}
return nil, fmt.Errorf("cannot perform a request: %w", err)
}
return resp.Body, err // nolint: wrapcheck
}
+24
View File
@@ -0,0 +1,24 @@
package api
import (
"fmt"
"io/ioutil"
)
const secretURL = "https://core.telegram.org/getProxySecret" // nolint: gas
func Secret() ([]byte, error) {
resp, err := request(secretURL)
if err != nil {
return nil, fmt.Errorf("cannot access telegram server: %w", err)
}
defer resp.Close()
secret, err := ioutil.ReadAll(resp)
if err != nil {
return nil, fmt.Errorf("cannot read response: %w", err)
}
return secret, nil
}
+91
View File
@@ -0,0 +1,91 @@
package telegram
import (
"errors"
"math/rand"
"net"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/utils"
"github.com/9seconds/mtg/wrappers/stream"
"go.uber.org/zap"
)
type baseTelegram struct {
dialer net.Dialer
logger *zap.SugaredLogger
secret []byte
v4DefaultDC conntypes.DC
v6DefaultDC conntypes.DC
v4Addresses map[conntypes.DC][]string
v6Addresses map[conntypes.DC][]string
}
func (b *baseTelegram) Secret() []byte {
return b.secret
}
func (b *baseTelegram) dial(dc conntypes.DC,
protocol conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) {
for _, addr := range b.getAddresses(dc, protocol) {
conn, err := b.dialer.Dial("tcp", addr)
if err != nil {
b.logger.Infow("Cannot dial to Telegram", "address", addr, "error", err)
continue
}
if err := utils.InitTCP(conn, config.C.ProxyReadBuffer(), config.C.ProxyWriteBuffer()); err != nil {
b.logger.Infow("Cannot initialize TCP socket", "address", addr, "error", err)
continue
}
return stream.NewTelegramConn(dc, conn), nil
}
return nil, errors.New("cannot dial to the chosen DC")
}
func (b *baseTelegram) getAddresses(dc conntypes.DC, protocol conntypes.ConnectionProtocol) []string {
addresses := make([]string, 0, 2)
protos := []conntypes.ConnectionProtocol{
conntypes.ConnectionProtocolIPv6,
conntypes.ConnectionProtocolIPv4,
}
if config.C.PreferIP == config.PreferIPv4 {
protos[0], protos[1] = protos[1], protos[0]
}
for _, proto := range protos {
switch {
case proto&protocol == 0:
case proto&conntypes.ConnectionProtocolIPv6 != 0:
addresses = append(addresses, b.chooseAddress(b.v6Addresses, dc, b.v6DefaultDC))
case proto&conntypes.ConnectionProtocolIPv4 != 0:
addresses = append(addresses, b.chooseAddress(b.v4Addresses, dc, b.v4DefaultDC))
}
}
return addresses
}
func (b *baseTelegram) chooseAddress(addresses map[conntypes.DC][]string,
dc, defaultDC conntypes.DC) string {
addrs, ok := addresses[dc]
if !ok {
addrs = addresses[defaultDC]
}
switch {
case len(addrs) == 1:
return addrs[0]
case len(addrs) > 1:
return addrs[rand.Intn(len(addrs))] // nolint: gosec
}
return ""
}
-53
View File
@@ -1,53 +0,0 @@
package telegram
import (
"net"
"time"
"github.com/juju/errors"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/wrappers"
)
const (
telegramDialTimeout = 10 * time.Second
readBufferSize = 64 * 1024
writeBufferSize = 64 * 1024
)
type tgDialer struct {
net.Dialer
conf *config.Config
}
func (t *tgDialer) dial(addr string) (net.Conn, error) {
conn, err := t.Dialer.Dial("tcp", addr)
if err != nil {
return nil, errors.Annotate(err, "Cannot connect to Telegram")
}
tcpSocket := conn.(*net.TCPConn)
if err = tcpSocket.SetNoDelay(true); err != nil {
return nil, errors.Annotate(err, "Cannot set NO_DELAY to Telegram")
}
if err = tcpSocket.SetReadBuffer(readBufferSize); err != nil {
return nil, errors.Annotate(err, "Cannot set read buffer size on telegram socket")
}
if err = tcpSocket.SetWriteBuffer(writeBufferSize); err != nil {
return nil, errors.Annotate(err, "Cannot set write buffer size on telegram socket")
}
return conn, nil
}
func (t *tgDialer) dialRWC(addr, connID string) (wrappers.StreamReadWriteCloser, error) {
conn, err := t.dial(addr)
if err != nil {
return nil, err
}
tgConn := wrappers.NewConn(conn, connID, wrappers.ConnPurposeTelegram, t.conf.PublicIPv4, t.conf.PublicIPv6)
return tgConn, nil
}
+23 -49
View File
@@ -1,30 +1,26 @@
package telegram package telegram
import ( import "github.com/9seconds/mtg/conntypes"
"net"
"github.com/juju/errors" const (
directV4DefaultIdx conntypes.DC = 1
"github.com/9seconds/mtg/config" directV6DefaultIdx conntypes.DC = 1
"github.com/9seconds/mtg/mtproto"
"github.com/9seconds/mtg/obfuscated2"
"github.com/9seconds/mtg/wrappers"
) )
var ( var (
directV4Addresses = map[int16][]string{ directV4Addresses = map[conntypes.DC][]string{
0: []string{"149.154.175.50:443"}, 0: {"149.154.175.50:443"},
1: []string{"149.154.167.51:443"}, 1: {"149.154.167.51:443"},
2: []string{"149.154.175.100:443"}, 2: {"149.154.175.100:443"},
3: []string{"149.154.167.91:443"}, 3: {"149.154.167.91:443"},
4: []string{"149.154.171.5:443"}, 4: {"149.154.171.5:443"},
} }
directV6Addresses = map[int16][]string{ directV6Addresses = map[conntypes.DC][]string{
0: []string{"[2001:b28:f23d:f001::a]:443"}, 0: {"[2001:b28:f23d:f001::a]:443"},
1: []string{"[2001:67c:04e8:f002::a]:443"}, 1: {"[2001:67c:04e8:f002::a]:443"},
2: []string{"[2001:b28:f23d:f003::a]:443"}, 2: {"[2001:b28:f23d:f003::a]:443"},
3: []string{"[2001:67c:04e8:f004::a]:443"}, 3: {"[2001:67c:04e8:f004::a]:443"},
4: []string{"[2001:b28:f23f:f005::a]:443"}, 4: {"[2001:b28:f23f:f005::a]:443"},
} }
) )
@@ -32,36 +28,14 @@ type directTelegram struct {
baseTelegram baseTelegram
} }
func (t *directTelegram) Dial(connID string, connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) { func (d *directTelegram) Dial(dc conntypes.DC,
dc := connOpts.DC protocol conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) {
if dc < 0 { switch {
case dc < 0:
dc = -dc dc = -dc
} else if dc == 0 { case dc == 0:
dc = 1 dc = conntypes.DCDefaultIdx
} }
return t.baseTelegram.dial(dc-1, connID, connOpts.ConnectionProto) return d.baseTelegram.dial(dc-1, conntypes.ConnectionProtocolAny)
}
func (t *directTelegram) Init(connOpts *mtproto.ConnectionOpts, conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) {
obfs2, frame := obfuscated2.MakeTelegramObfuscated2Frame(connOpts)
if _, err := conn.Write(frame); err != nil {
return nil, errors.Annotate(err, "Cannot write hadnshake frame")
}
return wrappers.NewStreamCipher(conn, obfs2.Encryptor, obfs2.Decryptor), nil
}
// NewDirectTelegram returns Telegram instance which connects directly
// to Telegram bypassing middleproxies.
func NewDirectTelegram(conf *config.Config) Telegram {
return &directTelegram{baseTelegram{
dialer: tgDialer{
Dialer: net.Dialer{Timeout: telegramDialTimeout},
conf: conf,
},
v4Addresses: directV4Addresses,
v6Addresses: directV6Addresses,
}}
} }
+48
View File
@@ -0,0 +1,48 @@
package telegram
import (
"net"
"sync"
"time"
"go.uber.org/zap"
)
const telegramDialTimeout = 10 * time.Second
var (
Direct Telegram
Middle Telegram
initOnce sync.Once
)
func Init() {
initOnce.Do(func() {
logger := zap.S().Named("telegram")
Direct = &directTelegram{
baseTelegram: baseTelegram{
dialer: net.Dialer{Timeout: telegramDialTimeout},
logger: logger.Named("direct"),
v4DefaultDC: directV4DefaultIdx,
v6DefaultDC: directV6DefaultIdx,
v4Addresses: directV4Addresses,
v6Addresses: directV6Addresses,
},
}
tg := &middleTelegram{
baseTelegram: baseTelegram{
dialer: net.Dialer{Timeout: telegramDialTimeout},
logger: logger.Named("middle"),
},
}
if err := tg.update(); err != nil {
panic(err)
}
go tg.backgroundUpdate()
Middle = tg
})
}
+8
View File
@@ -0,0 +1,8 @@
package telegram
import "github.com/9seconds/mtg/conntypes"
type Telegram interface {
Dial(conntypes.DC, conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error)
Secret() []byte
}
+48 -109
View File
@@ -1,136 +1,75 @@
package telegram package telegram
import ( import (
"io" "fmt"
"net"
"net/http"
"sync" "sync"
"time"
"github.com/juju/errors" "github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/telegram/api"
"github.com/9seconds/mtg/config" "go.uber.org/zap"
"github.com/9seconds/mtg/mtproto"
"github.com/9seconds/mtg/mtproto/rpc"
"github.com/9seconds/mtg/wrappers"
) )
const middleTelegramBackgroundUpdateEvery = time.Hour
type middleTelegram struct { type middleTelegram struct {
middleTelegramCaller baseTelegram
conf *config.Config mutex sync.RWMutex
} }
func (t *middleTelegram) Init(connOpts *mtproto.ConnectionOpts, conn wrappers.StreamReadWriteCloser) (wrappers.Wrap, error) { func (m *middleTelegram) Secret() []byte {
rpcNonceConn := wrappers.NewMTProtoFrame(conn, rpc.SeqNoNonce) m.mutex.RLock()
defer m.mutex.RUnlock()
rpcNonceReq, err := t.sendRPCNonceRequest(rpcNonceConn) return m.baseTelegram.Secret()
if err != nil {
return nil, err
}
rpcNonceResp, err := t.receiveRPCNonceResponse(rpcNonceConn, rpcNonceReq)
if err != nil {
return nil, err
}
secureConn := wrappers.NewMiddleProxyCipher(conn, rpcNonceReq, rpcNonceResp, t.proxySecret)
frameConn := wrappers.NewMTProtoFrame(secureConn, rpc.SeqNoHandshake)
rpcHandshakeReq, err := t.sendRPCHandshakeRequest(frameConn)
if err != nil {
return nil, err
}
_, err = t.receiveRPCHandshakeResponse(frameConn, rpcHandshakeReq)
if err != nil {
return nil, err
}
proxyConn, err := wrappers.NewMTProtoProxy(frameConn, connOpts, t.conf.AdTag)
if err != nil {
return nil, err
}
proxyConn.Logger().Infow("Telegram connection initialized")
return proxyConn, nil
} }
func (t *middleTelegram) sendRPCNonceRequest(conn io.Writer) (*rpc.NonceRequest, error) { func (m *middleTelegram) update() error {
rpcNonceReq, err := rpc.NewNonceRequest(t.proxySecret) secret, err := api.Secret()
if err != nil { if err != nil {
return nil, errors.Annotate(err, "Cannot create RPC nonce request") return fmt.Errorf("cannot fetch secret: %w", err)
}
if _, err = conn.Write(rpcNonceReq.Bytes()); err != nil {
return nil, errors.Annotate(err, "Cannot send RPC nonce request")
} }
return rpcNonceReq, nil v4Addresses, v4DefaultDC, err := api.AddressesV4()
if err != nil {
return fmt.Errorf("cannot fetch addresses for ipv4: %w", err)
}
v6Addresses, v6DefaultDC, err := api.AddressesV6()
if err != nil {
return fmt.Errorf("cannot fetch addresses for ipv6: %w", err)
}
m.mutex.Lock()
m.secret = secret
m.v4DefaultDC = v4DefaultDC
m.v6DefaultDC = v6DefaultDC
m.v4Addresses = v4Addresses
m.v6Addresses = v6Addresses
m.mutex.Unlock()
return nil
} }
func (t *middleTelegram) receiveRPCNonceResponse(conn wrappers.PacketReader, req *rpc.NonceRequest) (*rpc.NonceResponse, error) { func (m *middleTelegram) backgroundUpdate() {
packet, err := conn.Read() logger := zap.S().Named("telegram")
if err != nil {
return nil, errors.Annotate(err, "Cannot read RPC nonce response")
}
rpcNonceResp, err := rpc.NewNonceResponse(packet) for range time.Tick(middleTelegramBackgroundUpdateEvery) {
if err != nil { if err := m.update(); err != nil {
return nil, errors.Annotate(err, "Cannot initialize RPC nonce response") logger.Warnw("Cannot update Telegram proxies", "error", err)
}
} }
if err = rpcNonceResp.Valid(req); err != nil {
return nil, errors.Annotate(err, "Invalid RPC nonce response")
}
return rpcNonceResp, nil
} }
func (t *middleTelegram) sendRPCHandshakeRequest(conn io.Writer) (*rpc.HandshakeRequest, error) { func (m *middleTelegram) Dial(dc conntypes.DC,
req := rpc.NewHandshakeRequest() protocol conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) {
if _, err := conn.Write(req.Bytes()); err != nil { if dc == 0 {
return nil, errors.Annotate(err, "Cannot send RPC handshake request") dc = conntypes.DCDefaultIdx
} }
return req, nil m.mutex.RLock()
} defer m.mutex.RUnlock()
func (t *middleTelegram) receiveRPCHandshakeResponse(conn wrappers.PacketReader, req *rpc.HandshakeRequest) (*rpc.HandshakeResponse, error) { return m.baseTelegram.dial(dc, protocol)
packet, err := conn.Read()
if err != nil {
return nil, errors.Annotate(err, "Cannot read RPC handshake response")
}
rpcHandshakeResp, err := rpc.NewHandshakeResponse(packet)
if err != nil {
return nil, errors.Annotate(err, "Cannot initialize RPC handshake response")
}
if err = rpcHandshakeResp.Valid(req); err != nil {
return nil, errors.Annotate(err, "Invalid RPC handshake response")
}
return rpcHandshakeResp, nil
}
// NewMiddleTelegram creates new instance of Telegram which works with
// middle proxies.
func NewMiddleTelegram(conf *config.Config) Telegram {
tg := &middleTelegram{
middleTelegramCaller: middleTelegramCaller{
baseTelegram: baseTelegram{
dialer: tgDialer{
Dialer: net.Dialer{Timeout: telegramDialTimeout},
conf: conf,
},
},
httpClient: &http.Client{
Timeout: middleTelegramHTTPClientTimeout,
},
dialerMutex: &sync.RWMutex{},
},
conf: conf,
}
if err := tg.update(); err != nil {
panic(err)
}
go tg.autoUpdate()
return tg
} }
-152
View File
@@ -1,152 +0,0 @@
package telegram
import (
"bufio"
"io/ioutil"
"net"
"net/http"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/juju/errors"
"go.uber.org/zap"
"github.com/9seconds/mtg/mtproto"
"github.com/9seconds/mtg/wrappers"
)
const (
middleTelegramAutoUpdateInterval = 6 * time.Hour
middleTelegramHTTPClientTimeout = 30 * time.Second
tgAddrProxySecret = "https://core.telegram.org/getProxySecret" // nolint: gas
tgAddrProxyV4 = "https://core.telegram.org/getProxyConfig" // nolint: gas
tgAddrProxyV6 = "https://core.telegram.org/getProxyConfigV6" // nolint: gas
tgUserAgent = "mtg"
)
var middleTelegramProxyConfigSplitter = regexp.MustCompile(`\s+`)
type middleTelegramCaller struct {
baseTelegram
proxySecret []byte
dialerMutex *sync.RWMutex
httpClient *http.Client
}
func (t *middleTelegramCaller) Dial(connID string, connOpts *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error) {
dc := connOpts.DC
if dc == 0 {
dc = 1
}
t.dialerMutex.RLock()
defer t.dialerMutex.RUnlock()
return t.baseTelegram.dial(dc, connID, connOpts.ConnectionProto)
}
func (t *middleTelegramCaller) autoUpdate() {
for range time.Tick(middleTelegramAutoUpdateInterval) {
if err := t.update(); err != nil {
zap.S().Warnw("Cannot update from Telegram", "error", err)
}
}
}
func (t *middleTelegramCaller) update() error {
secret, err := t.getTelegramProxySecret()
if err != nil {
return errors.Annotate(err, "Cannot get proxy secret")
}
v4Addresses, err := t.getTelegramAddresses(tgAddrProxyV4)
if err != nil {
return errors.Annotate(err, "Cannot get ipv4 addresses")
}
v6Addresses, err := t.getTelegramAddresses(tgAddrProxyV6)
if err != nil {
return errors.Annotate(err, "Cannot get ipv6 addresses")
}
t.dialerMutex.Lock()
t.proxySecret = secret
t.v4Addresses = v4Addresses
t.v6Addresses = v6Addresses
t.dialerMutex.Unlock()
zap.S().Infow("Telegram middle proxy data has been updated")
return nil
}
func (t *middleTelegramCaller) getTelegramProxySecret() ([]byte, error) {
resp, err := t.call(tgAddrProxySecret)
if err != nil {
return nil, errors.Annotate(err, "Cannot access telegram server")
}
defer resp.Body.Close() // nolint: errcheck
secret, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Annotate(err, "Cannot read response")
}
return secret, nil
}
func (t *middleTelegramCaller) getTelegramAddresses(url string) (map[int16][]string, error) {
resp, err := t.call(url)
if err != nil {
return nil, errors.Annotate(err, "Cannot access telegram server")
}
defer resp.Body.Close() // nolint: errcheck
scanner := bufio.NewScanner(resp.Body)
data := map[int16][]string{}
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(text, "#") {
continue
}
chunks := middleTelegramProxyConfigSplitter.Split(text, 3)
if len(chunks) != 3 || chunks[0] != "proxy_for" {
return nil, errors.Errorf("Incorrect config '%s'", text)
}
dcIdx64, err2 := strconv.ParseInt(chunks[1], 10, 16)
if err2 != nil {
return nil, errors.Errorf("Incorrect config '%s'", text)
}
dcIdx := int16(dcIdx64)
addr := strings.TrimRight(chunks[2], ";")
if _, _, err2 = net.SplitHostPort(addr); err != nil {
return nil, errors.Annotatef(err2, "Incorrect config '%s'", text)
}
if addresses, ok := data[dcIdx]; ok {
data[dcIdx] = append(addresses, addr)
} else {
data[dcIdx] = []string{addr}
}
}
err = scanner.Err()
if err != nil {
return nil, errors.Annotate(err, "Cannot read response from the telegram")
}
return data, nil
}
func (t *middleTelegramCaller) call(url string) (*http.Response, error) {
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Accept", "text/plain")
req.Header.Set("User-Agent", tgUserAgent)
return t.httpClient.Do(req)
}
-46
View File
@@ -1,46 +0,0 @@
package telegram
import (
"math/rand"
"github.com/juju/errors"
"github.com/9seconds/mtg/mtproto"
"github.com/9seconds/mtg/wrappers"
)
// Telegram is an interface for different Telegram work modes.
type Telegram interface {
Dial(string, *mtproto.ConnectionOpts) (wrappers.StreamReadWriteCloser, error)
Init(*mtproto.ConnectionOpts, wrappers.StreamReadWriteCloser) (wrappers.Wrap, error)
}
type baseTelegram struct {
dialer tgDialer
v4Addresses map[int16][]string
v6Addresses map[int16][]string
}
func (b *baseTelegram) dial(dcIdx int16, connID string, proto mtproto.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) {
addrs := make([]string, 2)
if proto&mtproto.ConnectionProtocolIPv6 != 0 {
if addr, ok := b.v6Addresses[dcIdx]; ok && len(addr) > 0 {
addrs = append(addrs, addr[rand.Intn(len(addr))])
}
}
if proto&mtproto.ConnectionProtocolIPv4 != 0 {
if addr, ok := b.v4Addresses[dcIdx]; ok && len(addr) > 0 {
addrs = append(addrs, addr[rand.Intn(len(addr))])
}
}
for _, addr := range addrs {
if conn, err := b.dialer.dialRWC(addr, connID); err == nil {
return conn, err
}
}
return nil, errors.New("Cannot connect to Telegram")
}
+86
View File
@@ -0,0 +1,86 @@
package tlstypes
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"fmt"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/utils"
)
type ClientHello struct {
Handshake
}
func (c ClientHello) Digest() []byte {
dirtyDigest := c.Random
c.Random = [32]byte{}
rec := Record{
Type: RecordTypeHandshake,
Version: Version10,
Data: &c,
}
mac := hmac.New(sha256.New, config.C.Secret)
rec.WriteBytes(mac)
computedDigest := mac.Sum(nil)
for i := range computedDigest {
computedDigest[i] ^= dirtyDigest[i]
}
return computedDigest
}
func ParseClientHello(raw []byte) (*ClientHello, error) {
rv := &ClientHello{}
rv.Type = HandshakeType(raw[0])
if rv.Type != HandshakeTypeClient {
return nil, fmt.Errorf("incorrect handshake type %v", rv.Type)
}
raw = raw[1:]
sizeUint24 := utils.Uint24{}
copy(sizeUint24[:], utils.ReverseBytes(raw[:3]))
size := int(utils.FromUint24(sizeUint24))
raw = raw[3:]
if len(raw) != size {
return nil, fmt.Errorf("payload size mismatch (%d != %d)", len(raw), size)
}
versionRaw := raw[:2]
switch {
case bytes.Equal(versionRaw, Version13Bytes):
rv.Version = Version13
case bytes.Equal(versionRaw, Version12Bytes):
rv.Version = Version12
case bytes.Equal(versionRaw, Version11Bytes):
rv.Version = Version11
case bytes.Equal(versionRaw, Version10Bytes):
rv.Version = Version10
default:
return nil, fmt.Errorf("unknown protocol version %v", versionRaw)
}
raw = raw[2:]
copy(rv.Random[:], raw[:32])
raw = raw[32:]
sessionIDLength := int(raw[0])
raw = raw[1:]
rv.SessionID = make([]byte, sessionIDLength)
copy(rv.SessionID, raw)
raw = raw[sessionIDLength:]
tail := make([]byte, len(raw))
copy(tail, raw)
rv.Tail = RawBytes(tail)
return rv, nil
}
+90
View File
@@ -0,0 +1,90 @@
package tlstypes
import "io"
type RecordType uint8
const (
RecordTypeHandshake RecordType = 0x16
RecordTypeApplicationData RecordType = 0x17
RecordTypeChangeCipherSpec RecordType = 0x14
)
type HandshakeType uint8
const (
HandshakeTypeClient HandshakeType = 0x01
HandshakeTypeServer HandshakeType = 0x02
)
type CipherSuiteType uint8
const (
CipherSuiteType_TLS_AES_128_GCM_SHA256 CipherSuiteType = iota // nolint: stylecheck,golint,revive
CipherSuiteType_TLS_AES_256_GCM_SHA384 // nolint: stylecheck,golint,revive
CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256 // nolint: stylecheck,golint,revive
)
func (c CipherSuiteType) Bytes() []byte {
switch c {
case CipherSuiteType_TLS_AES_128_GCM_SHA256:
return CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes
case CipherSuiteType_TLS_AES_256_GCM_SHA384:
return CipherSuiteType_TLS_AES_256_GCM_SHA384_Bytes
case CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256:
return CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256_Bytes
}
return CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256_Bytes
}
type Version uint8
func (v Version) Bytes() []byte {
switch v {
case Version13:
return Version13Bytes
case Version12:
return Version12Bytes
case Version11:
return Version11Bytes
case Version10, VersionUnknown:
return Version10Bytes
}
return Version10Bytes
}
const (
VersionUnknown Version = iota
Version10
Version11
Version12
Version13
)
var (
Version10Bytes = []byte{0x03, 0x01}
Version11Bytes = []byte{0x03, 0x02}
Version12Bytes = []byte{0x03, 0x03}
Version13Bytes = []byte{0x03, 0x04}
CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes = []byte{0x13, 0x01} // nolint: stylecheck,golint,revive
CipherSuiteType_TLS_AES_256_GCM_SHA384_Bytes = []byte{0x13, 0x02} // nolint: stylecheck,golint,revive
CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256_Bytes = []byte{0x13, 0x03} // nolint: stylecheck,golint,revive
)
type Byter interface {
WriteBytes(io.Writer)
Len() int
}
type RawBytes []byte
func (r RawBytes) WriteBytes(writer io.Writer) {
writer.Write(r) // nolint: errcheck
}
func (r RawBytes) Len() int {
return len(r)
}
+43
View File
@@ -0,0 +1,43 @@
package tlstypes
import (
"bytes"
"io"
"github.com/9seconds/mtg/utils"
)
type Handshake struct {
Type HandshakeType
Version Version
Random [32]byte
SessionID []byte
Tail Byter
}
func (h *Handshake) WriteBytes(writer io.Writer) {
packetBuf := bytes.Buffer{}
writer.Write([]byte{byte(h.Type)}) // nolint: errcheck
packetBuf.Write(h.Version.Bytes())
packetBuf.Write(h.Random[:])
packetBuf.WriteByte(byte(len(h.SessionID)))
packetBuf.Write(h.SessionID)
h.Tail.WriteBytes(&packetBuf)
sizeUint24 := utils.ToUint24(uint32(packetBuf.Len()))
sizeUint24Bytes := sizeUint24[:]
sizeUint24Bytes[0], sizeUint24Bytes[2] = sizeUint24Bytes[2], sizeUint24Bytes[0]
writer.Write(sizeUint24Bytes) // nolint: errcheck
packetBuf.WriteTo(writer) // nolint: errcheck
}
func (h *Handshake) Len() int {
buf := bytes.Buffer{}
h.WriteBytes(&buf)
return buf.Len()
}
+84
View File
@@ -0,0 +1,84 @@
package tlstypes
import (
"bytes"
"encoding/binary"
"fmt"
"io"
)
const recordMaxChunkSize = 16384 + 24
type Record struct {
Type RecordType
Version Version
Data Byter
}
func (r Record) WriteBytes(writer io.Writer) {
writer.Write([]byte{byte(r.Type)}) // nolint: errcheck
writer.Write(r.Version.Bytes()) // nolint: errcheck
binary.Write(writer, binary.BigEndian, uint16(r.Data.Len())) // nolint: errcheck
r.Data.WriteBytes(writer)
}
func (r Record) Len() int {
return 1 + 2 + 2 + r.Data.Len()
}
func ReadRecord(reader io.Reader) (Record, error) {
buf := [2]byte{}
rec := Record{}
if _, err := io.ReadFull(reader, buf[:1]); err != nil {
return rec, fmt.Errorf("cannot read record type: %w", err)
}
rec.Type = RecordType(buf[0])
if _, err := io.ReadFull(reader, buf[:]); err != nil {
return rec, fmt.Errorf("cannot read version: %w", err)
}
switch {
case bytes.Equal(buf[:], Version13Bytes):
rec.Version = Version13
case bytes.Equal(buf[:], Version12Bytes):
rec.Version = Version12
case bytes.Equal(buf[:], Version11Bytes):
rec.Version = Version11
case bytes.Equal(buf[:], Version10Bytes):
rec.Version = Version10
}
if _, err := io.ReadFull(reader, buf[:]); err != nil {
return rec, fmt.Errorf("cannot read data length: %w", err)
}
data := make([]byte, binary.BigEndian.Uint16(buf[:]))
if _, err := io.ReadFull(reader, data); err != nil {
return rec, fmt.Errorf("cannot read data: %w", err)
}
rec.Data = RawBytes(data)
return rec, nil
}
func MakeRecords(raw []byte) (arr []Record) {
for len(raw) > 0 {
chunkSize := recordMaxChunkSize
if chunkSize > len(raw) {
chunkSize = len(raw)
}
arr = append(arr, Record{
Type: RecordTypeApplicationData,
Version: Version12,
Data: RawBytes(raw[:chunkSize]),
})
raw = raw[chunkSize:]
}
return
}
+97
View File
@@ -0,0 +1,97 @@
package tlstypes
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"io"
mrand "math/rand"
"github.com/9seconds/mtg/config"
"golang.org/x/crypto/curve25519"
)
type ServerHello struct {
Handshake
clientHello *ClientHello
}
func (s ServerHello) WelcomePacket() []byte {
buf := &bytes.Buffer{}
s.Random = [32]byte{}
rec := Record{
Type: RecordTypeHandshake,
Version: Version12,
Data: &s,
}
rec.WriteBytes(buf)
recChangeCipher := Record{
Type: RecordTypeChangeCipherSpec,
Version: Version12,
Data: RawBytes([]byte{0x01}),
}
recChangeCipher.WriteBytes(buf)
hostCert := make([]byte, 1024+mrand.Intn(3092)) // nolint: gosec
rand.Read(hostCert) // nolint: errcheck
recData := Record{
Type: RecordTypeApplicationData,
Version: Version12,
Data: RawBytes(hostCert),
}
recData.WriteBytes(buf)
packet := buf.Bytes()
mac := hmac.New(sha256.New, config.C.Secret)
mac.Write(s.clientHello.Random[:]) // nolint: errcheck
mac.Write(packet) // nolint: errcheck
copy(packet[11:], mac.Sum(nil))
return packet
}
func NewServerHello(clientHello *ClientHello) *ServerHello {
rv := &ServerHello{
clientHello: clientHello,
}
rv.Type = HandshakeTypeServer
rv.Version = Version12
rv.SessionID = make([]byte, len(clientHello.SessionID))
copy(rv.SessionID, clientHello.SessionID)
tail := bytes.NewBuffer(CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes)
tail.WriteByte(0x00) // no compression
makeTLSExtensions(tail)
rv.Tail = RawBytes(tail.Bytes())
return rv
}
func makeTLSExtensions(buf io.Writer) {
buf.Write([]byte{ // nolint: errcheck
0x00, 0x2e, // 46 bytes of data
0x00, 0x33, // Extension - Key Share
0x00, 0x24, // 36 bytes
0x00, 0x1d, // x25519 curve
0x00, 0x20, // 32 bytes of key
})
var scalar [32]byte
rand.Read(scalar[:]) // nolint: errcheck
curve, _ := curve25519.X25519(scalar[:], curve25519.Basepoint)
buf.Write(curve) // nolint: errcheck
buf.Write([]byte{ // nolint: errcheck
0x00, 0x2b, // Extension - Supported Versions
0x00, 0x02, // 2 bytes are following
0x03, 0x04, // TLS 1.3
})
}
+28
View File
@@ -0,0 +1,28 @@
package utils
import (
"fmt"
"net"
)
func InitTCP(conn net.Conn, readBufferSize int, writeBufferSize int) error {
tcpConn := conn.(*net.TCPConn)
if err := tcpConn.SetNoDelay(true); err != nil {
return fmt.Errorf("cannot set TCP_NO_DELAY: %w", err)
}
if err := tcpConn.SetReadBuffer(readBufferSize); err != nil {
return fmt.Errorf("cannot set read buffer size: %w", err)
}
if err := tcpConn.SetWriteBuffer(writeBufferSize); err != nil {
return fmt.Errorf("cannot set write buffer size: %w", err)
}
if err := tcpConn.SetKeepAlive(true); err != nil {
return fmt.Errorf("cannot enable keep-alive: %w", err)
}
return nil
}
-21
View File
@@ -1,21 +0,0 @@
package utils
import "io"
const readCurrentDataBufferSize = 1024 + 1 // + 1 because telegram operates with blocks mod 4
// ReadCurrentData reads all data from io.Reader which is ready to be read.
func ReadCurrentData(src io.Reader) (rv []byte, err error) {
buf := make([]byte, readCurrentDataBufferSize)
n := readCurrentDataBufferSize
for n == len(buf) {
n, err = src.Read(buf)
if err != nil {
return nil, err
}
rv = append(rv, buf[:n]...)
}
return rv, nil
}
+21
View File
@@ -0,0 +1,21 @@
package utils
import "io"
const readFullBufferSize = 1024 + 1 // +1 because telegram opreates with blocks mod 4
func ReadFull(src io.Reader) (rv []byte, err error) {
buf := make([]byte, readFullBufferSize)
n := readFullBufferSize
for n == len(buf) {
n, err = src.Read(buf)
if err != nil {
return nil, err // nolint: wrapcheck
}
rv = append(rv, buf[:n]...)
}
return rv, nil
}

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