REPOSITORY / ScuroNeko/mtg

Compare commits

DIFF REPOSITORY

Compare commits

...
233 Commits
Author SHA1 Message Date
9seconds 136eea551f Merge remote-tracking branch 'origin/stable' into v2 2026-02-24 18:46:18 +01:00
9seconds e6fa5906c9 Merge remote-tracking branch 'origin/master' into stable 2026-02-24 18:45:46 +01:00
9seconds 42f612f49e Use go tag for 1.26 2026-02-24 18:08:45 +01:00
Sergei ArkhipovandGitHub d7db8ca98b Merge pull request #339 from 9seconds/domain-fronting-config-grouping
Domain fronting config grouping
2026-02-24 18:07:02 +01:00
9seconds 1cb225f52c Introduce [domain-fronting] config 2026-02-24 18:05:12 +01:00
9seconds af72b2a574 Delete obsoleted setting 2026-02-24 16:56:42 +01:00
Sergei ArkhipovandGitHub 2cbee5d453 Merge pull request #338 from 9seconds/proxy-proto-front
Add support for domain fronting proxy protocol
2026-02-24 16:53:17 +01:00
9seconds cde313b359 Add support for domain fronting proxy protocol 2026-02-24 16:44:35 +01:00
Sergei ArkhipovandGitHub 58cb0b2caf Merge pull request #336 from 9seconds/obfuscated2
Fetch DC203 from Telegram
2026-02-24 16:41:06 +01:00
9seconds bb320e9d89 Update fuzz tests 2026-02-24 15:27:19 +01:00
Sergei ArkhipovandGitHub f6d2f2ffd8 Merge pull request #337 from 9seconds/govulncheck
Check for vulnerabilities
2026-02-24 15:25:06 +01:00
9seconds 5fe3fdd73c Check for vulnerabilities 2026-02-24 14:21:16 +01:00
9seconds 5b91edf5c4 Fix tests 2026-02-24 13:58:16 +01:00
9seconds 8b34c1b104 Merge remote-tracking branch 'origin/master' into obfuscated2 2026-02-24 13:37:10 +01:00
9seconds 36c766b331 Fix lint issues 2026-02-24 13:35:06 +01:00
9seconds e4a9a96309 Remove mentioning of DC overrides 2026-02-24 13:32:06 +01:00
9seconds 94d46d2c65 Add fetching of addresses from proxyGetConfig endpoint 2026-02-24 12:55:16 +01:00
9seconds 908842063a Do not use additional bytes buffer for faketls 2026-02-23 10:27:01 +01:00
9seconds e50cee5748 Do not use unnecessary lock in connRewind 2026-02-23 10:12:25 +01:00
9seconds ee524abdb5 Remove redundant copyBufferPool from relay 2026-02-23 10:12:25 +01:00
9seconds 3e75e4fa63 Delete old obfuscated2 package 2026-02-23 10:12:25 +01:00
9seconds 140e9dfc2e Integrate obfuscation package 2026-02-23 10:12:25 +01:00
9seconds d0065d35c2 Add new obfuscation package 2026-02-23 10:12:25 +01:00
Sergei ArkhipovandGitHub 45ce5c2f61 Merge pull request #334 from ivulit/master 2026-02-20 20:33:10 +01:00
ivulit 21129b6e00 Add domain-fronting-ip to example config 2026-02-20 12:34:22 +03:00
ivulit bf38f9f8af Add domain-fronting-ip option
Allow specifying an explicit IP address for the domain fronting host
instead of relying on DNS resolution. Useful when DNS resolution of
the fronting hostname is blocked.

The hostname from the secret is still used for SNI in TLS handshake.
2026-02-20 12:34:17 +03:00
9seconds b854b16e1a Merge branch 'stable' into v2 2026-02-19 14:37:52 +01:00
9seconds f4b296d1e0 Merge branch 'master' into stable 2026-02-19 14:37:36 +01:00
Sergei ArkhipovandGitHub 432e530f68 Adjust formatting in readme 2026-02-19 14:35:04 +01:00
Sergei ArkhipovandGitHub a2bf9a269a Merge pull request #333 from 9seconds/proxyproto
Add support of proxy protocol
2026-02-19 14:27:43 +01:00
9seconds cf3437bb63 Add support of proxy protocol 2026-02-19 14:22:00 +01:00
Sergei ArkhipovandGitHub d0e99dda2b Update README.md 2026-02-18 11:16:04 +01:00
Sergei ArkhipovandGitHub ac3bd16e83 Merge pull request #332 from 9seconds/telemt-recommend
Recommend to use telemt for adtag carvings
2026-02-18 11:14:22 +01:00
9seconds 222cef8c41 Recommend to use telemt for adtag carvings 2026-02-18 11:13:55 +01:00
Sergei ArkhipovandGitHub ed5da2864a Merge pull request #331 from 9seconds/go1.26
Chore updates for golang 1.26
2026-02-18 11:04:23 +01:00
9seconds 08393e426a Update mise dependencies 2026-02-18 10:37:41 +01:00
9seconds 0440ccf4ff Rewrite to WaitGroup.Go 2026-02-18 10:34:31 +01:00
9seconds a0aabf2391 Switch to rand/v2 2026-02-18 10:34:31 +01:00
9seconds 3b03c4a90a More idioms related to go 1.26 2026-02-17 23:38:12 +01:00
Sergei ArkhipovandGitHub 80b9159ce9 Merge pull request #330 from 9seconds/telemt
Mention telemt project in README
2026-02-17 22:19:50 +01:00
Sergei ArkhipovandGitHub bdabb0e59a Merge pull request #329 from 9seconds/tools
Tell about mise in readme
2026-02-17 22:19:32 +01:00
9seconds 543f5cde9c Mention telemt project in README 2026-02-17 21:46:15 +01:00
9seconds 1a247d18b1 Tell about mise in README 2026-02-17 21:43:16 +01:00
9seconds 5a63c7d5b4 Lock all mise dependencies 2026-02-17 21:39:12 +01:00
9seconds e7fdb02a29 Merge branch 'v2' into stable 2026-02-16 19:23:45 +01:00
9seconds 7a6ba6d8c6 Merge branch 'master' into v2 2026-02-16 19:23:28 +01:00
9seconds be398f9c79 Use mise lock 2026-02-16 19:22:37 +01:00
9seconds 4c029fc683 Use gofumpt from github 2026-02-16 19:22:37 +01:00
9seconds 3066672353 Add permissions for Docker job to write into packages 2026-02-16 18:01:27 +00:00
9seconds ae88c0cab0 More tags for relevant branches 2026-02-16 17:17:52 +00:00
9seconds 816b7f72b6 Change secret to Github token 2026-02-16 17:17:47 +00:00
Sergei ArkhipovandGitHub 1f7584c108 Merge pull request #326 from 9seconds/dc203
Correctly connect to DC203/CDN
2026-02-16 18:01:26 +01:00
Sergei ArkhipovandGitHub 2fb025bbfd Merge pull request #327 from 9seconds/workflow
Correctly build and push images into registry
2026-02-16 18:01:09 +01:00
Sergey Arkhipov c7ab53134a Correctly build and push images into registry 2026-02-16 16:39:29 +00:00
9seconds f83ee17361 Fix test 2026-02-16 17:30:11 +01:00
9seconds 36546cec2f Change default DOH to cloudflarte 2026-02-16 17:18:31 +01:00
9seconds 074c4017f5 Formatting by gofumpt 2026-02-16 17:10:06 +01:00
9seconds 8e87405d3e Remove integration with gotd 2026-02-16 16:30:10 +01:00
9seconds 82679ec20f Update config 2026-02-16 15:22:03 +01:00
9seconds 308e372a5d Propagate DcUpdateEach setting 2026-02-16 15:07:56 +01:00
9seconds 836a481026 Propagate DCOverrides 2026-02-16 14:58:41 +01:00
9seconds 0a5a45b32d Create internal DC package 2026-02-16 14:15:38 +01:00
9seconds 852ca713c8 Fetch DC ips from Telegram 2026-02-15 23:56:53 +01:00
9seconds 301bde88ac Add gopls to mise 2026-02-11 15:09:43 +01:00
9seconds 25bca76da5 Merge remote-tracking branch 'origin/stable' into v2 2026-02-11 14:26:31 +01:00
9seconds 76109f6204 Merge remote-tracking branch 'origin/master' into stable 2026-02-11 14:25:54 +01:00
9seconds 6db6415798 Enable tests back 2026-02-11 14:25:00 +01:00
9seconds 0faf482e52 Update dockerfile 2026-02-11 13:55:11 +01:00
9seconds 3e4faf6ba6 Test docker 2026-02-11 12:13:55 +01:00
9seconds f7f6a7637c Update for tag semver 2026-02-11 12:09:57 +01:00
9seconds 130b02013b More CI updates 2026-02-11 12:07:45 +01:00
9seconds 1b4c777ed3 Use mise for fuzzing 2026-02-11 11:58:30 +01:00
9seconds 60b4b5ad8f Fix lint issues 2026-02-11 11:54:47 +01:00
9seconds a4930a596a Update linter job 2026-02-11 11:49:51 +01:00
9seconds fb3e921bc6 Update linter job 2026-02-11 11:48:09 +01:00
9seconds afbf4f6f74 Disable push to docker hub 2026-02-11 11:44:59 +01:00
9seconds 592b7c5b87 Update setup-go actions 2026-02-11 11:44:52 +01:00
9seconds c170887499 Update checkouts 2026-02-11 11:44:44 +01:00
9seconds ad76304b43 Correct argument for coverage 2026-02-11 11:43:01 +01:00
9seconds 5e38819506 Update codecov action 2026-02-11 11:42:01 +01:00
9seconds f06fd5ca77 Update CI 2026-02-11 11:21:27 +01:00
9seconds ad88afeecd Remove makefile 2026-02-11 11:13:36 +01:00
9seconds c74c0c92c6 Update CI 2026-02-11 11:13:25 +01:00
9seconds e3dc03b4db Update dependencies 2026-02-11 10:46:07 +01:00
9seconds ecba88d2e3 Update to the latest golangci-lint 2026-02-11 10:20:04 +01:00
9seconds 37de052feb Disable failing real request test for a while 2026-02-11 10:02:39 +01:00
9seconds 2041f3154b Add tests into mise 2026-02-11 09:58:26 +01:00
9seconds b1728c3474 Add task for building image 2026-02-11 09:47:11 +01:00
9seconds b72af2b953 Add task for formatting source code 2026-02-11 09:45:40 +01:00
9seconds 75c317f35e Add task for static build 2026-02-11 09:45:40 +01:00
9seconds 795dabb80b Add docs task 2026-02-11 09:38:36 +01:00
9seconds 01b0166995 Add release mise task 2026-02-11 09:38:36 +01:00
9seconds e9a147c836 Add build task 2026-02-11 09:26:46 +01:00
9seconds de66835657 Update goreleaser 2026-02-11 08:59:17 +01:00
9seconds 75f11965d8 Switch to mise 2026-02-11 08:52:45 +01:00
Sergey ArkhipovandGitHub e68d0c7da5 Merge pull request #289 from arashnm80/arashnm80-fix-docker-command
edit docker command in README.md
2022-11-21 17:53:10 +01:00
Arash Nemat ZadehandGitHub 051180a170 edit docker command in README.md
added the missing slash mentioned in issue #287
2022-11-17 03:42:09 +03:30
9seconds b4e0143cb7 Merge remote-tracking branch 'origin/stable' 2022-08-09 17:57:09 +03:00
9seconds 269852a4f1 Merge remote-tracking branch 'origin/master' into stable 2022-08-09 17:26:42 +03:00
Sergey ArkhipovandGitHub bd8a7ed8c9 Merge pull request #278 from 9seconds/socks5-proxies-for-simple-run
Add parameter for a simple run that sets socks5 proxies up
2022-08-09 16:24:37 +02:00
9seconds 7e3e4a611d Add parameter for a simple run that sets socks5 proxies up 2022-08-09 17:02:49 +03:00
Sergey ArkhipovandGitHub db2be4001b Merge pull request #277 from 9seconds/go1.19 2022-08-08 22:12:13 +02:00
9seconds 05c99aee4a Update github workflow 2022-08-08 16:52:58 +03:00
9seconds 53dde2aafa Update dependencies 2022-08-08 16:51:47 +03:00
9seconds 36dad5a2f6 Update golangci-lint 2022-08-08 15:54:38 +03:00
9seconds 6a19ded78e Update docs 2022-08-04 18:39:00 +03:00
9seconds 008e17cdff Update golangci-lint to 1.47.3 2022-08-04 11:25:10 +03:00
9seconds 5ad64390d6 There is no need to set rlimit in go 1.19 2022-08-04 10:58:31 +03:00
9seconds f9ad93dd06 More comprehensive build info collection 2022-08-04 10:57:28 +03:00
9seconds 77edea0e18 Update Go in Dockerfile to 1.19 2022-08-04 10:18:32 +03:00
Sergey ArkhipovandGitHub 79f921fde9 Merge pull request #271 from Dank-del/patch-1 2022-07-02 21:41:17 +02:00
Sayan BiswasandGitHub 1cdaaa16b0 change usage of $PWD in docker command 2022-07-02 17:36:03 +05:30
Sayan BiswasandGitHub 5852f48ca5 Fix instructions in readme 2022-06-30 14:59:36 +05:30
9seconds 0e80222990 Merge remote-tracking branch 'origin/stable' 2022-03-21 16:14:15 +03:00
9seconds c07e3d573d Merge remote-tracking branch 'origin/master' into stable 2022-03-21 16:08:55 +03:00
9seconds f192a718f4 Fix test 2022-03-21 15:58:15 +03:00
Sergey ArkhipovandGitHub 0c4523d2c8 Merge pull request #263 from 9seconds/tags-for-ipblocklisted
Tags for ipblocklisted
2022-03-21 14:21:31 +03:00
9seconds b0063a4a25 Increase test time in CI 2022-03-21 14:21:07 +03:00
9seconds 6dc494e7d0 Update README 2022-03-21 13:42:13 +03:00
9seconds 534d5b755e Add tags for ip blocklisted metric 2022-03-21 13:42:13 +03:00
9seconds aa7e488a3a Update dependencies 2022-03-21 12:42:23 +03:00
Sergey ArkhipovandGitHub eba3673e27 Merge pull request #262 from 9seconds/better-whitelist
More elegant management of ip allowlists
2022-03-21 12:13:25 +03:00
9seconds a27facaa16 More elegant management of ip allowlists 2022-03-21 12:04:31 +03:00
Sergey ArkhipovandGitHub 78a9ff18a5 Merge pull request #261 from arch-btw/patch-1
Add AmbientCapabilities
2022-03-21 11:11:12 +03:00
9seconds c0899d0743 Add in-memory file 2022-03-21 10:18:50 +03:00
arch-btwandGitHub 12563219e6 Add AmbientCapabilities
AmbientCapabilities=CAP_NET_BIND_SERVICE
2022-03-20 08:35:20 -07:00
Sergey ArkhipovandGitHub 59b5ff4080 Merge pull request #259 from 9seconds/go118
Support of Go 1.18
2022-03-19 15:18:21 +03:00
9seconds d7e554540d Use recommended way of running golangci-lint install 2022-03-19 14:45:58 +03:00
9seconds 1575b82688 Update golangci-lint 2022-03-19 14:45:58 +03:00
9seconds 5df1f594cc Use embedded buildinfo 2022-03-18 18:06:56 +03:00
9seconds 02ad052c49 Update update-deps make command 2022-03-18 18:06:56 +03:00
9seconds 2b8c7825ca Run fuzzing in GitHub actions 2022-03-18 18:06:56 +03:00
9seconds 75357b3e3c Add fuzz to makefile 2022-03-18 17:26:57 +03:00
9seconds d8b14dc765 Add fuzzing for client hello 2022-03-18 17:24:36 +03:00
9seconds e0850869ba Linting 2022-03-18 14:58:08 +03:00
9seconds 9375552180 Add fuzz tests for obfuscated2 2022-03-18 14:58:03 +03:00
9seconds cbe5b8c94e Update go.mod to 1.18 2022-03-16 09:39:58 +03:00
9seconds 63b425f245 Use Go 1.18 only for CI
This is required due to fuzzing
2022-03-16 09:37:52 +03:00
9seconds b53ead4372 Build image on go 1.18 2022-03-16 09:37:30 +03:00
Sergey ArkhipovandGitHub 2cdd66c722 Merge pull request #258 from AHOHNMYC/readme-service-edit
More detail and secure service example
2022-03-14 09:37:45 +03:00
AHOHNMYCandGitHub 2ae0101d18 More detail and secure service example 2022-03-13 04:52:56 +03:00
9seconds 6da9c2e58d Merge remote-tracking branch 'origin/stable' 2022-03-11 18:17:18 +03:00
9seconds 6d89f14c9b Merge remote-tracking branch 'origin/master' into stable 2022-03-11 18:03:35 +03:00
9seconds 442e2da330 Revert "Consider test modules for update-deps"
This reverts commit 880dd04728.
2022-03-11 17:55:12 +03:00
9seconds a9b3560b25 Remove obsolete Golang 1.16 2022-03-11 17:27:01 +03:00
9seconds 880dd04728 Consider test modules for update-deps 2022-03-11 17:23:53 +03:00
9seconds 8ea1aa3f5e Update dependencies 2022-03-11 17:19:15 +03:00
Sergey ArkhipovandGitHub 4e5a83cfe9 Merge pull request #256 from 9seconds/golangcilint-1.44.2
Update golangci-lint to 1.44.2
2022-03-11 17:13:58 +03:00
9seconds 5282ca26f3 Update golangci-lint to 1.44.2 2022-03-11 17:08:33 +03:00
Sergey ArkhipovandGitHub 2077db1f1e Merge pull request #255 from 9seconds/iplistsize
Add iplist_size metric
2022-03-11 16:42:40 +03:00
9seconds 1a9bc80091 Unexport an error that is not required 2022-03-11 16:22:19 +03:00
9seconds 30170b9413 Add iplist_size metric 2022-03-11 16:20:11 +03:00
Sergey ArkhipovandGitHub 4687a7c899 Merge pull request #254 from 9seconds/cidranger
Use cidrranger instead of patricia
2022-03-11 16:16:52 +03:00
9seconds d467fba674 Use cidrranger instead of patricia 2022-03-11 16:12:45 +03:00
Sergey ArkhipovandGitHub 6e447b1d59 Merge pull request #253 from 9seconds/cannot-dial-err
More correct error if no addresses are found
2022-03-11 11:33:36 +03:00
9seconds b15a8ec4a5 More correct error if no addresses are found 2022-03-11 11:19:15 +03:00
Sergey ArkhipovandGitHub 6bef4df091 Merge pull request #252 from 9seconds/broken-whitelist
Fix broken ip lists
2022-03-11 11:05:11 +03:00
9seconds 0ce0c668b9 Fix broken whitelists 2022-03-11 10:50:15 +03:00
Sergey ArkhipovandGitHub 01e201365b Merge pull request #249 from themegabyte/master
modified docker commands for ease of use
2022-03-09 09:49:07 +03:00
Shayan 0b52367a82 modified docker commands for ease of use 2022-03-07 13:52:57 +05:00
Sergey ArkhipovandGitHub 25c842daf1 Fix typo 2022-03-02 10:25:46 +03:00
9seconds 4c543aaea2 Merge remote-tracking branch 'origin/stable' 2021-12-03 09:36:47 +03:00
9seconds fee133a62f Merge remote-tracking branch 'origin/master' into stable 2021-12-03 09:28:40 +03:00
9seconds 7d38fec74e Update dependencies 2021-12-03 09:28:05 +03:00
9seconds 15bb5be6c4 Update go mod tidy command 2021-12-03 09:27:58 +03:00
9seconds ad8c09a2a3 Fix gofumpt cli 2021-12-03 09:26:31 +03:00
Sergey ArkhipovandGitHub 2f626e2138 Merge pull request #234 from 9seconds/cant-send-attachment 2021-12-03 09:31:54 +04:00
9seconds 93bed24a0b Remove all cleverness that broke uploads 2021-12-03 07:39:45 +03:00
9seconds 219235e181 Merge remote-tracking branch 'origin/stable' 2021-12-02 07:29:59 +03:00
9seconds 79f54a4e67 Merge remote-tracking branch 'origin/master' into stable 2021-12-02 07:08:30 +03:00
9seconds 4b78e83be7 Update dependencies 2021-12-02 06:55:14 +03:00
Sergey ArkhipovandGitHub ad30eca406 Merge pull request #232 from 9seconds/golangcilint-1.43.0
Golangcilint 1.43.0
2021-12-02 07:53:40 +04:00
9seconds ecf947b86c Ignore noisy linters 2021-12-02 06:51:09 +03:00
9seconds 5be581154e Update golangci-lint to 1.43.0 2021-12-02 06:03:45 +03:00
9seconds bc3b517e8b Remove redundant setting of TCP keepalive since it is default 2021-12-02 05:47:45 +03:00
Sergey ArkhipovandGitHub 3f8f96b91f Merge pull request #230 from 9seconds/simplify-sockopts
Simplify sockopts
2021-12-01 16:03:52 +04:00
9seconds ef55fbba15 Add documentation for essentials 2021-12-01 14:56:39 +03:00
9seconds e7416bc04d Fix lint issues 2021-12-01 11:07:32 +03:00
9seconds 9a6264a89f Tidy deps 2021-12-01 10:52:08 +03:00
9seconds 33e0509c5a Optimize for a fast flush 2021-12-01 10:51:13 +03:00
9seconds ffad717829 Use CloseRead and CloseWrites 2021-12-01 10:37:31 +03:00
9seconds 7b1f86b75d wip 2021-11-30 15:19:37 +03:00
9seconds a5e59d9ef7 Add syncPair 2021-11-30 14:37:25 +03:00
Sergey ArkhipovandGitHub 16c06f247c Merge pull request #231 from 9seconds/whitelists
Whitelist support
2021-11-29 18:26:39 +04:00
9seconds 0ddaabb136 Add whitelist support 2021-11-29 17:02:53 +03:00
9seconds 558fec60de Refactor firehol 2021-11-29 16:25:33 +03:00
9seconds cc101c9a47 Rename rwMutex to updateMutex 2021-11-29 15:58:14 +03:00
9seconds e6fa69d288 Add tests for HTTP file abstraction 2021-11-29 15:56:53 +03:00
9seconds c14a2329c5 Add local file abstraction 2021-11-29 07:26:27 +03:00
9seconds 4c75066ef8 Update configuration 2021-11-29 05:35:37 +03:00
9seconds ce8163d1b7 Minor simplification 2021-11-28 18:45:24 +03:00
9seconds ca77157fd5 Deprecate and ignore simple-run setting for tcp-buffer 2021-11-28 18:15:05 +03:00
9seconds 66f4d967e7 Get rid of buffersize everywhere 2021-11-28 18:08:40 +03:00
9seconds d19cfb1df4 Deprecate bufferSize 2021-11-28 17:58:14 +03:00
Sergey ArkhipovandGitHub 3540408adf Merge pull request #224 from dariubs/patch-1 2021-11-04 22:40:14 +03:00
Dariush AbbasiandGitHub 7917434a37 fix git clone url
replace : with /
2021-11-03 14:09:18 +03:30
Sergey ArkhipovandGitHub 853395106b Merge pull request #223 from boypt/fixdohv6
fix ipv6 doh-ip
2021-11-02 18:17:42 +03:00
Bot Gitandboypt d1b0d1f133 fix ipv6 doh-ip 2021-11-02 20:56:58 +08:00
Sergey ArkhipovandGitHub 73bd7287f0 Merge pull request #221 from boypt/fixtimeskew 2021-10-31 21:07:31 +03:00
boypt 31b8ab4482 fix TolerateTimeSkewness not being passed 2021-10-30 22:06:51 +08:00
9seconds bae5407372 Merge remote-tracking branch 'origin/stable' 2021-10-05 12:09:05 +03:00
9seconds 4814b0fcc1 Merge remote-tracking branch 'origin/master' into stable 2021-10-05 11:33:57 +03:00
9seconds 3ce549bb16 Update dependencies 2021-10-05 11:16:57 +03:00
Sergey ArkhipovandGitHub 5c636a68dc Merge pull request #217 from 9seconds/golangcilint-1.42.1
golangci-lint 1.42.1
2021-10-05 11:10:08 +03:00
9seconds 9f6f906786 Add go:build comments 2021-10-05 10:51:41 +03:00
9seconds eb32766c1f Update to golangci-lint 1.42.1 2021-10-05 10:51:15 +03:00
9seconds 787d72cf52 Use install, not go get 2021-10-05 10:50:58 +03:00
Sergey ArkhipovandGitHub ce8f1ebb6c Merge pull request #216 from 9seconds/configure-fallback-dc
Add configuration option allow-fallback-on-unknown-dc
2021-10-04 15:09:47 +03:00
9seconds cd29f3e20b Add configuration option allow-fallback-on-unknown-dc 2021-10-04 14:42:26 +03:00
Sergey ArkhipovandGitHub 3e105f2beb Merge pull request #215 from 9seconds/9seconds-patch-1
Update Actions permissions
2021-10-04 14:31:58 +03:00
Sergey ArkhipovandGitHub 6bbdd99e7f Update codeql-analysis.yml 2021-10-04 14:18:50 +03:00
Sergey Arkhipovand9seconds 894c68019e Update permissions in files 2021-10-04 14:16:29 +03:00
Sergey ArkhipovandGitHub 98f18fc22b Merge pull request #214 from 9seconds/log-json
Add new BindJSON method to a logger
2021-10-04 12:22:43 +03:00
Sergey ArkhipovandGitHub 01b739aa7c Merge pull request #213 from 9seconds/fix-windows-build
Fix windows build
2021-10-04 10:20:13 +03:00
9seconds dbaa743e03 Add new BindJSON method to a logger 2021-10-04 10:10:14 +03:00
9seconds d1e5f9d145 Fix windows build 2021-10-04 09:46:49 +03:00
9seconds 686f177ab9 Correct error for non-getting of underlying connection 2021-10-04 09:34:10 +03:00
Sergey ArkhipovandGitHub 706aef8ca1 Merge pull request #211 from 9seconds/docker-build-timelimit 2021-09-24 22:00:06 +03:00
9seconds 14dfb9506a Bump docker build timeout to 20 minutes 2021-09-24 16:59:11 +03:00
Sergey ArkhipovandGitHub f742066c54 Merge pull request #210 from 9seconds/fallback-to-random-dc
Fallback to another DC if given is unknown
2021-09-24 12:18:05 +03:00
9seconds fbe4d32590 Fallback to another DC if given is unknown 2021-09-24 11:47:35 +03:00
9seconds d0f18be91d Merge remote-tracking branch 'origin/stable' 2021-08-30 15:50:27 +03:00
9seconds 929b73e2eb Merge remote-tracking branch 'origin/master' into stable 2021-08-30 15:50:15 +03:00
9seconds 6b7364238a Update dependencies 2021-08-30 14:51:56 +03:00
Sergey ArkhipovandGitHub 866906e770 Merge pull request #206 from 9seconds/golang-1.17-v2
Upgrade to golang 1.17
2021-08-30 14:39:28 +03:00
9seconds b3c8c4a47d Upgrade to golang 1.17 2021-08-30 11:53:31 +03:00
Sergey ArkhipovandGitHub fbc7499cda Merge pull request #203 from 9seconds/simplify-time-randomization
Simplify TCP relay time randomization
2021-08-30 10:58:47 +03:00
9seconds ca5800cf60 Simplify tcp relay time randomization 2021-08-30 10:47:36 +03:00
Sergey ArkhipovandGitHub 00bb7be900 Merge pull request #202 from 9seconds/golangcilint-1.42 2021-08-28 04:33:21 +03:00
9seconds e8c70603f7 Upgrade golangci-lint for 1.42 2021-08-27 18:05:51 +03:00
Sergey ArkhipovandGitHub 9d72904508 Merge pull request #200 from 9seconds/aggressive-tcp-relay
Change algorithm of TCP relaying
2021-08-27 17:50:33 +03:00
9seconds 456ed5b051 Change algorithm of TCP relaying 2021-08-27 17:22:36 +03:00
9seconds 4b7be8c565 Merge remote-tracking branch 'origin/stable' 2021-07-31 21:05:56 +03:00
159 changed files with 4412 additions and 3245 deletions
+68 -40
View File
@@ -2,6 +2,18 @@
name: CI name: CI
permissions:
actions: read
checks: read
contents: read
deployments: read
issues: read
discussions: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
on: on:
push: push:
tags: tags:
@@ -26,36 +38,40 @@ jobs:
test: test:
name: Test name: Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 5 timeout-minutes: 10
strategy:
matrix:
go_version:
- ^1.16
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Setup Go - uses: jdx/mise-action@v3
uses: actions/setup-go@v2 name: Install mise
with:
go-version: ${{ matrix.go_version }}
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- name: Run tests - name: Run tests
run: go test -coverprofile=./coverage.txt -covermode=atomic -v ./... run: mise tasks run covtest
- name: Collect coverage - name: Collect coverage
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v5
with: with:
file: ./coverage.txt files: ./coverage.txt
fuzz:
name: Fuzzing
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- uses: jdx/mise-action@v3
name: Install mise
- name: Run fuzzing
run: mise tasks run 'test:fuzz:*'
lint: lint:
name: Lint name: Lint
@@ -63,40 +79,52 @@ jobs:
timeout-minutes: 5 timeout-minutes: 5
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- uses: jdx/mise-action@v3
name: Install mise
- name: Run linter - name: Run linter
uses: golangci/golangci-lint-action@v2 run: mise tasks run lint
with:
version: v1.41.1
docker: docker:
name: Docker name: Docker
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15 timeout-minutes: 20
permissions:
contents: read
packages: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Get Docker meta - name: Get Docker meta
id: docker_meta id: meta
uses: crazy-max/ghaction-docker-meta@v1 uses: docker/metadata-action@v5
with: with:
images: nineseconds/mtg,ghcr.io/9seconds/mtg images: |
tag-semver: "{{version}},{{major}},{{major}}.{{minor}}" nineseconds/mtg
ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=master,enable=${{ github.ref == 'refs/heads/master' }}
type=raw,value=stable,enable=${{ github.ref == 'refs/heads/stable' }}
- name: Setup QEMU - name: Setup QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v3
- name: Setup BuildX - name: Setup BuildX
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v3
- name: Setup cache - name: Setup cache
uses: actions/cache@v2 uses: actions/cache@v5
with: with:
path: /tmp/buildx-cache path: /tmp/buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }} key: ${{ runner.os }}-buildx-${{ github.sha }}
@@ -105,18 +133,18 @@ jobs:
- name: Login to DockerHub - name: Login to DockerHub
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@v1 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }} password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to GHCR.io - name: Login to GitHub Container Registry
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@v1 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.actor }}
password: ${{ secrets.GH_PAT }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
@@ -125,7 +153,7 @@ jobs:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7,linux/arm/v6 platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7,linux/arm/v6
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=local,src=/tmp/buildx-cache cache-from: type=local,src=/tmp/buildx-cache
cache-to: type=local,dest=/tmp/buildx-cache cache-to: type=local,dest=/tmp/buildx-cache
+8
View File
@@ -11,6 +11,14 @@
# #
name: "CodeQL" name: "CodeQL"
# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions
# https://github.com/github/codeql-action/issues/572
permissions:
actions: read
contents: read
pull-requests: read
security-events: write
on: on:
push: push:
branches: branches:
+38
View File
@@ -0,0 +1,38 @@
---
name: Vulnerability checks
permissions:
actions: read
checks: read
contents: read
deployments: read
issues: read
discussions: read
pull-requests: read
repository-projects: read
security-events: read
statuses: read
on:
push:
pull_request:
schedule: # daily at 10:22 UTC
- cron: '22 10 * * *'
workflow_dispatch:
jobs:
vuln:
name: Test vulnerabilities
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- uses: jdx/mise-action@v3
name: Install mise
- name: Run tests
run: mise tasks run vuln
-3
View File
@@ -6,8 +6,5 @@
*.test *.test
*.out *.out
mtg mtg
vendor/
ccbuilds/
.bin/
coverage.txt coverage.txt
dist/ dist/
+17 -8
View File
@@ -1,12 +1,21 @@
# https://golangci-lint.run/docs/configuration/file/
version = '2'
[run] [run]
concurrency = 4 concurrency = 4
deadline = "2m"
tests = true tests = true
skip-dirs = ["vendor"]
[output] # [linters]
format = "colored-line-number" # enable-all = true
# disable = [
[linters] # "containedctx",
enable-all = true # "exhaustivestruct",
disable = ["gochecknoglobals", "gas", "goerr113", "exhaustivestruct"] # "exhaustruct",
# "gas",
# "gochecknoglobals",
# "goerr113",
# "ireturn",
# "thelper",
# "varnamelen",
# ]
+7 -3
View File
@@ -1,5 +1,7 @@
--- ---
version: 2
project_name: mtg project_name: mtg
before: before:
@@ -41,11 +43,13 @@ builds:
archives: archives:
- name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' - name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
format: tar.gz formats:
- tar.gz
wrap_in_directory: true wrap_in_directory: true
format_overrides: format_overrides:
- goos: windows - goos: windows
format: zip formats:
- zip
files: files:
- LICENSE - LICENSE
- README.md - README.md
@@ -55,7 +59,7 @@ gomod:
proxy: true proxy: true
snapshot: snapshot:
name_template: '{{ .Version }}' version_template: '{{ .Version }}'
checksum: checksum:
name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt' name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt'
+98
View File
@@ -0,0 +1,98 @@
[tools]
"go:golang.org/x/pkgsite/cmd/pkgsite" = "latest"
"go:golang.org/x/tools/gopls" = "latest"
"go:golang.org/x/vuln/cmd/govulncheck" = "latest"
"go:mvdan.cc/gofumpt" = "latest"
go = "latest"
golangci-lint = "latest"
goreleaser = "latest"
[vars]
fuzzflags = "-fuzztime=120s"
[tasks.build]
description = "Build binary"
sources = ["**/*.go", "go.mod", "go.sum"]
outputs = ["mtg"]
run = "go build"
[tasks.update]
description = "Update dependencies"
run = [
"go get -u",
"go mod tidy -go=1.26"
]
[tasks.lint]
description = "Run linter"
run = "golangci-lint run"
[tasks.vuln]
description = "Test for vulnerabilities"
run = "govulncheck ./..."
[tasks.test]
description = "Run tests"
run = "go test -v ./..."
[tasks.covtest]
description = "Run tests with code coverage"
run = "go test -coverprofile=coverage.txt -covermode=atomic -parallel 2 -race -v ./..."
[tasks.test-all]
description = "Run all tests"
depends = [
"test",
"test:fuzz:*"
]
[tasks."test:fuzz:client-hello"]
description = "Run fuzzy test for ClientHello"
run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzClientHello ./mtglib/internal/faketls"
[tasks."test:fuzz:client-handshake"]
description = "Run fuzzy test for ClientHandshake"
run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzClientServerHandshake ./mtglib/internal/obfuscation"
[tasks."test:fuzz:server-handshake-frame"]
description = "Run fuzzy test for GenerateHandshakeFrame"
run = "go test -v {{ vars.fuzzflags }} -fuzz=FuzzGenerateHandshakeFrame ./mtglib/internal/obfuscation"
[tasks.static]
description = "Build static binary"
sources = ["**/*.go", "go.mod", "go.sum"]
outputs = ["mtg"]
run = """
#!/bin/bash
version="$(git describe --exact-match HEAD 2>/dev/null || git describe --tags --always)"
go build \
-trimpath \
-mod=readonly \
-ldflags="-extldflags '-static' -s -w -X 'main.version=$version'" \
-a \
-tags netgo
"""
[tasks.release]
description = "Create release tarballs"
sources = ["**/*.go", "go.mod", "go.sum", ".goreleaser.yml"]
run = [
"goreleaser --snapshot --clean",
"find dist -depth 1 -type d | xargs -r rm -r",
"rm ./dist/config.yaml"
]
[tasks.image]
description = "Build docker image"
sources = ["**/*.go", "go.mod", "go.sum", "Dockerfile"]
run = "docker buildx build --pull -t mtg ."
[tasks.docs]
description = "Run doc server"
run = "pkgsite -http 0.0.0.0:10000"
[tasks.fmt]
description = "Reformat source code"
sources = ["**/*.go"]
run = "gofumpt -w --extra ."
+11 -5
View File
@@ -1,21 +1,27 @@
############################################################################### ###############################################################################
# BUILD STAGE # BUILD STAGE
FROM golang:1.16-alpine AS build FROM golang:1.26-alpine AS build
ENV CGO_ENABLED=0
RUN set -x \ RUN set -x \
&& apk --no-cache --update add \ && apk --no-cache --update add \
bash \ bash \
ca-certificates \ ca-certificates \
curl \ git
git \
make
COPY . /app COPY . /app
WORKDIR /app WORKDIR /app
RUN set -x \ RUN set -x \
&& make -j 4 static && version="$(git describe --exact-match HEAD 2>/dev/null || git describe --tags --always)" \
&& go build \
-trimpath \
-mod=readonly \
-ldflags="-extldflags '-static' -s -w -X 'main.version=$version'" \
-a \
-tags netgo
############################################################################### ###############################################################################
-98
View File
@@ -1,98 +0,0 @@
ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
IMAGE_NAME := mtg
APP_NAME := $(IMAGE_NAME)
GOLANGCI_LINT_VERSION := v1.41.1
VERSION_GO := $(shell go version)
VERSION_DATE := $(shell date -Ru)
VERSION_TAG := $(shell git describe --tags --always)
COMMON_BUILD_FLAGS := -trimpath -mod=readonly -ldflags="-extldflags '-static' -s -w -X 'main.version=$(VERSION_TAG) ($(VERSION_GO)) [$(VERSION_DATE)]'"
GOBIN := $(ROOT_DIR)/.bin
GOTOOL := env "GOBIN=$(GOBIN)" "PATH=$(ROOT_DIR)/.bin:$(PATH)"
# -----------------------------------------------------------------------------
.PHONY: all
all: build
.PHONY: build
build:
@go build $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)"
$(APP_NAME): build
.PHONY: static
static:
@env CGO_ENABLED=0 GOOS=linux go build \
$(COMMON_BUILD_FLAGS) \
-tags netgo \
-a \
-o "$(APP_NAME)"
vendor: go.mod go.sum
@$(MOD_ON) go mod vendor
.bin:
@mkdir -p "$(GOBIN)" || true
.PHONY: fmt
fmt:
@$(GOTOOL) gofumpt -w -s -extra "$(ROOT_DIR)"
.PHONY: test
test:
@go test -v ./...
.PHONY: citest
citest:
@go test -coverprofile=coverage.txt -covermode=atomic -parallel 2 -race -v ./...
.PHONY: clean
clean:
@git clean -xfd && \
git reset --hard >/dev/null && \
git submodule foreach --recursive sh -c 'git clean -xfd && git reset --hard' >/dev/null
.PHONY: lint
lint:
@$(GOTOOL) golangci-lint run
.PHONY: release
release:
@$(GOTOOL) goreleaser release --snapshot --rm-dist && \
find "$(ROOT_DIR)/dist" -type d | grep -vP "dist$$" | xargs -r rm -rf && \
rm -f "$(ROOT_DIR)/dist/config.yaml"
.PHONY: docker
docker:
@docker build --pull -t "$(IMAGE_NAME)" "$(ROOT_DIR)"
.PHONY: doc
doc:
@$(GOTOOL) godoc -http 0.0.0.0:10000
.PHONY: install-tools
install-tools: install-tools-lint install-tools-godoc install-tools-gofumpt install-tools-goreleaser
.PHONY: install-tools-lint
install-tools-lint: .bin
@curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh \
| bash -s -- -b "$(GOBIN)" "$(GOLANGCI_LINT_VERSION)"
.PHONY: install-tools-godoc
install-tools-godoc: .bin
@$(GOTOOL) go get -u golang.org/x/tools/cmd/godoc
.PHONY: install-tools-gofumpt
install-tools-gofumpt: .bin
@$(GOTOOL) go get -u mvdan.cc/gofumpt
.PHONY: goreleaser
install-tools-goreleaser: .bin
@$(GOTOOL) go get -u github.com/goreleaser/goreleaser
.PHONY: update-deps
update-deps:
@go get -u && go mod tidy
+56 -9
View File
@@ -1,6 +1,6 @@
# mtg # mtg
Highly-opionated (ex-bullshit-free) MTPROTO proxy for Highly-opinionated (ex-bullshit-free) MTPROTO proxy for
[Telegram](https://telegram.org/). [Telegram](https://telegram.org/).
[![CI](https://github.com/9seconds/mtg/actions/workflows/ci.yaml/badge.svg?branch=master)](https://github.com/9seconds/mtg/actions/workflows/ci.yaml) [![CI](https://github.com/9seconds/mtg/actions/workflows/ci.yaml/badge.svg?branch=master)](https://github.com/9seconds/mtg/actions/workflows/ci.yaml)
@@ -10,6 +10,17 @@ Highly-opionated (ex-bullshit-free) MTPROTO proxy for
**If you use v1.0 or upgrade broke you proxy, please read the chapter **If you use v1.0 or upgrade broke you proxy, please read the chapter
[Version 2](#version-2)** [Version 2](#version-2)**
If you want to have a proxy that _supports adtag_ (possibility to promote a
channel with a special Telegram bot), I recommend to use
[telemt](https://github.com/telemt/telemt) project. v1 of mtg supports it
but I do not see any reasonable point of using it: adtag requires communication
via a fragile set of middle proxies, requires complex setup that must expose
a public IPs, has lower bandwidth and latency.
mtg idea is simple: minimal unbloated proxy that can handle a reasonable scale
~10-20k simultaneous connections, has no user management, but ticks all
checkboxes related to its main intent: provide a way to use Telegram.
## Rationale ## Rationale
There are several available proxies for Telegram MTPROTO available. Here There are several available proxies for Telegram MTPROTO available. Here
@@ -18,6 +29,7 @@ are the most notable:
* [Official](https://github.com/TelegramMessenger/MTProxy) * [Official](https://github.com/TelegramMessenger/MTProxy)
* [Python](https://github.com/alexbers/mtprotoproxy) * [Python](https://github.com/alexbers/mtprotoproxy)
* [Erlang](https://github.com/seriyps/mtproto_proxy) * [Erlang](https://github.com/seriyps/mtproto_proxy)
* [Telemt (Rust)](https://github.com/telemt/telemt)
You can use any of these. They work great and all implementations have You can use any of these. They work great and all implementations have
feature parity now. This includes support of adtag, replay attack feature parity now. This includes support of adtag, replay attack
@@ -40,6 +52,12 @@ that probably matter.
way of doing business I suppose. I think the only viable way is to way of doing business I suppose. I think the only viable way is to
have a proxy that can be restored anywhere easily. have a proxy that can be restored anywhere easily.
* **Supports proxy protocol v1/v2**
This makes integration with loadbalancers like HAProxy and ELB a first class
citizen by supporting their
[commuication protocols](https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt).
* **A single secret** * **A single secret**
I think that multiple secrets solve no problems and just complex I think that multiple secrets solve no problems and just complex
@@ -162,6 +180,12 @@ This project has several main branches
## Getting started ## Getting started
### Download mise
mtg uses [mise](https://mise.jdx.dev/) to maintain its development
dependencies + replaces a make for building things. Please
[install](https://mise.jdx.dev/getting-started.html) it first.
### Download a tool ### Download a tool
#### Download binaries #### Download binaries
@@ -190,21 +214,22 @@ surprises. Always choose some version tag.
Also, if you have `go` installed, you can always download this tool with `go get`: Also, if you have `go` installed, you can always download this tool with `go get`:
```console ```console
go get github.com/9seconds/mtg/v2 go install github.com/9seconds/mtg/v2@latest
``` ```
#### Build from sources #### Build from sources
```console ```console
git clone https://github.com:9seconds/mtg.git git clone https://github.com/9seconds/mtg.git
cd mtg cd mtg
make static mise install
mise tasks run build
``` ```
or for the docker image: or for the docker image:
```console ```console
make docker mise tasks run image
``` ```
### Generate secret ### Generate secret
@@ -224,6 +249,16 @@ $ mtg generate-secret --hex google.com
ee473ce5d4958eb5f968c87680a23854a0676f6f676c652e636f6d ee473ce5d4958eb5f968c87680a23854a0676f6f676c652e636f6d
``` ```
equivalent commands with docker:
```console
$ docker run --rm nineseconds/mtg:2 generate-secret google.com
7ibaERuTSGPH1RdztfYnN4tnb29nbGUuY29t
$ docker run --rm nineseconds/mtg:2 generate-secret --hex google.com
ee473ce5d4958eb5f968c87680a23854a0676f6f676c652e636f6d
```
This secret is a keystone for a proxy and your password for a client. This secret is a keystone for a proxy and your password for a client.
You need to keep it secured. You need to keep it secured.
@@ -265,7 +300,7 @@ Flags:
-b, --tcp-buffer="4KB" Size of TCP buffer to use. -b, --tcp-buffer="4KB" Size of TCP buffer to use.
-i, --prefer-ip="prefer-ipv6" IP preference. By default we prefer IPv6 with fallback to IPv4. -i, --prefer-ip="prefer-ipv6" IP preference. By default we prefer IPv6 with fallback to IPv4.
-p, --domain-fronting-port=443 A port to access for domain fronting. -p, --domain-fronting-port=443 A port to access for domain fronting.
-n, --doh-ip=9.9.9.9 IP address of DNS-over-HTTP to use. -n, --doh-ip=1.1.1.1 IP address of DNS-over-HTTP to use.
-t, --timeout=10s Network timeout to use -t, --timeout=10s Network timeout to use
-a, --antireplay-cache-size="1MB" A size of anti-replay cache to use. -a, --antireplay-cache-size="1MB" A size of anti-replay cache to use.
``` ```
@@ -307,12 +342,16 @@ Now you can create a systemd unit:
```console ```console
$ cat /etc/systemd/system/mtg.service $ cat /etc/systemd/system/mtg.service
[Unit] [Unit]
Description=mtg Description=mtg - MTProto proxy server
Documentation=https://github.com/9seconds/mtg
After=network.target
[Service] [Service]
ExecStart=/usr/local/bin/mtg run /etc/mtg.toml ExecStart=/usr/local/bin/mtg run /etc/mtg.toml
Restart=always Restart=always
RestartSec=3 RestartSec=3
DynamicUser=true
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@@ -324,7 +363,7 @@ $ sudo systemctl start mtg
or you can run a docker image or you can run a docker image
```console ```console
docker run -d -v /etc/mtg.toml:/config.toml -p 443:3128 --restart=unless-stopped nineseconds/mtg:2 docker run -d -v $PWD/config.toml:/config.toml -p 443:3128 --name mtg-proxy --restart=unless-stopped nineseconds/mtg:2
``` ```
where _443_ is a host port (a port you want to connect to from a where _443_ is a host port (a port you want to connect to from a
@@ -353,6 +392,12 @@ $ mtg access /etc/mtg.toml
} }
``` ```
or if you are using docker:
```console
$ docker exec mtg-proxy /mtg access /config.toml
```
## Metrics ## Metrics
Out of the box, mtg works with Out of the box, mtg works with
@@ -367,11 +412,12 @@ Here goes a list of metrics with their types but without a prefix.
| client_connections | gauge | `ip_family` | Count of processing client connections. | | client_connections | gauge | `ip_family` | Count of processing client connections. |
| telegram_connections | gauge | `telegram_ip`, `dc` | Count of connections to Telegram servers. | | telegram_connections | gauge | `telegram_ip`, `dc` | Count of connections to Telegram servers. |
| domain_fronting_connections | gauge | `ip_family` | Count of connections to fronting domain. | | domain_fronting_connections | gauge | `ip_family` | Count of connections to fronting domain. |
| iplist_size | gauge | `ip_list` | A size of either allowlist or blocklist in use. |
| telegram_traffic | counter | `telegram_ip`, `dc`, `direction` | Count of bytes, transmitted to/from Telegram. | | telegram_traffic | counter | `telegram_ip`, `dc`, `direction` | Count of bytes, transmitted to/from Telegram. |
| domain_fronting_traffic | counter | `direction` | Count of bytes, transmitted to/from fronting domain. | | domain_fronting_traffic | counter | `direction` | Count of bytes, transmitted to/from fronting domain. |
| domain_fronting | counter | | Count of domain fronting events. | | domain_fronting | counter | | Count of domain fronting events. |
| concurrency_limited | counter | | Count of events, when client connection was rejected due to concurrency limit. | | concurrency_limited | counter | | Count of events, when client connection was rejected due to concurrency limit. |
| ip_blocklisted | counter | | Count of events when client connection was rejected because IP was found in the blacklist. | | ip_blocklisted | counter | `ip_list` | Count of events when client connection was rejected because IP was found in the blocklist. |
| replay_attacks | counter | | Count of detected replay attacks. | | replay_attacks | counter | | Count of detected replay attacks. |
Tag meaning: Tag meaning:
@@ -382,3 +428,4 @@ Tag meaning:
| dc | | A number of the Telegram DC for a connection. | | dc | | A number of the Telegram DC for a connection. |
| telegram_ip | | IP address of the Telegram server. | | telegram_ip | | IP address of the Telegram server. |
| direction | `to_client`, `from_client` | A direction of the traffic flow. | | direction | `to_client`, `from_client` | A direction of the traffic flow. |
| ip_list | `allowlist`, `blocklist` | A type of the IP list. |
+8 -8
View File
@@ -1,17 +1,17 @@
// Antireplay package has cache implementations that are effective // Antireplay package has cache implementations that are effective against
// against replay attacks. // replay attacks.
// //
// To understand more about replay attacks, please read documentation // To understand more about replay attacks, please read documentation for
// for mtglib.AntiReplayCache interface. This package has a list of some // [mtglib.AntiReplayCache] interface. This package has a list of some
// implementations of this interface. // implementations of this interface.
package antireplay package antireplay
const ( const (
// DefaultStableBloomFilterMaxSize is a recommended byte size for a // DefaultStableBloomFilterMaxSize is a recommended byte size for a stable
// stable bloom filter. // bloom filter.
DefaultStableBloomFilterMaxSize = 1024 * 1024 // 1MiB DefaultStableBloomFilterMaxSize = 1024 * 1024 // 1MiB
// DefaultStableBloomFilterErrorRate is a recommended default error // DefaultStableBloomFilterErrorRate is a recommended default error rate for a
// rate for a stable bloom filter. // stable bloom filter.
DefaultStableBloomFilterErrorRate = 0.001 DefaultStableBloomFilterErrorRate = 0.001
) )
+2 -3
View File
@@ -6,9 +6,8 @@ type noop struct{}
func (n noop) SeenBefore(_ []byte) bool { return false } func (n noop) SeenBefore(_ []byte) bool { return false }
// NewNoop returns an implementation that does nothing. A corresponding // NewNoop returns an implementation that does nothing. A corresponding method
// method always returns false, so this cache accepts everything you // always returns false, so this cache accepts everything you pass to it.
// pass to it.
func NewNoop() mtglib.AntiReplayCache { func NewNoop() mtglib.AntiReplayCache {
return noop{} return noop{}
} }
+9 -9
View File
@@ -20,19 +20,19 @@ func (s *stableBloomFilter) SeenBefore(digest []byte) bool {
return s.filter.TestAndAdd(digest) return s.filter.TestAndAdd(digest)
} }
// NewStableBloomFilter returns an implementation of AntiReplayCache // NewStableBloomFilter returns an implementation of AntiReplayCache based on
// based on stable bloom filter. // stable bloom filter.
// //
// http://webdocs.cs.ualberta.ca/~drafiei/papers/DupDet06Sigmod.pdf // http://webdocs.cs.ualberta.ca/~drafiei/papers/DupDet06Sigmod.pdf
// //
// The basic idea of a stable bloom filter is quite simple: each time // The basic idea of a stable bloom filter is quite simple: each time when you
// when you set a new element, you randomly reset P elements. There is a // set a new element, you randomly reset P elements. There is a hardcore math
// hardcore math which proves that if you choose this P correctly, you // which proves that if you choose this P correctly, you can maintain the same
// can maintain the same error rate for a stream of elements. // error rate for a stream of elements.
// //
// byteSize is the number of bytes you want to give to a bloom filter. // byteSize is the number of bytes you want to give to a bloom filter.
// errorRate is desired false-positive error rate. If you want to use // errorRate is desired false-positive error rate. If you want to use default
// default values, please pass 0 for byteSize and <0 for errorRate. // values, please pass 0 for byteSize and <0 for errorRate.
func NewStableBloomFilter(byteSize uint, errorRate float64) mtglib.AntiReplayCache { func NewStableBloomFilter(byteSize uint, errorRate float64) mtglib.AntiReplayCache {
if byteSize == 0 { if byteSize == 0 {
byteSize = DefaultStableBloomFilterMaxSize byteSize = DefaultStableBloomFilterMaxSize
@@ -42,7 +42,7 @@ func NewStableBloomFilter(byteSize uint, errorRate float64) mtglib.AntiReplayCac
errorRate = DefaultStableBloomFilterErrorRate errorRate = DefaultStableBloomFilterErrorRate
} }
sf := boom.NewDefaultStableBloomFilter(byteSize*8, errorRate) // nolint: gomnd sf := boom.NewDefaultStableBloomFilter(byteSize*8, errorRate)
sf.SetHash(xxhash.New64()) sf.SetHash(xxhash.New64())
return &stableBloomFilter{ return &stableBloomFilter{
+84
View File
@@ -0,0 +1,84 @@
package main
import (
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"fmt"
"io"
"runtime/debug"
"sort"
"strconv"
"time"
)
var version = "dev" // has to be set by ldflags
const (
buildInfoModuleStart byte = iota
buildInfoModuleFinish
buildInfoModuleDelimeter
)
func getVersion() string {
buildInfo, ok := debug.ReadBuildInfo()
if !ok {
return version
}
date := time.Now()
commit := ""
goVersion := buildInfo.GoVersion
dirtySuffix := ""
for _, setting := range buildInfo.Settings {
switch setting.Key {
case "vcs.time":
date, _ = time.Parse(time.RFC3339, setting.Value)
case "vcs.revision":
commit = setting.Value
case "vcs.modified":
if dirty, _ := strconv.ParseBool(setting.Value); dirty {
dirtySuffix = " [dirty]"
}
}
}
hasher := sha256.New()
checksumModule := func(mod *debug.Module) {
hasher.Write([]byte{buildInfoModuleStart})
io.WriteString(hasher, mod.Path) //nolint: errcheck
hasher.Write([]byte{buildInfoModuleDelimeter})
io.WriteString(hasher, mod.Version) //nolint: errcheck
hasher.Write([]byte{buildInfoModuleDelimeter})
io.WriteString(hasher, mod.Sum) //nolint: errcheck
hasher.Write([]byte{buildInfoModuleFinish})
}
io.WriteString(hasher, buildInfo.Path) //nolint: errcheck
binary.Write(hasher, binary.LittleEndian, uint64(1+len(buildInfo.Deps))) //nolint: errcheck
sort.Slice(buildInfo.Deps, func(i, j int) bool {
return buildInfo.Deps[i].Path > buildInfo.Deps[j].Path
})
checksumModule(&buildInfo.Main)
for _, module := range buildInfo.Deps {
checksumModule(module)
}
return fmt.Sprintf("%s (%s: %s on %s%s, modules checksum %s)",
version,
goVersion,
date.Format(time.RFC3339),
commit,
dirtySuffix,
base64.StdEncoding.EncodeToString(hasher.Sum(nil)))
}
+26
View File
@@ -0,0 +1,26 @@
package essentials
import (
"io"
"net"
)
// CloseableReader is an [io.Reader] interface that can close its reading end.
type CloseableReader interface {
io.Reader
CloseRead() error
}
// CloseableWriter is an [io.Writer] that can close its writing end.
type CloseableWriter interface {
io.Writer
CloseWrite() error
}
// Conn is an extension of [net.Conn] that can close its ends. This mostly
// implies TCP connections.
type Conn interface {
net.Conn
CloseableReader
CloseableWriter
}
+6
View File
@@ -0,0 +1,6 @@
// This is a minimal package that contains _essentials_ of mtglib and its
// complimentary packages. This is mostly required to comply some interfaces
// between mtglib and its internals to avoid circular dependencies.
//
// This package should contain only bare minimum and mostly technical.
package essentials
+6 -4
View File
@@ -2,14 +2,14 @@ package events
import ( import (
"context" "context"
"math/rand" "math/rand/v2"
"runtime" "runtime"
"github.com/9seconds/mtg/v2/mtglib" "github.com/9seconds/mtg/v2/mtglib"
"github.com/OneOfOne/xxhash" "github.com/OneOfOne/xxhash"
) )
// EventStream is a default implementation of the mtglib.EventStream // EventStream is a default implementation of the [mtglib.EventStream]
// interface. // interface.
// //
// EventStream manages a set of goroutines, observers. Main // EventStream manages a set of goroutines, observers. Main
@@ -64,7 +64,7 @@ func NewEventStream(observerFactories []ObserverFactory) EventStream {
chans: make([]chan mtglib.Event, runtime.NumCPU()), chans: make([]chan mtglib.Event, runtime.NumCPU()),
} }
for i := 0; i < runtime.NumCPU(); i++ { for i := range runtime.NumCPU() {
rv.chans[i] = make(chan mtglib.Event, 1) rv.chans[i] = make(chan mtglib.Event, 1)
if len(observerFactories) == 1 { if len(observerFactories) == 1 {
@@ -77,7 +77,7 @@ func NewEventStream(observerFactories []ObserverFactory) EventStream {
return rv return rv
} }
func eventStreamProcessor(ctx context.Context, eventChan <-chan mtglib.Event, observer Observer) { // nolint: cyclop func eventStreamProcessor(ctx context.Context, eventChan <-chan mtglib.Event, observer Observer) { //nolint: cyclop
defer observer.Shutdown() defer observer.Shutdown()
for { for {
@@ -102,6 +102,8 @@ func eventStreamProcessor(ctx context.Context, eventChan <-chan mtglib.Event, ob
observer.EventConcurrencyLimited(typedEvt) observer.EventConcurrencyLimited(typedEvt)
case mtglib.EventReplayAttack: case mtglib.EventReplayAttack:
observer.EventReplayAttack(typedEvt) observer.EventReplayAttack(typedEvt)
case mtglib.EventIPListSize:
observer.EventIPListSize(typedEvt)
} }
} }
} }
+21
View File
@@ -204,6 +204,27 @@ func (suite *EventStreamTestSuite) TestEventReplayAttack() {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
func (suite *EventStreamTestSuite) TestEventIPListSize() {
evt := mtglib.NewEventIPListSize(10, true)
for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
v.
On("EventIPListSize", mock.Anything).
Once().
Run(func(args mock.Arguments) {
caught, ok := args.Get(0).(mtglib.EventIPListSize)
suite.True(ok)
suite.Equal(evt.Timestamp(), caught.Timestamp())
suite.Equal(evt.Size, caught.Size)
suite.Equal(evt.IsBlockList, caught.IsBlockList)
})
}
suite.stream.Send(suite.ctx, evt)
time.Sleep(100 * time.Millisecond)
}
func (suite *EventStreamTestSuite) TearDownTest() { func (suite *EventStreamTestSuite) TearDownTest() {
suite.stream.Shutdown() suite.stream.Shutdown()
suite.ctxCancel() suite.ctxCancel()
+23 -20
View File
@@ -1,19 +1,19 @@
// Events has a default implementations of EventStream for mtglib. // Events has a default implementations of EventStream for mtglib.
// //
// Please see documentation for mtglib.EventStream interface to get an // Please see documentation for [mtglib.EventStream] interface to get an idea
// idea of such an abstraction. This package has implementations for the // of such an abstraction. This package has implementations for the default
// default event stream. // event stream.
// //
// Default event stream has a list of its own concepts. First, all it // Default event stream has a list of its own concepts. First, all it does is a
// does is a routing of messages to known observers. It takes an event, // routing of messages to known observers. It takes an event, defines its type
// defines its type and pass this message to a method of the observer. // and pass this message to a method of the observer.
// //
// There might be many observers, but default event stream has a // There might be many observers, but default event stream has a guarantee
// guarantee though. It uses StreamID as a sharding key and guarantees // though. It uses StreamID as a sharding key and guarantees that a message
// that a message with the same StreamID will be devlivered to the same // with the same StreamID will be devlivered to the same observer instance. So,
// observer instance. So, each producer is guarateed to get all relevant // each producer is guarateed to get all relevant messages related to the same
// messages related to the same session. It is not possible that it will // session. It is not possible that it will get EventFinish if it has not seen
// get EventFinish if it has not seen EventStart for that session yet. // EventStart for that session yet.
package events package events
import "github.com/9seconds/mtg/v2/mtglib" import "github.com/9seconds/mtg/v2/mtglib"
@@ -21,10 +21,10 @@ import "github.com/9seconds/mtg/v2/mtglib"
// Observer is an instance that listens for the incoming events. // Observer is an instance that listens for the incoming events.
// //
// As it is said in the package description, the default event stream // As it is said in the package description, the default event stream
// guarantees that all events with the same StreamID are going to be // guarantees that all events with the same StreamID are going to be routed to
// routed to the same instance of the observer. So, there is no need // the same instance of the observer. So, there is no need to synchronize
// to synchronize information about streams between many observers // information about streams between many observers instances, they can have
// instances, they can have their local storage. // their local storage.
type Observer interface { type Observer interface {
// EventStart reacts on incoming mtglib.EventStart event. // EventStart reacts on incoming mtglib.EventStart event.
EventStart(mtglib.EventStart) EventStart(mtglib.EventStart)
@@ -53,6 +53,9 @@ type Observer interface {
// EventReplayAttack reacts on incoming mtglib.EventReplayAttack event. // EventReplayAttack reacts on incoming mtglib.EventReplayAttack event.
EventReplayAttack(mtglib.EventReplayAttack) EventReplayAttack(mtglib.EventReplayAttack)
// EventIPListSize reacts on incoming mtglib.EventIPListSize
EventIPListSize(mtglib.EventIPListSize)
// Shutdown stop observer. Default event stream guarantees: // Shutdown stop observer. Default event stream guarantees:
// 1. If shutdown is executed, it is executed only once // 1. If shutdown is executed, it is executed only once
// 2. Observer won't receieve any new message after this // 2. Observer won't receieve any new message after this
@@ -62,8 +65,8 @@ type Observer interface {
// ObserverFactory creates a new instance of the observer. // ObserverFactory creates a new instance of the observer.
// //
// Default event stream creates a small set of goroutines to manage // Default event stream creates a small set of goroutines to manage incoming
// incoming messages. Each message is routed to an appropriate observer // messages. Each message is routed to an appropriate observer based on a
// based on a sharding key, stream id. So, it is possible that an // sharding key, stream id. So, it is possible that an instance of mtg will
// instance of mtg will have many observer instances, not a single one. // have many observer instances, not a single one.
type ObserverFactory func() Observer type ObserverFactory func() Observer
+4
View File
@@ -41,6 +41,10 @@ func (o *ObserverMock) EventReplayAttack(evt mtglib.EventReplayAttack) {
o.Called(evt) o.Called(evt)
} }
func (o *ObserverMock) EventIPListSize(evt mtglib.EventIPListSize) {
o.Called(evt)
}
func (o *ObserverMock) Shutdown() { func (o *ObserverMock) Shutdown() {
o.Called() o.Called()
} }
+35 -47
View File
@@ -12,14 +12,11 @@ type multiObserver struct {
func (m multiObserver) EventStart(evt mtglib.EventStart) { func (m multiObserver) EventStart(evt mtglib.EventStart) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventStart(evt)
})
obs.EventStart(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -27,14 +24,11 @@ func (m multiObserver) EventStart(evt mtglib.EventStart) {
func (m multiObserver) EventConnectedToDC(evt mtglib.EventConnectedToDC) { func (m multiObserver) EventConnectedToDC(evt mtglib.EventConnectedToDC) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventConnectedToDC(evt)
})
obs.EventConnectedToDC(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -42,14 +36,11 @@ func (m multiObserver) EventConnectedToDC(evt mtglib.EventConnectedToDC) {
func (m multiObserver) EventDomainFronting(evt mtglib.EventDomainFronting) { func (m multiObserver) EventDomainFronting(evt mtglib.EventDomainFronting) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventDomainFronting(evt)
})
obs.EventDomainFronting(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -57,14 +48,11 @@ func (m multiObserver) EventDomainFronting(evt mtglib.EventDomainFronting) {
func (m multiObserver) EventTraffic(evt mtglib.EventTraffic) { func (m multiObserver) EventTraffic(evt mtglib.EventTraffic) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventTraffic(evt)
})
obs.EventTraffic(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -72,14 +60,11 @@ func (m multiObserver) EventTraffic(evt mtglib.EventTraffic) {
func (m multiObserver) EventFinish(evt mtglib.EventFinish) { func (m multiObserver) EventFinish(evt mtglib.EventFinish) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventFinish(evt)
})
obs.EventFinish(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -87,14 +72,11 @@ func (m multiObserver) EventFinish(evt mtglib.EventFinish) {
func (m multiObserver) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimited) { func (m multiObserver) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimited) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventConcurrencyLimited(evt)
})
obs.EventConcurrencyLimited(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -102,14 +84,11 @@ func (m multiObserver) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimite
func (m multiObserver) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) { func (m multiObserver) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventIPBlocklisted(evt)
})
obs.EventIPBlocklisted(evt)
}(v)
} }
wg.Wait() wg.Wait()
@@ -117,14 +96,23 @@ func (m multiObserver) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
func (m multiObserver) EventReplayAttack(evt mtglib.EventReplayAttack) { func (m multiObserver) EventReplayAttack(evt mtglib.EventReplayAttack) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers { for _, v := range m.observers {
go func(obs Observer) { wg.Go(func() {
defer wg.Done() v.EventReplayAttack(evt)
})
}
obs.EventReplayAttack(evt) wg.Wait()
}(v) }
func (m multiObserver) EventIPListSize(evt mtglib.EventIPListSize) {
wg := &sync.WaitGroup{}
for _, v := range m.observers {
wg.Go(func() {
v.EventIPListSize(evt)
})
} }
wg.Wait() wg.Wait()
+1
View File
@@ -25,6 +25,7 @@ func (n noopObserver) EventFinish(_ mtglib.EventFinish)
func (n noopObserver) EventConcurrencyLimited(_ mtglib.EventConcurrencyLimited) {} func (n noopObserver) EventConcurrencyLimited(_ mtglib.EventConcurrencyLimited) {}
func (n noopObserver) EventIPBlocklisted(_ mtglib.EventIPBlocklisted) {} func (n noopObserver) EventIPBlocklisted(_ mtglib.EventIPBlocklisted) {}
func (n noopObserver) EventReplayAttack(_ mtglib.EventReplayAttack) {} func (n noopObserver) EventReplayAttack(_ mtglib.EventReplayAttack) {}
func (n noopObserver) EventIPListSize(_ mtglib.EventIPListSize) {}
func (n noopObserver) Shutdown() {} func (n noopObserver) Shutdown() {}
// NewNoopObserver creates an observer which discards each message. // NewNoopObserver creates an observer which discards each message.
+3
View File
@@ -27,6 +27,7 @@ func (suite *NoopTestSuite) SetupSuite() {
"concurrency-limited": mtglib.NewEventConcurrencyLimited(), "concurrency-limited": mtglib.NewEventConcurrencyLimited(),
"ip-blacklisted": mtglib.NewEventIPBlocklisted(net.ParseIP("10.0.0.10")), "ip-blacklisted": mtglib.NewEventIPBlocklisted(net.ParseIP("10.0.0.10")),
"replay-attack": mtglib.NewEventReplayAttack("connID"), "replay-attack": mtglib.NewEventReplayAttack("connID"),
"ip-list-size": mtglib.NewEventIPListSize(10, true),
} }
suite.ctx = context.Background() suite.ctx = context.Background()
} }
@@ -65,6 +66,8 @@ func (suite *NoopTestSuite) TestObserver() {
observer.EventIPBlocklisted(typedEvt) observer.EventIPBlocklisted(typedEvt)
case mtglib.EventReplayAttack: case mtglib.EventReplayAttack:
observer.EventReplayAttack(typedEvt) observer.EventReplayAttack(typedEvt)
case mtglib.EventIPListSize:
observer.EventIPListSize(typedEvt)
} }
}) })
} }
+89 -9
View File
@@ -23,15 +23,19 @@ secret = "ee367a189aee18fa31c190054efd4a8e9573746f726167652e676f6f676c6561706973
# Host:port pair to run proxy on. # Host:port pair to run proxy on.
bind-to = "0.0.0.0:3128" bind-to = "0.0.0.0:3128"
# This defines what types of traffic mtg listens to. If you are not sure,
# then definitely keep it disable. Enable it only and only if incoming traffic
# is coming from some sort of load-balancer like HAProxy or ELB.
# https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt
#
# mtg uses a library that supports v1 and v2 versions of ProxyProtocol.
# default value is false.
# proxy-protocol-listener = false
# Defines how many concurrent connections are allowed to this proxy. # Defines how many concurrent connections are allowed to this proxy.
# All other incoming connections are going to be dropped. # All other incoming connections are going to be dropped.
concurrency = 8192 concurrency = 8192
# A size of user-space buffer for TCP to use. Since we do 2 connections,
# then we have tcp-buffer * (4 + 2) per each connection: read/write for
# each connection + 2 copy buffers to pump the data between sockets.
tcp-buffer = "4kb"
# Sometimes you want to enforce mtg to use some types of # Sometimes you want to enforce mtg to use some types of
# IP connectivity to Telegram. We have 4 modes: # IP connectivity to Telegram. We have 4 modes:
# - prefer-ipv6: # - prefer-ipv6:
@@ -46,7 +50,28 @@ prefer-ip = "prefer-ipv6"
# FakeTLS uses domain fronting protection. So it needs to know a port to # FakeTLS uses domain fronting protection. So it needs to know a port to
# access. # access.
domain-fronting-port = 443 #
# Deprecated: use [domain-fronting] configuration block. If relevant option
# is defined there, this one would be ignored.
# domain-fronting-port = 443
# By default, mtg resolves the fronting hostname (from the secret) via DNS
# to establish a TCP connection. If DNS resolution of that hostname is blocked,
# you can specify an IP address to connect to directly. The hostname is still
# used for SNI in the TLS handshake.
#
# default value is not set (DNS resolution is used).
#
# Deprecated: use [domain-fronting] configuration block. If relevant option
# is defined there, this one would be ignored.
# domain-fronting-ip = "10.0.0.10"
# This makes a communication between both fronting website and mtg to use
# proxy protocol.
#
# Deprecated: use [domain-fronting] configuration block. If relevant option
# is defined there, this one would be ignored.
# domain-fronting-proxy-protocol = false
# FakeTLS can compare timestamps to prevent probes. Each message has # FakeTLS can compare timestamps to prevent probes. Each message has
# encrypted timestamp. So, mtg can compare this timestamp and decide if # encrypted timestamp. So, mtg can compare this timestamp and decide if
@@ -56,6 +81,40 @@ domain-fronting-port = 443
# time range of this parameter. # time range of this parameter.
tolerate-time-skewness = "5s" tolerate-time-skewness = "5s"
# Telegram has a concept of DC. You can think about DC as a number of a cluster
# with a certain purpose. Some clusters serve media, some - messages, some rule
# channels and so on. But sometimes unknown DC number is requested by client.
# It could be a bug or some global reconfiguration of the Telegram.
#
# By default, proxy rejects such requests. But it is also possible to fallback
# this request to any DC. Telegram works in a way that any DC is able to serve
# any request but sacrificing a latency.
#
# If this setting is disabled (default), mtg will reject a connection.
# Otherwise, chose a new DC.
allow-fallback-on-unknown-dc = false
# This section is relevant to communication with fronting domain. Usually
# you do not need to setup anything here but there are plenty of cases, especially
# if you put mtg behind load balancer, when some specific configuration is
# required.
[domain-fronting]
# By default, mtg resolves the fronting hostname (from the secret) via DNS
# to establish a TCP connection. If DNS resolution of that hostname is blocked,
# you can specify an IP address to connect to directly. The hostname is still
# used for SNI in the TLS handshake.
#
# default value is not set (DNS resolution is used).
# ip = "10.10.10.11"
# FakeTLS uses domain fronting protection. So it needs to know a port to
# access. Default value is 443
# port = 443
# This makes a communication between both fronting website and mtg to use
# proxy protocol.
# proxy-protocol = false
# network defines different network-related settings # network defines different network-related settings
[network] [network]
# please be aware that mtg needs to do some external requests. For # please be aware that mtg needs to do some external requests. For
@@ -69,8 +128,8 @@ tolerate-time-skewness = "5s"
# resolver of the operating system and uses DOH instead. This is a host # resolver of the operating system and uses DOH instead. This is a host
# it has to access. # it has to access.
# #
# By default we use Quad9. # By default we use Cloudflare.
doh-ip = "9.9.9.9" doh-ip = "1.1.1.1"
# mtg can work via proxies (for now, we support only socks5). Proxy # mtg can work via proxies (for now, we support only socks5). Proxy
# configuration is done via list. So, you can specify many proxies # configuration is done via list. So, you can specify many proxies
@@ -155,12 +214,33 @@ download-concurrency = 2
# You can provider links here (starts with https:// or http://) or # You can provider links here (starts with https:// or http://) or
# path to a local file, but in this case it should be absolute. # path to a local file, but in this case it should be absolute.
urls = [ urls = [
# "https://iplists.firehol.org/files/firehol_level1.netset", "https://iplists.firehol.org/files/firehol_level1.netset",
# "/local.file" # "/local.file"
] ]
# How often do we need to update a blocklist set. # How often do we need to update a blocklist set.
update-each = "24h" update-each = "24h"
# Allowlist is an opposite to a blocklist. Only those IPs that are coming from
# subnets defined in these lists are allowed. All others will be rejected.
#
# If this feature is disabled, then there won't be any check performed by this
# validator. It is possible to combine both blocklist and whitelist.
[defense.allowlist]
# You can enable/disable this feature.
enabled = false
# This is a limiter for concurrency. In order to protect website
# from overloading, we download files in this number of threads.
download-concurrency = 2
# A list of URLs in FireHOL format (https://iplists.firehol.org/)
# You can provider links here (starts with https:// or http://) or
# path to a local file, but in this case it should be absolute.
urls = [
# "https://iplists.firehol.org/files/firehol_level1.netset",
# "/local.file"
]
update-each = "24h"
# statsd statistics integration. # statsd statistics integration.
[stats.statsd] [stats.statsd]
# enabled/disabled # enabled/disabled
+43 -21
View File
@@ -1,32 +1,54 @@
module github.com/9seconds/mtg/v2 module github.com/9seconds/mtg/v2
go 1.16 go 1.26
require ( require (
github.com/OneOfOne/xxhash v1.2.8 github.com/OneOfOne/xxhash v1.2.8
github.com/alecthomas/kong v0.2.17 github.com/alecthomas/kong v1.14.0
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6
github.com/d4l3k/messagediff v1.2.1 // indirect github.com/d4l3k/messagediff v1.2.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gotd/td v0.34.0
github.com/jarcoal/httpmock v1.0.8 github.com/jarcoal/httpmock v1.0.8
github.com/kentik/patricia v0.0.0-20201202224819-f9447a6e25f1
github.com/libp2p/go-reuseport v0.0.2
github.com/mccutchen/go-httpbin v1.1.1 github.com/mccutchen/go-httpbin v1.1.1
github.com/panjf2000/ants/v2 v2.4.6 github.com/panjf2000/ants/v2 v2.11.5
github.com/pelletier/go-toml v1.9.3 github.com/prometheus/client_golang v1.23.2
github.com/prometheus/client_golang v1.11.0 github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/common v0.30.0 // indirect github.com/prometheus/procfs v0.19.2 // indirect
github.com/prometheus/procfs v0.7.1 // indirect github.com/rs/zerolog v1.34.0
github.com/rs/zerolog v1.23.0 github.com/smira/go-statsd v1.3.4
github.com/smira/go-statsd v1.3.2 github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/objx v0.3.0 // indirect github.com/stretchr/testify v1.11.1
github.com/stretchr/testify v1.7.0 github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43 golang.org/x/crypto v0.48.0
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 golang.org/x/net v0.49.0 // indirect
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 golang.org/x/sys v0.41.0
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c google.golang.org/protobuf v1.36.11 // indirect
google.golang.org/protobuf v1.27.1 // indirect )
require (
github.com/pelletier/go-toml/v2 v2.2.4
github.com/pires/go-proxyproto v0.11.0
github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e
github.com/yl2chen/cidranger v1.0.2
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/klauspost/compress v1.18.3 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/txthinking/runnergroup v0.0.0-20250224021307-5864ffeb65ae // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/tools v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+119 -535
View File
@@ -1,574 +1,158 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/kong v0.2.17 h1:URDISCI96MIgcIlQyoCAlhOmrSw6pZScBNkctg8r0W0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v0.2.17/go.mod h1:ka3VZ8GZNPXv9Ov+j4YNLkI8mTuhXyr/0ktSlqIydQQ= github.com/alecthomas/kong v1.14.0 h1:gFgEUZWu2ZmZ+UhyZ1bDhuutbKN1nTtJTwh19Wsn21s=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/kong v1.14.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
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/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0= github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg= github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gotd/getdoc v0.6.2/go.mod h1:6Ul8cyaq+Aw0gVyuU+TKZPCmNjdlylzSIWVxjzI5t/U=
github.com/gotd/ige v0.1.5 h1:qITql4hZpqPM/DSeO5IVlxzXJxrs4ZZiKRwPif2K76U=
github.com/gotd/ige v0.1.5/go.mod h1:LbvqjUuGELVuHcKjfJZ2NPNzegICQU4eqrPAkXy0nTs=
github.com/gotd/neo v0.1.2 h1:hayFfKNSH5RP/L+KKKnsKMdDNCdhSfzVuo5TF4wvpa8=
github.com/gotd/neo v0.1.2/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ=
github.com/gotd/td v0.34.0 h1:BLIdpv2sxbCuWI2fyXCECRQuLJdnYk3VMZDRKNog/E8=
github.com/gotd/td v0.34.0/go.mod h1:4s/7cuEscdvZIecM/pVi5L3nTK+djSoVLAhgfk40OE4=
github.com/gotd/tl v0.4.0/go.mod h1:CMIcjPWFS4qxxJ+1Ce7U/ilbtPrkoVo/t8uhN5Y/D7c=
github.com/gotd/xor v0.1.0/go.mod h1:ZTmdgqf6SOHder8/MFp9CNkXIadzID5lIiaZxRZICH0=
github.com/gotd/xor v0.1.1 h1:LSPEeuf7noTo4fi4PrEsAaWXOSwjsY2e+IINPiR+c7s=
github.com/gotd/xor v0.1.1/go.mod h1:ZTmdgqf6SOHder8/MFp9CNkXIadzID5lIiaZxRZICH0=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k= github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k=
github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/k0kubun/pp v2.4.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/kentik/patricia v0.0.0-20201202224819-f9447a6e25f1 h1:D7qhJP3R49ZjUzpzKQ6B2H3lgejPs6DTO5gRomhhOpE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/kentik/patricia v0.0.0-20201202224819-f9447a6e25f1/go.mod h1:2OfLA+0esiUJpwMjrH39pEk79cb8MvGTBS9YlZpejJ4= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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/libp2p/go-reuseport v0.0.2 h1:XSG94b1FJfGA01BUrT82imejHQyTxO4jEWqheyCXYvU=
github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mccutchen/go-httpbin v1.1.1 h1:aEws49HEJEyXHLDnshQVswfUlCVoS8g6h9YaDyaW7RE= github.com/mccutchen/go-httpbin v1.1.1 h1:aEws49HEJEyXHLDnshQVswfUlCVoS8g6h9YaDyaW7RE=
github.com/mccutchen/go-httpbin v1.1.1/go.mod h1:fhpOYavp5g2K74XDl/ao2y4KvhqVtKlkg1e+0UaQv7I= github.com/mccutchen/go-httpbin v1.1.1/go.mod h1:fhpOYavp5g2K74XDl/ao2y4KvhqVtKlkg1e+0UaQv7I=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/panjf2000/ants/v2 v2.11.5 h1:a7LMnMEeux/ebqTux140tRiaqcFTV0q2bEHF03nl6Rg=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/panjf2000/ants/v2 v2.11.5/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
github.com/panjf2000/ants/v2 v2.4.6 h1:drmj9mcygn2gawZ155dRbo+NfXEfAssjZNU1qoIb4gQ= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/panjf2000/ants/v2 v2.4.6/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= github.com/smira/go-statsd v1.3.4 h1:kBYWcLSGT+qC6JVbvfz48kX7mQys32fjDOPrfmsSx2c=
github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/smira/go-statsd v1.3.4/go.mod h1:RjdsESPgDODtg1VpVVf9MJrEW2Hw0wtRNbmB1CAhu6A=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.1 h1:TlEtJq5GvGqMykEwWzbZWjjztF86swFhsPix1i0bkgA=
github.com/prometheus/procfs v0.7.1/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/quasilyte/go-ruleguard/dsl v0.3.2/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g=
github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo=
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smira/go-statsd v1.3.2 h1:1EeuzxNZ/TD9apbTOFSM9nulqfcsQFmT4u1A2DREabI=
github.com/smira/go-statsd v1.3.2/go.mod h1:1srXJ9/pbnN04G8f4F1jUzsGOnwkPKXciyqpewGlkC4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/testify v1.1.5-0.20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43 h1:QEePdg0ty2r0t1+qwfZmQ4OOl/MB2UXIeJSpIZv56lg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43/go.mod h1:OYRfF6eb5wY9VRFkXJH8FFBi3plw2v+giaIu7P054pM= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/txthinking/runnergroup v0.0.0-20250224021307-5864ffeb65ae h1:ArVM1jICfm7g4E4dBet+KHUFMLuxmj1Nxdp/tr3ByCU=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/txthinking/runnergroup v0.0.0-20250224021307-5864ffeb65ae/go.mod h1:cldYm15/XHcGt7ndItnEWHwFZo7dinU+2QoyjfErhsI=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e h1:xA7GVlbz6teIF4FdvuqwbX6C4tiqNk2PH7FRPIDerao=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e/go.mod h1:ntmMHL/xPq1WLeKiw8p/eRATaae6PiVRNipHFJxI8PM=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b h1:p+bJ3v5uUdEVMCoeFUs+BNJPsqt+Y6BLbDaPfTcbcH8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b/go.mod h1:OYRfF6eb5wY9VRFkXJH8FFBi3plw2v+giaIu7P054pM=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.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-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+18 -23
View File
@@ -13,6 +13,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/9seconds/mtg/v2/essentials"
"github.com/9seconds/mtg/v2/internal/config" "github.com/9seconds/mtg/v2/internal/config"
"github.com/9seconds/mtg/v2/internal/utils" "github.com/9seconds/mtg/v2/internal/utils"
"github.com/9seconds/mtg/v2/mtglib" "github.com/9seconds/mtg/v2/mtglib"
@@ -30,17 +31,17 @@ type accessResponse struct {
type accessResponseURLs struct { type accessResponseURLs struct {
IP net.IP `json:"ip"` IP net.IP `json:"ip"`
Port uint `json:"port"` Port uint `json:"port"`
TgURL string `json:"tg_url"` // nolint: tagliatelle TgURL string `json:"tg_url"` //nolint: tagliatelle
TgQrCode string `json:"tg_qrcode"` // nolint: tagliatelle TgQrCode string `json:"tg_qrcode"` //nolint: tagliatelle
TmeURL string `json:"tme_url"` // nolint: tagliatelle TmeURL string `json:"tme_url"` //nolint: tagliatelle
TmeQrCode string `json:"tme_qrcode"` // nolint: tagliatelle TmeQrCode string `json:"tme_qrcode"` //nolint: tagliatelle
} }
type Access struct { type Access struct {
ConfigPath string `kong:"arg,required,type='existingfile',help='Path to the configuration file.',name='config-path'"` // nolint: lll ConfigPath string `kong:"arg,required,type='existingfile',help='Path to the configuration file.',name='config-path'"` //nolint: lll
PublicIPv4 net.IP `kong:"help='Public IPv4 address for proxy. By default it is resolved via remote website',name='ipv4',short='i'"` // nolint: lll PublicIPv4 net.IP `kong:"help='Public IPv4 address for proxy. By default it is resolved via remote website',name='ipv4',short='i'"` //nolint: lll
PublicIPv6 net.IP `kong:"help='Public IPv6 address for proxy. By default it is resolved via remote website',name='ipv6',short='I'"` // nolint: lll PublicIPv6 net.IP `kong:"help='Public IPv6 address for proxy. By default it is resolved via remote website',name='ipv6',short='I'"` //nolint: lll
Port uint `kong:"help='Port number. Default port is taken from configuration file, bind-to parameter',type:'uint',short='p'"` // nolint: lll Port uint `kong:"help='Port number. Default port is taken from configuration file, bind-to parameter',type:'uint',short='p'"` //nolint: lll
Hex bool `kong:"help='Print secret in hex encoding.',short='x'"` Hex bool `kong:"help='Print secret in hex encoding.',short='x'"`
} }
@@ -60,11 +61,8 @@ func (a *Access) Run(cli *CLI, version string) error {
} }
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(2) // nolint: gomnd
go func() {
defer wg.Done()
wg.Go(func() {
ip := a.PublicIPv4 ip := a.PublicIPv4
if ip == nil { if ip == nil {
ip = a.getIP(ntw, "tcp4") ip = a.getIP(ntw, "tcp4")
@@ -75,11 +73,8 @@ func (a *Access) Run(cli *CLI, version string) error {
} }
resp.IPv4 = a.makeURLs(conf, ip) resp.IPv4 = a.makeURLs(conf, ip)
}() })
wg.Go(func() {
go func() {
defer wg.Done()
ip := a.PublicIPv6 ip := a.PublicIPv6
if ip == nil { if ip == nil {
ip = a.getIP(ntw, "tcp6") ip = a.getIP(ntw, "tcp6")
@@ -90,7 +85,7 @@ func (a *Access) Run(cli *CLI, version string) error {
} }
resp.IPv6 = a.makeURLs(conf, ip) resp.IPv6 = a.makeURLs(conf, ip)
}() })
wg.Wait() wg.Wait()
@@ -106,11 +101,11 @@ func (a *Access) Run(cli *CLI, version string) error {
} }
func (a *Access) getIP(ntw mtglib.Network, protocol string) net.IP { func (a *Access) getIP(ntw mtglib.Network, protocol string) net.IP {
client := ntw.MakeHTTPClient(func(ctx context.Context, network, address string) (net.Conn, error) { client := ntw.MakeHTTPClient(func(ctx context.Context, network, address string) (essentials.Conn, error) {
return ntw.DialContext(ctx, protocol, address) // nolint: wrapcheck return ntw.DialContext(ctx, protocol, address) //nolint: wrapcheck
}) })
req, err := http.NewRequest(http.MethodGet, "https://ifconfig.co", nil) // nolint: noctx req, err := http.NewRequest(http.MethodGet, "https://ifconfig.co", nil) //nolint: noctx
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -127,8 +122,8 @@ func (a *Access) getIP(ntw mtglib.Network, protocol string) net.IP {
} }
defer func() { defer func() {
io.Copy(io.Discard, resp.Body) // nolint: errcheck io.Copy(io.Discard, resp.Body) //nolint: errcheck
resp.Body.Close() resp.Body.Close() //nolint: errcheck
}() }()
data, err := io.ReadAll(resp.Body) data, err := io.ReadAll(resp.Body)
+2 -2
View File
@@ -15,9 +15,9 @@ func (g *GenerateSecret) Run(cli *CLI, _ string) error {
secret := mtglib.GenerateSecret(cli.GenerateSecret.HostName) secret := mtglib.GenerateSecret(cli.GenerateSecret.HostName)
if g.Hex { if g.Hex {
fmt.Println(secret.Hex()) // nolint: forbidigo fmt.Println(secret.Hex()) //nolint: forbidigo
} else { } else {
fmt.Println(secret.Base64()) // nolint: forbidigo fmt.Println(secret.Base64()) //nolint: forbidigo
} }
return nil return nil
+1 -1
View File
@@ -7,7 +7,7 @@ import (
) )
type Run struct { type Run struct {
ConfigPath string `kong:"arg,required,type='existingfile',help='Path to the configuration file.',name='config-path'"` // nolint: lll ConfigPath string `kong:"arg,required,type='existingfile',help='Path to the configuration file.',name='config-path'"` //nolint: lll
} }
func (r *Run) Run(cli *CLI, version string) error { func (r *Run) Run(cli *CLI, version string) error {
+108 -27
View File
@@ -1,6 +1,7 @@
package cli package cli
import ( import (
"context"
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
@@ -9,13 +10,17 @@ import (
"github.com/9seconds/mtg/v2/antireplay" "github.com/9seconds/mtg/v2/antireplay"
"github.com/9seconds/mtg/v2/events" "github.com/9seconds/mtg/v2/events"
"github.com/9seconds/mtg/v2/internal/config" "github.com/9seconds/mtg/v2/internal/config"
"github.com/9seconds/mtg/v2/internal/proxyprotocol"
"github.com/9seconds/mtg/v2/internal/utils" "github.com/9seconds/mtg/v2/internal/utils"
"github.com/9seconds/mtg/v2/ipblocklist" "github.com/9seconds/mtg/v2/ipblocklist"
"github.com/9seconds/mtg/v2/ipblocklist/files"
"github.com/9seconds/mtg/v2/logger" "github.com/9seconds/mtg/v2/logger"
"github.com/9seconds/mtg/v2/mtglib" "github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/network" "github.com/9seconds/mtg/v2/network"
"github.com/9seconds/mtg/v2/stats" "github.com/9seconds/mtg/v2/stats"
"github.com/pires/go-proxyproto"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/yl2chen/cidranger"
) )
func makeLogger(conf *config.Config) mtglib.Logger { func makeLogger(conf *config.Config) mtglib.Logger {
@@ -38,16 +43,15 @@ func makeNetwork(conf *config.Config, version string) (mtglib.Network, error) {
tcpTimeout := conf.Network.Timeout.TCP.Get(network.DefaultTimeout) tcpTimeout := conf.Network.Timeout.TCP.Get(network.DefaultTimeout)
httpTimeout := conf.Network.Timeout.HTTP.Get(network.DefaultHTTPTimeout) httpTimeout := conf.Network.Timeout.HTTP.Get(network.DefaultHTTPTimeout)
dohIP := conf.Network.DOHIP.Get(net.ParseIP(network.DefaultDOHHostname)).String() dohIP := conf.Network.DOHIP.Get(net.ParseIP(network.DefaultDOHHostname)).String()
bufferSize := conf.TCPBuffer.Get(network.DefaultBufferSize)
userAgent := "mtg/" + version userAgent := "mtg/" + version
baseDialer, err := network.NewDefaultDialer(tcpTimeout, int(bufferSize)) baseDialer, err := network.NewDefaultDialer(tcpTimeout, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot build a default dialer: %w", err) return nil, fmt.Errorf("cannot build a default dialer: %w", err)
} }
if len(conf.Network.Proxies) == 0 { if len(conf.Network.Proxies) == 0 {
return network.NewNetwork(baseDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck return network.NewNetwork(baseDialer, userAgent, dohIP, httpTimeout) //nolint: wrapcheck
} }
proxyURLs := make([]*url.URL, 0, len(conf.Network.Proxies)) proxyURLs := make([]*url.URL, 0, len(conf.Network.Proxies))
@@ -64,7 +68,7 @@ func makeNetwork(conf *config.Config, version string) (mtglib.Network, error) {
return nil, fmt.Errorf("cannot build socks5 dialer: %w", err) return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
} }
return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) //nolint: wrapcheck
} }
socksDialer, err := network.NewLoadBalancedSocks5Dialer(baseDialer, proxyURLs) socksDialer, err := network.NewLoadBalancedSocks5Dialer(baseDialer, proxyURLs)
@@ -72,7 +76,7 @@ func makeNetwork(conf *config.Config, version string) (mtglib.Network, error) {
return nil, fmt.Errorf("cannot build socks5 dialer: %w", err) return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
} }
return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) //nolint: wrapcheck
} }
func makeAntiReplayCache(conf *config.Config) mtglib.AntiReplayCache { func makeAntiReplayCache(conf *config.Config) mtglib.AntiReplayCache {
@@ -86,15 +90,19 @@ func makeAntiReplayCache(conf *config.Config) mtglib.AntiReplayCache {
) )
} }
func makeIPBlocklist(conf *config.Config, logger mtglib.Logger, ntw mtglib.Network) (mtglib.IPBlocklist, error) { func makeIPBlocklist(conf config.ListConfig,
if !conf.Defense.Blocklist.Enabled.Get(false) { logger mtglib.Logger,
ntw mtglib.Network,
updateCallback ipblocklist.FireholUpdateCallback,
) (mtglib.IPBlocklist, error) {
if !conf.Enabled.Get(false) {
return ipblocklist.NewNoop(), nil return ipblocklist.NewNoop(), nil
} }
remoteURLs := []string{} remoteURLs := []string{}
localFiles := []string{} localFiles := []string{}
for _, v := range conf.Defense.Blocklist.URLs { for _, v := range conf.URLs {
if v.IsRemote() { if v.IsRemote() {
remoteURLs = append(remoteURLs, v.String()) remoteURLs = append(remoteURLs, v.String())
} else { } else {
@@ -102,20 +110,63 @@ func makeIPBlocklist(conf *config.Config, logger mtglib.Logger, ntw mtglib.Netwo
} }
} }
firehol, err := ipblocklist.NewFirehol(logger.Named("ipblockist"), blocklist, err := ipblocklist.NewFirehol(logger.Named("ipblockist"),
ntw, ntw,
conf.Defense.Blocklist.DownloadConcurrency.Get(1), conf.DownloadConcurrency.Get(1),
remoteURLs, remoteURLs,
localFiles) localFiles,
updateCallback)
if err != nil { if err != nil {
return nil, fmt.Errorf("incorrect parameters for firehol: %w", err) return nil, fmt.Errorf("incorrect parameters for firehol: %w", err)
} }
return firehol, nil go blocklist.Run(conf.UpdateEach.Get(ipblocklist.DefaultFireholUpdateEach))
return blocklist, nil
}
func makeIPAllowlist(conf config.ListConfig,
logger mtglib.Logger,
ntw mtglib.Network,
updateCallback ipblocklist.FireholUpdateCallback,
) (mtglib.IPBlocklist, error) {
var (
allowlist mtglib.IPBlocklist
err error
)
if !conf.Enabled.Get(false) {
allowlist, err = ipblocklist.NewFireholFromFiles(
logger.Named("ipblocklist"),
1,
[]files.File{
files.NewMem([]*net.IPNet{
cidranger.AllIPv4,
cidranger.AllIPv6,
}),
},
updateCallback,
)
go allowlist.Run(conf.UpdateEach.Get(ipblocklist.DefaultFireholUpdateEach))
} else {
allowlist, err = makeIPBlocklist(
conf,
logger,
ntw,
updateCallback,
)
}
if err != nil {
return nil, fmt.Errorf("cannot build allowlist: %w", err)
}
return allowlist, nil
} }
func makeEventStream(conf *config.Config, logger mtglib.Logger) (mtglib.EventStream, error) { func makeEventStream(conf *config.Config, logger mtglib.Logger) (mtglib.EventStream, error) {
factories := make([]events.ObserverFactory, 0, 2) // nolint: gomnd factories := make([]events.ObserverFactory, 0, 2)
if conf.Stats.StatsD.Enabled.Get(false) { if conf.Stats.StatsD.Enabled.Get(false) {
statsdFactory, err := stats.NewStatsd( statsdFactory, err := stats.NewStatsd(
@@ -141,7 +192,7 @@ func makeEventStream(conf *config.Config, logger mtglib.Logger) (mtglib.EventStr
return nil, fmt.Errorf("cannot start a listener for prometheus: %w", err) return nil, fmt.Errorf("cannot start a listener for prometheus: %w", err)
} }
go prometheus.Serve(listener) // nolint: errcheck go prometheus.Serve(listener) //nolint: errcheck
factories = append(factories, prometheus.Make) factories = append(factories, prometheus.Make)
} }
@@ -153,24 +204,42 @@ func makeEventStream(conf *config.Config, logger mtglib.Logger) (mtglib.EventStr
return events.NewNoopStream(), nil return events.NewNoopStream(), nil
} }
func runProxy(conf *config.Config, version string) error { func runProxy(conf *config.Config, version string) error { //nolint: funlen
logger := makeLogger(conf) logger := makeLogger(conf)
logger.BindStr("configuration", conf.String()).Debug("configuration") logger.BindJSON("configuration", conf.String()).Debug("configuration")
eventStream, err := makeEventStream(conf, logger)
if err != nil {
return fmt.Errorf("cannot build event stream: %w", err)
}
ntw, err := makeNetwork(conf, version) ntw, err := makeNetwork(conf, version)
if err != nil { if err != nil {
return fmt.Errorf("cannot build network: %w", err) return fmt.Errorf("cannot build network: %w", err)
} }
blocklist, err := makeIPBlocklist(conf, logger, ntw) blocklist, err := makeIPBlocklist(
conf.Defense.Blocklist,
logger.Named("blocklist"),
ntw,
func(ctx context.Context, size int) {
eventStream.Send(ctx, mtglib.NewEventIPListSize(size, true))
})
if err != nil { if err != nil {
return fmt.Errorf("cannot build ip blocklist: %w", err) return fmt.Errorf("cannot build ip blocklist: %w", err)
} }
eventStream, err := makeEventStream(conf, logger) allowlist, err := makeIPAllowlist(
conf.Defense.Allowlist,
logger.Named("allowlist"),
ntw,
func(ctx context.Context, size int) {
eventStream.Send(ctx, mtglib.NewEventIPListSize(size, false))
},
)
if err != nil { if err != nil {
return fmt.Errorf("cannot build event stream: %w", err) return fmt.Errorf("cannot build ip allowlist: %w", err)
} }
opts := mtglib.ProxyOpts{ opts := mtglib.ProxyOpts{
@@ -178,13 +247,17 @@ func runProxy(conf *config.Config, version string) error {
Network: ntw, Network: ntw,
AntiReplayCache: makeAntiReplayCache(conf), AntiReplayCache: makeAntiReplayCache(conf),
IPBlocklist: blocklist, IPBlocklist: blocklist,
IPAllowlist: allowlist,
EventStream: eventStream, EventStream: eventStream,
Secret: conf.Secret, Secret: conf.Secret,
BufferSize: conf.TCPBuffer.Get(mtglib.DefaultBufferSize), DomainFrontingPort: conf.GetDomainFrontingPort(mtglib.DefaultDomainFrontingPort),
DomainFrontingPort: conf.DomainFrontingPort.Get(mtglib.DefaultDomainFrontingPort), DomainFrontingIP: conf.GetDomainFrontingIP(nil),
IdleTimeout: conf.Network.Timeout.Idle.Get(mtglib.DefaultIdleTimeout), DomainFrontingProxyProtocol: conf.GetDomainFrontingProxyProtocol(false),
PreferIP: conf.PreferIP.Get(mtglib.DefaultPreferIP), PreferIP: conf.PreferIP.Get(mtglib.DefaultPreferIP),
AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false),
TolerateTimeSkewness: conf.TolerateTimeSkewness.Value,
} }
proxy, err := mtglib.NewProxy(opts) proxy, err := mtglib.NewProxy(opts)
@@ -192,17 +265,25 @@ func runProxy(conf *config.Config, version string) error {
return fmt.Errorf("cannot create a proxy: %w", err) return fmt.Errorf("cannot create a proxy: %w", err)
} }
listener, err := net.Listen("tcp", conf.BindTo.Get("")) listener, err := utils.NewListener(conf.BindTo.Get(""), 0)
if err != nil { if err != nil {
return fmt.Errorf("cannot start proxy: %w", err) return fmt.Errorf("cannot start proxy: %w", err)
} }
if conf.ProxyProtocolListener.Get(false) {
listener = &proxyprotocol.ListenerAdapter{
Listener: proxyproto.Listener{
Listener: listener,
},
}
}
ctx := utils.RootContext() ctx := utils.RootContext()
go proxy.Serve(listener) // nolint: errcheck go proxy.Serve(listener) //nolint: errcheck
<-ctx.Done() <-ctx.Done()
listener.Close() listener.Close() //nolint: errcheck
proxy.Shutdown() proxy.Shutdown()
return nil return nil
+30 -15
View File
@@ -13,17 +13,19 @@ type SimpleRun struct {
BindTo string `kong:"arg,required,name='bind-to',help='A host:port to bind proxy to.'"` BindTo string `kong:"arg,required,name='bind-to',help='A host:port to bind proxy to.'"`
Secret string `kong:"arg,required,name='secret',help='Proxy secret.'"` Secret string `kong:"arg,required,name='secret',help='Proxy secret.'"`
Debug bool `kong:"name='debug',short='d',help='Run in debug mode.'"` // nolint: lll Debug bool `kong:"name='debug',short='d',help='Run in debug mode.'"` //nolint: lll
Concurrency uint64 `kong:"name='concurrency',short='c',default='8192',help='Max number of concurrent connection to proxy.'"` // nolint: lll Concurrency uint64 `kong:"name='concurrency',short='c',default='8192',help='Max number of concurrent connection to proxy.'"` //nolint: lll
TCPBuffer string `kong:"name='tcp-buffer',short='b',default='4KB',help='Size of TCP buffer to use.'"` // nolint: lll TCPBuffer string `kong:"name='tcp-buffer',short='b',default='4KB',help='Deprecated and ignored'"` //nolint: lll
PreferIP string `kong:"name='prefer-ip',short='i',default='prefer-ipv6',help='IP preference. By default we prefer IPv6 with fallback to IPv4.'"` // nolint: lll PreferIP string `kong:"name='prefer-ip',short='i',default='prefer-ipv6',help='IP preference. By default we prefer IPv6 with fallback to IPv4.'"` //nolint: lll
DomainFrontingPort uint64 `kong:"name='domain-fronting-port',short='p',default='443',help='A port to access for domain fronting.'"` // nolint: lll DomainFrontingPort uint64 `kong:"name='domain-fronting-port',short='p',default='443',help='A port to access for domain fronting.'"` //nolint: lll
DOHIP net.IP `kong:"name='doh-ip',short='n',default='9.9.9.9',help='IP address of DNS-over-HTTP to use.'"` // nolint: lll DomainFrontingIP string `kong:"name='domain-fronting-ip',help='An IP address to use for domain fronting instead of resolving the hostname via DNS.'"` //nolint: lll
Timeout time.Duration `kong:"name='timeout',short='t',default='10s',help='Network timeout to use'"` // nolint: lll DOHIP net.IP `kong:"name='doh-ip',short='n',default='1.1.1.1',help='IP address of DNS-over-HTTP to use.'"` //nolint: lll
AntiReplayCacheSize string `kong:"name='antireplay-cache-size',short='a',default='1MB',help='A size of anti-replay cache to use.'"` // nolint: lll Timeout time.Duration `kong:"name='timeout',short='t',default='10s',help='Network timeout to use'"` //nolint: lll
Socks5Proxies []string `kong:"name='socks5-proxy',short='s',help='Socks5 proxies to use for network access.'"` //nolint: lll
AntiReplayCacheSize string `kong:"name='antireplay-cache-size',short='a',default='1MB',help='A size of anti-replay cache to use.'"` //nolint: lll
} }
func (s *SimpleRun) Run(cli *CLI, version string) error { // nolint: cyclop func (s *SimpleRun) Run(cli *CLI, version string) error { //nolint: cyclop,funlen
conf := &config.Config{} conf := &config.Config{}
if err := conf.BindTo.Set(s.BindTo); err != nil { if err := conf.BindTo.Set(s.BindTo); err != nil {
@@ -34,22 +36,24 @@ func (s *SimpleRun) Run(cli *CLI, version string) error { // nolint: cyclop
return fmt.Errorf("incorrect secret: %w", err) return fmt.Errorf("incorrect secret: %w", err)
} }
if err := conf.Concurrency.Set(strconv.FormatUint(s.Concurrency, 10)); err != nil { // nolint: gomnd if err := conf.Concurrency.Set(strconv.FormatUint(s.Concurrency, 10)); err != nil {
return fmt.Errorf("incorrect concurrency: %w", err) return fmt.Errorf("incorrect concurrency: %w", err)
} }
if err := conf.TCPBuffer.Set(s.TCPBuffer); err != nil {
return fmt.Errorf("incorrect tcp-buffer: %w", err)
}
if err := conf.PreferIP.Set(s.PreferIP); err != nil { if err := conf.PreferIP.Set(s.PreferIP); err != nil {
return fmt.Errorf("incorrect prefer-ip: %w", err) return fmt.Errorf("incorrect prefer-ip: %w", err)
} }
if err := conf.DomainFrontingPort.Set(strconv.FormatUint(s.DomainFrontingPort, 10)); err != nil { // nolint: gomnd if err := conf.DomainFrontingPort.Set(strconv.FormatUint(s.DomainFrontingPort, 10)); err != nil {
return fmt.Errorf("incorrect domain-fronting-port: %w", err) return fmt.Errorf("incorrect domain-fronting-port: %w", err)
} }
if s.DomainFrontingIP != "" {
if err := conf.DomainFrontingIP.Set(s.DomainFrontingIP); err != nil {
return fmt.Errorf("incorrect domain-fronting-ip: %w", err)
}
}
if err := conf.Network.DOHIP.Set(s.DOHIP.String()); err != nil { if err := conf.Network.DOHIP.Set(s.DOHIP.String()); err != nil {
return fmt.Errorf("incorrect doh-ip: %w", err) return fmt.Errorf("incorrect doh-ip: %w", err)
} }
@@ -70,7 +74,18 @@ func (s *SimpleRun) Run(cli *CLI, version string) error { // nolint: cyclop
return fmt.Errorf("incorrect antireplay-cache-size: %w", err) return fmt.Errorf("incorrect antireplay-cache-size: %w", err)
} }
for _, v := range s.Socks5Proxies {
proxyURL := config.TypeProxyURL{}
if err := proxyURL.Set(v); err != nil {
return fmt.Errorf("incorrect socks5 proxy URL: %w", err)
}
conf.Network.Proxies = append(conf.Network.Proxies, proxyURL)
}
conf.Debug.Value = s.Debug conf.Debug.Value = s.Debug
conf.AllowFallbackOnUnknownDC.Value = true
conf.Defense.AntiReplay.Enabled.Value = true conf.Defense.AntiReplay.Enabled.Value = true
if err := conf.Validate(); err != nil { if err := conf.Validate(); err != nil {
+59 -18
View File
@@ -4,31 +4,49 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net"
"github.com/9seconds/mtg/v2/mtglib" "github.com/9seconds/mtg/v2/mtglib"
) )
type Optional struct {
Enabled TypeBool `json:"enabled"`
}
type ListConfig struct {
Optional
DownloadConcurrency TypeConcurrency `json:"downloadConcurrency"`
URLs []TypeBlocklistURI `json:"urls"`
UpdateEach TypeDuration `json:"updateEach"`
}
type Config struct { type Config struct {
Debug TypeBool `json:"debug"` Debug TypeBool `json:"debug"`
Secret mtglib.Secret `json:"secret"` AllowFallbackOnUnknownDC TypeBool `json:"allowFallbackOnUnknownDc"`
BindTo TypeHostPort `json:"bindTo"` Secret mtglib.Secret `json:"secret"`
TCPBuffer TypeBytes `json:"tcpBuffer"` BindTo TypeHostPort `json:"bindTo"`
PreferIP TypePreferIP `json:"preferIp"` ProxyProtocolListener TypeBool `json:"proxyProtocolListener"`
DomainFrontingPort TypePort `json:"domainFrontingPort"` PreferIP TypePreferIP `json:"preferIp"`
TolerateTimeSkewness TypeDuration `json:"tolerateTimeSkewness"` DomainFrontingPort TypePort `json:"domainFrontingPort"`
Concurrency TypeConcurrency `json:"concurrency"` DomainFrontingIP TypeIP `json:"domainFrontingIp"`
Defense struct { DomainFrontingProxyProtocol TypeBool `json:"domainFrontingProxyProtocol"`
TolerateTimeSkewness TypeDuration `json:"tolerateTimeSkewness"`
Concurrency TypeConcurrency `json:"concurrency"`
DomainFronting struct {
IP TypeIP `json:"ip"`
Port TypePort `json:"port"`
ProxyProtocol TypeBool `json:"proxyProtocol"`
} `json:"domainFronting"`
Defense struct {
AntiReplay struct { AntiReplay struct {
Enabled TypeBool `json:"enabled"` Optional
MaxSize TypeBytes `json:"maxSize"` MaxSize TypeBytes `json:"maxSize"`
ErrorRate TypeErrorRate `json:"errorRate"` ErrorRate TypeErrorRate `json:"errorRate"`
} `json:"antiReplay"` } `json:"antiReplay"`
Blocklist struct { Blocklist ListConfig `json:"blocklist"`
Enabled TypeBool `json:"enabled"` Allowlist ListConfig `json:"allowlist"`
DownloadConcurrency TypeConcurrency `json:"downloadConcurrency"`
URLs []TypeBlocklistURI `json:"urls"`
UpdateEach TypeDuration `json:"updateEach"`
} `json:"blocklist"`
} `json:"defense"` } `json:"defense"`
Network struct { Network struct {
Timeout struct { Timeout struct {
@@ -41,13 +59,15 @@ type Config struct {
} `json:"network"` } `json:"network"`
Stats struct { Stats struct {
StatsD struct { StatsD struct {
Enabled TypeBool `json:"enabled"` Optional
Address TypeHostPort `json:"address"` Address TypeHostPort `json:"address"`
MetricPrefix TypeMetricPrefix `json:"metricPrefix"` MetricPrefix TypeMetricPrefix `json:"metricPrefix"`
TagFormat TypeStatsdTagFormat `json:"tagFormat"` TagFormat TypeStatsdTagFormat `json:"tagFormat"`
} `json:"statsd"` } `json:"statsd"`
Prometheus struct { Prometheus struct {
Enabled TypeBool `json:"enabled"` Optional
BindTo TypeHostPort `json:"bindTo"` BindTo TypeHostPort `json:"bindTo"`
HTTPPath TypeHTTPPath `json:"httpPath"` HTTPPath TypeHTTPPath `json:"httpPath"`
MetricPrefix TypeMetricPrefix `json:"metricPrefix"` MetricPrefix TypeMetricPrefix `json:"metricPrefix"`
@@ -55,6 +75,27 @@ type Config struct {
} `json:"stats"` } `json:"stats"`
} }
func (c *Config) GetDomainFrontingPort(defaultValue uint) uint {
if port := c.DomainFronting.Port.Get(0); port != 0 {
return port
}
return c.DomainFrontingPort.Get(defaultValue)
}
func (c *Config) GetDomainFrontingIP(defaultValue net.IP) string {
if ip := c.DomainFronting.IP.Get(nil); ip != nil {
return ip.String()
}
if ip := c.DomainFrontingIP.Get(defaultValue); ip != nil {
return ip.String()
}
return ""
}
func (c *Config) GetDomainFrontingProxyProtocol(defaultValue bool) bool {
return c.DomainFronting.ProxyProtocol.Get(false) || c.DomainFrontingProxyProtocol.Get(defaultValue)
}
func (c *Config) Validate() error { func (c *Config) Validate() error {
if !c.Secret.Valid() { if !c.Secret.Valid() {
return fmt.Errorf("invalid secret %s", c.Secret.String()) return fmt.Errorf("invalid secret %s", c.Secret.String())
+24 -10
View File
@@ -5,19 +5,27 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/pelletier/go-toml" "github.com/pelletier/go-toml/v2"
) )
type tomlConfig struct { type tomlConfig struct {
Debug bool `toml:"debug" json:"debug,omitempty"` Debug bool `toml:"debug" json:"debug,omitempty"`
Secret string `toml:"secret" json:"secret"` AllowFallbackOnUnknownDC bool `toml:"allow-fallback-on-unknown-dc" json:"allowFallbackOnUnknownDc,omitempty"`
BindTo string `toml:"bind-to" json:"bindTo"` Secret string `toml:"secret" json:"secret"`
TCPBuffer string `toml:"tcp-buffer" json:"tcpBuffer,omitempty"` BindTo string `toml:"bind-to" json:"bindTo"`
PreferIP string `toml:"prefer-ip" json:"preferIp,omitempty"` ProxyProtocolListener bool `toml:"proxy-protocol-listener" json:"proxyProtocolListener"`
DomainFrontingPort uint `toml:"domain-fronting-port" json:"domainFrontingPort,omitempty"` PreferIP string `toml:"prefer-ip" json:"preferIp,omitempty"`
TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"` DomainFrontingPort uint `toml:"domain-fronting-port" json:"domainFrontingPort,omitempty"`
Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"` DomainFrontingIP string `toml:"domain-fronting-ip" json:"domainFrontingIp,omitempty"`
Defense struct { DomainFrontingProxyProtocol bool `toml:"domain-fronting-proxy-protocol" json:"domainFrontingProxyProtocol,omitempty"`
TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,omitempty"`
Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"`
DomainFronting struct {
IP string `toml:"ip" json:"ip,omitempty"`
Port uint `toml:"port" json:"port,omitempty"`
ProxyProtocol bool `toml:"proxy-protocol" json:"proxyProtocol,omitempty"`
} `toml:"domain-fronting" json:"domainFronting,omitempty"`
Defense struct {
AntiReplay struct { AntiReplay struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"` Enabled bool `toml:"enabled" json:"enabled,omitempty"`
MaxSize string `toml:"max-size" json:"maxSize,omitempty"` MaxSize string `toml:"max-size" json:"maxSize,omitempty"`
@@ -29,6 +37,12 @@ type tomlConfig struct {
URLs []string `toml:"urls" json:"urls,omitempty"` URLs []string `toml:"urls" json:"urls,omitempty"`
UpdateEach string `toml:"update-each" json:"updateEach,omitempty"` UpdateEach string `toml:"update-each" json:"updateEach,omitempty"`
} `toml:"blocklist" json:"blocklist,omitempty"` } `toml:"blocklist" json:"blocklist,omitempty"`
Allowlist struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
DownloadConcurrency uint `toml:"download-concurrency" json:"downloadConcurrency,omitempty"`
URLs []string `toml:"urls" json:"urls,omitempty"`
UpdateEach string `toml:"update-each" json:"updateEach,omitempty"`
} `toml:"allowlist" json:"allowlist,omitempty"`
} `toml:"defense" json:"defense,omitempty"` } `toml:"defense" json:"defense,omitempty"`
Network struct { Network struct {
Timeout struct { Timeout struct {
+2 -2
View File
@@ -20,7 +20,7 @@ type TypeBoolTestSuite struct {
} }
func (suite *TypeBoolTestSuite) TestUnmarshalFail() { func (suite *TypeBoolTestSuite) TestUnmarshalFail() {
testData := []interface{}{ testData := []any{
"", "",
"np", "np",
"нет", "нет",
@@ -29,7 +29,7 @@ func (suite *TypeBoolTestSuite) TestUnmarshalFail() {
} }
for _, v := range testData { for _, v := range testData {
data, err := json.Marshal(map[string]interface{}{ data, err := json.Marshal(map[string]any{
"value": v, "value": v,
}) })
suite.NoError(err) suite.NoError(err)
+2 -2
View File
@@ -10,7 +10,7 @@ type TypeConcurrency struct {
} }
func (t *TypeConcurrency) Set(value string) error { func (t *TypeConcurrency) Set(value string) error {
concurrencyValue, err := strconv.ParseUint(value, 10, 16) // nolint: gomnd concurrencyValue, err := strconv.ParseUint(value, 10, 16)
if err != nil { if err != nil {
return fmt.Errorf("value is not uint (%s): %w", value, err) return fmt.Errorf("value is not uint (%s): %w", value, err)
} }
@@ -41,5 +41,5 @@ func (t TypeConcurrency) MarshalJSON() ([]byte, error) {
} }
func (t TypeConcurrency) String() string { func (t TypeConcurrency) String() string {
return strconv.FormatUint(uint64(t.Value), 10) // nolint: gomnd return strconv.FormatUint(uint64(t.Value), 10)
} }
+41
View File
@@ -0,0 +1,41 @@
package config
import (
"fmt"
"strconv"
)
type TypeDC struct {
Value int
}
func (t *TypeDC) Set(value string) error {
parsed, err := strconv.ParseInt(value, 10, 16)
if err != nil {
return fmt.Errorf("cannot parse dc: %w", err)
}
if parsed < 0 {
parsed = -parsed
}
t.Value = int(parsed)
return nil
}
func (t *TypeDC) UnmarshalJSON(data []byte) error {
return t.Set(string(data))
}
func (t TypeDC) MarshalJSON() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeDC) String() string {
return strconv.Itoa(t.Value)
}
func (t TypeDC) Get() int {
return t.Value
}
+96
View File
@@ -0,0 +1,96 @@
package config_test
import (
"encoding/json"
"strconv"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeDCTestStruct struct {
Value config.TypeDC `json:"value"`
}
type TypeDCTestSuite struct {
suite.Suite
}
func (suite *TypeDCTestSuite) TestUnmarshalFail() {
testData := []string{
"-1s",
"1202002020202",
"xxx",
"-11111111111111",
"",
}
for _, v := range testData {
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typeDCTestStruct{}))
})
}
}
func (suite *TypeDCTestSuite) TestUnmarshalOk() {
testData := map[int]int{
1: 1,
-1: 1,
203: 203,
}
for value, expected := range testData {
data, err := json.Marshal(map[string]int{
"value": value,
})
suite.NoError(err)
suite.T().Run(strconv.Itoa(value), func(t *testing.T) {
testStruct := &typeDCTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, expected, testStruct.Value.Value)
assert.Equal(t, expected, testStruct.Value.Get())
})
}
}
func (suite *TypeDCTestSuite) TestMarshalOk() {
testData := map[string]int{
"1": 1,
"203": 203,
}
for k, v := range testData {
value := k
expected := v
suite.T().Run(value, func(t *testing.T) {
testStruct := &typeDCTestStruct{}
assert.NoError(t, testStruct.Value.Set(value))
data, err := json.Marshal(testStruct)
assert.NoError(t, err)
expectedJSON, err := json.Marshal(map[string]int{
"value": expected,
})
assert.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(data))
})
}
}
func TestTypeDC(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeDCTestSuite{})
}
+2 -2
View File
@@ -12,7 +12,7 @@ type TypeErrorRate struct {
} }
func (t *TypeErrorRate) Set(value string) error { func (t *TypeErrorRate) Set(value string) error {
parsedValue, err := strconv.ParseFloat(value, 64) // nolint: gomnd parsedValue, err := strconv.ParseFloat(value, 64)
if err != nil { if err != nil {
return fmt.Errorf("value is not a float (%s): %w", value, err) return fmt.Errorf("value is not a float (%s): %w", value, err)
} }
@@ -43,5 +43,5 @@ func (t TypeErrorRate) MarshalJSON() ([]byte, error) {
} }
func (t TypeErrorRate) String() string { func (t TypeErrorRate) String() string {
return strconv.FormatFloat(t.Value, 'f', -1, 64) // nolint: gomnd return strconv.FormatFloat(t.Value, 'f', -1, 64)
} }
+1 -1
View File
@@ -18,7 +18,7 @@ func (t *TypeHostPort) Set(value string) error {
return fmt.Errorf("incorrect host:port value (%v): %w", value, err) return fmt.Errorf("incorrect host:port value (%v): %w", value, err)
} }
portValue, err := strconv.ParseUint(port, 10, 16) // nolint: gomnd portValue, err := strconv.ParseUint(port, 10, 16)
if err != nil { if err != nil {
return fmt.Errorf("incorrect port number (%v): %w", value, err) return fmt.Errorf("incorrect port number (%v): %w", value, err)
} }
+1 -1
View File
@@ -10,7 +10,7 @@ type TypePort struct {
} }
func (t *TypePort) Set(value string) error { func (t *TypePort) Set(value string) error {
portValue, err := strconv.ParseUint(value, 10, 16) // nolint: gomnd portValue, err := strconv.ParseUint(value, 10, 16)
if err != nil { if err != nil {
return fmt.Errorf("incorrect port number (%v): %w", value, err) return fmt.Errorf("incorrect port number (%v): %w", value, err)
} }
+20
View File
@@ -0,0 +1,20 @@
package proxyprotocol
import (
"net"
"github.com/pires/go-proxyproto"
)
type ListenerAdapter struct {
proxyproto.Listener
}
func (l *ListenerAdapter) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
return nil, err
}
return connWrapper{conn.(*proxyproto.Conn)}, nil
}
+25
View File
@@ -0,0 +1,25 @@
package proxyprotocol
import "github.com/pires/go-proxyproto"
type connWrapper struct {
*proxyproto.Conn
}
func (c connWrapper) CloseRead() error {
tcpConn, ok := c.TCPConn()
if !ok {
panic("we support only tcp connections")
}
return tcpConn.CloseRead()
}
func (c connWrapper) CloseWrite() error {
tcpConn, ok := c.TCPConn()
if !ok {
panic("we support only tcp connections")
}
return tcpConn.CloseWrite()
}
+2 -2
View File
@@ -27,7 +27,7 @@ func captureOutput(filefp **os.File, callback func()) string {
closeChan := make(chan bool) closeChan := make(chan bool)
go func() { go func() {
io.Copy(buf, reader) // nolint: errcheck io.Copy(buf, reader) //nolint: errcheck
close(closeChan) close(closeChan)
}() }()
@@ -35,7 +35,7 @@ func captureOutput(filefp **os.File, callback func()) string {
callback() callback()
writer.Close() writer.Close() //nolint: errcheck
<-closeChan <-closeChan
return strings.TrimSpace(buf.String()) return strings.TrimSpace(buf.String())
+8 -7
View File
@@ -2,9 +2,9 @@ package testlib
import ( import (
"context" "context"
"net"
"net/http" "net/http"
"github.com/9seconds/mtg/v2/essentials"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
@@ -12,19 +12,20 @@ type MtglibNetworkMock struct {
mock.Mock mock.Mock
} }
func (m *MtglibNetworkMock) Dial(network, address string) (net.Conn, error) { func (m *MtglibNetworkMock) Dial(network, address string) (essentials.Conn, error) {
args := m.Called(network, address) args := m.Called(network, address)
return args.Get(0).(net.Conn), args.Error(1) // nolint: wrapcheck return args.Get(0).(essentials.Conn), args.Error(1) //nolint: wrapcheck, forcetypeassert
} }
func (m *MtglibNetworkMock) DialContext(ctx context.Context, network, address string) (net.Conn, error) { func (m *MtglibNetworkMock) DialContext(ctx context.Context, network, address string) (essentials.Conn, error) {
args := m.Called(ctx, network, address) args := m.Called(ctx, network, address)
return args.Get(0).(net.Conn), args.Error(1) // nolint: wrapcheck return args.Get(0).(essentials.Conn), args.Error(1) //nolint: wrapcheck, forcetypeassert
} }
func (m *MtglibNetworkMock) MakeHTTPClient(dialFunc func(ctx context.Context, func (m *MtglibNetworkMock) MakeHTTPClient(dialFunc func(ctx context.Context,
network, address string) (net.Conn, error)) *http.Client { network, address string) (essentials.Conn, error),
return m.Called(dialFunc).Get(0).(*http.Client) ) *http.Client {
return m.Called(dialFunc).Get(0).(*http.Client) //nolint: forcetypeassert
} }
+23 -15
View File
@@ -7,42 +7,50 @@ import (
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
type NetConnMock struct { type EssentialsConnMock struct {
mock.Mock mock.Mock
} }
func (n *NetConnMock) Read(b []byte) (int, error) { func (n *EssentialsConnMock) Read(b []byte) (int, error) {
args := n.Called(b) args := n.Called(b)
return args.Int(0), args.Error(1) return args.Int(0), args.Error(1)
} }
func (n *NetConnMock) Write(b []byte) (int, error) { func (n *EssentialsConnMock) Write(b []byte) (int, error) {
args := n.Called(b) args := n.Called(b)
return args.Int(0), args.Error(1) return args.Int(0), args.Error(1)
} }
func (n *NetConnMock) Close() error { func (n *EssentialsConnMock) Close() error {
return n.Called().Error(0) // nolint: wrapcheck return n.Called().Error(0) //nolint: wrapcheck
} }
func (n *NetConnMock) LocalAddr() net.Addr { func (n *EssentialsConnMock) CloseRead() error {
return n.Called().Get(0).(net.Addr) return n.Called().Error(0) //nolint: wrapcheck
} }
func (n *NetConnMock) RemoteAddr() net.Addr { func (n *EssentialsConnMock) CloseWrite() error {
return n.Called().Get(0).(net.Addr) return n.Called().Error(0) //nolint: wrapcheck
} }
func (n *NetConnMock) SetDeadline(t time.Time) error { func (n *EssentialsConnMock) LocalAddr() net.Addr {
return n.Called(t).Error(0) // nolint: wrapcheck return n.Called().Get(0).(net.Addr) //nolint: forcetypeassert
} }
func (n *NetConnMock) SetReadDeadline(t time.Time) error { func (n *EssentialsConnMock) RemoteAddr() net.Addr {
return n.Called(t).Error(0) // nolint: wrapcheck return n.Called().Get(0).(net.Addr) //nolint: forcetypeassert
} }
func (n *NetConnMock) SetWriteDeadline(t time.Time) error { func (n *EssentialsConnMock) SetDeadline(t time.Time) error {
return n.Called(t).Error(0) // nolint: wrapcheck return n.Called(t).Error(0) //nolint: wrapcheck
}
func (n *EssentialsConnMock) SetReadDeadline(t time.Time) error {
return n.Called(t).Error(0) //nolint: wrapcheck
}
func (n *EssentialsConnMock) SetWriteDeadline(t time.Time) error {
return n.Called(t).Error(0) //nolint: wrapcheck
} }
+38
View File
@@ -0,0 +1,38 @@
package utils
import (
"fmt"
"net"
"github.com/9seconds/mtg/v2/network"
)
type Listener struct {
net.Listener
}
func (l Listener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
return nil, err //nolint: wrapcheck
}
if err := network.SetClientSocketOptions(conn, 0); err != nil {
conn.Close() //nolint: errcheck
return nil, fmt.Errorf("cannot set TCP options: %w", err)
}
return conn, nil
}
func NewListener(bindTo string, bufferSize int) (net.Listener, error) {
base, err := net.Listen("tcp", bindTo)
if err != nil {
return nil, fmt.Errorf("cannot build a base listener: %w", err)
}
return Listener{
Listener: base,
}, nil
}
-24
View File
@@ -1,24 +0,0 @@
// +build !windows
package utils
import (
"fmt"
"golang.org/x/sys/unix"
)
func SetLimits() error {
rLimit := unix.Rlimit{}
if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil {
return fmt.Errorf("cannot get rlimit: %w", err)
}
rLimit.Cur = rLimit.Max
if err := unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil {
return fmt.Errorf("cannot set rlimit: %w", err)
}
return nil
}
-7
View File
@@ -1,7 +0,0 @@
// +build windows
package utils
func SetLimits() error {
return nil
}
+1
View File
@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows // +build !windows
package utils package utils
+1
View File
@@ -1,3 +1,4 @@
//go:build windows
// +build windows // +build windows
package utils package utils
+8
View File
@@ -0,0 +1,8 @@
// files defines a set of abstraction for 'files': an openable entities that
// could be read after.
//
// This is not a file on a filesystem of your local machine, it also can
// include "in memory" files or even remote ones, like HTTP endpoints. If you
// make a GET request to HTTP endpoint, then a body is readable and you can
// consider it as an openable file.
package files
+65
View File
@@ -0,0 +1,65 @@
package files
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
)
type httpFile struct {
http *http.Client
url string
}
func (h httpFile) Open(ctx context.Context) (io.ReadCloser, error) {
request, err := http.NewRequestWithContext(ctx, http.MethodGet, h.url, nil)
if err != nil {
panic(err)
}
response, err := h.http.Do(request)
if err != nil {
if response != nil {
io.Copy(io.Discard, response.Body) //nolint: errcheck
response.Body.Close() //nolint: errcheck
}
return nil, fmt.Errorf("cannot get url %s: %w", h.url, err)
}
if response.StatusCode >= http.StatusBadRequest {
return nil, fmt.Errorf("unexpected status code %d", response.StatusCode)
}
return response.Body, nil
}
func (h httpFile) String() string {
return h.url
}
// NewHTTP returns a file abstraction for HTTP/HTTPS endpoint. You also need to
// provide a valid instance of [http.Client] to access it.
func NewHTTP(client *http.Client, endpoint string) (File, error) {
if client == nil {
return nil, ErrBadHTTPClient
}
parsed, err := url.Parse(endpoint)
if err != nil {
return nil, fmt.Errorf("incorrect url %s: %w", endpoint, err)
}
switch parsed.Scheme {
case "http", "https":
default:
return nil, fmt.Errorf("unsupported url %s", endpoint)
}
return httpFile{
http: client,
url: endpoint,
}, nil
}
+90
View File
@@ -0,0 +1,90 @@
package files_test
import (
"context"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/9seconds/mtg/v2/ipblocklist/files"
"github.com/stretchr/testify/suite"
)
type HTTPTestSuite struct {
suite.Suite
httpClient *http.Client
httpServer *httptest.Server
ctx context.Context
ctxCancel context.CancelFunc
}
func (suite *HTTPTestSuite) makeFile(path string) (files.File, error) {
return files.NewHTTP(suite.httpClient, suite.httpServer.URL+"/"+path) //nolint: wrapcheck
}
func (suite *HTTPTestSuite) SetupSuite() {
mux := http.NewServeMux()
mux.Handle("/", http.FileServer(http.Dir("testdata")))
suite.httpServer = httptest.NewServer(mux)
suite.httpClient = suite.httpServer.Client()
}
func (suite *HTTPTestSuite) SetupTest() {
suite.ctx, suite.ctxCancel = context.WithCancel(context.Background())
}
func (suite *HTTPTestSuite) TearDownTest() {
suite.ctxCancel()
suite.httpServer.CloseClientConnections()
}
func (suite *HTTPTestSuite) TearDownSuite() {
suite.httpServer.Close()
}
func (suite *HTTPTestSuite) TestBadURL() {
_, err := files.NewHTTP(suite.httpClient, "sdfsdf")
suite.Error(err)
}
func (suite *HTTPTestSuite) TestBadSchema() {
_, err := files.NewHTTP(suite.httpClient, "gopher://lala")
suite.Error(err)
}
func (suite *HTTPTestSuite) TestNilHTTPClient() {
_, err := files.NewHTTP(nil, "")
suite.Error(err)
}
func (suite *HTTPTestSuite) TestAbsentFile() {
file, err := suite.makeFile("absent")
suite.NoError(err)
_, err = file.Open(suite.ctx)
suite.Error(err)
}
func (suite *HTTPTestSuite) TestOk() {
file, err := suite.makeFile("readable")
suite.NoError(err)
readCloser, err := file.Open(suite.ctx)
suite.NoError(err)
defer readCloser.Close() //nolint: errcheck
data, err := io.ReadAll(readCloser)
suite.NoError(err)
suite.Equal("Hooray!", strings.TrimSpace(string(data)))
}
func TestHTTP(t *testing.T) {
t.Parallel()
suite.Run(t, &HTTPTestSuite{})
}
+21
View File
@@ -0,0 +1,21 @@
package files
import (
"context"
"errors"
"io"
)
// ErrBadHTTPClient is returned if given HTTP client is initialized
// incorrectly.
var ErrBadHTTPClient = errors.New("incorrect http client")
// File is an abstraction for a entity that can be opened in some context.
type File interface {
// Open returns an readable entity for a file. It is important to not forget
// to close it after the usage.
Open(context.Context) (io.ReadCloser, error)
// String returns a short text description for the file
String() string
}
+31
View File
@@ -0,0 +1,31 @@
package files
import (
"context"
"fmt"
"io"
"os"
)
type localFile struct {
path string
}
func (l localFile) Open(ctx context.Context) (io.ReadCloser, error) {
return os.Open(l.path) //nolint: wrapcheck
}
func (l localFile) String() string {
return l.path
}
// NewLocal returns an openable File for a path on a local file system.
func NewLocal(path string) (File, error) {
if stat, err := os.Stat(path); os.IsNotExist(err) || stat.IsDir() || stat.Mode().Perm()&0o400 == 0 {
return nil, fmt.Errorf("%s is not a readable file", path)
}
return localFile{
path: path,
}, nil
}
+55
View File
@@ -0,0 +1,55 @@
package files_test
import (
"context"
"io"
"path/filepath"
"strings"
"testing"
"github.com/9seconds/mtg/v2/ipblocklist/files"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type LocalTestSuite struct {
suite.Suite
}
func (suite *LocalTestSuite) getLocalFile(name string) string {
return filepath.Join("testdata", name)
}
func (suite *LocalTestSuite) TestIncorrect() {
names := []string{
"absent",
"directory",
}
for _, v := range names {
value := v
suite.T().Run(v, func(t *testing.T) {
_, err := files.NewLocal(suite.getLocalFile(value))
assert.Error(t, err)
})
}
}
func (suite *LocalTestSuite) TestOk() {
file, err := files.NewLocal(suite.getLocalFile("readable"))
suite.NoError(err)
reader, err := file.Open(context.Background())
suite.NoError(err)
data, err := io.ReadAll(reader)
suite.NoError(err)
suite.Equal("Hooray!", strings.TrimSpace(string(data)))
}
func TestLocal(t *testing.T) {
t.Parallel()
suite.Run(t, &LocalTestSuite{})
}
+38
View File
@@ -0,0 +1,38 @@
package files
import (
"context"
"io"
"net"
"strings"
)
type memFile struct {
data string
}
func (m memFile) Open(ctx context.Context) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader(m.data)), nil
}
func (m memFile) String() string {
return "mem"
}
// NewMem returns an openable file that is kept in RAM.
func NewMem(networks []*net.IPNet) File {
builder := strings.Builder{}
if len(networks) > 0 {
builder.WriteString(networks[0].String())
}
for i := 1; i < len(networks); i++ {
builder.WriteString("\n")
builder.WriteString(networks[i].String())
}
return memFile{
data: builder.String(),
}
}
+42
View File
@@ -0,0 +1,42 @@
package files_test
import (
"context"
"io"
"net"
"strings"
"testing"
"github.com/9seconds/mtg/v2/ipblocklist/files"
"github.com/stretchr/testify/suite"
)
type MemTestSuite struct {
suite.Suite
}
func (suite *MemTestSuite) TestOk() {
_, network1, _ := net.ParseCIDR("192.168.0.1/24")
_, network2, _ := net.ParseCIDR("2001:0db8:85a3:0000:0000:8a2e:0370:7334/36")
file := files.NewMem([]*net.IPNet{
network1,
network2,
})
reader, err := file.Open(context.Background())
suite.NoError(err)
data, err := io.ReadAll(reader)
suite.NoError(err)
strData := strings.TrimSpace(string(data))
suite.Contains(strData, "192.168.0.0/24")
suite.Contains(strData, "2001:db8:8000::/36")
}
func TestMem(t *testing.T) {
t.Parallel()
suite.Run(t, &MemTestSuite{})
}
View File
+1
View File
@@ -0,0 +1 @@
Hooray!
+124 -228
View File
@@ -4,57 +4,54 @@ import (
"bufio" "bufio"
"context" "context"
"fmt" "fmt"
"io"
"net" "net"
"net/http"
"net/url"
"os"
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/9seconds/mtg/v2/ipblocklist/files"
"github.com/9seconds/mtg/v2/mtglib" "github.com/9seconds/mtg/v2/mtglib"
"github.com/kentik/patricia"
"github.com/kentik/patricia/bool_tree"
"github.com/panjf2000/ants/v2" "github.com/panjf2000/ants/v2"
"github.com/yl2chen/cidranger"
) )
const ( var (
fireholIPv4DefaultCIDR = 32 fireholRegexpComment = regexp.MustCompile(`\s*#.*?$`)
fireholIPv6DefaultCIDR = 128
fireholIPv4DefaultCIDR = net.CIDRMask(32, 32)
fireholIPv6DefaultCIDR = net.CIDRMask(128, 128)
) )
var fireholRegexpComment = regexp.MustCompile(`\s*#.*?$`) // FireholUpdateCallback defines a signature of the callback that has to be
// execute when ip list is updated.
type FireholUpdateCallback func(context.Context, int)
// Firehol is IPBlocklist which uses lists from FireHOL: // Firehol is [mtglib.IPBlocklist] which uses lists from FireHOL:
// https://iplists.firehol.org/ // https://iplists.firehol.org/
// //
// It can use both local files and remote URLs. This is not necessary // It can use both local files and remote URLs. This is not necessary that
// that blocklists should be taken from this website, we expect only // blocklists should be taken from this website, we expect only compatible
// compatible formats here. // formats here.
// //
// Example of the format: // Example of the format:
// //
// # this is a comment // # this is a comment
// # to ignore // # to ignore
// 127.0.0.1 # you can specify an IP // 127.0.0.1 # you can specify an IP
// 10.0.0.0/8 # or cidr // 10.0.0.0/8 # or cidr
type Firehol struct { type Firehol struct {
ctx context.Context ctx context.Context
ctxCancel context.CancelFunc ctxCancel context.CancelFunc
logger mtglib.Logger logger mtglib.Logger
updateMutex sync.RWMutex
rwMutex sync.RWMutex updateCallback FireholUpdateCallback
ranger cidranger.Ranger
remoteURLs []string blocklists []files.File
localFiles []string
httpClient *http.Client
workerPool *ants.Pool workerPool *ants.Pool
treeV4 *bool_tree.TreeV4
treeV6 *bool_tree.TreeV6
} }
// Shutdown stop a background update process. // Shutdown stop a background update process.
@@ -68,20 +65,20 @@ func (f *Firehol) Contains(ip net.IP) bool {
return true return true
} }
f.rwMutex.RLock() f.updateMutex.RLock()
defer f.rwMutex.RUnlock() defer f.updateMutex.RUnlock()
if ip4 := ip.To4(); ip4 != nil { ok, err := f.ranger.Contains(ip)
return f.containsIPv4(ip4) if err != nil {
f.logger.BindStr("ip", ip.String()).DebugError("Cannot check if ip is present", err)
} }
return f.containsIPv6(ip.To16()) return ok && err == nil
} }
// Run starts a background update process. // Run starts a background update process.
// //
// This is a blocking method so you probably want to run it in a // This is a blocking method so you probably want to run it in a goroutine.
// goroutine.
func (f *Firehol) Run(updateEach time.Duration) { func (f *Firehol) Run(updateEach time.Duration) {
if updateEach == 0 { if updateEach == 0 {
updateEach = DefaultFireholUpdateEach updateEach = DefaultFireholUpdateEach
@@ -98,161 +95,64 @@ func (f *Firehol) Run(updateEach time.Duration) {
} }
}() }()
if err := f.update(); err != nil { f.update()
f.logger.WarningError("cannot update blocklist", err)
} else {
f.logger.Info("blocklist was updated")
}
for { for {
select { select {
case <-f.ctx.Done(): case <-f.ctx.Done():
return return
case <-ticker.C: case <-ticker.C:
if err := f.update(); err != nil { f.update()
f.logger.WarningError("cannot update blocklist", err)
} else {
f.logger.Info("blocklist was updated")
}
} }
} }
} }
func (f *Firehol) containsIPv4(addr net.IP) bool { func (f *Firehol) update() {
ip := patricia.NewIPv4AddressFromBytes(addr, 32) // nolint: gomnd
if ok, _, err := f.treeV4.FindDeepestTag(ip); ok && err == nil {
return true
}
return false
}
func (f *Firehol) containsIPv6(addr net.IP) bool {
ip := patricia.NewIPv6Address(addr, 128) // nolint: gomnd
if ok, _, err := f.treeV6.FindDeepestTag(ip); ok && err == nil {
return true
}
return false
}
func (f *Firehol) update() error { // nolint: funlen, cyclop
ctx, cancel := context.WithCancel(f.ctx) ctx, cancel := context.WithCancel(f.ctx)
defer cancel() defer cancel()
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(f.remoteURLs) + len(f.localFiles))
treeMutex := &sync.Mutex{} mutex := &sync.Mutex{}
v4tree := bool_tree.NewTreeV4() ranger := cidranger.NewPCTrieRanger()
v6tree := bool_tree.NewTreeV6()
errorChan := make(chan error, 1) for _, v := range f.blocklists {
defer close(errorChan) wg.Go(func() {
logger := f.logger.BindStr("filename", v.String())
for _, v := range f.localFiles { fileContent, err := v.Open(ctx)
go func(filename string) { if err != nil {
defer wg.Done() logger.WarningError("update has failed", err)
if err := f.updateLocalFile(ctx, filename, treeMutex, v4tree, v6tree); err != nil { return
cancel()
f.logger.BindStr("filename", filename).WarningError("cannot update", err)
select {
case errorChan <- err:
default:
}
} }
}(v)
}
for _, v := range f.remoteURLs { defer fileContent.Close() //nolint: errcheck
value := v
f.workerPool.Submit(func() { // nolint: errcheck if err := f.updateFromFile(mutex, ranger, bufio.NewScanner(fileContent)); err != nil {
defer wg.Done() logger.WarningError("update has failed", err)
if err := f.updateRemoteURL(ctx, value, treeMutex, v4tree, v6tree); err != nil {
cancel()
f.logger.BindStr("url", value).WarningError("cannot update", err)
select {
case errorChan <- err:
default:
}
} }
}) })
} }
wg.Wait() wg.Wait()
select { f.updateMutex.Lock()
case err := <-errorChan: defer f.updateMutex.Unlock()
return fmt.Errorf("cannot update trees: %w", err)
default: f.ranger = ranger
if f.updateCallback != nil {
f.updateCallback(ctx, ranger.Len())
} }
f.rwMutex.Lock() f.logger.Info("ip list was updated")
defer f.rwMutex.Unlock()
f.treeV4 = v4tree
f.treeV6 = v6tree
return nil
} }
func (f *Firehol) updateLocalFile(ctx context.Context, filename string, func (f *Firehol) updateFromFile(mutex sync.Locker,
mutex sync.Locker, ranger cidranger.Ranger,
v4tree *bool_tree.TreeV4, v6tree *bool_tree.TreeV6) error { scanner *bufio.Scanner,
filefp, err := os.Open(filename) ) error {
if err != nil {
return fmt.Errorf("cannot open file: %w", err)
}
go func(ctx context.Context, closer io.Closer) {
<-ctx.Done()
closer.Close()
}(ctx, filefp)
defer filefp.Close()
return f.updateTrees(mutex, filefp, v4tree, v6tree)
}
func (f *Firehol) updateRemoteURL(ctx context.Context, url string,
mutex sync.Locker,
v4tree *bool_tree.TreeV4, v6tree *bool_tree.TreeV6) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return fmt.Errorf("cannot build a request: %w", err)
}
resp, err := f.httpClient.Do(req) // nolint: bodyclose
if err != nil {
return fmt.Errorf("cannot request a remote URL %s: %w", url, err)
}
go func(ctx context.Context, closer io.Closer) {
<-ctx.Done()
closer.Close()
}(ctx, resp.Body)
defer func(rc io.ReadCloser) {
io.Copy(io.Discard, rc) // nolint: errcheck
rc.Close()
}(resp.Body)
return f.updateTrees(mutex, resp.Body, v4tree, v6tree)
}
func (f *Firehol) updateTrees(mutex sync.Locker,
reader io.Reader,
v4tree *bool_tree.TreeV4,
v6tree *bool_tree.TreeV6) error {
scanner := bufio.NewScanner(reader)
for scanner.Scan() { for scanner.Scan() {
text := scanner.Text() text := scanner.Text()
text = fireholRegexpComment.ReplaceAllLiteralString(text, "") text = fireholRegexpComment.ReplaceAllLiteralString(text, "")
@@ -262,94 +162,92 @@ func (f *Firehol) updateTrees(mutex sync.Locker,
continue continue
} }
ip, cidr, err := f.updateParseLine(text) ipnet, err := f.updateParseLine(text)
if err != nil { if err != nil {
return fmt.Errorf("cannot parse a line: %w", err) return fmt.Errorf("cannot parse a line: %w", err)
} }
if err := f.updateAddToTrees(ip, cidr, mutex, v4tree, v6tree); err != nil { mutex.Lock()
return fmt.Errorf("cannot add a node to the tree: %w", err) err = ranger.Insert(cidranger.NewBasicRangerEntry(*ipnet))
mutex.Unlock()
if err != nil {
return fmt.Errorf("cannot insert %v into ranger: %w", ipnet, err)
} }
} }
if scanner.Err() != nil { if scanner.Err() != nil {
return fmt.Errorf("cannot parse a response: %w", scanner.Err()) return fmt.Errorf("cannot parse a file: %w", scanner.Err())
} }
return nil return nil
} }
func (f *Firehol) updateParseLine(text string) (net.IP, uint, error) { func (f *Firehol) updateParseLine(text string) (*net.IPNet, error) {
_, ipnet, err := net.ParseCIDR(text) if _, ipnet, err := net.ParseCIDR(text); err == nil {
if err != nil { return ipnet, nil
ipaddr := net.ParseIP(text)
if ipaddr == nil {
return nil, 0, fmt.Errorf("incorrect ip address %s", text)
}
ip4 := ipaddr.To4()
if ip4 != nil {
return ip4, fireholIPv4DefaultCIDR, nil
}
return ipaddr.To16(), fireholIPv6DefaultCIDR, nil
} }
ones, _ := ipnet.Mask.Size() ipaddr := net.ParseIP(text)
if ipaddr == nil {
return ipnet.IP, uint(ones), nil return nil, fmt.Errorf("incorrect ip address %s", text)
}
func (f *Firehol) updateAddToTrees(ip net.IP, cidr uint,
mutex sync.Locker,
v4tree *bool_tree.TreeV4, v6tree *bool_tree.TreeV6) error {
mutex.Lock()
defer mutex.Unlock()
if ip.To4() != nil {
addr := patricia.NewIPv4AddressFromBytes(ip, cidr)
if _, _, err := v4tree.Set(addr, true); err != nil {
return err // nolint: wrapcheck
}
} else {
addr := patricia.NewIPv6Address(ip, cidr)
if _, _, err := v6tree.Set(addr, true); err != nil {
return err // nolint: wrapcheck
}
} }
return nil mask := fireholIPv4DefaultCIDR
if ipaddr.To4() == nil {
mask = fireholIPv6DefaultCIDR
}
return &net.IPNet{
IP: ipaddr,
Mask: mask,
}, nil
} }
// NewFirehol creates a new instance of FireHOL IP blocklist. // NewFirehol creates a new instance of FireHOL IP blocklist.
// //
// This method does not start an update process so please execute Run // This method does not start an update process so please execute Run when it
// when it is necessary. // is necessary.
func NewFirehol(logger mtglib.Logger, network mtglib.Network, func NewFirehol(logger mtglib.Logger, network mtglib.Network,
downloadConcurrency uint, downloadConcurrency uint,
remoteURLs []string, urls []string,
localFiles []string) (*Firehol, error) { localFiles []string,
for _, v := range remoteURLs { updateCallback FireholUpdateCallback,
parsed, err := url.Parse(v) ) (*Firehol, error) {
if err != nil { blocklists := []files.File{}
return nil, fmt.Errorf("incorrect url %s: %w", v, err)
}
switch parsed.Scheme {
case "http", "https":
default:
return nil, fmt.Errorf("unsupported url %s", v)
}
}
for _, v := range localFiles { for _, v := range localFiles {
if stat, err := os.Stat(v); os.IsNotExist(err) || stat.IsDir() || stat.Mode().Perm()&0o400 == 0 { file, err := files.NewLocal(v)
return nil, fmt.Errorf("%s is not a readable file", v) if err != nil {
return nil, fmt.Errorf("cannot create a local file %s: %w", v, err)
} }
blocklists = append(blocklists, file)
} }
httpClient := network.MakeHTTPClient(nil)
for _, v := range urls {
file, err := files.NewHTTP(httpClient, v)
if err != nil {
return nil, fmt.Errorf("cannot create a HTTP file %s: %w", v, err)
}
blocklists = append(blocklists, file)
}
return NewFireholFromFiles(logger, downloadConcurrency, blocklists, updateCallback)
}
// NewFirehol creates a new instance of FireHOL IP blocklist.
//
// This method creates this instances from a given list of files.
func NewFireholFromFiles(logger mtglib.Logger,
downloadConcurrency uint,
blocklists []files.File,
updateCallback FireholUpdateCallback,
) (*Firehol, error) {
if downloadConcurrency == 0 { if downloadConcurrency == 0 {
downloadConcurrency = DefaultFireholDownloadConcurrency downloadConcurrency = DefaultFireholDownloadConcurrency
} }
@@ -358,14 +256,12 @@ func NewFirehol(logger mtglib.Logger, network mtglib.Network,
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
return &Firehol{ return &Firehol{
ctx: ctx, ctx: ctx,
ctxCancel: cancel, ctxCancel: cancel,
logger: logger.Named("firehol"), logger: logger.Named("firehol"),
httpClient: network.MakeHTTPClient(nil), ranger: cidranger.NewPCTrieRanger(),
treeV4: bool_tree.NewTreeV4(), workerPool: workerPool,
treeV6: bool_tree.NewTreeV6(), blocklists: blocklists,
workerPool: workerPool, updateCallback: updateCallback,
remoteURLs: remoteURLs,
localFiles: localFiles,
}, nil }, nil
} }
+8 -6
View File
@@ -35,9 +35,9 @@ func (suite *FireholTestSuite) SetupSuite() {
panic(err) panic(err)
} }
defer filefp.Close() defer filefp.Close() //nolint: errcheck
io.Copy(w, filefp) // nolint: errcheck io.Copy(w, filefp) //nolint: errcheck
}) })
suite.httpServer = httptest.NewServer(mux) suite.httpServer = httptest.NewServer(mux)
@@ -67,7 +67,8 @@ func (suite *FireholTestSuite) TearDownSuite() {
func (suite *FireholTestSuite) TestLocalFail() { func (suite *FireholTestSuite) TestLocalFail() {
blocklist, err := ipblocklist.NewFirehol(logger.NewNoopLogger(), blocklist, err := ipblocklist.NewFirehol(logger.NewNoopLogger(),
suite.networkMock, 2, suite.networkMock, 2,
nil, []string{filepath.Join("testdata", "broken_ipset.ipset")}) nil, []string{filepath.Join("testdata", "broken_ipset.ipset")},
nil)
suite.NoError(err) suite.NoError(err)
@@ -85,7 +86,8 @@ func (suite *FireholTestSuite) TestLocalFail() {
func (suite *FireholTestSuite) TestLocalOk() { func (suite *FireholTestSuite) TestLocalOk() {
blocklist, err := ipblocklist.NewFirehol(logger.NewNoopLogger(), blocklist, err := ipblocklist.NewFirehol(logger.NewNoopLogger(),
suite.networkMock, 2, suite.networkMock, 2,
nil, []string{filepath.Join("testdata", "good_ipset.ipset")}) nil, []string{filepath.Join("testdata", "good_ipset.ipset")},
nil)
suite.NoError(err) suite.NoError(err)
@@ -103,7 +105,7 @@ func (suite *FireholTestSuite) TestLocalOk() {
func (suite *FireholTestSuite) TestRemoteFail() { func (suite *FireholTestSuite) TestRemoteFail() {
blocklist, err := ipblocklist.NewFirehol(logger.NewNoopLogger(), blocklist, err := ipblocklist.NewFirehol(logger.NewNoopLogger(),
suite.networkMock, 2, suite.networkMock, 2,
[]string{"https://google.com"}, nil) []string{"https://google.com"}, nil, nil)
suite.NoError(err) suite.NoError(err)
@@ -127,7 +129,7 @@ func (suite *FireholTestSuite) TestMixed() {
suite.httpServer.URL, suite.httpServer.URL,
}, []string{ }, []string{
filepath.Join("testdata", "good_ipset.ipset"), filepath.Join("testdata", "good_ipset.ipset"),
}) }, nil)
suite.NoError(err) suite.NoError(err)
+5 -5
View File
@@ -1,8 +1,8 @@
// Package ipblocklist contains default implementation of the // Package ipblocklist contains default implementation of the
// IPBlocklist for mtg. // [mtglib.IPBlocklist] for mtg.
// //
// Please check documentation for mtglib.IPBlocklist interface to get an // Please check documentation for [mtglib.IPBlocklist] interface to get an idea
// idea of this abstraction. // of this abstraction.
package ipblocklist package ipblocklist
import "time" import "time"
@@ -12,7 +12,7 @@ const (
// concurrent downloads of ip blocklists for Firehol. // concurrent downloads of ip blocklists for Firehol.
DefaultFireholDownloadConcurrency = 1 DefaultFireholDownloadConcurrency = 1
// DefaultFireholUpdateEach defines a default time period when // DefaultFireholUpdateEach defines a default time period when Firehol
// Firehol requests updates of the blocklists. // requests updates of the blocklists.
DefaultFireholUpdateEach = 6 * time.Hour DefaultFireholUpdateEach = 6 * time.Hour
) )
+5 -3
View File
@@ -2,16 +2,18 @@ package ipblocklist
import ( import (
"net" "net"
"time"
"github.com/9seconds/mtg/v2/mtglib" "github.com/9seconds/mtg/v2/mtglib"
) )
type noop struct{} type noop struct{}
func (n noop) Contains(ip net.IP) bool { return false } func (n noop) Contains(ip net.IP) bool { return false }
func (n noop) Run(updateEach time.Duration) {}
func (n noop) Shutdown() {}
// NewNoop returns a dummy ipblocklist which allows all incoming // NewNoop returns a dummy ipblocklist which allows all incoming connections.
// connections.
func NewNoop() mtglib.IPBlocklist { func NewNoop() mtglib.IPBlocklist {
return noop{} return noop{}
} }
+7
View File
@@ -17,6 +17,13 @@ func (suite *NoopTestSuite) TestOp() {
suite.False(ipblocklist.NewNoop().Contains(net.ParseIP("10.0.0.10"))) suite.False(ipblocklist.NewNoop().Contains(net.ParseIP("10.0.0.10")))
} }
func (suite *NoopTestSuite) TestRun() {
blocklist := ipblocklist.NewNoop()
blocklist.Run(0)
blocklist.Shutdown()
}
func TestNoop(t *testing.T) { func TestNoop(t *testing.T) {
t.Parallel() t.Parallel()
suite.Run(t, &NoopTestSuite{}) suite.Run(t, &NoopTestSuite{})
+7 -9
View File
@@ -1,14 +1,12 @@
// Package logger has implementation of loggers for mtglib.Logger // Package logger has implementation of loggers for [mtglib.Logger] interface.
// interface.
// //
// Please see a description of that interface to get some agreements // Please see a description of that interface to get some agreements which are
// which are used by mtglib. // used by mtglib.
package logger package logger
// StdLikeLogger is an interface which is close to log.Logger. This is // StdLikeLogger is an interface which is close to [log.Logger]. This is
// commonly used by many 3pp tools. While mtglib itself does not need // commonly used by many 3pp tools. While mtglib itself does not need it, it is
// it, it is always a good idea to support it and have a transient end // always a good idea to support it and have a transient end to end logging.
// to end logging.
type StdLikeLogger interface { type StdLikeLogger interface {
Printf(format string, args ...interface{}) Printf(format string, args ...any)
} }
+2 -1
View File
@@ -7,7 +7,8 @@ type noopLogger struct{}
func (n noopLogger) Named(_ string) mtglib.Logger { return n } func (n noopLogger) Named(_ string) mtglib.Logger { return n }
func (n noopLogger) BindInt(_ string, _ int) mtglib.Logger { return n } func (n noopLogger) BindInt(_ string, _ int) mtglib.Logger { return n }
func (n noopLogger) BindStr(_, _ string) mtglib.Logger { return n } func (n noopLogger) BindStr(_, _ string) mtglib.Logger { return n }
func (n noopLogger) Printf(_ string, _ ...interface{}) {} func (n noopLogger) BindJSON(_, _ string) mtglib.Logger { return n }
func (n noopLogger) Printf(_ string, _ ...any) {}
func (n noopLogger) Info(_ string) {} func (n noopLogger) Info(_ string) {}
func (n noopLogger) Warning(_ string) {} func (n noopLogger) Warning(_ string) {}
func (n noopLogger) Debug(_ string) {} func (n noopLogger) Debug(_ string) {}
+15 -1
View File
@@ -15,6 +15,7 @@ const (
zeroLogContextVarTypeUnknown zeroLogContextVarType = iota zeroLogContextVarTypeUnknown zeroLogContextVarType = iota
zeroLogContextVarTypeStr zeroLogContextVarTypeStr
zeroLogContextVarTypeInt zeroLogContextVarTypeInt
zeroLogContextVarTypeJSON
) )
type zeroLogContext struct { type zeroLogContext struct {
@@ -66,7 +67,18 @@ func (z *zeroLogContext) BindStr(name, value string) mtglib.Logger {
} }
} }
func (z *zeroLogContext) Printf(format string, args ...interface{}) { func (z *zeroLogContext) BindJSON(name, value string) mtglib.Logger {
return &zeroLogContext{
name: z.name,
log: z.log,
ctxVarType: zeroLogContextVarTypeJSON,
ctxVarName: name,
ctxVarStr: value,
parent: z,
}
}
func (z *zeroLogContext) Printf(format string, args ...any) {
z.Debug(fmt.Sprintf(format, args...)) z.Debug(fmt.Sprintf(format, args...))
} }
@@ -110,6 +122,8 @@ func (z *zeroLogContext) attachCtx(evt *zerolog.Event) {
evt.Str(z.ctxVarName, z.ctxVarStr) evt.Str(z.ctxVarName, z.ctxVarStr)
case zeroLogContextVarTypeInt: case zeroLogContextVarTypeInt:
evt.Int(z.ctxVarName, z.ctxVarInt) evt.Int(z.ctxVarName, z.ctxVarInt)
case zeroLogContextVarTypeJSON:
evt.RawJSON(z.ctxVarName, []byte(z.ctxVarStr))
case zeroLogContextVarTypeUnknown: case zeroLogContextVarTypeUnknown:
} }
} }
+1 -1
View File
@@ -116,6 +116,6 @@ func (suite *ZeroLoggerTestSuite) TestIndependence() {
suite.NotContains("lalala", log12Output) suite.NotContains("lalala", log12Output)
} }
func TestZeroLogger(t *testing.T) { // nolint: paralleltest func TestZeroLogger(t *testing.T) { //nolint: paralleltest
suite.Run(t, &ZeroLoggerTestSuite{}) suite.Run(t, &ZeroLoggerTestSuite{})
} }
+1 -13
View File
@@ -9,26 +9,14 @@
package main package main
import ( import (
"math/rand"
"time"
"github.com/9seconds/mtg/v2/internal/cli" "github.com/9seconds/mtg/v2/internal/cli"
"github.com/9seconds/mtg/v2/internal/utils"
"github.com/alecthomas/kong" "github.com/alecthomas/kong"
) )
var version = "dev" // has to be set by ldflags
func main() { func main() {
rand.Seed(time.Now().UTC().UnixNano())
if err := utils.SetLimits(); err != nil {
panic(err)
}
cli := &cli.CLI{} cli := &cli.CLI{}
ctx := kong.Parse(cli, kong.Vars{ ctx := kong.Parse(cli, kong.Vars{
"version": version, "version": getVersion(),
}) })
ctx.FatalIfErrorf(ctx.Run(cli, version)) ctx.FatalIfErrorf(ctx.Run(cli, version))
+42
View File
@@ -0,0 +1,42 @@
[[tools.go]]
version = "1.26.0"
backend = "core:go"
"platforms.linux-arm64" = { checksum = "sha256:bd03b743eb6eb4193ea3c3fd3956546bf0e3ca5b7076c8226334afe6b75704cd", url = "https://dl.google.com/go/go1.26.0.linux-arm64.tar.gz"}
"platforms.linux-x64" = { checksum = "sha256:aac1b08a0fb0c4e0a7c1555beb7b59180b05dfc5a3d62e40e9de90cd42f88235", url = "https://dl.google.com/go/go1.26.0.linux-amd64.tar.gz"}
"platforms.macos-arm64" = { checksum = "sha256:b1640525dfe68f066d56f200bef7bf4dce955a1a893bd061de6754c211431023", url = "https://dl.google.com/go/go1.26.0.darwin-arm64.tar.gz"}
"platforms.macos-x64" = { checksum = "sha256:1ca28b7703cbea05a65b2a1d92d6b308610ef92f8824578a0874f2e60c9d5a22", url = "https://dl.google.com/go/go1.26.0.darwin-amd64.tar.gz"}
"platforms.windows-x64" = { checksum = "sha256:9bbe0fc64236b2b51f6255c05c4232532b8ecc0e6d2e00950bd3021d8a4d07d4", url = "https://dl.google.com/go/go1.26.0.windows-amd64.zip"}
[[tools."go:golang.org/x/pkgsite/cmd/pkgsite"]]
version = "latest"
backend = "go:golang.org/x/pkgsite/cmd/pkgsite"
[[tools."go:golang.org/x/tools/gopls"]]
version = "0.21.1"
backend = "go:golang.org/x/tools/gopls"
[[tools."go:golang.org/x/vuln/cmd/govulncheck"]]
version = "1.1.4"
backend = "go:golang.org/x/vuln/cmd/govulncheck"
[[tools."go:mvdan.cc/gofumpt"]]
version = "0.9.2"
backend = "go:mvdan.cc/gofumpt"
[[tools.golangci-lint]]
version = "2.10.1"
backend = "aqua:golangci/golangci-lint"
"platforms.linux-arm64" = { checksum = "sha256:6652b42ae02915eb2f9cb2a2e0cac99514c8eded8388d88ae3e06e1a52c00de8", url = "https://github.com/golangci/golangci-lint/releases/download/v2.10.1/golangci-lint-2.10.1-linux-arm64.tar.gz"}
"platforms.linux-x64" = { checksum = "sha256:dfa775874cf0561b404a02a8f4481fc69b28091da95aa697259820d429b09c99", url = "https://github.com/golangci/golangci-lint/releases/download/v2.10.1/golangci-lint-2.10.1-linux-amd64.tar.gz"}
"platforms.macos-arm64" = { checksum = "sha256:03bfadf67e52b441b7ec21305e501c717df93c959836d66c7f97312654acb297", url = "https://github.com/golangci/golangci-lint/releases/download/v2.10.1/golangci-lint-2.10.1-darwin-arm64.tar.gz"}
"platforms.macos-x64" = { checksum = "sha256:66fb0da81b8033b477f97eea420d4b46b230ca172b8bb87c6610109f3772b6b6", url = "https://github.com/golangci/golangci-lint/releases/download/v2.10.1/golangci-lint-2.10.1-darwin-amd64.tar.gz"}
"platforms.windows-x64" = { checksum = "sha256:c60c87695e79db8e320f0e5be885059859de52bb5ee5f11be5577828570bc2a3", url = "https://github.com/golangci/golangci-lint/releases/download/v2.10.1/golangci-lint-2.10.1-windows-amd64.zip"}
[[tools.goreleaser]]
version = "2.13.3"
backend = "aqua:goreleaser/goreleaser"
"platforms.linux-arm64" = { checksum = "sha256:156656d0f874542d618568bd50afd3d33ced2e8aab2c60cc7c21e1b9fa52031e", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.13.3/goreleaser_Linux_arm64.tar.gz"}
"platforms.linux-x64" = { checksum = "sha256:4b66f2f78f78561330350651ade557b70328664718490f37834749073af21d20", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.13.3/goreleaser_Linux_x86_64.tar.gz"}
"platforms.macos-arm64" = { checksum = "sha256:5516c37779efb3935d5b213cda3b0b9025ae94ddbcb51df6919acbcdef4194b0", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.13.3/goreleaser_Darwin_all.tar.gz"}
"platforms.macos-x64" = { checksum = "sha256:5516c37779efb3935d5b213cda3b0b9025ae94ddbcb51df6919acbcdef4194b0", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.13.3/goreleaser_Darwin_all.tar.gz"}
"platforms.windows-x64" = { checksum = "sha256:c5586c4ed749ca358ad61ed73ee4b8039cfa68daae8c23e69fb086d549dfb31d", url = "https://github.com/goreleaser/goreleaser/releases/download/v2.13.3/goreleaser_Windows_x86_64.zip"}
+44 -15
View File
@@ -3,13 +3,16 @@ package mtglib
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"io" "io"
"net" "net"
"sync"
"github.com/9seconds/mtg/v2/essentials"
"github.com/pires/go-proxyproto"
) )
type connTraffic struct { type connTraffic struct {
net.Conn essentials.Conn
streamID string streamID string
stream EventStream stream EventStream
@@ -23,7 +26,7 @@ func (c connTraffic) Read(b []byte) (int, error) {
c.stream.Send(c.ctx, NewEventTraffic(c.streamID, uint(n), true)) c.stream.Send(c.ctx, NewEventTraffic(c.streamID, uint(n), true))
} }
return n, err // nolint: wrapcheck return n, err //nolint: wrapcheck
} }
func (c connTraffic) Write(b []byte) (int, error) { func (c connTraffic) Write(b []byte) (int, error) {
@@ -33,32 +36,25 @@ func (c connTraffic) Write(b []byte) (int, error) {
c.stream.Send(c.ctx, NewEventTraffic(c.streamID, uint(n), false)) c.stream.Send(c.ctx, NewEventTraffic(c.streamID, uint(n), false))
} }
return n, err // nolint: wrapcheck return n, err //nolint: wrapcheck
} }
type connRewind struct { type connRewind struct {
net.Conn essentials.Conn
active io.Reader
buf bytes.Buffer buf bytes.Buffer
mutex sync.RWMutex active io.Reader
} }
func (c *connRewind) Read(p []byte) (int, error) { func (c *connRewind) Read(p []byte) (int, error) {
c.mutex.RLock() return c.active.Read(p)
defer c.mutex.RUnlock()
return c.active.Read(p) // nolint: wrapcheck
} }
func (c *connRewind) Rewind() { func (c *connRewind) Rewind() {
c.mutex.Lock()
defer c.mutex.Unlock()
c.active = io.MultiReader(&c.buf, c.Conn) c.active = io.MultiReader(&c.buf, c.Conn)
} }
func newConnRewind(conn net.Conn) *connRewind { func newConnRewind(conn essentials.Conn) *connRewind {
rv := &connRewind{ rv := &connRewind{
Conn: conn, Conn: conn,
} }
@@ -66,3 +62,36 @@ func newConnRewind(conn net.Conn) *connRewind {
return rv return rv
} }
type connProxyProtocol struct {
essentials.Conn
sourceAddr net.Addr
headersWritten bool
}
func (c *connProxyProtocol) Write(p []byte) (int, error) {
if !c.headersWritten {
headers := proxyproto.HeaderProxyFromAddrs(2, c.sourceAddr, c.RemoteAddr())
toSend, err := headers.Format()
if err != nil {
panic(err)
}
if _, err := c.Conn.Write(toSend); err != nil {
return 0, fmt.Errorf("cannot send proxy protocol header: %w", err)
}
c.headersWritten = true
}
return c.Conn.Write(p)
}
func newConnProxyProtocol(source, target essentials.Conn) *connProxyProtocol {
return &connProxyProtocol{
Conn: target,
sourceAddr: source.RemoteAddr(),
}
}
+102 -6
View File
@@ -1,20 +1,23 @@
package mtglib package mtglib
import ( import (
"bufio"
"bytes" "bytes"
"context" "context"
"errors" "errors"
"io" "io"
"net"
"testing" "testing"
"time" "time"
"github.com/9seconds/mtg/v2/internal/testlib" "github.com/9seconds/mtg/v2/internal/testlib"
"github.com/pires/go-proxyproto"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
type ConnRewindBaseConn struct { type ConnRewindBaseConn struct {
testlib.NetConnMock testlib.EssentialsConnMock
readBuffer bytes.Buffer readBuffer bytes.Buffer
} }
@@ -22,20 +25,20 @@ type ConnRewindBaseConn struct {
func (c *ConnRewindBaseConn) Read(p []byte) (int, error) { func (c *ConnRewindBaseConn) Read(p []byte) (int, error) {
c.Called(p) c.Called(p)
return c.readBuffer.Read(p) // nolint: wrapcheck return c.readBuffer.Read(p) //nolint: wrapcheck
} }
type ConnTrafficTestSuite struct { type ConnTrafficTestSuite struct {
suite.Suite suite.Suite
eventStreamMock *EventStreamMock eventStreamMock *EventStreamMock
connMock *testlib.NetConnMock connMock *testlib.EssentialsConnMock
conn io.ReadWriter conn io.ReadWriter
} }
func (suite *ConnTrafficTestSuite) SetupTest() { func (suite *ConnTrafficTestSuite) SetupTest() {
suite.eventStreamMock = &EventStreamMock{} suite.eventStreamMock = &EventStreamMock{}
suite.connMock = &testlib.NetConnMock{} suite.connMock = &testlib.EssentialsConnMock{}
suite.conn = connTraffic{ suite.conn = connTraffic{
Conn: suite.connMock, Conn: suite.connMock,
streamID: "CONNID", streamID: "CONNID",
@@ -69,7 +72,7 @@ func (suite *ConnTrafficTestSuite) TestReadOk() {
suite.Equal(10, n) suite.Equal(10, n)
} }
func (suite *ConnTrafficTestSuite) TestReadErr() { // nolint: dupl func (suite *ConnTrafficTestSuite) TestReadErr() { //nolint: dupl
suite.eventStreamMock. suite.eventStreamMock.
On("Send", mock.Anything, mock.Anything). On("Send", mock.Anything, mock.Anything).
Once(). Once().
@@ -125,7 +128,7 @@ func (suite *ConnTrafficTestSuite) TestWriteOk() {
suite.Equal(10, n) suite.Equal(10, n)
} }
func (suite *ConnTrafficTestSuite) TestWriteErr() { // nolint: dupl func (suite *ConnTrafficTestSuite) TestWriteErr() { //nolint: dupl
suite.eventStreamMock. suite.eventStreamMock.
On("Send", mock.Anything, mock.Anything). On("Send", mock.Anything, mock.Anything).
Once(). Once().
@@ -200,6 +203,94 @@ func (suite *ConnRewindTestSuite) TestRead() {
suite.Equal([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, data) suite.Equal([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, data)
} }
type ConnProxyProtocolTestSuite struct {
suite.Suite
sourceConnMock *testlib.EssentialsConnMock
targetConnMock *testlib.EssentialsConnMock
conn *connProxyProtocol
}
func (suite *ConnProxyProtocolTestSuite) SetupTest() {
suite.sourceConnMock = &testlib.EssentialsConnMock{}
suite.targetConnMock = &testlib.EssentialsConnMock{}
localAddr := &net.TCPAddr{
IP: net.ParseIP("127.0.0.1").To4(),
}
remoteAddr := &net.TCPAddr{
IP: net.ParseIP("127.0.0.2").To4(),
}
suite.sourceConnMock.
On("RemoteAddr").
Return(localAddr)
suite.targetConnMock.
On("RemoteAddr").
Maybe().
Return(remoteAddr)
suite.conn = newConnProxyProtocol(suite.sourceConnMock, suite.targetConnMock)
}
func (suite *ConnProxyProtocolTestSuite) TestRead() {
value := []byte{1, 2, 3, 4, 5}
toRead := make([]byte, len(value))
suite.targetConnMock.
On("Read", mock.AnythingOfType("[]uint8")).
Once().
Return(len(toRead), nil).
Run(func(args mock.Arguments) {
arr := args.Get(0).([]byte)
copy(arr, value)
})
n, err := suite.conn.Read(toRead)
suite.Equal(len(value), n)
suite.NoError(err)
suite.Equal(value, toRead)
}
func (suite *ConnProxyProtocolTestSuite) TestWrite() {
value := []byte{1, 2, 3, 4, 5}
buf := &bytes.Buffer{}
bufReader := bufio.NewReader(buf)
suite.targetConnMock.
On("Write", mock.AnythingOfType("[]uint8")).
Return(28, nil).
Run(func(args mock.Arguments) {
arr := args.Get(0).([]byte)
buf.Write(arr)
})
_, err := suite.conn.Write(value)
suite.NoError(err)
header, err := proxyproto.Read(bufReader)
suite.NoError(err)
sourceAddr, destAddr, ok := header.TCPAddrs()
suite.True(ok)
suite.Equal(suite.sourceConnMock.RemoteAddr(), sourceAddr)
suite.Equal(suite.targetConnMock.RemoteAddr(), destAddr)
read, _ := io.ReadAll(bufReader)
suite.Equal(value, read)
_, err = suite.conn.Write(value)
suite.NoError(err)
read, _ = io.ReadAll(bufReader)
suite.Equal(value, read)
}
func (suite *ConnProxyProtocolTestSuite) TearDownTest() {
suite.sourceConnMock.AssertExpectations(suite.T())
suite.targetConnMock.AssertExpectations(suite.T())
}
func TestConnTraffic(t *testing.T) { func TestConnTraffic(t *testing.T) {
t.Parallel() t.Parallel()
suite.Run(t, &ConnTrafficTestSuite{}) suite.Run(t, &ConnTrafficTestSuite{})
@@ -209,3 +300,8 @@ func TestConnRewind(t *testing.T) {
t.Parallel() t.Parallel()
suite.Run(t, &ConnRewindTestSuite{}) suite.Run(t, &ConnRewindTestSuite{})
} }
func TestConnProxyProtocol(t *testing.T) {
t.Parallel()
suite.Run(t, &ConnProxyProtocolTestSuite{})
}
+53 -19
View File
@@ -29,13 +29,13 @@ type EventStart struct {
RemoteIP net.IP RemoteIP net.IP
} }
// EventConnectedToDC is emitted when mtg proxy has connected to a // EventConnectedToDC is emitted when mtg proxy has connected to a Telegram
// Telegram server. // server.
type EventConnectedToDC struct { type EventConnectedToDC struct {
eventBase eventBase
// RemoteIP is an IP address of the Telegram server proxy has been // RemoteIP is an IP address of the Telegram server proxy has been connected
// connected to. // to.
RemoteIP net.IP RemoteIP net.IP
// DC is an index of the datacenter proxy has been connected to. // DC is an index of the datacenter proxy has been connected to.
@@ -49,15 +49,15 @@ type EventTraffic struct {
// Traffic is a count of bytes which were transmitted. // Traffic is a count of bytes which were transmitted.
Traffic uint Traffic uint
// IsRead defines if we _read_ or _write_ to connection. A rule of // IsRead defines if we _read_ or _write_ to connection. A rule of thumb is
// thumb is simple: EventTraffic is bound to a remote connection. Not // simple: EventTraffic is bound to a remote connection. Not to a client one,
// to a client one, but either to Telegram or front domain one. // but either to Telegram or front domain one.
// //
// In the case of Telegram, isRead means that we've fetched some bytes // In the case of Telegram, isRead means that we've fetched some bytes from
// from Telegram to send it to a client. // Telegram to send it to a client.
// //
// In the case of the front domain, it means that we've fetched some // In the case of the front domain, it means that we've fetched some bytes
// bytes from this domain to send it to a client. // from this domain to send it to a client.
IsRead bool IsRead bool
} }
@@ -66,24 +66,25 @@ type EventFinish struct {
eventBase eventBase
} }
// EventDomainFronting is emitted when we connect to a front domain // EventDomainFronting is emitted when we connect to a front domain instead of
// instead of Telegram server. // Telegram server.
type EventDomainFronting struct { type EventDomainFronting struct {
eventBase eventBase
} }
// EventConcurrencyLimited is emitted when connection was declined // EventConcurrencyLimited is emitted when connection was declined because of
// because of the concurrency limit of the worker pool. // the concurrency limit of the worker pool.
type EventConcurrencyLimited struct { type EventConcurrencyLimited struct {
eventBase eventBase
} }
// EventIPBlocklisted is emitted when connection was declined because // EventIPBlocklisted is emitted when connection was declined because IP
// IP address was found in IP blocklist. // address was found in IP blocklist.
type EventIPBlocklisted struct { type EventIPBlocklisted struct {
eventBase eventBase
RemoteIP net.IP RemoteIP net.IP
IsBlockList bool
} }
// EventReplayAttack is emitted when mtg detects a replay attack on a // EventReplayAttack is emitted when mtg detects a replay attack on a
@@ -92,6 +93,15 @@ type EventReplayAttack struct {
eventBase eventBase
} }
// EventIPListSize is emitted when mtg updates a contents of the ip lists:
// allowlist or blocklist.
type EventIPListSize struct {
eventBase
Size int
IsBlockList bool
}
// NewEventStart creates a new EventStart event. // NewEventStart creates a new EventStart event.
func NewEventStart(streamID string, remoteIP net.IP) EventStart { func NewEventStart(streamID string, remoteIP net.IP) EventStart {
return EventStart{ return EventStart{
@@ -163,7 +173,20 @@ func NewEventIPBlocklisted(remoteIP net.IP) EventIPBlocklisted {
eventBase: eventBase{ eventBase: eventBase{
timestamp: time.Now(), timestamp: time.Now(),
}, },
RemoteIP: remoteIP, RemoteIP: remoteIP,
IsBlockList: true,
}
}
// NewEventIPAllowlisted creates a NewEventIPBlocklisted event with a mark that
// it is supposed to be for allow list.
func NewEventIPAllowlisted(remoteIP net.IP) EventIPBlocklisted {
return EventIPBlocklisted{
eventBase: eventBase{
timestamp: time.Now(),
},
RemoteIP: remoteIP,
IsBlockList: false,
} }
} }
@@ -176,3 +199,14 @@ func NewEventReplayAttack(streamID string) EventReplayAttack {
}, },
} }
} }
// NewEventIPListSize creates a new EventIPListSize event.
func NewEventIPListSize(size int, isBlockList bool) EventIPListSize {
return EventIPListSize{
eventBase: eventBase{
timestamp: time.Now(),
},
Size: size,
IsBlockList: isBlockList,
}
}
+18
View File
@@ -60,6 +60,15 @@ func (suite *EventsTestSuite) TestEventIPBlocklisted() {
suite.Empty(evt.StreamID()) suite.Empty(evt.StreamID())
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond) suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
suite.True(evt.IsBlockList)
}
func (suite *EventsTestSuite) TestEventIPAllowlisted() {
evt := mtglib.NewEventIPAllowlisted(net.ParseIP("10.0.0.10"))
suite.Empty(evt.StreamID())
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
suite.False(evt.IsBlockList)
} }
func (suite *EventsTestSuite) TestEventReplayAttack() { func (suite *EventsTestSuite) TestEventReplayAttack() {
@@ -69,6 +78,15 @@ func (suite *EventsTestSuite) TestEventReplayAttack() {
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond) suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
} }
func (suite *EventsTestSuite) TestEventIPListSize() {
evt := mtglib.NewEventIPListSize(10, false)
suite.Empty(evt.StreamID())
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
suite.Equal(10, evt.Size)
suite.False(evt.IsBlockList)
}
func TestEvents(t *testing.T) { func TestEvents(t *testing.T) {
t.Parallel() t.Parallel()
suite.Run(t, &EventsTestSuite{}) suite.Run(t, &EventsTestSuite{})
+149 -139
View File
@@ -1,20 +1,19 @@
// mtglib defines a package with MTPROTO proxy. // mtglib defines a package with MTPROTO proxy.
// //
// Since mtg itself is build as an example of how to work with mtglib, // Since mtg itself is build as an example of how to work with mtglib, it worth
// it worth to telling a couple of words about a project organization. // to telling a couple of words about a project organization.
// //
// A core object of the project is mtglib.Proxy. This is a proxy you // A core object of the project is [mtglib.Proxy]. This is a proxy you expect:
// expect: that one which you configure, set to serve on a listener // that one which you configure, set to serve on a listener and/or shutdown on
// and/or shutdown on application termination. // application termination.
// //
// But it also has a core logic unrelated to Telegram per se: anti // But it also has a core logic unrelated to Telegram per se: anti replay
// replay cache, network connectivity (who knows, maybe you want to have // cache, network connectivity (who knows, maybe you want to have a native
// a native VMESS integration) and so on. // VMESS integration) and so on.
// //
// You can supply such parts to a proxy with interfaces. The rest of // You can supply such parts to a proxy with interfaces. The rest of the
// the packages in mtg define some default implementations of these // packages in mtg define some default implementations of these interfaces. But
// interfaces. But if you want to integrate it with, let say, influxdb, // if you want to integrate it with, let say, influxdb, you can do it easily.
// you can do it easily.
package mtglib package mtglib
import ( import (
@@ -23,183 +22,193 @@ import (
"net" "net"
"net/http" "net/http"
"time" "time"
"github.com/9seconds/mtg/v2/essentials"
) )
var ( var (
// ErrSecretEmpty is returned if you are trying to create a proxy // ErrSecretEmpty is returned if you are trying to create a proxy but do not
// but do not provide a secret. // provide a secret.
ErrSecretEmpty = errors.New("secret is empty") ErrSecretEmpty = errors.New("secret is empty")
// ErrSecretInvalid is returned if you are trying to create a proxy // ErrSecretInvalid is returned if you are trying to create a proxy but secret
// but secret value is invalid (no host or payload are zeroes). // value is invalid (no host or payload are zeroes).
ErrSecretInvalid = errors.New("secret is invalid") ErrSecretInvalid = errors.New("secret is invalid")
// ErrNetworkIsNotDefined is returned if you are trying to create a // ErrNetworkIsNotDefined is returned if you are trying to create a proxy but
// proxy but network value is undefined. // network value is undefined.
ErrNetworkIsNotDefined = errors.New("network is not defined") ErrNetworkIsNotDefined = errors.New("network is not defined")
// ErrAntiReplayCacheIsNotDefined is returned if you are trying to // ErrAntiReplayCacheIsNotDefined is returned if you are trying to create a
// create a proxy but anti replay cache value is undefined. // proxy but anti replay cache value is undefined.
ErrAntiReplayCacheIsNotDefined = errors.New("anti-replay cache is not defined") ErrAntiReplayCacheIsNotDefined = errors.New("anti-replay cache is not defined")
// ErrIPBlocklistIsNotDefined is returned if you are trying to // ErrIPBlocklistIsNotDefined is returned if you are trying to create a proxy
// create a proxy but ip blocklist instance is not defined. // but ip blocklist instance is not defined.
ErrIPBlocklistIsNotDefined = errors.New("ip blocklist is not defined") ErrIPBlocklistIsNotDefined = errors.New("ip blocklist is not defined")
// ErrEventStreamIsNotDefined is returned if you are trying to create a // ErrIPAllowlistIsNotDefined is returned if you are trying to create a proxy
// proxy but event stream instance is not defined. // but ip allowlist instance is not defined.
ErrIPAllowlistIsNotDefined = errors.New("ip allowlist is not defined")
// ErrEventStreamIsNotDefined is returned if you are trying to create a proxy
// but event stream instance is not defined.
ErrEventStreamIsNotDefined = errors.New("event stream is not defined") ErrEventStreamIsNotDefined = errors.New("event stream is not defined")
// ErrLoggerIsNotDefined is returned if you are trying to // ErrLoggerIsNotDefined is returned if you are trying to create a proxy but
// create a proxy but logger is not defined. // logger is not defined.
ErrLoggerIsNotDefined = errors.New("logger is not defined") ErrLoggerIsNotDefined = errors.New("logger is not defined")
) )
const ( const (
// DefaultConcurrency is a default max count of simultaneously // DefaultConcurrency is a default max count of simultaneously connected
// connected clients. // clients.
DefaultConcurrency = 4096 DefaultConcurrency = 4096
// DefaultBufferSize is a default size of a copy buffer. // DefaultBufferSize is a default size of a copy buffer.
//
// Deprecated: this setting no longer makes any effect.
DefaultBufferSize = 16 * 1024 // 16 kib DefaultBufferSize = 16 * 1024 // 16 kib
// DefaultDomainFrontingPort is a default port (HTTPS) to connect to in // DefaultDomainFrontingPort is a default port (HTTPS) to connect to in case
// case of probe-resistance activity. // of probe-resistance activity.
DefaultDomainFrontingPort = 443 DefaultDomainFrontingPort = 443
// DefaultIdleTimeout is a default timeout for closing a connection // DefaultIdleTimeout is a default timeout for closing a connection in case of
// in case of idling. // idling.
//
// Deprecated: no longer in use because of changed TCP relay algorithm.
DefaultIdleTimeout = time.Minute DefaultIdleTimeout = time.Minute
// DefaultTolerateTimeSkewness is a default timeout for time // DefaultTolerateTimeSkewness is a default timeout for time skewness on a
// skewness on a faketls timeout verification. // faketls timeout verification.
DefaultTolerateTimeSkewness = 3 * time.Second DefaultTolerateTimeSkewness = 3 * time.Second
// DefaultPreferIP is a default value for Telegram IP connectivity // DefaultPreferIP is a default value for Telegram IP connectivity preference.
// preference.
DefaultPreferIP = "prefer-ipv6" DefaultPreferIP = "prefer-ipv6"
// SecretKeyLength defines a length of the secret bytes used // SecretKeyLength defines a length of the secret bytes used by Telegram and a
// by Telegram and a proxy. // proxy.
SecretKeyLength = 16 SecretKeyLength = 16
// ConnectionIDBytesLength defines a count of random bytes // ConnectionIDBytesLength defines a count of random bytes used to generate a
// used to generate a stream/connection ids. // stream/connection ids.
ConnectionIDBytesLength = 16 ConnectionIDBytesLength = 16
// TCPRelayReadTimeout defines a max time period between two consecuitive
// reads from Telegram after which connection will be terminated. This is
// required to abort stale connections.
TCPRelayReadTimeout = 20 * time.Second
) )
// Network defines a knowledge how to work with a network. It may sound // Network defines a knowledge how to work with a network. It may sound fun but
// fun but it encapsulates all the knowledge how to properly establish // it encapsulates all the knowledge how to properly establish connections to
// connections to remote hosts and configure HTTP clients. // remote hosts and configure HTTP clients.
// //
// For example, if you want to use SOCKS5 proxy, you probably want to // For example, if you want to use SOCKS5 proxy, you probably want to have all
// have all traffic routed to this proxy: telegram connections, http // traffic routed to this proxy: telegram connections, http requests and so on.
// requests and so on. This knowledge is encapsulated into instances of // This knowledge is encapsulated into instances of such interface.
// such interface.
// //
// mtglib uses Network for: // mtglib uses Network for:
// // 1. Dialing to Telegram
// 1. Dialing to Telegram // 2. Dialing to front domain
// // 3. Doing HTTP requests (for example, for FireHOL ipblocklist).
// 2. Dialing to front domain
//
// 3. Doing HTTP requests (for example, for FireHOL ipblocklist).
type Network interface { type Network interface {
// Dial establishes context-free TCP connections. // Dial establishes context-free TCP connections.
Dial(network, address string) (net.Conn, error) Dial(network, address string) (essentials.Conn, error)
// DialContext dials using a context. This is a preferrable // DialContext dials using a context. This is a preferrable way of
// way of establishing TCP connections. // establishing TCP connections.
DialContext(ctx context.Context, network, address string) (net.Conn, error) DialContext(ctx context.Context, network, address string) (essentials.Conn, error)
// MakeHTTPClient build an HTTP client with given dial function. If // MakeHTTPClient build an HTTP client with given dial function. If nothing is
// nothing is provided, then DialContext of this interface is going // provided, then DialContext of this interface is going to be used.
// to be used. MakeHTTPClient(func(ctx context.Context, network, address string) (essentials.Conn, error)) *http.Client
MakeHTTPClient(func(ctx context.Context, network, address string) (net.Conn, error)) *http.Client
} }
// AntiReplayCache is an interface that is used to detect replay attacks // AntiReplayCache is an interface that is used to detect replay attacks based
// based on some traffic fingerprints. // on some traffic fingerprints.
// //
// Replay attacks are probe attacks whose main goal is to identify if // Replay attacks are probe attacks whose main goal is to identify if server
// server software can be classified in some way. For example, if you // software can be classified in some way. For example, if you send some HTTP
// send some HTTP request to a web server, then you can expect that this // request to a web server, then you can expect that this server will respond
// server will respond with HTTP response back. // with HTTP response back.
// //
// There is a problem though. Let's imagine, that connection is // There is a problem though. Let's imagine, that connection is encrypted.
// encrypted. Let's imagine, that it is encrypted with some static key // Let's imagine, that it is encrypted with some static key like [ShadowSocks].
// like ShadowSocks (https://shadowsocks.org/assets/whitepaper.pdf). // In that case, in theory, if you repeat the same bytes, you can get the same
// In that case, in theory, if you repeat the same bytes, you can get // responses. Let's imagine, that you've cracked the key. then if you send the
// the same responses. Let's imagine, that you've cracked the key. then // same bytes, you can decrypt a response and see its structure. Based on its
// if you send the same bytes, you can decrypt a response and see its // structure you can identify if this server is SOCKS5, MTPROTO proxy etc.
// structure. Based on its structure you can identify if this server is
// SOCKS5, MTPROTO proxy etc.
// //
// This is just one example, maybe not the best or not the most // This is just one example, maybe not the best or not the most relevant. In
// relevant. In real life, different organizations use such replay // real life, different organizations use such replay attacks to perform some
// attacks to perform some reverse engineering of the proxy, do some // reverse engineering of the proxy, do some statical analysis to identify
// statical analysis to identify server software. // server software.
// //
// There are many ways how to protect your proxy against them. One // There are many ways how to protect your proxy against them. One is domain
// is domain fronting which is a core part of mtg. Another one is to // fronting which is a core part of mtg. Another one is to collect some
// collect some 'handshake fingerprints' and forbid duplication. // 'handshake fingerprints' and forbid duplication.
// //
// So, it one is sending the same byte flow right after you (or a couple // So, it one is sending the same byte flow right after you (or a couple of
// of hours after), mtg should detect that and reject this connection // hours after), mtg should detect that and reject this connection (or redirect
// (or redirect to fronting domain). // to fronting domain).
//
// [ShadowSocks]: https://shadowsocks.org/assets/whitepaper.pdf
type AntiReplayCache interface { type AntiReplayCache interface {
// Seen before checks if this set of bytes was observed before or // Seen before checks if this set of bytes was observed before or not. If it
// not. If it is required to store this information somewhere else, // is required to store this information somewhere else, then it has to do
// then it has to do that. // that.
SeenBefore(data []byte) bool SeenBefore(data []byte) bool
} }
// IPBlocklist filters requests based on IP address. // IPBlocklist filters requests based on IP address.
// //
// If this filter has an IP address, then mtg closes a request without // If this filter has an IP address, then mtg closes a request without reading
// reading anything from a socket. It also does not give such request to // anything from a socket. It also does not give such request to a worker pool,
// a worker pool, so in worst cases you can expect that you invoke this // so in worst cases you can expect that you invoke this object more frequent
// object more frequent than defined proxy concurrency. // than defined proxy concurrency.
type IPBlocklist interface { type IPBlocklist interface {
// Contains checks if given IP address belongs to this blocklist If. // Contains checks if given IP address belongs to this blocklist If. it is, a
// it is, a connection is terminated . // connection is terminated .
Contains(net.IP) bool Contains(net.IP) bool
// Run starts a background update procedure for a blocklist
Run(time.Duration)
// Shutdown stops a blocklist. It is assumed that none will access it after.
Shutdown()
} }
// Event is a data structure which is populated during mtg request // Event is a data structure which is populated during mtg request processing
// processing lifecycle. Each request popluates many events: // lifecycle. Each request popluates many events:
// 1. Client connected
// 2. Request is finished
// 3. Connection to Telegram server is established
// //
// 1. Client connected // and so on. All these events are data structures but all of them must conform
// // the same interface.
// 2. Request is finished
//
// 3. Connection to Telegram server is established
//
// and so on. All these events are data structures but all of them
// must conform the same interface.
type Event interface { type Event interface {
// StreamID returns an identifier of the stream, connection, // StreamID returns an identifier of the stream, connection, request, you name
// request, you name it. All events within the same stream returns // it. All events within the same stream returns the same stream id.
// the same stream id.
StreamID() string StreamID() string
// Timestamp returns a timestamp when this event was generated. // Timestamp returns a timestamp when this event was generated.
Timestamp() time.Time Timestamp() time.Time
} }
// EventStream is an abstraction that accepts a set of events produced // EventStream is an abstraction that accepts a set of events produced by mtg.
// by mtg. Its main goal is to inject your logging or monitoring system. // Its main goal is to inject your logging or monitoring system.
// //
// The idea is simple. When mtg works, it emits a set of events during // The idea is simple. When mtg works, it emits a set of events during a
// a lifecycle of the requestor: EventStart, EventFinish etc. mtg is a // lifecycle of the requestor: EventStart, EventFinish etc. mtg is a producer
// producer which puts these events into a stream. Responsibility of // which puts these events into a stream. Responsibility of the stream is to
// the stream is to deliver this event to consumers/observers. There // deliver this event to consumers/observers. There might be many different
// might be many different observers (for example, you want to have both // observers (for example, you want to have both statsd and prometheus), mtg
// statsd and prometheus), mtg should know nothing about them. // should know nothing about them.
type EventStream interface { type EventStream interface {
// Send delivers an event to observers. Given context has to be // Send delivers an event to observers. Given context has to be respected. If
// respected. If the context is closed, all blocking operations should // the context is closed, all blocking operations should be released ASAP.
// be released ASAP.
// //
// It is possible that context is closed but the message is delivered. // It is possible that context is closed but the message is delivered.
// EventStream implementations should solve this issue somehow. // EventStream implementations should solve this issue somehow.
@@ -208,27 +217,26 @@ type EventStream interface {
// Logger defines an interface of the logger used by mtglib. // Logger defines an interface of the logger used by mtglib.
// //
// Each logger has a name. It is possible to stack names to organize // Each logger has a name. It is possible to stack names to organize poor-man
// poor-man namespaces. Also, each logger must be able to bind // namespaces. Also, each logger must be able to bind parameters to avoid
// parameters to avoid pushing them all the time. // pushing them all the time.
// //
// Example // Example
// //
// logger := SomeLogger{} // logger := SomeLogger{} logger = logger.BindStr("ip", net.IP{127, 0, 0, 1})
// logger = logger.BindStr("ip", net.IP{127, 0, 0, 1}) // logger.Info("Hello")
// logger.Info("Hello")
// //
// In that case, ip is bound as a parameter. It is a great idea to // In that case, ip is bound as a parameter. It is a great idea to put this
// put this parameter somewhere in a log message. // parameter somewhere in a log message.
// //
// logger1 = logger.BindStr("param1", "11") // logger1 = logger.BindStr("param1", "11") logger2 = logger.BindInt("param2",
// logger2 = logger.BindInt("param2", 11) // 11)
// //
// logger1 should see no param2 and vice versa, logger2 should not see param1 // logger1 should see no param2 and vice versa, logger2 should not see param1
// If you attach a parameter to a logger, parents should not know about that. // If you attach a parameter to a logger, parents should not know about that.
type Logger interface { type Logger interface {
// Named returns a new logger with a bound name. Name chaining is // Named returns a new logger with a bound name. Name chaining is allowed and
// allowed and appreciated. // appreciated.
Named(name string) Logger Named(name string) Logger
// BindInt binds new integer parameter to a new logger instance. // BindInt binds new integer parameter to a new logger instance.
@@ -237,28 +245,30 @@ type Logger interface {
// BindStr binds new string parameter to a new logger instance. // BindStr binds new string parameter to a new logger instance.
BindStr(name, value string) Logger BindStr(name, value string) Logger
// BindJSON binds a new JSON-encoded string to a new logger instance.
BindJSON(name, value string) Logger
// Printf is to support log.Logger behavior. // Printf is to support log.Logger behavior.
Printf(format string, args ...interface{}) Printf(format string, args ...any)
// Info puts a message about some normal situation. // Info puts a message about some normal situation.
Info(msg string) Info(msg string)
// InfoError puts a message about some normal situation but this // InfoError puts a message about some normal situation but this situation is
// situation is related to a given error. // related to a given error.
InfoError(msg string, err error) InfoError(msg string, err error)
// Warning puts a message about some extraordinary situation // Warning puts a message about some extraordinary situation worth to look at.
// worth to look at.
Warning(msg string) Warning(msg string)
// WarningError puts a message about some extraordinary situation // WarningError puts a message about some extraordinary situation worth to
// worth to look at. This situation is related to a given error. // look at. This situation is related to a given error.
WarningError(msg string, err error) WarningError(msg string, err error)
// Debug puts a message useful for debugging only. // Debug puts a message useful for debugging only.
Debug(msg string) Debug(msg string)
// Debug puts a message useful for debugging only. This message is // Debug puts a message useful for debugging only. This message is related to
// related to a given error. // a given error.
DebugError(msg string, err error) DebugError(msg string, err error)
} }
+11 -10
View File
@@ -8,16 +8,17 @@ import (
type NoopLogger struct{} type NoopLogger struct{}
func (n NoopLogger) Named(_ string) Logger { return n } func (n NoopLogger) Named(_ string) Logger { return n }
func (n NoopLogger) BindInt(_ string, _ int) Logger { return n } func (n NoopLogger) BindInt(_ string, _ int) Logger { return n }
func (n NoopLogger) BindStr(_, _ string) Logger { return n } func (n NoopLogger) BindStr(_, _ string) Logger { return n }
func (n NoopLogger) Printf(_ string, _ ...interface{}) {} func (n NoopLogger) BindJSON(_, _ string) Logger { return n }
func (n NoopLogger) Info(_ string) {} func (n NoopLogger) Printf(_ string, _ ...any) {}
func (n NoopLogger) Warning(_ string) {} func (n NoopLogger) Info(_ string) {}
func (n NoopLogger) Debug(_ string) {} func (n NoopLogger) Warning(_ string) {}
func (n NoopLogger) InfoError(_ string, _ error) {} func (n NoopLogger) Debug(_ string) {}
func (n NoopLogger) WarningError(_ string, _ error) {} func (n NoopLogger) InfoError(_ string, _ error) {}
func (n NoopLogger) DebugError(_ string, _ error) {} func (n NoopLogger) WarningError(_ string, _ error) {}
func (n NoopLogger) DebugError(_ string, _ error) {}
type EventStreamMock struct { type EventStreamMock struct {
mock.Mock mock.Mock
+17
View File
@@ -0,0 +1,17 @@
package dc
import (
"fmt"
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscation"
)
type Addr struct {
Network string
Address string
Obfuscator obfuscation.Obfuscator
}
func (d Addr) String() string {
return fmt.Sprintf("addr=%s, secret=%v", d.Address, d.Obfuscator.Secret)
}
+33
View File
@@ -0,0 +1,33 @@
package dc
import "math/rand/v2"
type dcAddrSet struct {
v4 map[int][]Addr
v6 map[int][]Addr
}
func (d dcAddrSet) getV4(dc int) []Addr {
if d.v4 == nil {
return nil
}
return d.get(d.v4[dc])
}
func (d dcAddrSet) getV6(dc int) []Addr {
if d.v6 == nil {
return nil
}
return d.get(d.v6[dc])
}
func (d dcAddrSet) get(addrs []Addr) []Addr {
otherSet := make([]Addr, 0, len(addrs))
otherSet = append(otherSet, addrs...)
rand.Shuffle(len(otherSet), func(i, j int) {
otherSet[i], otherSet[j] = otherSet[j], otherSet[i]
})
return otherSet
}
+78
View File
@@ -0,0 +1,78 @@
package dc
import (
"context"
"time"
)
type preferIP uint8
const (
preferIPOnlyIPv4 preferIP = iota
preferIPOnlyIPv6
preferIPPreferIPv4
preferIPPreferIPv6
)
const (
// Default DC to connect to if not sure.
DefaultDC = 2
// How often should we request updates from
// https://core.telegram.org/getProxyConfig
PublicConfigUpdateEach = time.Hour
PublicConfigUpdateURLv4 = "https://core.telegram.org/getProxyConfig"
PublicConfigUpdateURLv6 = "https://core.telegram.org/getProxyConfigV6"
// How often should we extract hosts from Telegram using help.getConfig
// method.
OwnConfigUpdateEach = time.Hour
)
type Logger interface {
Info(msg string)
WarningError(msg string, err error)
}
type Updater interface {
Run(ctx context.Context)
}
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp#L30
var defaultDCAddrSet = dcAddrSet{
v4: map[int][]Addr{
1: {
{Network: "tcp4", Address: "149.154.175.50:443"},
},
2: {
{Network: "tcp4", Address: "149.154.167.51:443"},
{Network: "tcp4", Address: "95.161.76.100:443"},
},
3: {
{Network: "tcp4", Address: "149.154.175.100:443"},
},
4: {
{Network: "tcp4", Address: "149.154.167.91:443"},
},
5: {
{Network: "tcp4", Address: "149.154.171.5:443"},
},
},
v6: map[int][]Addr{
1: {
{Network: "tcp6", Address: "[2001:b28:f23d:f001::a]:443"},
},
2: {
{Network: "tcp6", Address: "[2001:67c:04e8:f002::a]:443"},
},
3: {
{Network: "tcp6", Address: "[2001:b28:f23d:f003::a]:443"},
},
4: {
{Network: "tcp6", Address: "[2001:67c:04e8:f004::a]:443"},
},
5: {
{Network: "tcp6", Address: "[2001:b28:f23f:f005::a]:443"},
},
},
}
+43
View File
@@ -0,0 +1,43 @@
package dc
import (
"context"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)
type LoggerMock struct {
mock.Mock
}
func (m *LoggerMock) Info(msg string) {
m.Called(msg)
}
func (m *LoggerMock) WarningError(msg string, err error) {
m.Called(msg, err)
}
type UpdaterTestSuiteBase struct {
suite.Suite
ctx context.Context
ctxCancel context.CancelFunc
loggerMock *LoggerMock
}
func (s *UpdaterTestSuiteBase) SetupTest() {
ctx, cancel := context.WithCancel(context.Background())
s.loggerMock = &LoggerMock{}
s.loggerMock.On("Info", mock.AnythingOfType("string"))
s.loggerMock.On("WarningError", mock.AnythingOfType("string"), mock.Anything)
s.ctx = ctx
s.ctxCancel = cancel
}
func (s *UpdaterTestSuiteBase) TearDownTest() {
s.ctxCancel()
}
@@ -0,0 +1,93 @@
package dc
import (
"bufio"
"context"
"fmt"
"io"
"net/http"
"regexp"
"strconv"
)
var publicConfigRe = regexp.MustCompile(`^\s*proxy_for\s+(\d+)\s+(\S+?)?;\s*$`)
type PublicConfigUpdater struct {
updater
http *http.Client
tg *Telegram
}
func (p *PublicConfigUpdater) Run(ctx context.Context, url, network string) {
p.run(ctx, func() error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
panic(err)
}
resp, err := p.http.Do(req)
if err != nil {
if resp != nil {
io.Copy(io.Discard, resp.Body) //nolint: errcheck
resp.Body.Close() //nolint: errcheck
}
return fmt.Errorf("cannot fetch url %s: %w", url, err)
}
if resp.StatusCode >= http.StatusBadRequest {
return fmt.Errorf("unexpected status code from %s: %d", url, resp.StatusCode)
}
scanner := bufio.NewScanner(resp.Body)
addrs := map[int][]Addr{}
for scanner.Scan() {
matches := publicConfigRe.FindStringSubmatch(scanner.Text())
if len(matches) != 3 {
continue
}
dc, err := strconv.Atoi(matches[1])
if err != nil {
continue
}
switch dc {
// this is a list of DC we currently support. Other are ignored.
case 203: // CDN DC
p.logger.Info(fmt.Sprintf("found %s address for DC %d", matches[2], dc))
addrs[dc] = append(addrs[dc], Addr{
Network: network,
Address: matches[2],
})
}
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("cannot read response body from %s: %w", url, err)
}
p.tg.lock.Lock()
defer p.tg.lock.Unlock()
if network == "tcp4" {
p.tg.view.publicConfigs.v4 = addrs
} else {
p.tg.view.publicConfigs.v6 = addrs
}
return nil
})
}
func NewPublicConfigUpdater(tg *Telegram, logger Logger, client *http.Client) *PublicConfigUpdater {
return &PublicConfigUpdater{
updater: updater{
logger: logger,
period: PublicConfigUpdateEach,
},
http: client,
tg: tg,
}
}
@@ -0,0 +1,113 @@
package dc
import (
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type PublicConfigUpdaterTestSuite struct {
UpdaterTestSuiteBase
u *PublicConfigUpdater
lock sync.Mutex
srv *httptest.Server
responseHandler func(w http.ResponseWriter)
}
func (s *PublicConfigUpdaterTestSuite) SetupSuite() {
s.srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s.lock.Lock()
s.responseHandler(w)
s.lock.Unlock()
}))
}
func (s *PublicConfigUpdaterTestSuite) TearDownSuite() {
s.srv.Close()
}
func (s *PublicConfigUpdaterTestSuite) SetupTest() {
s.UpdaterTestSuiteBase.SetupTest()
tg, err := New("prefer-ipv4")
require.NoError(s.T(), err)
s.u = NewPublicConfigUpdater(tg, s.loggerMock, s.srv.Client())
}
func (s *PublicConfigUpdaterTestSuite) Test502StatusCode() {
s.responseHandler = func(w http.ResponseWriter) {
w.WriteHeader(http.StatusBadGateway)
}
s.u.Run(s.ctx, s.srv.URL, "tcp4")
time.Sleep(100 * time.Millisecond)
s.ctxCancel()
s.u.Wait()
s.Len(s.u.tg.view.publicConfigs.v4, 0)
}
func (s *PublicConfigUpdaterTestSuite) TestEmptyFile() {
s.responseHandler = func(w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
}
s.u.Run(s.ctx, s.srv.URL, "tcp4")
time.Sleep(100 * time.Millisecond)
s.ctxCancel()
s.u.Wait()
s.Len(s.u.tg.view.publicConfigs.v4, 0)
}
func (s *PublicConfigUpdaterTestSuite) TestGarbage() {
result := `
proxy_for -1 -1;
proxy_for 100 100.10.0.0:3333;
lala 0 0
`
s.responseHandler = func(w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(result)) //nolint: errcheck
}
s.u.Run(s.ctx, s.srv.URL, "tcp4")
time.Sleep(100 * time.Millisecond)
s.ctxCancel()
s.u.Wait()
s.Len(s.u.tg.view.publicConfigs.v4, 0)
}
func (s *PublicConfigUpdaterTestSuite) TestOk() {
result := `
proxy_for 203 100.10.0.0:3333;
proxy_for -100 101.10.0.0:3333;
`
s.responseHandler = func(w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(result)) //nolint: errcheck
}
s.u.Run(s.ctx, s.srv.URL, "tcp4")
time.Sleep(100 * time.Millisecond)
s.ctxCancel()
s.u.Wait()
s.Len(s.u.tg.view.publicConfigs.v4, 1)
s.Len(s.u.tg.view.publicConfigs.v4[203], 1)
s.Equal("100.10.0.0:3333", s.u.tg.view.publicConfigs.v4[203][0].Address)
}
func TestPublicConfigUpdater(t *testing.T) {
suite.Run(t, &PublicConfigUpdaterTestSuite{})
}
+50
View File
@@ -0,0 +1,50 @@
package dc
import (
"fmt"
"strings"
"sync"
)
type Telegram struct {
lock sync.RWMutex
view dcView
preferIP preferIP
}
func (t *Telegram) GetAddresses(dc int) []Addr {
t.lock.RLock()
defer t.lock.RUnlock()
switch t.preferIP {
case preferIPOnlyIPv4:
return t.view.getV4(dc)
case preferIPOnlyIPv6:
return t.view.getV4(dc)
case preferIPPreferIPv4:
return append(t.view.getV4(dc), t.view.getV6(dc)...)
}
return append(t.view.getV6(dc), t.view.getV4(dc)...)
}
func New(ipPreference string) (*Telegram, error) {
var pref preferIP
switch strings.ToLower(ipPreference) {
case "prefer-ipv4":
pref = preferIPPreferIPv4
case "prefer-ipv6":
pref = preferIPPreferIPv6
case "only-ipv4":
pref = preferIPOnlyIPv4
case "only-ipv6":
pref = preferIPOnlyIPv6
default:
return nil, fmt.Errorf("unknown ip preference %s", ipPreference)
}
return &Telegram{
preferIP: pref,
}, nil
}
+47
View File
@@ -0,0 +1,47 @@
package dc
import (
"context"
"sync"
"time"
)
type updater struct {
wg sync.WaitGroup
logger Logger
period time.Duration
}
func (u *updater) Wait() {
u.wg.Wait()
}
func (u *updater) run(ctx context.Context, callback func() error) {
u.wg.Go(func() {
ticker := time.NewTicker(u.period)
defer func() {
ticker.Stop()
select {
case <-ticker.C:
default:
}
}()
for {
u.logger.Info("start update")
if err := callback(); err != nil {
u.logger.WarningError("cannot update: %w", err)
}
u.logger.Info("updated")
select {
case <-ctx.Done():
u.logger.Info("stop updating")
return
case <-ticker.C:
}
}
})
}
+55
View File
@@ -0,0 +1,55 @@
package dc
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/suite"
)
type UpdaterTestSuite struct {
UpdaterTestSuiteBase
u updater
}
func (s *UpdaterTestSuite) SetupTest() {
s.UpdaterTestSuiteBase.SetupTest()
s.u = updater{
logger: s.loggerMock,
period: 100 * time.Millisecond,
}
}
func (s *UpdaterTestSuite) TestPeriodicUpdates() {
ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
lock := &sync.Mutex{}
collected := []time.Time{}
go s.u.run(s.ctx, func() error {
select {
case <-s.ctx.Done():
case value := <-ticker.C:
lock.Lock()
collected = append(collected, value)
lock.Unlock()
}
return nil
})
s.Eventually(func() bool {
lock.Lock()
defer lock.Unlock()
return len(collected) == 3
}, time.Second, 10*time.Millisecond)
}
func TestUpdater(t *testing.T) {
t.Parallel()
suite.Run(t, &UpdaterTestSuite{})
}
+19
View File
@@ -0,0 +1,19 @@
package dc
type dcView struct {
publicConfigs dcAddrSet
}
func (d dcView) getV4(dc int) []Addr {
addrs := d.publicConfigs.getV4(dc)
addrs = append(addrs, defaultDCAddrSet.getV4(dc)...)
return addrs
}
func (d dcView) getV6(dc int) []Addr {
addrs := d.publicConfigs.getV6(dc)
addrs = append(addrs, defaultDCAddrSet.getV6(dc)...)
return addrs
}
+79
View File
@@ -0,0 +1,79 @@
package dc
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type ViewTestSuite struct {
suite.Suite
view dcView
}
func (suite *ViewTestSuite) SetupSuite() {
suite.view = dcView{
publicConfigs: dcAddrSet{
v4: map[int][]Addr{
111: {
{Network: "tcp4", Address: "127.0.0.1:443"},
},
203: {
{Network: "tcp4", Address: "127.0.0.2:443"},
},
},
v6: map[int][]Addr{
203: {
{Network: "tcp6", Address: "xxx"},
},
},
},
}
}
func (suite *ViewTestSuite) TestGetV4() {
testData := map[int][]Addr{
111: {
{Network: "tcp4", Address: "127.0.0.1:443"},
},
203: {
{Network: "tcp4", Address: "127.0.0.2:443"},
},
2: {
{Network: "tcp4", Address: "149.154.167.51:443"},
{Network: "tcp4", Address: "95.161.76.100:443"},
},
}
for dc, addresses := range testData {
suite.T().Run(fmt.Sprintf("dc%d", dc), func(t *testing.T) {
assert.ElementsMatch(t, addresses, suite.view.getV4(dc))
})
}
}
func (suite *ViewTestSuite) TestGetV6() {
testData := map[int][]Addr{
111: {},
203: {
{Network: "tcp6", Address: "xxx"},
},
1: {
{Network: "tcp6", Address: "[2001:b28:f23d:f001::a]:443"},
},
}
for dc, addresses := range testData {
suite.T().Run(fmt.Sprintf("dc%d", dc), func(t *testing.T) {
assert.ElementsMatch(t, addresses, suite.view.getV6(dc))
})
}
}
func TestView(t *testing.T) {
t.Parallel()
suite.Run(t, &ViewTestSuite{})
}
+4 -4
View File
@@ -56,7 +56,7 @@ func ParseClientHello(secret, handshake []byte) (ClientHello, error) {
if len(handshake)-4 != int(handshakeLength) { if len(handshake)-4 != int(handshakeLength) {
return hello, return hello,
fmt.Errorf("incorrect handshake size. manifested=%d, real=%d", fmt.Errorf("incorrect handshake size. manifested=%d, real=%d",
handshakeLength, len(handshake)-4) // nolint: gomnd handshakeLength, len(handshake)-4)
} }
copy(hello.Random[:], handshake[ClientHelloRandomOffset:]) copy(hello.Random[:], handshake[ClientHelloRandomOffset:])
@@ -72,11 +72,11 @@ func ParseClientHello(secret, handshake []byte) (ClientHello, error) {
// mac is calculated for the whole record, not only // mac is calculated for the whole record, not only
// for the payload part // for the payload part
mac := hmac.New(sha256.New, secret) mac := hmac.New(sha256.New, secret)
rec.Dump(mac) // nolint: errcheck rec.Dump(mac) //nolint: errcheck
computedRandom := mac.Sum(nil) computedRandom := mac.Sum(nil)
for i := 0; i < RandomLen; i++ { for i := range RandomLen {
computedRandom[i] ^= hello.Random[i] computedRandom[i] ^= hello.Random[i]
} }
@@ -100,7 +100,7 @@ func parseSessionID(hello *ClientHello, handshake []byte) {
} }
func parseCipherSuite(hello *ClientHello, handshake []byte) { func parseCipherSuite(hello *ClientHello, handshake []byte) {
cipherSuiteOffset := ClientHelloSessionIDOffset + len(hello.SessionID) + 3 // nolint: gomnd cipherSuiteOffset := ClientHelloSessionIDOffset + len(hello.SessionID) + 3
hello.CipherSuite = binary.BigEndian.Uint16(handshake[cipherSuiteOffset : cipherSuiteOffset+2]) hello.CipherSuite = binary.BigEndian.Uint16(handshake[cipherSuiteOffset : cipherSuiteOffset+2])
} }
@@ -0,0 +1,21 @@
package faketls_test
import (
"testing"
"github.com/9seconds/mtg/v2/mtglib/internal/faketls"
"github.com/stretchr/testify/require"
)
var FuzzClientHelloSecret = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
func FuzzClientHello(f *testing.F) {
f.Add([]byte{1, 2, 3})
f.Fuzz(func(t *testing.T, frame []byte) {
_, err := faketls.ParseClientHello(FuzzClientHelloSecret, frame)
// a probability of having != err is almost negligible
require.Error(t, err)
})
}
+14 -11
View File
@@ -3,14 +3,14 @@ package faketls
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"math/rand" "math/rand/v2"
"net"
"github.com/9seconds/mtg/v2/essentials"
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record" "github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
) )
type Conn struct { type Conn struct {
net.Conn essentials.Conn
readBuffer bytes.Buffer readBuffer bytes.Buffer
} }
@@ -25,14 +25,14 @@ func (c *Conn) Read(p []byte) (int, error) {
for { for {
if err := rec.Read(c.Conn); err != nil { if err := rec.Read(c.Conn); err != nil {
return 0, err // nolint: wrapcheck return 0, err //nolint: wrapcheck
} }
switch rec.Type { // nolint: exhaustive switch rec.Type { //nolint: exhaustive
case record.TypeApplicationData: case record.TypeApplicationData:
rec.Payload.WriteTo(&c.readBuffer) // nolint: errcheck rec.Payload.WriteTo(&c.readBuffer) //nolint: errcheck
return c.readBuffer.Read(p) // nolint: wrapcheck return c.readBuffer.Read(p) //nolint: wrapcheck
case record.TypeChangeCipherSpec: case record.TypeChangeCipherSpec:
default: default:
return 0, fmt.Errorf("unsupported record type %v", rec.Type) return 0, fmt.Errorf("unsupported record type %v", rec.Type)
@@ -46,10 +46,11 @@ func (c *Conn) Write(p []byte) (int, error) {
rec.Type = record.TypeApplicationData rec.Type = record.TypeApplicationData
rec.Version = record.Version12 rec.Version = record.Version12
written := 0 written := 0
for len(p) > 0 { for len(p) > 0 {
chunkSize := rand.Intn(record.TLSMaxRecordSize) chunkSize := rand.IntN(record.TLSMaxRecordSize)
if chunkSize > len(p) || chunkSize == 0 { if chunkSize > len(p) || chunkSize == 0 {
chunkSize = len(p) chunkSize = len(p)
} }
@@ -57,11 +58,13 @@ func (c *Conn) Write(p []byte) (int, error) {
rec.Payload.Reset() rec.Payload.Reset()
rec.Payload.Write(p[:chunkSize]) rec.Payload.Write(p[:chunkSize])
if err := rec.Dump(c.Conn); err != nil { err := rec.Dump(c.Conn)
return written, err // nolint: wrapcheck written += chunkSize
if err != nil {
return written, err
} }
written += chunkSize
p = p[chunkSize:] p = p[chunkSize:]
} }
+10 -10
View File
@@ -2,9 +2,9 @@ package faketls_test
import ( import (
"bytes" "bytes"
"crypto/rand"
"errors" "errors"
"io" "io"
"math/rand"
"testing" "testing"
"github.com/9seconds/mtg/v2/internal/testlib" "github.com/9seconds/mtg/v2/internal/testlib"
@@ -15,7 +15,7 @@ import (
) )
type ConnMock struct { type ConnMock struct {
testlib.NetConnMock testlib.EssentialsConnMock
readBuffer bytes.Buffer readBuffer bytes.Buffer
writeBuffer bytes.Buffer writeBuffer bytes.Buffer
@@ -24,13 +24,13 @@ type ConnMock struct {
func (m *ConnMock) Read(p []byte) (int, error) { func (m *ConnMock) Read(p []byte) (int, error) {
m.Called(p) m.Called(p)
return m.readBuffer.Read(p) // nolint: wrapcheck return m.readBuffer.Read(p) //nolint: wrapcheck
} }
func (m *ConnMock) Write(p []byte) (int, error) { func (m *ConnMock) Write(p []byte) (int, error) {
m.Called(p) m.Called(p)
return m.writeBuffer.Write(p) // nolint: wrapcheck return m.writeBuffer.Write(p) //nolint: wrapcheck
} }
type ConnTestSuite struct { type ConnTestSuite struct {
@@ -61,14 +61,14 @@ func (suite *ConnTestSuite) TestRead() {
rec.Version = record.Version12 rec.Version = record.Version12
rec.Payload.WriteByte(0x01) rec.Payload.WriteByte(0x01)
rec.Dump(&suite.connMock.readBuffer) // nolint: errcheck rec.Dump(&suite.connMock.readBuffer) //nolint: errcheck
rec.Reset() rec.Reset()
rec.Type = record.TypeApplicationData rec.Type = record.TypeApplicationData
rec.Version = record.Version12 rec.Version = record.Version12
rec.Payload.Write([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) rec.Payload.Write([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
rec.Dump(&suite.connMock.readBuffer) // nolint: errcheck rec.Dump(&suite.connMock.readBuffer) //nolint: errcheck
resultBuffer := &bytes.Buffer{} resultBuffer := &bytes.Buffer{}
buf := make([]byte, 2) buf := make([]byte, 2)
@@ -95,14 +95,14 @@ func (suite *ConnTestSuite) TestReadUnexpected() {
rec.Version = record.Version12 rec.Version = record.Version12
rec.Payload.WriteByte(0x01) rec.Payload.WriteByte(0x01)
rec.Dump(&suite.connMock.readBuffer) // nolint: errcheck rec.Dump(&suite.connMock.readBuffer) //nolint: errcheck
rec.Reset() rec.Reset()
rec.Type = record.TypeHandshake rec.Type = record.TypeHandshake
rec.Version = record.Version12 rec.Version = record.Version12
rec.Payload.Write([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) rec.Payload.Write([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
rec.Dump(&suite.connMock.readBuffer) // nolint: errcheck rec.Dump(&suite.connMock.readBuffer) //nolint: errcheck
buf := make([]byte, 2) buf := make([]byte, 2)
@@ -123,7 +123,7 @@ func (suite *ConnTestSuite) TestWrite() {
suite.connMock.On("Write", mock.Anything).Return(0, nil) suite.connMock.On("Write", mock.Anything).Return(0, nil)
dataToRec := make([]byte, record.TLSMaxRecordSize*2) dataToRec := make([]byte, record.TLSMaxRecordSize*2)
rand.Read(dataToRec) rand.Read(dataToRec) //nolint: staticcheck, errcheck
n, err := suite.c.Write(dataToRec) n, err := suite.c.Write(dataToRec)
suite.NoError(err) suite.NoError(err)
@@ -141,7 +141,7 @@ func (suite *ConnTestSuite) TestWrite() {
suite.Equal(record.TypeApplicationData, rec.Type) suite.Equal(record.TypeApplicationData, rec.Type)
suite.Equal(record.Version12, rec.Version) suite.Equal(record.Version12, rec.Version)
rec.Payload.WriteTo(buf) // nolint: errcheck rec.Payload.WriteTo(buf) //nolint: errcheck
} }
suite.Equal(dataToRec, buf.Bytes()) suite.Equal(dataToRec, buf.Bytes())
+1 -1
View File
@@ -19,7 +19,7 @@ const (
// ClientHelloMinLen is a minimal possible length of // ClientHelloMinLen is a minimal possible length of
// ClientHello record. // ClientHello record.
ClientHelloMinLen = 4 ClientHelloMinLen = 6
// WelcomePacketRandomOffset is an offset of random in ServerHello // WelcomePacketRandomOffset is an offset of random in ServerHello
// packet (including record envelope). // packet (including record envelope).
-21
View File
@@ -1,21 +0,0 @@
package faketls
import (
"bytes"
"sync"
)
var bytesBufferPool = sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
}
func acquireBytesBuffer() *bytes.Buffer {
return bytesBufferPool.Get().(*bytes.Buffer)
}
func releaseBytesBuffer(b *bytes.Buffer) {
b.Reset()
bytesBufferPool.Put(b)
}
+2 -2
View File
@@ -5,13 +5,13 @@ import (
) )
var recordPool = sync.Pool{ var recordPool = sync.Pool{
New: func() interface{} { New: func() any {
return &Record{} return &Record{}
}, },
} }
func AcquireRecord() *Record { func AcquireRecord() *Record {
return recordPool.Get().(*Record) return recordPool.Get().(*Record) //nolint: forcetypeassert
} }
func ReleaseRecord(r *Record) { func ReleaseRecord(r *Record) {
+11 -12
View File
@@ -1,20 +1,20 @@
package faketls package faketls
import ( import (
"bytes"
"crypto/hmac" "crypto/hmac"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"io" "io"
mrand "math/rand" mrand "math/rand/v2"
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record" "github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
"golang.org/x/crypto/curve25519" "golang.org/x/crypto/curve25519"
) )
func SendWelcomePacket(writer io.Writer, secret []byte, clientHello ClientHello) error { func SendWelcomePacket(writer io.Writer, secret []byte, clientHello ClientHello) error {
buf := acquireBytesBuffer() buf := &bytes.Buffer{}
defer releaseBytesBuffer(buf)
rec := record.AcquireRecord() rec := record.AcquireRecord()
defer record.ReleaseRecord(rec) defer record.ReleaseRecord(rec)
@@ -23,24 +23,24 @@ func SendWelcomePacket(writer io.Writer, secret []byte, clientHello ClientHello)
rec.Version = record.Version12 rec.Version = record.Version12
generateServerHello(&rec.Payload, clientHello) generateServerHello(&rec.Payload, clientHello)
rec.Dump(buf) // nolint: errcheck rec.Dump(buf) //nolint: errcheck
rec.Reset() rec.Reset()
rec.Type = record.TypeChangeCipherSpec rec.Type = record.TypeChangeCipherSpec
rec.Version = record.Version12 rec.Version = record.Version12
rec.Payload.WriteByte(ChangeCipherValue) rec.Payload.WriteByte(ChangeCipherValue)
rec.Dump(buf) // nolint: errcheck rec.Dump(buf) //nolint: errcheck
rec.Reset() rec.Reset()
rec.Type = record.TypeApplicationData rec.Type = record.TypeApplicationData
rec.Version = record.Version12 rec.Version = record.Version12
if _, err := io.CopyN(&rec.Payload, rand.Reader, int64(1024+mrand.Intn(3092))); err != nil { // nolint: gomnd if _, err := io.CopyN(&rec.Payload, rand.Reader, int64(1024+mrand.IntN(3092))); err != nil {
panic(err) panic(err)
} }
rec.Dump(buf) // nolint: errcheck rec.Dump(buf) //nolint: errcheck
packet := buf.Bytes() packet := buf.Bytes()
mac := hmac.New(sha256.New, secret) mac := hmac.New(sha256.New, secret)
@@ -51,15 +51,14 @@ func SendWelcomePacket(writer io.Writer, secret []byte, clientHello ClientHello)
copy(packet[WelcomePacketRandomOffset:], mac.Sum(nil)) copy(packet[WelcomePacketRandomOffset:], mac.Sum(nil))
if _, err := writer.Write(packet); err != nil { if _, err := writer.Write(packet); err != nil {
return err // nolint: wrapcheck return err //nolint: wrapcheck
} }
return nil return nil
} }
func generateServerHello(writer io.Writer, clientHello ClientHello) { func generateServerHello(writer io.Writer, clientHello ClientHello) {
bodyBuf := acquireBytesBuffer() bodyBuf := &bytes.Buffer{}
defer releaseBytesBuffer(bodyBuf)
sliceBuf := [2]byte{} sliceBuf := [2]byte{}
digest := [RandomLen]byte{} digest := [RandomLen]byte{}
@@ -87,6 +86,6 @@ func generateServerHello(writer io.Writer, clientHello ClientHello) {
binary.BigEndian.PutUint32(header[:], uint32(bodyBuf.Len())) binary.BigEndian.PutUint32(header[:], uint32(bodyBuf.Len()))
header[0] = HandshakeTypeServer header[0] = HandshakeTypeServer
writer.Write(header[:]) // nolint: errcheck writer.Write(header[:]) //nolint: errcheck
bodyBuf.WriteTo(writer) // nolint: errcheck bodyBuf.WriteTo(writer) //nolint: errcheck
} }
+3 -3
View File
@@ -3,8 +3,8 @@ package faketls_test
import ( import (
"bytes" "bytes"
"crypto/hmac" "crypto/hmac"
"crypto/rand"
"crypto/sha256" "crypto/sha256"
"math/rand"
"testing" "testing"
"time" "time"
@@ -30,10 +30,10 @@ func (suite *WelcomeTestSuite) SetupTest() {
SessionID: make([]byte, 32), SessionID: make([]byte, 32),
} }
_, err := rand.Read(suite.h.SessionID) _, err := rand.Read(suite.h.SessionID) //nolint: staticcheck
suite.NoError(err) suite.NoError(err)
_, err = rand.Read(suite.h.Random[:]) _, err = rand.Read(suite.h.Random[:]) //nolint: staticcheck
suite.NoError(err) suite.NoError(err)
suite.buf = &bytes.Buffer{} suite.buf = &bytes.Buffer{}
@@ -1,54 +0,0 @@
package obfuscated2
import (
"crypto/cipher"
"crypto/subtle"
"encoding/hex"
"fmt"
"io"
)
type clientHandhakeFrame struct {
handshakeFrame
}
func (c *clientHandhakeFrame) decryptor(secret []byte) cipher.Stream {
hasher := acquireSha256Hasher()
defer releaseSha256Hasher(hasher)
hasher.Write(c.key())
hasher.Write(secret)
return makeAesCtr(hasher.Sum(nil), c.iv())
}
func (c *clientHandhakeFrame) encryptor(secret []byte) cipher.Stream {
invertedHandshake := c.invert()
hasher := acquireSha256Hasher()
defer releaseSha256Hasher(hasher)
hasher.Write(invertedHandshake.key())
hasher.Write(secret)
return makeAesCtr(hasher.Sum(nil), invertedHandshake.iv())
}
func ClientHandshake(secret []byte, reader io.Reader) (int, cipher.Stream, cipher.Stream, error) {
handshake := clientHandhakeFrame{}
if _, err := io.ReadFull(reader, handshake.data[:]); err != nil {
return 0, nil, nil, fmt.Errorf("cannot read frame: %w", err)
}
decryptor := handshake.decryptor(secret)
encryptor := handshake.encryptor(secret)
decryptor.XORKeyStream(handshake.data[:], handshake.data[:])
if val := handshake.connectionType(); subtle.ConstantTimeCompare(handshakeConnectionType, val) != 1 {
return 0, nil, nil, fmt.Errorf("unsupported connection type: %s", hex.EncodeToString(val))
}
return handshake.dc(), encryptor, decryptor, nil
}
@@ -1,89 +0,0 @@
package obfuscated2_test
import (
"bytes"
"testing"
"github.com/9seconds/mtg/v2/internal/testlib"
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)
type ClientHandshakeTestSuite struct {
suite.Suite
SnapshotTestSuite
}
func (suite *ClientHandshakeTestSuite) SetupSuite() {
suite.NoError(suite.IngestSnapshots(".", "client-handshake-snapshot-"))
}
func (suite *ClientHandshakeTestSuite) TestCannotRead() {
buf := bytes.NewBuffer([]byte{1, 2, 3})
_, _, _, err := obfuscated2.ClientHandshake([]byte{1, 2, 3}, buf) // nolint: dogsled
suite.Error(err)
}
func (suite *ClientHandshakeTestSuite) TestOk() {
for nameV, snapshotV := range suite.snapshots {
snapshot := snapshotV
suite.T().Run(nameV, func(t *testing.T) {
buf := bytes.NewBuffer(snapshot.Frame.data)
dc, encryptor, decryptor, err := obfuscated2.ClientHandshake(
snapshot.Secret.data, buf)
assert.NoError(t, err)
assert.EqualValues(t, snapshot.DC, dc)
writeData := make([]byte, len(snapshot.Encrypted.Text.data))
readData := make([]byte, len(snapshot.Decrypted.Text.data))
connMock := &testlib.NetConnMock{}
connMock.On("Read", mock.Anything).
Once().
Return(len(snapshot.Decrypted.Text.data), nil).
Run(func(args mock.Arguments) {
arr, ok := args.Get(0).([]byte)
suite.True(ok)
copy(arr, snapshot.Decrypted.Cipher.data)
})
connMock.On("Write", mock.Anything).
Once().
Return(len(snapshot.Encrypted.Text.data), nil).
Run(func(args mock.Arguments) {
arr, ok := args.Get(0).([]byte)
suite.True(ok)
copy(writeData, arr)
})
conn := obfuscated2.Conn{
Conn: connMock,
Encryptor: encryptor,
Decryptor: decryptor,
}
n, err := conn.Read(readData)
assert.Equal(t, len(readData), n)
assert.NoError(t, err)
assert.Equal(t, snapshot.Decrypted.Text.data, readData)
n, err = conn.Write(snapshot.Encrypted.Text.data)
assert.Equal(t, len(writeData), n)
assert.NoError(t, err)
assert.Equal(t, snapshot.Encrypted.Cipher.data, writeData)
connMock.AssertExpectations(t)
})
}
}
func TestClientHandshake(t *testing.T) {
t.Parallel()
suite.Run(t, &ClientHandshakeTestSuite{})
}
-36
View File
@@ -1,36 +0,0 @@
package obfuscated2
import (
"crypto/cipher"
"net"
)
type Conn struct {
net.Conn
Encryptor cipher.Stream
Decryptor cipher.Stream
}
func (c Conn) Read(p []byte) (int, error) {
n, err := c.Conn.Read(p)
if err != nil {
return n, err // nolint: wrapcheck
}
c.Decryptor.XORKeyStream(p, p[:n])
return n, nil
}
func (c Conn) Write(p []byte) (int, error) {
buf := acquireBytesBuffer()
defer releaseBytesBuffer(buf)
buf.Write(p)
payload := buf.Bytes()
c.Encryptor.XORKeyStream(payload, payload)
return c.Conn.Write(payload) // nolint: wrapcheck
}

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