REPOSITORY / ScuroNeko/mtg

Compare commits

DIFF REPOSITORY

Compare commits

..
394 Commits
Author SHA1 Message Date
9seconds 006fba1046 Merge remote-tracking branch 'origin/stable' into v2 2026-03-24 09:59:03 +01:00
9seconds 7b333ed833 Merge remote-tracking branch 'origin/master' into stable 2026-03-24 09:58:48 +01:00
9seconds 5adfee5dd4 Remove wrong binary 2026-03-24 09:58:13 +01:00
9seconds de89de2ad6 Merge remote-tracking branch 'origin/master' into stable 2026-03-24 09:57:43 +01:00
9seconds b0d37de0ec Update linter 2026-03-24 09:57:20 +01:00
9seconds 0cb25ba7ff Update go dependencies 2026-03-24 09:55:53 +01:00
9seconds 614acd7303 Mention doctor in README 2026-03-24 09:55:30 +01:00
Sergei ArkhipovandGitHub 4f5368aa2a Merge pull request #398 from 9seconds/docker-directory
Allow using directory bind mounts for a docker container
2026-03-24 09:00:59 +01:00
9seconds cfb5fe66be Allow using directory bind mounts for a docker container
This helps with a situation when some applications do not allow mounting
individual files, but whole directories. In that case users could mount
`/config` directory with a single file, `config.toml`: `-v
/path/to/dir:/config`. Also, there is a backward compatibility to using
a single `/config.toml`
2026-03-24 08:48:42 +01:00
Sergei ArkhipovandGitHub fb390d3417 Merge pull request #397 from 9seconds/doctor 2026-03-23 19:35:49 +01:00
9seconds f0ae4ce290 Validate domain fronting availability 2026-03-23 19:22:21 +01:00
9seconds b6b900e430 Refactoring 2026-03-23 19:12:14 +01:00
9seconds 8154f65e0e Add validation of telegram connectivity 2026-03-23 18:34:38 +01:00
9seconds a60523fed0 Add verification of time skewness 2026-03-23 15:28:48 +01:00
9seconds 63b147c287 Add doctor command for deprecated config values 2026-03-23 14:45:10 +01:00
Sergei ArkhipovandGitHub 21c0d18c7c Merge pull request #395 from roman901/master 2026-03-21 23:31:24 +01:00
Roman Shishkin 8f0bf47d56 Add Config.GetConcurrency with default fallback 2026-03-21 21:53:38 +03:00
9seconds 7fec30908a Merge remote-tracking branch 'origin/stable' into v2 2026-03-20 11:29:39 +01:00
9seconds 2eb0828f72 Merge remote-tracking branch 'origin/master' into stable 2026-03-20 11:29:24 +01:00
Sergei ArkhipovandGitHub d01e089f54 Merge pull request #386 from 9seconds/architectures
Add more architectures for mtg
2026-03-20 11:22:58 +01:00
Sergei ArkhipovandGitHub c736881792 Merge pull request #388 from 9seconds/doc-limits
Document a necessety of increasing limits for systemd unit
2026-03-20 11:16:06 +01:00
9seconds d5a118f125 Remove explicit pgo 2026-03-20 11:15:01 +01:00
9seconds d79a8f8406 Fix failed builds 2026-03-20 11:14:25 +01:00
9seconds 97932758d1 Add mips support 2026-03-20 11:14:25 +01:00
9seconds 1f7d1c0eea Add windows builds 2026-03-20 11:14:25 +01:00
9seconds 8c73dde928 Add build for AMD64v3 2026-03-20 11:14:25 +01:00
9seconds ded3fe26b9 Build for ARMv9 2026-03-20 11:14:25 +01:00
Sergei ArkhipovandGitHub 2f00adfe91 Merge pull request #385 from 9seconds/pgo
Add PGO
2026-03-20 11:14:00 +01:00
9seconds 049bee3d84 Document a necessety of increasing limits for systemd unit
It seems that default DynamicUser limits are very low. We have to
increase them anyway.
2026-03-20 11:13:03 +01:00
9seconds 4fbabfda2a Add PGO 2026-03-20 10:54:30 +01:00
9seconds fc72de9e39 Merge remote-tracking branch 'origin/stable' into v2 2026-03-19 18:52:35 +01:00
9seconds cb627f2a66 Merge remote-tracking branch 'origin/master' into stable 2026-03-19 18:52:11 +01:00
Sergei ArkhipovandGitHub 9ba6df0d1c Merge pull request #383 from 9seconds/avoid-double-buffering
Avoid double buffering in TLS hot path
2026-03-19 17:46:36 +01:00
9seconds 4a8d099aca Remove unused buffer 2026-03-19 17:39:57 +01:00
9seconds feb57004e1 Fix reslicing 2026-03-19 17:39:48 +01:00
9seconds cb436efd87 Avoid double buffering in TLS hot path 2026-03-19 17:37:51 +01:00
Sergei ArkhipovandGitHub 24148ea95c Merge pull request #382 from 9seconds/write-cond
Optimize waiting time for TLS chunker
2026-03-19 15:51:11 +01:00
9seconds 724904f50d Wait in doppel.Conn if there is anything to write 2026-03-19 15:42:00 +01:00
9seconds a23ae05f3b Remove SyncWrite 2026-03-19 13:47:08 +01:00
Sergei ArkhipovandGitHub b153a55149 Merge pull request #379 from 9seconds/fix-telegram-ips
Show ip of telegram endpoints in event stream
2026-03-18 22:46:47 +01:00
9seconds 913a38d13a Show real IP of the telegram endpoint in event stream 2026-03-18 22:05:34 +01:00
9seconds dc81f7981c Merge remote-tracking branch 'origin/stable' into v2 2026-03-16 23:56:10 +01:00
9seconds 9d5fd989e5 Merge remote-tracking branch 'origin/master' into stable 2026-03-16 23:55:56 +01:00
Sergei ArkhipovandGitHub 81703233b0 Merge pull request #368 from 9seconds/flake-tests
Fix flaky test
2026-03-16 23:55:01 +01:00
9seconds eb7720b11e Fix flaky test 2026-03-16 23:44:06 +01:00
Sergei ArkhipovandGitHub df7ddc3d6a Merge pull request #367 from saleacy/patch-1
fix: ensure network.Dial and MakeHTTPClient use socks5 proxy
2026-03-16 23:38:43 +01:00
saleacyandGitHub 3bc1e415f9 fix: ensure network.Dial and MakeHTTPClient use socks5 proxy
The package `network/v2/proxy_network.go` does not wrap `network.Dial`
and `network.MakeHTTPClient`, which causes them to bypass the SOCKS5
proxy and initiate TCP connections directly from the local machine.
2026-03-17 01:35:18 +08:00
Sergei ArkhipovandGitHub 306fa19ad6 Merge pull request #366 from Maks-2012/patch-1
Fix preferIPOnlyIPv6
2026-03-16 15:31:09 +01:00
Maks-2012andGitHub 079252d810 Fix preferIPOnlyIPv6 2026-03-16 16:10:38 +03:00
9seconds d0502e7083 Merge remote-tracking branch 'origin/stable' into v2 2026-03-15 22:04:35 +01:00
9seconds 30aa9d3a44 Merge remote-tracking branch 'origin/master' into stable 2026-03-15 22:04:09 +01:00
9seconds d98d5be3a7 Update go dependencies 2026-03-15 22:01:04 +01:00
9seconds 94ac9d6ffa Update tooling 2026-03-15 22:00:08 +01:00
9seconds c036558df4 Update docs 2026-03-15 21:58:04 +01:00
Sergei ArkhipovandGitHub 4167dec5e1 Merge pull request #365 from 9seconds/best-practices
Add document about best practices
2026-03-15 21:52:16 +01:00
9seconds 57529904e3 Add document about best practices 2026-03-15 21:50:43 +01:00
Sergei ArkhipovandGitHub ed4e511560 Merge pull request #363 from 9seconds/relay-buffer
Decrease relay buffer size
2026-03-14 14:15:52 +01:00
9seconds bb90bcc127 Decrease relay buffer size
Even if it makes sense to have a huge buffers, we do artificial delays
now. In that case we could achieve the same results with a lower buffer.
If not, then we won't send a packet bigger that this value
2026-03-14 14:06:48 +01:00
9seconds 879ebd132d Formatting of the README 2026-03-14 09:44:07 +01:00
Sergei ArkhipovandGitHub 58e6dfd603 Merge pull request #361 from 9seconds/docs
Add docs about doppelganger
2026-03-14 09:43:19 +01:00
9seconds e675baa860 Add docs about doppelganger 2026-03-14 09:42:28 +01:00
Sergei ArkhipovandGitHub 4a17849763 Merge pull request #360 from 9seconds/fix-android
Have a noise of variable size
2026-03-13 17:11:46 +01:00
9seconds 7adfc0352e Have a noise of variable size 2026-03-13 17:08:44 +01:00
Sergei ArkhipovandGitHub a5d602b538 Merge pull request #359 from 9seconds/fix-android
Fix android ping
2026-03-13 16:46:18 +01:00
9seconds b35c212d65 Have a noise of variable size 2026-03-13 16:39:16 +01:00
9seconds 45b0964afd Fix android ping 2026-03-13 16:20:18 +01:00
Sergei ArkhipovandGitHub 6d8d2961e8 Merge pull request #358 from 9seconds/doppel-drs
Make DRS optional
2026-03-13 14:51:15 +01:00
9seconds 4138cc6494 Use SyncWrite call to send a packet to proxy 2026-03-13 14:44:56 +01:00
9seconds 33c0fa9bf7 Add SyncWrite method to doppel.Conn 2026-03-13 11:30:52 +01:00
9seconds 21d7522356 Make DRS optional 2026-03-13 11:04:01 +01:00
9seconds ea71fe81b2 Update stats defaults 2026-03-13 08:59:59 +01:00
Sergei ArkhipovandGitHub 0871c2d790 Merge pull request #357 from 9seconds/access-native-dialer
Access command should use native dialer instead of network one
2026-03-13 08:47:24 +01:00
9seconds 8df1629882 Access command should use native dialer instead of network one 2026-03-13 08:42:15 +01:00
9seconds ad945c77a9 Enable caching for govulncheck 2026-03-12 23:01:26 +01:00
Sergei ArkhipovandGitHub a051518def Merge pull request #356 from 9seconds/gh-cache
Speed up Github actions
2026-03-12 22:51:59 +01:00
9seconds 4dca1d2b07 Add cache mount for apk downloads 2026-03-12 22:46:19 +01:00
9seconds 287a794772 Always run tests with race detector 2026-03-12 22:22:09 +01:00
9seconds d4822989ec Add count number to covtest 2026-03-12 22:22:00 +01:00
Sergei ArkhipovandGitHub d6a17c502f Merge pull request #355 from 9seconds/scout-native
Use native dialer for scout
2026-03-12 22:14:52 +01:00
9seconds 3db1be0687 Use cache to speed up jobs 2026-03-12 22:13:17 +01:00
9seconds 991346621c Speedup docker builds by using cache 2026-03-12 22:13:00 +01:00
9seconds cc63e762e3 Use native dialer for scout 2026-03-12 21:47:03 +01:00
9seconds a85348d6be Use curl as user agent 2026-03-12 21:43:04 +01:00
Sergei ArkhipovandGitHub 7762cf3a55 Merge pull request #353 from 9seconds/domain-fronting-native
Use native dialer to communicate with fronting domain
2026-03-12 21:40:38 +01:00
9seconds 62cba24071 Use native dialer to communicate with fronting domain 2026-03-12 20:51:02 +01:00
9seconds 94e4179fb7 Add NativeDialer method to mtglib.Network 2026-03-12 19:25:53 +01:00
Sergei ArkhipovandGitHub 6493688282 Merge pull request #352 from 9seconds/doppleganger
Doppleganger
2026-03-12 19:18:44 +01:00
9seconds ffd3ab03cc Formatting with gofumpt 2026-03-12 19:13:33 +01:00
9seconds 00403e3a94 Remove old faketls package 2026-03-12 19:11:39 +01:00
9seconds 0bfc1ef2d4 Fix double TLS wrapping for noise 2026-03-12 19:07:11 +01:00
9seconds 7aa01dcebe Limit min number of collected stats 2026-03-12 19:07:11 +01:00
9seconds 1c0e847247 Set statistics measured from real life 2026-03-12 19:07:11 +01:00
9seconds 83ff4ee266 Align scout defaults with config defaults 2026-03-12 19:07:11 +01:00
9seconds 7a58c74cfe Correctly process doppel conns 2026-03-12 19:07:11 +01:00
9seconds d43d6692d7 Align naming about scout raids 2026-03-12 19:07:11 +01:00
9seconds 75392941da Propagate settings to Proxy 2026-03-12 19:07:11 +01:00
9seconds 25ad776b6f Propagate doppelganger to config 2026-03-12 19:07:11 +01:00
9seconds 5557393b38 Add config section 2026-03-12 19:07:11 +01:00
9seconds bb49c6a55d Integrate new fake package and doppel into proxy 2026-03-12 19:07:11 +01:00
9seconds 37f8d18be5 Add server side of things 2026-03-12 19:07:11 +01:00
9seconds 59557059df ReadClientHello function 2026-03-12 19:07:10 +01:00
9seconds 1182b9ef6f Add doppel and tls packages 2026-03-12 19:07:10 +01:00
9seconds c886ffdd81 Add TrustTLS method to networkHTTPTransport 2026-03-11 23:21:41 +01:00
Sergei ArkhipovandGitHub e9b38a5b74 Merge pull request #348 from 9seconds/bundle-config 2026-03-05 19:09:48 +01:00
9seconds 036b10be67 Include example config into distribution bundle 2026-03-05 17:14:52 +01:00
9seconds fefc479f94 Merge remote-tracking branch 'origin/stable' into v2 2026-02-28 14:52:05 +01:00
9seconds 9f12620dba Merge remote-tracking branch 'origin/master' into stable 2026-02-28 14:51:26 +01:00
9seconds 23aa2eefad More details on DOH-IP option 2026-02-28 14:50:48 +01:00
9seconds ddc34bf918 Comment out doh-ip 2026-02-28 14:48:37 +01:00
9seconds f5244c2bfd Merge remote-tracking branch 'origin/stable' into v2 2026-02-28 14:47:43 +01:00
9seconds 9946cdfeb5 Merge remote-tracking branch 'origin/master' into stable 2026-02-28 14:47:25 +01:00
Sergei ArkhipovandGitHub ffe052617b Merge pull request #346 from 9seconds/simple-network
Simplify network implementation
2026-02-27 16:25:23 +01:00
9seconds 700417b1d0 Integrate DNS resolver in our codebase 2026-02-27 16:23:24 +01:00
9seconds 897e6bf505 Propagate DNS setting to configuration 2026-02-27 16:23:24 +01:00
9seconds 317d7380cb Add support for custom DNS resolvers 2026-02-27 16:08:28 +01:00
9seconds 1151291535 Use network v2 instead of network v1 2026-02-27 15:52:29 +01:00
9seconds 42927c8bdc Add v2 network package 2026-02-27 15:50:21 +01:00
9seconds 7d748077e9 Merge remote-tracking branch 'origin/stable' into v2 2026-02-26 13:53:32 +01:00
9seconds f61a63a59b Merge remote-tracking branch 'origin/master' into stable 2026-02-26 13:53:17 +01:00
9seconds 282896be09 Update own dependencies 2026-02-26 13:52:26 +01:00
9seconds 3809f034ad Update go packages 2026-02-26 13:51:29 +01:00
Sergei ArkhipovandGitHub 6feef02d08 Merge pull request #344 from 9seconds/blocked-telegram
Make auto updating optional
2026-02-26 13:46:01 +01:00
Sergei ArkhipovandGitHub 5549a82d19 Merge pull request #345 from 9seconds/fix-no-addresses
Do not panic if no addresses were found
2026-02-26 13:35:47 +01:00
9seconds 189dce07c6 Make auto updating optional 2026-02-26 13:35:05 +01:00
9seconds 449f84133a Do not panic if no addresses were found 2026-02-26 12:31:30 +01:00
9seconds 9c122f942f Always use latest govulncheck to check vulnerabilities 2026-02-24 19:06:16 +01:00
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
9seconds 8758208de6 Merge remote-tracking branch 'origin/master' into stable 2021-07-31 20:51:36 +03:00
9seconds 6cd91600b5 Update dependencies 2021-07-31 11:44:58 +03:00
Sergey ArkhipovandGitHub bd39fa63cb Merge pull request #199 from 9seconds/update-lint
Update golangci-lint to 1.41.1
2021-07-31 11:43:10 +03:00
9seconds eaa757b6d0 Update code to the latest golangci-lint 2021-07-31 11:35:05 +03:00
9seconds dc99dd165e Update golangci-lint to 1.41.1 2021-07-31 11:29:30 +03:00
Sergey ArkhipovandGitHub 7c70fd8079 Merge pull request #198 from 9seconds/simple-run
Simple run mode
2021-07-30 16:50:36 +03:00
9seconds dcbbb49607 Update README 2021-07-30 16:36:12 +03:00
9seconds 1050ca0b97 Fix lint issues 2021-07-30 16:34:00 +03:00
9seconds c85c88efd6 Add simple-run command 2021-07-30 16:15:36 +03:00
9seconds c53364d952 Add test for making QR code url 2021-07-30 15:18:26 +03:00
9seconds ed91290e47 Add test for reading config 2021-07-30 15:07:35 +03:00
9seconds 3fd5e9eb19 Rework cli 2021-07-30 15:00:48 +03:00
9seconds 87ed1d1aa7 Move config2 into config 2021-07-29 16:21:22 +03:00
9seconds ec4f0656fb Add updated version of config 2021-07-28 18:16:01 +03:00
9seconds 0c5d487fae Merge remote-tracking branch 'origin/master' into stable 2021-05-17 16:08:19 +03:00
Sergey ArkhipovandGitHub 889ab6c227 Merge pull request #195 from 9seconds/lint-1.40
Update golangci-lint to 1.40.1
2021-05-17 15:51:33 +03:00
9seconds e1864377c2 Update golangci-lint to 1.40.1 2021-05-17 15:26:16 +03:00
9seconds 4c8925ab65 Update depdendencies 2021-05-17 12:13:17 +03:00
Sergey ArkhipovandGitHub bf34f0c92d Merge pull request #191 from 9seconds/goreleaser
Add support of goreleaser
2021-05-07 16:36:32 +03:00
9seconds 604b1d2597 Add support of goreleaser 2021-05-07 16:23:31 +03:00
Sergey ArkhipovandGitHub b19f491fc8 Merge pull request #189 from 9seconds/relay-close-racecondition
Fix race condition in closing a relay
2021-05-07 11:40:06 +03:00
9seconds 16f9ec690b Fix race condition in closing a relay
This commit fixes a situration when relay can be reset before all
waiting goroutines are finished. For example, we terminate processing
based on some event: socket error etc. So, error happens and context is
cancelled. After that a main relay goroutine starts to wait. Meanwhile a
second goroutine reaches deferred function and set wg to done. It means
that main goroutine can continue.

In this case this is really possible that we can start resetting before
transmit goroutine really exits.

A correct solution is to always do wg.Done() as a first deferred thing
on entering to a function. In that case we do not need reordering and so
on.
2021-05-06 22:08:39 +03:00
9seconds fec60083a3 Merge remote-tracking branch 'origin/master' into stable 2021-05-05 11:57:47 +03:00
9seconds 28fe63dbdb Merge remote-tracking branch 'origin/master' into stable 2021-03-03 17:55:00 +03:00
9seconds 4aab3b686c Merge remote-tracking branch 'origin/master' into stable 2021-03-03 17:25:34 +03:00
9seconds 121200cfad Merge remote-tracking branch 'origin/master' into stable 2021-02-26 08:27:25 +03:00
9seconds a8c4d0a9c4 Merge remote-tracking branch 'origin/master' into stable 2020-09-21 12:03:19 +03:00
9seconds 790bf21166 Merge remote-tracking branch 'origin/master' into stable 2020-08-11 13:32:01 +03:00
9seconds e5b9c841cd Merge remote-tracking branch 'origin/master' into stable 2020-03-31 10:09:47 +03:00
9seconds 8ccf61c23e Merge remote-tracking branch 'origin/master' into stable 2020-03-25 10:24:31 +03:00
9seconds 0e033b59b3 Merge remote-tracking branch 'origin/master' into stable 2020-03-20 14:39:45 +03:00
9seconds cdd93bd25f Merge remote-tracking branch 'origin/master' into stable 2020-02-04 11:23:40 +03:00
9seconds cac476901d Merge remote-tracking branch 'origin/master' into stable 2019-11-19 18:13:43 +03:00
9seconds bb21bf814f Merge remote-tracking branch 'origin/master' into stable 2019-11-19 17:58:10 +03:00
9seconds 68e1d66017 Merge remote-tracking branch 'origin/master' into stable 2019-11-18 12:56:21 +03:00
282 changed files with 11678 additions and 6268 deletions
+3
View File
@@ -0,0 +1,3 @@
# git config merge.theirs.name "Always accept theirs"
# git config merge.theirs.driver "cp %B %A"
default.pgo binary merge=theirs
+98 -48
View File
@@ -2,6 +2,18 @@
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:
push:
tags:
@@ -26,36 +38,60 @@ jobs:
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 5
strategy:
matrix:
go_version:
- ^1.16
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v6
with:
submodules: recursive
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go_version }}
- uses: jdx/mise-action@v3
name: Install mise
- name: Cache dependencies
uses: actions/cache@v2
- name: Cache Go modules and build
uses: actions/cache@v5
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run tests
run: go test -coverprofile=./coverage.txt -covermode=atomic -v ./...
run: mise tasks run covtest
- name: Collect coverage
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v5
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: Cache Go modules and build
uses: actions/cache@v5
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run fuzzing
run: mise tasks run 'test:fuzz:*'
lint:
name: Lint
@@ -63,69 +99,83 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v6
with:
submodules: recursive
- name: Run linter
uses: golangci/golangci-lint-action@v2
- uses: jdx/mise-action@v3
name: Install mise
- name: Cache Go modules and build
uses: actions/cache@v5
with:
version: v1.39.0
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run linter
run: mise tasks run lint
docker:
name: Docker
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 20
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v6
with:
submodules: recursive
- name: Get Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
id: meta
uses: docker/metadata-action@v5
with:
images: nineseconds/mtg,ghcr.io/9seconds/mtg
tag-semver: "{{version}},{{major}},{{major}}.{{minor}}"
images: |
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
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v3
- name: Setup BuildX
uses: docker/setup-buildx-action@v1
- name: Setup cache
uses: actions/cache@v2
with:
path: /tmp/buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to GHCR.io
- name: Login to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
uses: docker/build-push-action@v6
with:
pull: true
context: .
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7,linux/arm/v6
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
cache-from: type=local,src=/tmp/buildx-cache
cache-to: type=local,dest=/tmp/buildx-cache
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
+8
View File
@@ -11,6 +11,14 @@
#
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:
push:
branches:
+42
View File
@@ -0,0 +1,42 @@
---
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
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true
- name: Check for vulnerabilities
run: |
go run golang.org/x/vuln/cmd/govulncheck@latest ./...
+1 -3
View File
@@ -6,7 +6,5 @@
*.test
*.out
mtg
vendor/
ccbuilds/
.bin/
coverage.txt
dist/
+17 -8
View File
@@ -1,12 +1,21 @@
# https://golangci-lint.run/docs/configuration/file/
version = '2'
[run]
concurrency = 4
deadline = "2m"
tests = true
skip-dirs = ["vendor"]
[output]
format = "colored-line-number"
[linters]
enable-all = true
disable = ["gochecknoglobals", "gas", "goerr113", "exhaustivestruct"]
# [linters]
# enable-all = true
# disable = [
# "containedctx",
# "exhaustivestruct",
# "exhaustruct",
# "gas",
# "gochecknoglobals",
# "goerr113",
# "ireturn",
# "thelper",
# "varnamelen",
# ]
+150
View File
@@ -0,0 +1,150 @@
---
version: 2
project_name: mtg
before:
hooks:
- go mod tidy
- go generate ./...
builds:
- id: default
binary: '{{ .ProjectName }}'
goos:
- darwin
- freebsd
- linux
- netbsd
- openbsd
- windows
goarch:
- 386
- amd64
- arm
- arm64
goarm:
- 6
- 7
env:
- CGO_ENABLED=0
flags:
- -trimpath
- -mod=readonly
ldflags: -s -w -X main.version={{ .Version }}
ignore:
- goos: darwin
goarch: 386
- goos: darwin
goarch: arm
- goos: freebsd
goarch: arm64
- goos: netbsd
goarch: arm64
- goos: openbsd
goarch: arm64
- goos: windows
goarch: 386
- goos: windows
goarch: arm
- id: mips
binary: '{{ .ProjectName }}'
goos:
- linux
goarch:
- mips
- mipsle
gomips:
- softfloat
env:
- CGO_ENABLED=0
flags:
- -trimpath
- -mod=readonly
ldflags: -s -w -X main.version={{ .Version }}
- id: arm64-v9
binary: '{{ .ProjectName }}'
goos:
- darwin
- linux
goarch:
- arm64
goarm64:
- v9.0
env:
- CGO_ENABLED=0
flags:
- -trimpath
- -mod=readonly
ldflags: -s -w -X main.version={{ .Version }}
- id: amd64-v3
binary: '{{ .ProjectName }}'
goos:
- darwin
- freebsd
- linux
- netbsd
- openbsd
- windows
goarch:
- amd64
goamd64:
- v3
env:
- CGO_ENABLED=0
flags:
- -trimpath
- -mod=readonly
ldflags: -s -w -X main.version={{ .Version }}
archives:
- id: default
ids:
- default
- mips
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
formats:
- tar.gz
wrap_in_directory: true
format_overrides:
- goos: windows
formats:
- zip
files:
- LICENSE
- README.md
- SECURITY.md
- BEST_PRACTICES.md
- example.config.toml
- id: optimized
ids:
- arm64-v9
- amd64-v3
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm64 }}-{{ .Arm64 }}{{ end }}{{ if .Amd64 }}-{{ .Amd64 }}{{ end }}'
formats:
- tar.gz
wrap_in_directory: true
format_overrides:
- goos: windows
formats:
- zip
files:
- LICENSE
- README.md
- SECURITY.md
- BEST_PRACTICES.md
- example.config.toml
gomod:
proxy: true
snapshot:
version_template: '{{ .Version }}'
checksum:
name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt'
source:
enabled: true
name_template: '{{ .ProjectName }}-sources'
+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 -race ./..."
[tasks.covtest]
description = "Run tests with code coverage"
run = "go test -coverprofile=coverage.txt -covermode=atomic -count=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=FuzzReadClientHello ./mtglib/internal/tls/fake"
[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 ."
+55
View File
@@ -0,0 +1,55 @@
# Best practices
This is unfortunate, but since 2018 many things were changed. Most of them
became way worse. Previous iterations of censorship systems were very dumb,
DPI were primitive and filtered very obvious things. Nowadays they are
way more intelligent and it is very naive to treat them frivolously.
In 2026 is not enough to pretend that your mtg installation is a Microsoft
website that sits in Amsterdam Digital Ocean location. Now your installation
has to be a website that is mtg in disguise. Yes, it requires a bit more effort
but this effort is probably less than rotating proxies each other day.
mtproto traffic, even with FakeTLS, has its specifics that are probably
very well known by DPI systems. These specifics are not something unique but
could mark an IP address as suspicious. Now let's think:
1. You have a proxy in Amsterdam Digital Ocean that tells it is microsoft.com
how hard could it be to find out that this is probably fake? 1 or probably 2
DNS queries for `microsoft.com`? In case of some CDN, there are ECS-powered
resolvers that are very capable to return results from POV of some subnets.
If censor sees no relevant results, will they be afraid to block IP?
2. You have a proxy in Amsterdam Digital Ocean that tells it is a website from
the same public subnet. But not the same. Would it be hard to make these DNS
queries and ban IP?
The correct way of having this proxy is following:
1. Register a domain name
2. Get some VPS, probably in your domestic location
3. Set that domain name from a step 1 to IP address of that VPS
4. Generate a couple of HTML pages by LLMs or even copy them from elsewhere
5. Set some webserver and issue TLS certificates with Let's Encrypt or any other
name
6. Set mtg before this webserver.
7. Use sing-box or anything like that to provide local socks5 interface and
have VPNized uplinks
8. Set up mtg to use socks5 from a 7 step.
In that case you will get a match of DNS and SNI in requests. As a side effect,
your proxy will work with XTLS and its friends: XTLS in sniff mode ignores
IP address a client wants to connect to. Instead, it reads SNI and connect
to resolved address: a clever idea if user does not have a trustworthy DNS
set up.
Yes, this is much longer that usual technique, and requires more effort. But
this is could probably be very well automated to some reasonable extent.
Unfortunately, this is a best practice right now.
Do not also forget about other implementation, like
[telemt](https://github.com/telemt/telemt). Try everything. Use VPNs. It does
not really matter which project you are going to use as long it helps you to
stay connected.
_March 2026._
+35 -10
View File
@@ -1,21 +1,45 @@
###############################################################################
# BUILD STAGE
FROM golang:1.16-alpine AS build
FROM golang:1.26-alpine AS build
ENV CGO_ENABLED=0
# this is done for backward compatibility: before that we mounted a config
# into /config.toml. Some application allow mounting directories only,
# so it makes problems. So, instead we are going to do 2 steps:
# 1. Create /config/config.toml as a symlink to /config.toml
# 2. Force /mtg to use /config/config.toml
#
# it helps in both ways: users with directories could use /config directory
# and overlap a symlink by their bind mount. Old users could continue using
# /config.toml as a real config.
RUN set -x \
&& apk --no-cache --update add \
bash \
ca-certificates \
curl \
git \
make
&& mkdir -p /config \
&& ln -sv /config.toml /config/config.toml
COPY . /app
RUN --mount=type=cache,target=/var/cache/apk \
set -x \
&& apk --update add \
bash \
ca-certificates \
git
COPY go.mod go.sum /app/
WORKDIR /app
RUN go mod download
COPY . /app
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
###############################################################################
@@ -24,8 +48,9 @@ RUN set -x \
FROM scratch
ENTRYPOINT ["/mtg"]
CMD ["run", "/config.toml"]
CMD ["run", "/config/config.toml"]
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=build /app/mtg /mtg
COPY --from=build /app/example.config.toml /config.toml
COPY --from=build /config /config
-107
View File
@@ -1,107 +0,0 @@
ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
IMAGE_NAME := mtg
APP_NAME := $(IMAGE_NAME)
CC_BINARIES := $(shell bash -c "echo -n $(APP_NAME)-{linux,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}")
GOLANGCI_LINT_VERSION := v1.39.0
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)"
$(APP_NAME)-%: GOOS=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f1 -d-)
$(APP_NAME)-%: GOARCH=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f2 -d-)
$(APP_NAME)-%: ccbuilds
@env "GOOS=$(GOOS)" "GOARCH=$(GOARCH)" \
go build \
$(COMMON_BUILD_FLAGS) \
-tags netgo \
-a \
-o "./ccbuilds/$(APP_NAME)-$(GOOS)-$(GOARCH)"
.PHONY: ccbuilds
ccbuilds:
@rm -rf ./ccbuilds && mkdir -p ./ccbuilds
vendor: go.mod go.sum
@$(MOD_ON) go mod vendor
.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: crosscompile
crosscompile: $(CC_BINARIES)
.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: 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
.PHONY: install-tools-lint
install-tools-lint:
@mkdir -p "$(GOBIN)" || true && \
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh \
| bash -s -- -b "$(GOBIN)" "$(GOLANGCI_LINT_VERSION)"
.PHONY: install-tools-godoc
install-tools-godoc:
@mkdir -p "$(GOBIN)" || true && \
$(GOTOOL) go get -u golang.org/x/tools/cmd/godoc
.PHONY: install-tools-gofumpt
install-tools-gofumpt:
@mkdir -p "$(GOBIN)" || true && \
$(GOTOOL) go get -u mvdan.cc/gofumpt
.PHONY: update-deps
update-deps:
@go get -u && go mod tidy
+210 -8
View File
@@ -1,6 +1,6 @@
# mtg
Highly-opionated (ex-bullshit-free) MTPROTO proxy for
Highly-opinionated (ex-bullshit-free) MTPROTO proxy for
[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)
@@ -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
[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
There are several available proxies for Telegram MTPROTO available. Here
@@ -18,6 +29,7 @@ are the most notable:
* [Official](https://github.com/TelegramMessenger/MTProxy)
* [Python](https://github.com/alexbers/mtprotoproxy)
* [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
feature parity now. This includes support of adtag, replay attack
@@ -26,6 +38,33 @@ goal: to give a possibility to connect to Telegram in a restricted,
censored environment. But it does it slightly differently in details
that probably matter.
* **Domain fronting**
For years mtg supports domain fronting. This technique means that it fallbacks
to accessing a real website in case if request fails. It could fail by many
reasons: anti-replay protection, accidental access to the webserver or
stale request. Anyway, if mtg rejects this request, it does not break a
connection. It connects to the websites and replicates everything that client
has sent, and simply proxies it back as is. Users will see a response from
the real website, _byte-to-byte identical_ to the response of the real netloc.
* **Doppelganger**
mtg also is a doppelganger of the website it fronts. Sure, with domain fronting
users will see replies of the real website in case if something will go wrong.
But what about such cases when _everything is fine_?
In that case mtg mimics TLS connection statistical characteristics as close as
possible. Different application have different statistics of their patterns.
Big CDN steadily pumping the data, small websites burst with short easily
compressiable chunks of traffic.
mtg artificially emulates those delays to be statistically indistinguishable
from the real website even if it covers connection of the very specific app.
It also follows 2 most common patterns of traffic chunking, so censors
will have to put more resources to find out that we have Telegram here
but not a hookah webshop served by nginx.
* **Resource-efficient**
It has to be resource-efficient. It does not mean that you will see
@@ -40,6 +79,12 @@ that probably matter.
way of doing business I suppose. I think the only viable way is to
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**
I think that multiple secrets solve no problems and just complex
@@ -75,6 +120,8 @@ that probably matter.
software (written in Golang) with a minimum effort + you can replace
some parts with those you want.
Please also to read about [best practices](https://github.com/9seconds/mtg/blob/master/BEST_PRACTICES.md).
### Version 2
If you use version 1.x before, you are probably noticed some major
@@ -162,6 +209,12 @@ This project has several main branches
## 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 binaries
@@ -190,21 +243,22 @@ surprises. Always choose some version tag.
Also, if you have `go` installed, you can always download this tool with `go get`:
```console
go get github.com/9seconds/mtg/v2
go install github.com/9seconds/mtg/v2@latest
```
#### Build from sources
```console
git clone https://github.com:9seconds/mtg.git
git clone https://github.com/9seconds/mtg.git
cd mtg
make static
mise install
mise tasks run build
```
or for the docker image:
```console
make docker
mise tasks run image
```
### Generate secret
@@ -224,6 +278,16 @@ $ mtg generate-secret --hex google.com
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.
You need to keep it secured.
@@ -237,6 +301,82 @@ For example, you've bought a VPS from [Digital
Ocean](https://www.digitalocean.com/). Then it might be a good idea to
generate a secret for _digitalocean.com_ then.
### Check configuration
There is a special command for secret verification:
```
$ mtg doctor /path/to/my/config.toml
Deprecated options
✅ All good
Time skewness
✅ Time drift is -607.048µs, but tolerate-time-skewness is 5s
Validate native network connectivity
✅ DC 1
✅ DC 2
✅ DC 3
✅ DC 4
✅ DC 5
✅ DC 203
Validate network connectivity with proxy socks5://127.0.0.1:1080
✅ DC 1
✅ DC 2
✅ DC 3
✅ DC 4
✅ DC 5
✅ DC 203
Validate fronting domain connectivity
✅ xx.xx.xx.xx:yyy is reachable
Validate SNI-DNS match
✅ IP address xx.xx.xx.xx matches secret hostname <REDACTED>
```
It aims to find out possible inconsistencies and problems with your
configuration. It makes sense to run it before executing any relevant commands.
### Simple run mode
mtg supports 2 modes: simple and normal. Simple mode allows starting
proxy with a small subset of configuration options you usually want to
modify. This is quite good for oneliners that you can copy-paste and do
not bother about external files whatsoever.
Let's take a look:
```console
Usage: mtg simple-run <bind-to> <secret>
Run proxy without config file.
Arguments:
<bind-to> A host:port to bind proxy to.
<secret> Proxy secret.
Flags:
-h, --help Show context-sensitive help.
-v, --version Print version.
-d, --debug Run in debug mode.
-c, --concurrency=8192 Max number of concurrent connection to proxy.
-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.
-p, --domain-fronting-port=443 A port to access for domain fronting.
-n, --doh-ip=1.1.1.1 IP address of DNS-over-HTTP to use.
-t, --timeout=10s Network timeout to use
-a, --antireplay-cache-size="1MB" A size of anti-replay cache to use.
```
So, if you want to startup a proxy with CLI only, you can do something like
```console
$ mtg simple-run -n 1.1.1.1 -t 30s -a 512kib 127.0.0.1:3128 7hBO-dCS4EBzenlKbdLFxyNnb29nbGUuY29t
```
The rest of the configuration will be taken from default values. But
a simple run is fine if you do not have any special requirements or
granular tuning. If you want it, please checkout the configuration
files.
### Prepare a configuration file
Please checkout an example configuration file. All options except of
@@ -263,12 +403,17 @@ Now you can create a systemd unit:
```console
$ cat /etc/systemd/system/mtg.service
[Unit]
Description=mtg
Description=mtg - MTProto proxy server
Documentation=https://github.com/9seconds/mtg
After=network.target
[Service]
ExecStart=/usr/local/bin/mtg run /etc/mtg.toml
Restart=always
RestartSec=3
DynamicUser=true
LimitNOFILE=65536
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
@@ -280,7 +425,7 @@ $ sudo systemctl start mtg
or you can run a docker image
```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
@@ -309,6 +454,61 @@ $ mtg access /etc/mtg.toml
}
```
or if you are using docker:
```console
$ docker exec mtg-proxy /mtg access /config.toml
```
## Doppelganger
mtg can mimic real websites, please take a look at relevant section in example
config file.
mtg comes with some very good precollected statistics coming from
[ok.ru](https://ok.ru/). It does not mean that you have to cover yourself
by pretending that mtg is _ok.ru_. **Do not do that: ok.ru comes from very specific
ASNs, but not from VPS providers you are going to use.** What I want to say
is that defaults are very good enough to use as is because ok.ru for public
pages has a very generic profile of TLS packets delay.
But for better results it is recommended to teach mtg about the website you
will use as a domain front. In order to do that, you need to specify URLs
from this website. Just go to it, open WebDeveloper console and pick up
random URLs. For better results they have to be **from the same domain name
you are going to use as a disguise** but serve light and heavy content: pages,
images etc. Do not use many, 2-3 will probably work.
mtg will crawl these pages periodically, accumulating statistics and
using it as you go.
```toml
[defense.doppelganger]
urls = [
"https://lalala.com/index.html",
"https://lalala.com/contacts.html",
]
```
This is not very necessary. Keep in mind these rules:
1. If you are not sure what is this all about, do nothing. Defaults are good.
2. All URLs must be HTTPS
3. All URLs should be from the same domain name (but this is not a rule)
4. Do not use a lot of pages. Use _different_ pages. mtg will start using this
statistics when it will accumulate enough anyway.
5. These URLs should be directly accessible from mtg without proxies whatsoever
6. Do not create huge raids. mtg will repeatedly crawl in raids, making N repeats.
Do not use high N, you do not want to be noticeable.
7. It makes no sense to have small delay between raids. Usually webservers
do not update their TLS settings each hour.
8. If you have some specific knowledge if webserver is using
[TLS Dynamic Record Sizing](https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency/), you
can use a very specific setting. This are Cloudflare, Go standard webservers,
[caddy](https://caddyserver.com/) and [H2O](https://h2o.examp1e.net/). If so,
you can enable `drs` setting.
9. **If you are not sure, touch nothing!**
## Metrics
Out of the box, mtg works with
@@ -323,11 +523,12 @@ Here goes a list of metrics with their types but without a prefix.
| client_connections | gauge | `ip_family` | Count of processing client connections. |
| telegram_connections | gauge | `telegram_ip`, `dc` | Count of connections to Telegram servers. |
| 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. |
| domain_fronting_traffic | counter | `direction` | Count of bytes, transmitted to/from fronting domain. |
| domain_fronting | counter | | Count of domain fronting events. |
| 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. |
Tag meaning:
@@ -338,3 +539,4 @@ Tag meaning:
| dc | | A number of the Telegram DC for a connection. |
| telegram_ip | | IP address of the Telegram server. |
| 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
// against replay attacks.
// Antireplay package has cache implementations that are effective against
// replay attacks.
//
// To understand more about replay attacks, please read documentation
// for mtglib.AntiReplayCache interface. This package has a list of some
// To understand more about replay attacks, please read documentation for
// [mtglib.AntiReplayCache] interface. This package has a list of some
// implementations of this interface.
package antireplay
const (
// DefaultStableBloomFilterMaxSize is a recommended byte size for a
// stable bloom filter.
// DefaultStableBloomFilterMaxSize is a recommended byte size for a stable
// bloom filter.
DefaultStableBloomFilterMaxSize = 1024 * 1024 // 1MiB
// DefaultStableBloomFilterErrorRate is a recommended default error
// rate for a stable bloom filter.
// DefaultStableBloomFilterErrorRate is a recommended default error rate for a
// stable bloom filter.
DefaultStableBloomFilterErrorRate = 0.001
)
+2 -3
View File
@@ -6,9 +6,8 @@ type noop struct{}
func (n noop) SeenBefore(_ []byte) bool { return false }
// NewNoop returns an implementation that does nothing. A corresponding
// method always returns false, so this cache accepts everything you
// pass to it.
// NewNoop returns an implementation that does nothing. A corresponding method
// always returns false, so this cache accepts everything you pass to it.
func NewNoop() mtglib.AntiReplayCache {
return noop{}
}
+9 -9
View File
@@ -20,19 +20,19 @@ func (s *stableBloomFilter) SeenBefore(digest []byte) bool {
return s.filter.TestAndAdd(digest)
}
// NewStableBloomFilter returns an implementation of AntiReplayCache
// based on stable bloom filter.
// NewStableBloomFilter returns an implementation of AntiReplayCache based on
// stable bloom filter.
//
// http://webdocs.cs.ualberta.ca/~drafiei/papers/DupDet06Sigmod.pdf
//
// The basic idea of a stable bloom filter is quite simple: each time
// when you set a new element, you randomly reset P elements. There is a
// hardcore math which proves that if you choose this P correctly, you
// can maintain the same error rate for a stream of elements.
// The basic idea of a stable bloom filter is quite simple: each time when you
// set a new element, you randomly reset P elements. There is a hardcore math
// which proves that if you choose this P correctly, you can maintain the same
// error rate for a stream of elements.
//
// 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
// default values, please pass 0 for byteSize and <0 for errorRate.
// errorRate is desired false-positive error rate. If you want to use default
// values, please pass 0 for byteSize and <0 for errorRate.
func NewStableBloomFilter(byteSize uint, errorRate float64) mtglib.AntiReplayCache {
if byteSize == 0 {
byteSize = DefaultStableBloomFilterMaxSize
@@ -42,7 +42,7 @@ func NewStableBloomFilter(byteSize uint, errorRate float64) mtglib.AntiReplayCac
errorRate = DefaultStableBloomFilterErrorRate
}
sf := boom.NewDefaultStableBloomFilter(byteSize*8, errorRate) // nolint: gomnd
sf := boom.NewDefaultStableBloomFilter(byteSize*8, errorRate)
sf.SetHash(xxhash.New64())
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)))
}
BIN
View File
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
package essentials
// TelegramCoreAddresses are publicly known addresses of Telegram core network.
var TelegramCoreAddresses = map[int][]string{
1: {
"149.154.175.50:443",
"[2001:b28:f23d:f001::a]:443",
},
2: {
"149.154.167.51:443",
"95.161.76.100:443",
"[2001:67c:04e8:f002::a]:443",
},
3: {
"149.154.175.100:443",
"[2001:b28:f23d:f003::a]:443",
},
4: {
"149.154.167.91:443",
"[2001:67c:04e8:f004::a]:443",
},
5: {
"149.154.171.5:443",
"[2001:b28:f23f:f005::a]:443",
},
203: {
"91.105.192.100:443",
"[2a0a:f280:0203:000a:5000:0000:0000:0100]:443",
},
}
+51
View File
@@ -0,0 +1,51 @@
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
}
type netConnWrapper struct {
net.Conn
}
func (n netConnWrapper) CloseRead() error {
if conn, ok := n.Conn.(CloseableReader); ok {
return conn.CloseRead()
}
return n.Close()
}
func (n netConnWrapper) CloseWrite() error {
if conn, ok := n.Conn.(CloseableWriter); ok {
return conn.CloseWrite()
}
return n.Close()
}
// WrapConn wraps a generic [net.Conn] into Conn.
func WrapNetConn(conn net.Conn) Conn {
return netConnWrapper{conn}
}
+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 (
"context"
"math/rand"
"math/rand/v2"
"runtime"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/OneOfOne/xxhash"
)
// EventStream is a default implementation of the mtglib.EventStream
// EventStream is a default implementation of the [mtglib.EventStream]
// interface.
//
// EventStream manages a set of goroutines, observers. Main
@@ -64,7 +64,7 @@ func NewEventStream(observerFactories []ObserverFactory) EventStream {
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)
if len(observerFactories) == 1 {
@@ -77,7 +77,7 @@ func NewEventStream(observerFactories []ObserverFactory) EventStream {
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()
for {
@@ -102,6 +102,8 @@ func eventStreamProcessor(ctx context.Context, eventChan <-chan mtglib.Event, ob
observer.EventConcurrencyLimited(typedEvt)
case mtglib.EventReplayAttack:
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)
}
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() {
suite.stream.Shutdown()
suite.ctxCancel()
+23 -20
View File
@@ -1,19 +1,19 @@
// Events has a default implementations of EventStream for mtglib.
//
// Please see documentation for mtglib.EventStream interface to get an
// idea of such an abstraction. This package has implementations for the
// default event stream.
// Please see documentation for [mtglib.EventStream] interface to get an idea
// of such an abstraction. This package has implementations for the default
// event stream.
//
// Default event stream has a list of its own concepts. First, all it
// does is a routing of messages to known observers. It takes an event,
// defines its type and pass this message to a method of the observer.
// Default event stream has a list of its own concepts. First, all it does is a
// routing of messages to known observers. It takes an event, defines its type
// and pass this message to a method of the observer.
//
// There might be many observers, but default event stream has a
// guarantee though. It uses StreamID as a sharding key and guarantees
// that a message with the same StreamID will be devlivered to the same
// observer instance. So, each producer is guarateed to get all relevant
// messages related to the same session. It is not possible that it will
// get EventFinish if it has not seen EventStart for that session yet.
// There might be many observers, but default event stream has a guarantee
// though. It uses StreamID as a sharding key and guarantees that a message
// with the same StreamID will be devlivered to the same observer instance. So,
// each producer is guarateed to get all relevant messages related to the same
// session. It is not possible that it will get EventFinish if it has not seen
// EventStart for that session yet.
package events
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.
//
// As it is said in the package description, the default event stream
// guarantees that all events with the same StreamID are going to be
// routed to the same instance of the observer. So, there is no need
// to synchronize information about streams between many observers
// instances, they can have their local storage.
// guarantees that all events with the same StreamID are going to be routed to
// the same instance of the observer. So, there is no need to synchronize
// information about streams between many observers instances, they can have
// their local storage.
type Observer interface {
// EventStart reacts on incoming mtglib.EventStart event.
EventStart(mtglib.EventStart)
@@ -53,6 +53,9 @@ type Observer interface {
// EventReplayAttack reacts on incoming mtglib.EventReplayAttack event.
EventReplayAttack(mtglib.EventReplayAttack)
// EventIPListSize reacts on incoming mtglib.EventIPListSize
EventIPListSize(mtglib.EventIPListSize)
// Shutdown stop observer. Default event stream guarantees:
// 1. If shutdown is executed, it is executed only once
// 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.
//
// Default event stream creates a small set of goroutines to manage
// incoming messages. Each message is routed to an appropriate observer
// based on a sharding key, stream id. So, it is possible that an
// instance of mtg will have many observer instances, not a single one.
// Default event stream creates a small set of goroutines to manage incoming
// messages. Each message is routed to an appropriate observer based on a
// sharding key, stream id. So, it is possible that an instance of mtg will
// have many observer instances, not a single one.
type ObserverFactory func() Observer
+4
View File
@@ -41,6 +41,10 @@ func (o *ObserverMock) EventReplayAttack(evt mtglib.EventReplayAttack) {
o.Called(evt)
}
func (o *ObserverMock) EventIPListSize(evt mtglib.EventIPListSize) {
o.Called(evt)
}
func (o *ObserverMock) Shutdown() {
o.Called()
}
+35 -47
View File
@@ -12,14 +12,11 @@ type multiObserver struct {
func (m multiObserver) EventStart(evt mtglib.EventStart) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventStart(evt)
}(v)
wg.Go(func() {
v.EventStart(evt)
})
}
wg.Wait()
@@ -27,14 +24,11 @@ func (m multiObserver) EventStart(evt mtglib.EventStart) {
func (m multiObserver) EventConnectedToDC(evt mtglib.EventConnectedToDC) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventConnectedToDC(evt)
}(v)
wg.Go(func() {
v.EventConnectedToDC(evt)
})
}
wg.Wait()
@@ -42,14 +36,11 @@ func (m multiObserver) EventConnectedToDC(evt mtglib.EventConnectedToDC) {
func (m multiObserver) EventDomainFronting(evt mtglib.EventDomainFronting) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventDomainFronting(evt)
}(v)
wg.Go(func() {
v.EventDomainFronting(evt)
})
}
wg.Wait()
@@ -57,14 +48,11 @@ func (m multiObserver) EventDomainFronting(evt mtglib.EventDomainFronting) {
func (m multiObserver) EventTraffic(evt mtglib.EventTraffic) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventTraffic(evt)
}(v)
wg.Go(func() {
v.EventTraffic(evt)
})
}
wg.Wait()
@@ -72,14 +60,11 @@ func (m multiObserver) EventTraffic(evt mtglib.EventTraffic) {
func (m multiObserver) EventFinish(evt mtglib.EventFinish) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventFinish(evt)
}(v)
wg.Go(func() {
v.EventFinish(evt)
})
}
wg.Wait()
@@ -87,14 +72,11 @@ func (m multiObserver) EventFinish(evt mtglib.EventFinish) {
func (m multiObserver) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimited) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventConcurrencyLimited(evt)
}(v)
wg.Go(func() {
v.EventConcurrencyLimited(evt)
})
}
wg.Wait()
@@ -102,14 +84,11 @@ func (m multiObserver) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimite
func (m multiObserver) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventIPBlocklisted(evt)
}(v)
wg.Go(func() {
v.EventIPBlocklisted(evt)
})
}
wg.Wait()
@@ -117,14 +96,23 @@ func (m multiObserver) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
func (m multiObserver) EventReplayAttack(evt mtglib.EventReplayAttack) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
wg.Go(func() {
v.EventReplayAttack(evt)
})
}
obs.EventReplayAttack(evt)
}(v)
wg.Wait()
}
func (m multiObserver) EventIPListSize(evt mtglib.EventIPListSize) {
wg := &sync.WaitGroup{}
for _, v := range m.observers {
wg.Go(func() {
v.EventIPListSize(evt)
})
}
wg.Wait()
+1
View File
@@ -25,6 +25,7 @@ func (n noopObserver) EventFinish(_ mtglib.EventFinish)
func (n noopObserver) EventConcurrencyLimited(_ mtglib.EventConcurrencyLimited) {}
func (n noopObserver) EventIPBlocklisted(_ mtglib.EventIPBlocklisted) {}
func (n noopObserver) EventReplayAttack(_ mtglib.EventReplayAttack) {}
func (n noopObserver) EventIPListSize(_ mtglib.EventIPListSize) {}
func (n noopObserver) Shutdown() {}
// NewNoopObserver creates an observer which discards each message.
+3
View File
@@ -27,6 +27,7 @@ func (suite *NoopTestSuite) SetupSuite() {
"concurrency-limited": mtglib.NewEventConcurrencyLimited(),
"ip-blacklisted": mtglib.NewEventIPBlocklisted(net.ParseIP("10.0.0.10")),
"replay-attack": mtglib.NewEventReplayAttack("connID"),
"ip-list-size": mtglib.NewEventIPListSize(10, true),
}
suite.ctx = context.Background()
}
@@ -65,6 +66,8 @@ func (suite *NoopTestSuite) TestObserver() {
observer.EventIPBlocklisted(typedEvt)
case mtglib.EventReplayAttack:
observer.EventReplayAttack(typedEvt)
case mtglib.EventIPListSize:
observer.EventIPListSize(typedEvt)
}
})
}
+191 -23
View File
@@ -23,15 +23,19 @@ secret = "ee367a189aee18fa31c190054efd4a8e9573746f726167652e676f6f676c6561706973
# Host:port pair to run proxy on.
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.
# All other incoming connections are going to be dropped.
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
# IP connectivity to Telegram. We have 4 modes:
# - prefer-ipv6:
@@ -44,9 +48,35 @@ tcp-buffer = "4kb"
# Only ipv4 connectivity is used
prefer-ip = "prefer-ipv6"
# If this setting is set, then mtg will try to get proxy updates from Telegram
# Usually this is completely fine to have it disabled, because mtg has a list
# of some core proxies hardcoded.
auto-update = false
# FakeTLS uses domain fronting protection. So it needs to know a port to
# 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
# encrypted timestamp. So, mtg can compare this timestamp and decide if
@@ -56,6 +86,40 @@ domain-fronting-port = 443
# time range of this parameter.
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]
# please be aware that mtg needs to do some external requests. For
@@ -69,8 +133,43 @@ tolerate-time-skewness = "5s"
# resolver of the operating system and uses DOH instead. This is a host
# it has to access.
#
# By default we use Quad9.
doh-ip = "9.9.9.9"
# By default we use Cloudflare.
#
# DEPRECATED option:
# If dns option is specified, it will be used instead. No default is defined
# anymore. But if you set this option, this is literally the same providing
# https:// URL to `dns`.
#
# In other words:
#
# doh-ip = "1.1.1.1"
#
# is literally the same as
#
# dns = "https://1.1.1.1"
# doh-ip = "1.1.1.1"
# Starting from mtg v2.1.12 we have changed a configuration for DNS. Now it
# supports DNS-over-HTTPS, DNS-over-TLS, custom UDP resolver and system
# resolver.
#
# Here is how to define DNS-over-HTTPS:
# - https://1.1.1.1
# - https://1.1.1.1/dns-query
# - https://cloudflare-dns.com/dns-query
# - https://cloudflare-dns.com
#
# Here is how to define DNS-over-TLS:
# - tls://1.1.1.1
# - tls://cloudflare-dns.com
#
# Here is how to define a custom UDP resolver (we support only IPs here)
# - 1.1.1.1
# - udp://1.1.1.1
#
# If you set it to empty string, default resolver will be used.
# But please comment out doh-ip
dns = "https://1.1.1.1"
# mtg can work via proxies (for now, we support only socks5). Proxy
# configuration is done via list. So, you can specify many proxies
@@ -85,25 +184,13 @@ doh-ip = "9.9.9.9"
#
# Proxy configuration is done via ordinary URI schema:
#
# socks5://user:password@host:port?open_threshold=5&half_open_timeout=1m&reset_failures_timeout=10s
# socks5://user:password@host:port
#
# Only socks5 proxy is used. user/password is optional. As you can
# see, you can specify some parameters in GET query. These parameters
# configure circuit breaker.
#
# open_threshold means a number of errors which should happen so we stop
# use a proxy.
#
# half_open_timeout means a time period (in Golang duration notation)
# after which we can retry with this proxy
#
# reset_failures_timeout means a time period when we flush out errors
# when circuit breaker in closed state.
#
# Please see https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker
# on details about circuit breakers.
proxies = [
# "socks5://user:password@host:port?open_threshold=5&half_open_timeout=1m&reset_failures_timeout=10s"
# "socks5://user:password@host:port"
]
# network timeouts define different settings for timeouts. tcp timeout
@@ -119,6 +206,66 @@ tcp = "5s"
http = "10s"
idle = "1m"
# mtg has to mimic real websites. It does not mean domain fronting, it also
# means that traffic characteristics should be similar to real world traffic.
# websites and applications behave differently, their traffic patterns are also
# different. Applications do bursts of RPC-style messages (or JSON communication,
# does not really matter), while websites pump heavy content in HTTP2 streams
#
# It means that statistically there is a different between traffic shape:
# delays between packets are also different.
# In order to avoid censorship detection based on these patterns, there is a
# mtg subsystem called "Doppelganger" that aims to mimic website statistics
# as close as it could.
#
# Delays between TLS packets are not constant. There are many factors
# that come in play. Application should generate some response, it could
# send some headers first and stream content with chunked encoding. So
# some first packets could come as soon as possible, with some delays
# after first ones. Such phenomenon is described by different statistic
# distribution. There are 2 distribution that describe it: lognormal
# distribution and Weibul distribution. Lognormal is all about steady streams
# of heavy content like a video. Weibul is great about short bursts like
# user who requested a static page an a couple of images.
[defense.doppelganger]
# This is a list of URLs that would be crawled by mtg to approximate delay
# statistics. They MUST be HTTPS urls.
#
# You can come to the website and collect different URLs, with light and
# heavy content. We recommend to search for CDNs.
urls = [
# "https://st-ok.cdn-vk.ru/res/react/vendor/clsx-2.1.1-amd.js"
]
# A collection is done in raids. Each raid makes this number of requests to
# each URL in this list. Do not use a huge number, 10 is probably ok.
repeats-per-raid = 10
# This is a duration between each raid. It makes no sense to have a small number
# here as you would start to make a noticeable activity. Usually traffic patterns
# do not change a lot, so do not expect different results if you request
# each 10 minutes.
raid-each = "6h"
# This enables dynamic tls record sizing.
#
# Some modern stacks and platforms start to use the technique that is called
# DRS. They start with small TLS packets and ramp up eventually. First packets
# are usually about MTU size, after that we get 4k and eventually max size.
# This is done with a good intention: to minimize a time to the first byte,
# so application could start doing something with the data right after first
# RTT.
#
# Apparently, about 90% of application do not employ this technique, they use
# max size always: nginx, apache, java stuff. But Golang tools, angie and
# some specific patches activate this technique.
#
# In order to mimic a real website we need to know something about software
# it uses. Usually nobody cares: openssl does 16384, Python does it, nginx
# does it. So this setting is disabled by default.
#
# https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency/
# https://aws.github.io/s2n-tls/usage-guide/ch08-record-sizes.html
# https://github.com/cloudflare/sslconfig/blob/master/patches/nginx__dynamic_tls_records.patch
drs = false
# Some countries do active probing on Telegram connections. This technique
# allows to protect from such effort.
#
@@ -155,12 +302,33 @@ download-concurrency = 2
# 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",
"https://iplists.firehol.org/files/firehol_level1.netset",
# "/local.file"
]
# How often do we need to update a blocklist set.
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.
[stats.statsd]
# enabled/disabled
+46 -20
View File
@@ -1,31 +1,57 @@
module github.com/9seconds/mtg/v2
go 1.16
go 1.26
require (
github.com/OneOfOne/xxhash v1.2.8
github.com/alecthomas/kong v0.2.16
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15
github.com/alecthomas/kong v1.14.0
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6
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/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/panjf2000/ants/v2 v2.4.4
github.com/pelletier/go-toml v1.9.0
github.com/prometheus/client_golang v1.10.0
github.com/prometheus/common v0.23.0 // indirect
github.com/rs/zerolog v1.21.0
github.com/smira/go-statsd v1.3.2
github.com/stretchr/objx v0.3.0 // indirect
github.com/stretchr/testify v1.7.0
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6
github.com/panjf2000/ants/v2 v2.12.0
github.com/prometheus/client_golang v1.23.2
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/rs/zerolog v1.34.0
github.com/smira/go-statsd v1.3.4
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.11.1
github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b
golang.org/x/crypto v0.49.0
golang.org/x/net v0.52.0
golang.org/x/sys v0.42.0
google.golang.org/protobuf v1.36.11 // indirect
)
require (
github.com/beevik/ntp v1.5.0
github.com/ncruces/go-dns v1.3.2
github.com/pelletier/go-toml/v2 v2.2.4
github.com/pires/go-proxyproto v0.11.0
github.com/things-go/go-socks5 v0.1.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.4 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/tools v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+127 -499
View File
@@ -1,536 +1,164 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
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/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/kong v0.2.16 h1:F232CiYSn54Tnl1sJGTeHmx4vJDNLVP2b9yCVMOQwHQ=
github.com/alecthomas/kong v0.2.16/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4=
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/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/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.14.0 h1:gFgEUZWu2ZmZ+UhyZ1bDhuutbKN1nTtJTwh19Wsn21s=
github.com/alecthomas/kong v1.14.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
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/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/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/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/beevik/ntp v1.5.0 h1:y+uj/JjNwlY2JahivxYvtmv4ehfi3h74fAuABB9ZSM4=
github.com/beevik/ntp v1.5.0/go.mod h1:mJEhBrwT76w9D+IfOEGvuzyuudiW9E52U2BaTrMOYow=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/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/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
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/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/pp v2.4.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
github.com/kentik/patricia v0.0.0-20201202224819-f9447a6e25f1 h1:D7qhJP3R49ZjUzpzKQ6B2H3lgejPs6DTO5gRomhhOpE=
github.com/kentik/patricia v0.0.0-20201202224819-f9447a6e25f1/go.mod h1:2OfLA+0esiUJpwMjrH39pEk79cb8MvGTBS9YlZpejJ4=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/libp2p/go-reuseport v0.0.2 h1:XSG94b1FJfGA01BUrT82imejHQyTxO4jEWqheyCXYvU=
github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-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/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/panjf2000/ants/v2 v2.4.4 h1:kebk2KSiXHGeiYS6b+w2RqNN5+IKoqlBNd7cuC7MvQI=
github.com/panjf2000/ants/v2 v2.4.4/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.9.0 h1:NOd0BRdOKpPf0SxkL3HxSQOG7rNh+4kl6PHcBPFs7Q0=
github.com/pelletier/go-toml v1.9.0/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-dns v1.3.2 h1:kBLuUZBgkQ4qF4WDXZRQ4rG0Gk6sLVJQ5tESkWrxUa0=
github.com/ncruces/go-dns v1.3.2/go.mod h1:tuzixNY8PY/M7yUzcvRbUaeLs3ifIdydpi5H2bfRU+s=
github.com/panjf2000/ants/v2 v2.12.0 h1:u9JhESo83i/GkZnhfTNuFMMWcNt7mnV1bGJ6FT4wXH8=
github.com/panjf2000/ants/v2 v2.12.0/go.mod h1:tSQuaNQ6r6NRhPt+IZVUevvDyFMTs+eS4ztZc52uJTY=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg=
github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/common v0.23.0 h1:GXWvPYuTUenIa+BhOq/x+L/QZzCqASkVRny5KTlPDGM=
github.com/prometheus/common v0.23.0/go.mod h1:H6QK/N6XVT42whUeIdI3dp36w49c+/iMDk7UAI2qm7Q=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/quasilyte/go-ruleguard/dsl v0.3.2/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/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.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM=
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smira/go-statsd v1.3.2 h1:1EeuzxNZ/TD9apbTOFSM9nulqfcsQFmT4u1A2DREabI=
github.com/smira/go-statsd v1.3.2/go.mod h1:1srXJ9/pbnN04G8f4F1jUzsGOnwkPKXciyqpewGlkC4=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/smira/go-statsd v1.3.4 h1:kBYWcLSGT+qC6JVbvfz48kX7mQys32fjDOPrfmsSx2c=
github.com/smira/go-statsd v1.3.4/go.mod h1:RjdsESPgDODtg1VpVVf9MJrEW2Hw0wtRNbmB1CAhu6A=
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.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.1.5-0.20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43 h1:QEePdg0ty2r0t1+qwfZmQ4OOl/MB2UXIeJSpIZv56lg=
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43/go.mod h1:OYRfF6eb5wY9VRFkXJH8FFBi3plw2v+giaIu7P054pM=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/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.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
github.com/stretchr/testify v1.7.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.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/things-go/go-socks5 v0.1.0 h1:4f5dz0iMQ6cA4wseFmyLmCHmg3SWJTW92ndrKS6oERg=
github.com/things-go/go-socks5 v0.1.0/go.mod h1:Riabiyu52kLsla0YmJqunt1c1JEl6iXSr4bRd7swFEA=
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
github.com/txthinking/runnergroup v0.0.0-20250224021307-5864ffeb65ae h1:ArVM1jICfm7g4E4dBet+KHUFMLuxmj1Nxdp/tr3ByCU=
github.com/txthinking/runnergroup v0.0.0-20250224021307-5864ffeb65ae/go.mod h1:cldYm15/XHcGt7ndItnEWHwFZo7dinU+2QoyjfErhsI=
github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e h1:xA7GVlbz6teIF4FdvuqwbX6C4tiqNk2PH7FRPIDerao=
github.com/txthinking/socks5 v0.0.0-20251011041537-5c31f201a10e/go.mod h1:ntmMHL/xPq1WLeKiw8p/eRATaae6PiVRNipHFJxI8PM=
github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b h1:p+bJ3v5uUdEVMCoeFUs+BNJPsqt+Y6BLbDaPfTcbcH8=
github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b/go.mod h1:OYRfF6eb5wY9VRFkXJH8FFBi3plw2v+giaIu7P054pM=
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI=
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 h1:Ugb8sMTWuWRC3+sz5WeN/4kejDx9BvIwnPUiJBjJE+8=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
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-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a 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-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6 h1:cdsMqa2nXzqlgs183pHxtvoVwU7CyzaCTAUOg94af4c=
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/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-20220520151302-bc2c85ada10a/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.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/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.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
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 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+39 -93
View File
@@ -1,17 +1,16 @@
package cli
import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/9seconds/mtg/v2/internal/utils"
)
type accessResponse struct {
@@ -26,66 +25,61 @@ type accessResponse struct {
type accessResponseURLs struct {
IP net.IP `json:"ip"`
Port uint `json:"port"`
TgURL string `json:"tg_url"`
TgQrCode string `json:"tg_qrcode"`
TmeURL string `json:"tme_url"`
TmeQrCode string `json:"tme_qrcode"`
TgURL string `json:"tg_url"` //nolint: tagliatelle
TgQrCode string `json:"tg_qrcode"` //nolint: tagliatelle
TmeURL string `json:"tme_url"` //nolint: tagliatelle
TmeQrCode string `json:"tme_qrcode"` //nolint: tagliatelle
}
type Access struct {
base
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
Port uint `kong:"help='Port number. Default port is taken from configuration file, bind-to parameter',type:'uint',short='p'"` // 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
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
Hex bool `kong:"help='Print secret in hex encoding.',short='x'"`
}
func (c *Access) Run(cli *CLI, version string) error {
if err := c.ReadConfig(version); err != nil {
func (a *Access) Run(cli *CLI, version string) error {
conf, err := utils.ReadConfig(a.ConfigPath)
if err != nil {
return fmt.Errorf("cannot init config: %w", err)
}
return c.Execute(cli)
}
func (c *Access) Execute(cli *CLI) error {
resp := &accessResponse{}
resp.Secret.Base64 = c.Config.Secret.Base64()
resp.Secret.Hex = c.Config.Secret.Hex()
resp.Secret.Base64 = conf.Secret.Base64()
resp.Secret.Hex = conf.Secret.Hex()
ntw, err := makeNetwork(conf, version)
if err != nil {
return fmt.Errorf("cannot init network: %w", err)
}
wg := &sync.WaitGroup{}
wg.Add(2) // nolint: gomnd
go func() {
defer wg.Done()
ip := cli.Access.PublicIPv4
wg.Go(func() {
ip := a.PublicIPv4
if ip == nil {
ip = c.getIP("tcp4")
ip = getIP(ntw, "tcp4")
}
if ip != nil {
ip = ip.To4()
}
resp.IPv4 = c.makeURLs(ip, cli)
}()
go func() {
defer wg.Done()
ip := cli.Access.PublicIPv6
resp.IPv4 = a.makeURLs(conf, ip)
})
wg.Go(func() {
ip := a.PublicIPv6
if ip == nil {
ip = c.getIP("tcp6")
ip = getIP(ntw, "tcp6")
}
if ip != nil {
ip = ip.To16()
}
resp.IPv6 = c.makeURLs(ip, cli)
}()
resp.IPv6 = a.makeURLs(conf, ip)
})
wg.Wait()
@@ -100,58 +94,24 @@ func (c *Access) Execute(cli *CLI) error {
return nil
}
func (c *Access) getIP(protocol string) net.IP {
client := c.Network.MakeHTTPClient(func(ctx context.Context, network, address string) (net.Conn, error) {
return c.Network.DialContext(ctx, protocol, address) // nolint: wrapcheck
})
req, err := http.NewRequest(http.MethodGet, "https://ifconfig.co", nil) // nolint: noctx
if err != nil {
panic(err)
}
req.Header.Add("Accept", "text/plain")
resp, err := client.Do(req)
if err != nil {
return nil
}
if resp.StatusCode != http.StatusOK {
return nil
}
defer func() {
io.Copy(io.Discard, resp.Body) // nolint: errcheck
resp.Body.Close()
}()
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil
}
return net.ParseIP(strings.TrimSpace(string(data)))
}
func (c *Access) makeURLs(ip net.IP, cli *CLI) *accessResponseURLs {
func (a *Access) makeURLs(conf *config.Config, ip net.IP) *accessResponseURLs {
if ip == nil {
return nil
}
portNo := cli.Access.Port
portNo := a.Port
if portNo == 0 {
portNo = c.Config.BindTo.PortValue(0)
portNo = conf.BindTo.Port
}
values := url.Values{}
values.Set("server", ip.String())
values.Set("port", strconv.Itoa(int(portNo)))
if cli.Access.Hex {
values.Set("secret", c.Config.Secret.Hex())
if a.Hex {
values.Set("secret", conf.Secret.Hex())
} else {
values.Set("secret", c.Config.Secret.Base64())
values.Set("secret", conf.Secret.Base64())
}
urlQuery := values.Encode()
@@ -171,22 +131,8 @@ func (c *Access) makeURLs(ip net.IP, cli *CLI) *accessResponseURLs {
RawQuery: urlQuery,
}).String(),
}
rv.TgQrCode = c.makeQRCode(rv.TgURL)
rv.TmeQrCode = c.makeQRCode(rv.TmeURL)
rv.TgQrCode = utils.MakeQRCodeURL(rv.TgURL)
rv.TmeQrCode = utils.MakeQRCodeURL(rv.TmeURL)
return rv
}
func (c *Access) makeQRCode(data string) string {
values := url.Values{}
values.Set("qzone", "4")
values.Set("format", "svg")
values.Set("data", data)
return (&url.URL{
Scheme: "https",
Host: "api.qrserver.com",
Path: "v1/create-qr-code",
RawQuery: values.Encode(),
}).String()
}
-197
View File
@@ -1,197 +0,0 @@
package cli_test
import (
"net"
"net/http"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/9seconds/mtg/v2/internal/testlib"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/suite"
"github.com/xeipuuv/gojsonschema"
)
var accressResponseJSONSchema = func() *gojsonschema.Schema {
schema, err := gojsonschema.NewSchema(gojsonschema.NewStringLoader(`
{
"type": "object",
"required": ["secret"],
"additionalProperties": true,
"properties": {
"secret": {
"type": "object",
"required": [
"hex",
"base64"
],
"additionalProperties": false,
"properties": {
"hex": {
"type": "string",
"minLength": 34
},
"base64": {
"type": "string",
"minLength": 10
}
}
},
"ipv4": {
"$ref": "#/definitions/ip"
},
"ipv6": {
"$ref": "#/definitions/ip"
}
},
"definitions": {
"ip": {
"type": "object",
"required": [
"ip",
"port",
"tg_url",
"tg_qrcode",
"tme_url",
"tme_qrcode"
],
"additionalProperties": false,
"properties": {
"ip": {
"type": "string",
"minLength": 1,
"anyOf": [
{
"format": "ipv4"
},
{
"format": "ipv6"
}
]
},
"port": {
"type": "integer",
"multipleOf": 1.0,
"exclusiveMinimum": 0,
"exclusiveMaximum": 65536
},
"tg_url": {
"type": "string",
"minLength": 1,
"format": "uri"
},
"tg_qrcode": {
"type": "string",
"minLength": 1,
"format": "uri"
},
"tme_url": {
"type": "string",
"minLength": 1,
"format": "uri"
},
"tme_qrcode": {
"type": "string",
"minLength": 1,
"format": "uri"
}
}
}
}
}
`))
if err != nil {
panic(err)
}
return schema
}()
type AccessTestSuite struct {
CommonTestSuite
}
func (suite *AccessTestSuite) SetupTest() {
suite.CommonTestSuite.SetupTest()
suite.cli.Access.Config = &config.Config{}
suite.cli.Access.Config.Secret = mtglib.GenerateSecret("google.com")
suite.cli.Access.Network = suite.networkMock
suite.NoError(
suite.cli.Access.Config.BindTo.UnmarshalText([]byte("0.0.0.0:80")))
}
func (suite *AccessTestSuite) TestGenerateNoCalls() {
suite.cli.Access.PublicIPv4 = net.ParseIP("10.0.0.10")
suite.cli.Access.PublicIPv6 = net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
output := testlib.CaptureStdout(func() {
suite.NoError(suite.cli.Access.Execute(suite.cli))
})
validated, err := accressResponseJSONSchema.Validate(
gojsonschema.NewStringLoader(output))
suite.NoError(err)
suite.Empty(validated.Errors())
suite.True(validated.Valid())
suite.Contains(output, "10.0.0.10")
suite.Contains(output, "2001:db8:85a3::8a2e:370:7334")
suite.Contains(output, "ipv4")
suite.Contains(output, "ipv6")
suite.Contains(output, suite.cli.Access.Config.Secret.Base64())
suite.Contains(output, suite.cli.Access.Config.Secret.Hex())
}
func (suite *AccessTestSuite) TestGenerateIPv4Call() {
suite.cli.Access.PublicIPv6 = net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
httpmock.RegisterResponder(http.MethodGet, "https://ifconfig.co",
httpmock.NewStringResponder(http.StatusOK, "10.11.12.13"))
output := testlib.CaptureStdout(func() {
suite.NoError(suite.cli.Access.Execute(suite.cli))
})
validated, err := accressResponseJSONSchema.Validate(
gojsonschema.NewStringLoader(output))
suite.NoError(err)
suite.Empty(validated.Errors())
suite.True(validated.Valid())
suite.Contains(output, "10.11.12.13")
suite.Contains(output, "2001:db8:85a3::8a2e:370:7334")
suite.Contains(output, "ipv4")
suite.Contains(output, "ipv6")
suite.Contains(output, suite.cli.Access.Config.Secret.Base64())
suite.Contains(output, suite.cli.Access.Config.Secret.Hex())
}
func (suite *AccessTestSuite) TestIPv4CallFail() {
suite.cli.Access.PublicIPv6 = net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
httpmock.RegisterResponder(http.MethodGet, "https://ifconfig.co",
httpmock.NewStringResponder(http.StatusForbidden, ""))
output := testlib.CaptureStdout(func() {
suite.NoError(suite.cli.Access.Execute(suite.cli))
})
validated, err := accressResponseJSONSchema.Validate(
gojsonschema.NewStringLoader(output))
suite.NoError(err)
suite.Empty(validated.Errors())
suite.True(validated.Valid())
suite.Contains(output, "2001:db8:85a3::8a2e:370:7334")
suite.NotContains(output, "ipv4")
suite.Contains(output, "ipv6")
suite.Contains(output, suite.cli.Access.Config.Secret.Base64())
suite.Contains(output, suite.cli.Access.Config.Secret.Hex())
}
func TestAccess(t *testing.T) { // nolint: paralleltest
suite.Run(t, &AccessTestSuite{})
}
-81
View File
@@ -1,81 +0,0 @@
package cli
import (
"fmt"
"net"
"net/url"
"os"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/network"
)
type base struct {
ConfigPath string `kong:"arg,required,type='existingfile',help='Path to the configuration file.',name='config-path'"` // nolint: lll
Network mtglib.Network `kong:"-"`
Config *config.Config `kong:"-"`
}
func (b *base) ReadConfig(version string) error {
content, err := os.ReadFile(b.ConfigPath)
if err != nil {
return fmt.Errorf("cannot read config file: %w", err)
}
conf, err := config.Parse(content)
if err != nil {
return fmt.Errorf("cannot parse config: %w", err)
}
ntw, err := b.makeNetwork(conf, version)
if err != nil {
return fmt.Errorf("cannot build a network: %w", err)
}
b.Config = conf
b.Network = ntw
return nil
}
func (b *base) makeNetwork(conf *config.Config, version string) (mtglib.Network, error) {
tcpTimeout := conf.Network.Timeout.TCP.Value(network.DefaultTimeout)
httpTimeout := conf.Network.Timeout.HTTP.Value(network.DefaultHTTPTimeout)
dohIP := conf.Network.DOHIP.Value(net.ParseIP(network.DefaultDOHHostname)).String()
bufferSize := conf.TCPBuffer.Value(network.DefaultBufferSize)
userAgent := "mtg/" + version
baseDialer, err := network.NewDefaultDialer(tcpTimeout, int(bufferSize))
if err != nil {
return nil, fmt.Errorf("cannot build a default dialer: %w", err)
}
proxyURLs := make([]*url.URL, 0, len(conf.Network.Proxies))
for _, v := range conf.Network.Proxies {
if value := v.Value(nil); value != nil {
proxyURLs = append(proxyURLs, v.Value(nil))
}
}
switch len(proxyURLs) {
case 0:
return network.NewNetwork(baseDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
case 1:
socksDialer, err := network.NewSocks5Dialer(baseDialer, proxyURLs[0])
if err != nil {
return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
}
return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
}
socksDialer, err := network.NewLoadBalancedSocks5Dialer(baseDialer, proxyURLs)
if err != nil {
return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
}
return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
}
-33
View File
@@ -1,33 +0,0 @@
package cli
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/suite"
)
type BaseTestSuite struct {
suite.Suite
b base
}
func (suite *BaseTestSuite) SetupTest() {
suite.b = base{}
}
func (suite *BaseTestSuite) TestReadConfigNok() {
suite.b.ConfigPath = filepath.Join("testdata", "unknown")
suite.Error(suite.b.ReadConfig("dev"))
}
func (suite *BaseTestSuite) TestReadConfig() {
suite.b.ConfigPath = filepath.Join("testdata", "minimal.toml")
suite.NoError(suite.b.ReadConfig("dev"))
}
func TestBase(t *testing.T) {
t.Parallel()
suite.Run(t, &BaseTestSuite{})
}
+3 -1
View File
@@ -4,7 +4,9 @@ import "github.com/alecthomas/kong"
type CLI struct {
GenerateSecret GenerateSecret `kong:"cmd,help='Generate new proxy secret'"`
Doctor Doctor `kong:"cmd,help='Check that proxy can run correctly'"`
Access Access `kong:"cmd,help='Print access information.'"`
Run Proxy `kong:"cmd,help='Run proxy.'"`
Run Run `kong:"cmd,help='Run proxy.'"`
SimpleRun SimpleRun `kong:"cmd,help='Run proxy without config file.'"`
Version kong.VersionFlag `kong:"help='Print version.',short='v'"`
}
+368
View File
@@ -0,0 +1,368 @@
package cli
import (
"context"
"errors"
"fmt"
"maps"
"net"
"os"
"slices"
"strconv"
"strings"
"text/template"
"time"
"github.com/9seconds/mtg/v2/essentials"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/9seconds/mtg/v2/internal/utils"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/network/v2"
"github.com/beevik/ntp"
)
var (
tplError = template.Must(
template.New("").Parse(" ‼️ {{ .description }}: {{ .error }}\n"),
)
tplWDeprecatedConfig = template.Must(
template.New("").
Parse(` ⚠️ Option {{ .old | printf "%q" }}{{ if .old_section }} from section [{{ .old_section }}]{{ end }} is deprecated and will be removed in v{{ .when }}. Please use {{ .new | printf "%q" }}{{ if .new_section }} in [{{ .new_section }}] section{{ end }} instead.` + "\n"),
)
tplOTimeSkewness = template.Must(
template.New("").
Parse(" ✅ Time drift is {{ .drift }}, but tolerate-time-skewness is {{ .value }}\n"),
)
tplWTimeSkewness = template.Must(
template.New("").
Parse(" ⚠️ Time drift is {{ .drift }}, but tolerate-time-skewness is {{ .value }}. Please check ntp.\n"),
)
tplETimeSkewness = template.Must(
template.New("").
Parse(" ❌ Time drift is {{ .drift }}, but tolerate-time-skewness is {{ .value }}. You will get many rejected connections!\n"),
)
tplODCConnect = template.Must(
template.New("").Parse(" ✅ DC {{ .dc }}\n"),
)
tplEDCConnect = template.Must(
template.New("").Parse(" ❌ DC {{ .dc }}: {{ .error }}\n"),
)
tplODNSSNIMatch = template.Must(
template.New("").Parse(" ✅ IP address {{ .ip }} matches secret hostname {{ .hostname }}\n"),
)
tplEDNSSNIMatch = template.Must(
template.New("").Parse(" ❌ Hostname {{ .hostname }} {{ if .resolved }}is resolved to {{ .resolved }} addresses, not {{ if .ip4 }}{{ .ip4 }}{{ else }}{{ .ip6 }}{{ end }}{{ else }}cannot be resolved to any host{{ end }}\n"),
)
tplOFrontingDomain = template.Must(
template.New("").Parse(" ✅ {{ .address }} is reachable\n"),
)
tplEFrontingDomain = template.Must(
template.New("").Parse(" ❌ {{ .address }}: {{ .error }}\n"),
)
)
type Doctor struct {
conf *config.Config
ConfigPath string `kong:"arg,required,type='existingfile',help='Path to the configuration file.',name='config-path'"` //nolint: lll
}
func (d *Doctor) Run(cli *CLI, version string) error {
conf, err := utils.ReadConfig(d.ConfigPath)
if err != nil {
return fmt.Errorf("cannot init config: %w", err)
}
d.conf = conf
fmt.Println("Deprecated options")
everythingOK := d.checkDeprecatedConfig()
fmt.Println("Time skewness")
everythingOK = d.checkTimeSkewness() && everythingOK
resolver, err := network.GetDNS(conf.GetDNS())
if err != nil {
return fmt.Errorf("cannot create DNS resolver: %w", err)
}
base := network.New(
resolver,
"",
conf.Network.Timeout.TCP.Get(10*time.Second),
conf.Network.Timeout.HTTP.Get(0),
conf.Network.Timeout.Idle.Get(0),
)
fmt.Println("Validate native network connectivity")
everythingOK = d.checkNetwork(base) && everythingOK
for _, url := range conf.Network.Proxies {
value, err := network.NewProxyNetwork(base, url.Get(nil))
if err != nil {
return err
}
fmt.Printf("Validate network connectivity with proxy %s\n", url.Get(nil))
everythingOK = d.checkNetwork(value) && everythingOK
}
fmt.Println("Validate fronting domain connectivity")
everythingOK = d.checkFrontingDomain(base) && everythingOK
fmt.Println("Validate SNI-DNS match")
everythingOK = d.checkSecretHost(resolver, base) && everythingOK
if !everythingOK {
os.Exit(1)
}
return nil
}
func (d *Doctor) checkDeprecatedConfig() bool {
ok := true
if d.conf.DomainFrontingIP.Value != nil {
ok = false
tplWDeprecatedConfig.Execute(os.Stdout, map[string]string{ //nolint: errcheck
"when": "2.3.0",
"old": "domain-fronting-ip",
"old_section": "",
"new": "ip",
"new_section": "domain-fronting",
})
}
if d.conf.DomainFrontingPort.Value != 0 {
ok = false
tplWDeprecatedConfig.Execute(os.Stdout, map[string]string{ //nolint: errcheck
"when": "2.3.0",
"old": "domain-fronting-port",
"old_section": "",
"new": "port",
"new_section": "domain-fronting",
})
}
if d.conf.DomainFrontingProxyProtocol.Value {
ok = false
tplWDeprecatedConfig.Execute(os.Stdout, map[string]string{ //nolint: errcheck
"when": "2.3.0",
"old": "domain-fronting-proxy-protocol",
"old_section": "",
"new": "proxy-protocol",
"new_section": "domain-fronting",
})
}
if d.conf.Network.DOHIP.Value != nil {
ok = false
tplWDeprecatedConfig.Execute(os.Stdout, map[string]string{ //nolint: errcheck
"when": "2.3.0",
"old": "doh-ip",
"old_section": "network",
"new": "dns",
"new_section": "network",
})
}
if ok {
fmt.Println(" ✅ All good")
}
return ok
}
func (d *Doctor) checkTimeSkewness() bool {
response, err := ntp.Query("0.pool.ntp.org")
if err != nil {
tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck
"description": "cannot access ntp pool",
"error": err,
})
return false
}
skewness := response.ClockOffset.Abs()
confValue := d.conf.TolerateTimeSkewness.Get(mtglib.DefaultTolerateTimeSkewness)
diff := float64(skewness) / float64(confValue)
tplData := map[string]any{
"drift": response.ClockOffset,
"value": confValue,
}
switch {
case diff < 0.3:
tplOTimeSkewness.Execute(os.Stdout, tplData) //nolint: errcheck
return true
case diff < 0.7:
tplWTimeSkewness.Execute(os.Stdout, tplData) //nolint: errcheck
default:
tplETimeSkewness.Execute(os.Stdout, tplData) //nolint: errcheck
}
return false
}
func (d *Doctor) checkNetwork(ntw mtglib.Network) bool {
dcs := slices.Collect(maps.Keys(essentials.TelegramCoreAddresses))
slices.Sort(dcs)
ok := true
for _, dc := range dcs {
err := d.checkNetworkAddresses(ntw, essentials.TelegramCoreAddresses[dc])
if err == nil {
tplODCConnect.Execute(os.Stdout, map[string]any{ //nolint: errcheck
"dc": dc,
})
} else {
tplEDCConnect.Execute(os.Stdout, map[string]any{ //nolint: errcheck
"dc": dc,
"error": err,
})
ok = false
}
}
return ok
}
func (d *Doctor) checkNetworkAddresses(ntw mtglib.Network, addresses []string) error {
checkAddresses := []string{}
switch d.conf.PreferIP.Get("prefer-ip4") {
case "only-ipv4":
for _, addr := range addresses {
host, _, err := net.SplitHostPort(addr)
if err != nil {
panic(err)
}
if ip := net.ParseIP(host); ip != nil && ip.To4() != nil {
checkAddresses = append(checkAddresses, addr)
}
}
case "only-ipv6":
for _, addr := range addresses {
host, _, err := net.SplitHostPort(addr)
if err != nil {
panic(err)
}
if ip := net.ParseIP(host); ip != nil && ip.To4() == nil {
checkAddresses = append(checkAddresses, addr)
}
}
default:
checkAddresses = addresses
}
if len(checkAddresses) == 0 {
return fmt.Errorf("no suitable addresses after IP version filtering")
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var (
conn net.Conn
err error
)
for _, addr := range checkAddresses {
conn, err = ntw.DialContext(ctx, "tcp", addr)
if err != nil {
continue
}
conn.Close() //nolint: errcheck
return nil
}
return err
}
func (d *Doctor) checkFrontingDomain(ntw mtglib.Network) bool {
host := d.conf.Secret.Host
if ip := d.conf.GetDomainFrontingIP(nil); ip != "" {
host = ip
}
port := d.conf.GetDomainFrontingPort(mtglib.DefaultDomainFrontingPort)
address := net.JoinHostPort(host, strconv.Itoa(int(port)))
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
dialer := ntw.NativeDialer()
conn, err := dialer.DialContext(ctx, "tcp", address)
if err != nil {
tplEFrontingDomain.Execute(os.Stdout, map[string]any{ //nolint: errcheck
"address": address,
"error": err,
})
return false
}
conn.Close() //nolint: errcheck
tplOFrontingDomain.Execute(os.Stdout, map[string]any{ //nolint: errcheck
"address": address,
})
return true
}
func (d *Doctor) checkSecretHost(resolver *net.Resolver, ntw mtglib.Network) bool {
addresses, err := resolver.LookupIPAddr(context.Background(), d.conf.Secret.Host)
if err != nil {
tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck
"description": fmt.Sprintf("cannot resolve DNS name of %s", d.conf.Secret.Host),
"error": err,
})
return false
}
ourIP4 := getIP(ntw, "tcp4")
ourIP6 := getIP(ntw, "tcp6")
if ourIP4 == nil && ourIP6 == nil {
tplError.Execute(os.Stdout, map[string]any{ //nolint: errcheck
"description": "cannot detect public IP address",
"error": errors.New("ifconfig.co is unreachable for both IPv4 and IPv6"),
})
return false
}
strAddresses := []string{}
for _, value := range addresses {
if (ourIP4 != nil && value.IP.String() == ourIP4.String()) ||
(ourIP6 != nil && value.IP.String() == ourIP6.String()) {
tplODNSSNIMatch.Execute(os.Stdout, map[string]any{ //nolint: errcheck
"ip": value.IP,
"hostname": d.conf.Secret.Host,
})
return true
}
strAddresses = append(strAddresses, `"`+value.IP.String()+`"`)
}
tplEDNSSNIMatch.Execute(os.Stdout, map[string]any{ //nolint: errcheck
"hostname": d.conf.Secret.Host,
"resolved": strings.Join(strAddresses, ", "),
"ip4": ourIP4,
"ip6": ourIP6,
})
return false
}
+4 -4
View File
@@ -11,13 +11,13 @@ type GenerateSecret struct {
Hex bool `kong:"help='Print secret in hex encoding.',short='x'"`
}
func (c *GenerateSecret) Run(cli *CLI, _ string) error {
func (g *GenerateSecret) Run(cli *CLI, _ string) error {
secret := mtglib.GenerateSecret(cli.GenerateSecret.HostName)
if cli.GenerateSecret.Hex {
fmt.Println(secret.Hex()) // nolint: forbidigo
if g.Hex {
fmt.Println(secret.Hex()) //nolint: forbidigo
} else {
fmt.Println(secret.Base64()) // nolint: forbidigo
fmt.Println(secret.Base64()) //nolint: forbidigo
}
return nil
-51
View File
@@ -1,51 +0,0 @@
package cli_test
import (
"strings"
"testing"
"github.com/9seconds/mtg/v2/internal/testlib"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/stretchr/testify/suite"
)
type GenerateSecretTestSuite struct {
CommonTestSuite
}
func (suite *GenerateSecretTestSuite) SetupTest() {
suite.CommonTestSuite.SetupTest()
suite.cli.GenerateSecret.HostName = "google.com"
}
func (suite *GenerateSecretTestSuite) TestDefault() {
output := testlib.CaptureStdout(func() {
suite.NoError(suite.cli.GenerateSecret.Run(suite.cli, "dev"))
})
suite.True(strings.HasPrefix(output, "7"))
secret, err := mtglib.ParseSecret(output)
suite.NoError(err)
suite.True(secret.Valid())
suite.Equal("google.com", secret.Host)
}
func (suite *GenerateSecretTestSuite) TestHex() {
suite.cli.GenerateSecret.Hex = true
output := testlib.CaptureStdout(func() {
suite.NoError(suite.cli.GenerateSecret.Run(suite.cli, "dev"))
})
suite.True(strings.HasPrefix(output, "ee"))
secret, err := mtglib.ParseSecret(output)
suite.NoError(err)
suite.True(secret.Valid())
suite.Equal("google.com", secret.Host)
}
func TestGenerateSecret(t *testing.T) {
t.Parallel()
suite.Run(t, &GenerateSecretTestSuite{})
}
-37
View File
@@ -1,37 +0,0 @@
package cli_test
import (
"net/http"
"github.com/9seconds/mtg/v2/internal/cli"
"github.com/9seconds/mtg/v2/internal/testlib"
"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)
type CommonTestSuite struct {
suite.Suite
cli *cli.CLI
networkMock *testlib.MtglibNetworkMock
httpClient *http.Client
}
func (suite *CommonTestSuite) SetupTest() {
suite.networkMock = &testlib.MtglibNetworkMock{}
suite.httpClient = &http.Client{}
suite.cli = &cli.CLI{}
httpmock.ActivateNonDefault(suite.httpClient)
suite.networkMock.
On("MakeHTTPClient", mock.Anything).
Maybe().
Return(suite.httpClient)
}
func (suite *CommonTestSuite) TearDownTest() {
suite.networkMock.AssertExpectations(suite.T())
httpmock.DeactivateAndReset()
}
-167
View File
@@ -1,167 +0,0 @@
package cli
import (
"fmt"
"net"
"os"
"github.com/9seconds/mtg/v2/antireplay"
"github.com/9seconds/mtg/v2/events"
"github.com/9seconds/mtg/v2/internal/utils"
"github.com/9seconds/mtg/v2/ipblocklist"
"github.com/9seconds/mtg/v2/logger"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/stats"
"github.com/rs/zerolog"
)
type Proxy struct {
base
}
func (c *Proxy) Run(cli *CLI, version string) error {
if err := c.ReadConfig(version); err != nil {
return fmt.Errorf("cannot init config: %w", err)
}
return c.Execute()
}
func (c *Proxy) Execute() error {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
zerolog.TimestampFieldName = "timestamp"
zerolog.LevelFieldName = "level"
if c.Config.Debug {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
} else {
zerolog.SetGlobalLevel(zerolog.WarnLevel)
}
ctx := utils.RootContext()
opts := mtglib.ProxyOpts{
Logger: logger.NewZeroLogger(zerolog.New(os.Stdout).With().Timestamp().Logger()),
Network: c.Network,
AntiReplayCache: antireplay.NewNoop(),
IPBlocklist: ipblocklist.NewNoop(),
EventStream: events.NewNoopStream(),
Secret: c.Config.Secret,
BufferSize: c.Config.TCPBuffer.Value(mtglib.DefaultBufferSize),
DomainFrontingPort: c.Config.DomainFrontingPort.Value(mtglib.DefaultDomainFrontingPort),
IdleTimeout: c.Config.Network.Timeout.Idle.Value(mtglib.DefaultIdleTimeout),
PreferIP: c.Config.PreferIP.Value(mtglib.DefaultPreferIP),
}
opts.Logger.BindStr("configuration", c.Config.String()).Debug("configuration")
c.setupAntiReplayCache(&opts)
if err := c.setupIPBlocklist(&opts); err != nil {
return fmt.Errorf("cannot setup ipblocklist: %w", err)
}
if err := c.setupEventStream(&opts); err != nil {
return fmt.Errorf("cannot setup event stream: %w", err)
}
proxy, err := mtglib.NewProxy(opts)
if err != nil {
return fmt.Errorf("cannot create a proxy: %w", err)
}
listener, err := net.Listen("tcp", c.Config.BindTo.String())
if err != nil {
return fmt.Errorf("cannot start proxy: %w", err)
}
go proxy.Serve(listener) // nolint: errcheck
<-ctx.Done()
listener.Close()
proxy.Shutdown()
return nil
}
func (c *Proxy) setupAntiReplayCache(opts *mtglib.ProxyOpts) {
if !c.Config.Defense.AntiReplay.Enabled {
return
}
opts.AntiReplayCache = antireplay.NewStableBloomFilter(
c.Config.Defense.AntiReplay.MaxSize.Value(antireplay.DefaultStableBloomFilterMaxSize),
c.Config.Defense.AntiReplay.ErrorRate.Value(antireplay.DefaultStableBloomFilterErrorRate),
)
}
func (c *Proxy) setupIPBlocklist(opts *mtglib.ProxyOpts) error {
if !c.Config.Defense.Blocklist.Enabled {
return nil
}
remoteURLs := []string{}
localFiles := []string{}
for _, v := range c.Config.Defense.Blocklist.URLs {
if v.IsRemote() {
remoteURLs = append(remoteURLs, v.String())
} else {
localFiles = append(localFiles, v.String())
}
}
firehol, err := ipblocklist.NewFirehol(opts.Logger.Named("ipblockist"),
c.Network,
c.Config.Defense.Blocklist.DownloadConcurrency,
remoteURLs,
localFiles)
if err != nil {
return err // nolint: wrapcheck
}
go firehol.Run(c.Config.Defense.Blocklist.UpdateEach.Value(ipblocklist.DefaultFireholUpdateEach))
opts.IPBlocklist = firehol
return nil
}
func (c *Proxy) setupEventStream(opts *mtglib.ProxyOpts) error {
factories := make([]events.ObserverFactory, 0, 2)
if c.Config.Stats.StatsD.Enabled {
statsdFactory, err := stats.NewStatsd(
c.Config.Stats.StatsD.Address.String(),
opts.Logger.Named("statsd"),
c.Config.Stats.StatsD.MetricPrefix.Value(stats.DefaultStatsdMetricPrefix),
c.Config.Stats.StatsD.TagFormat.Value(stats.DefaultStatsdTagFormat))
if err != nil {
return fmt.Errorf("cannot build statsd observer: %w", err)
}
factories = append(factories, statsdFactory.Make)
}
if c.Config.Stats.Prometheus.Enabled {
prometheus := stats.NewPrometheus(
c.Config.Stats.Prometheus.MetricPrefix.Value(stats.DefaultMetricPrefix),
c.Config.Stats.Prometheus.HTTPPath.Value("/"),
)
listener, err := net.Listen("tcp", c.Config.Stats.Prometheus.BindTo.String())
if err != nil {
return fmt.Errorf("cannot start a listener for prometheus: %w", err)
}
go prometheus.Serve(listener) // nolint: errcheck
factories = append(factories, prometheus.Make)
}
if len(factories) > 0 {
opts.EventStream = events.NewEventStream(factories)
}
return nil
}
+20
View File
@@ -0,0 +1,20 @@
package cli
import (
"fmt"
"github.com/9seconds/mtg/v2/internal/utils"
)
type Run struct {
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 {
conf, err := utils.ReadConfig(r.ConfigPath)
if err != nil {
return fmt.Errorf("cannot init config: %w", err)
}
return runProxy(conf, version)
}
+299
View File
@@ -0,0 +1,299 @@
package cli
import (
"context"
"fmt"
"net"
"os"
"github.com/9seconds/mtg/v2/antireplay"
"github.com/9seconds/mtg/v2/events"
"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/ipblocklist"
"github.com/9seconds/mtg/v2/ipblocklist/files"
"github.com/9seconds/mtg/v2/logger"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/network/v2"
"github.com/9seconds/mtg/v2/stats"
"github.com/pires/go-proxyproto"
"github.com/rs/zerolog"
"github.com/yl2chen/cidranger"
)
func makeLogger(conf *config.Config) mtglib.Logger {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
zerolog.TimestampFieldName = "timestamp"
zerolog.LevelFieldName = "level"
if conf.Debug.Get(false) {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
} else {
zerolog.SetGlobalLevel(zerolog.WarnLevel)
}
baseLogger := zerolog.New(os.Stdout).With().Timestamp().Logger()
return logger.NewZeroLogger(baseLogger)
}
func makeNetwork(conf *config.Config, version string) (mtglib.Network, error) {
resolver, err := network.GetDNS(conf.GetDNS())
if err != nil {
return nil, fmt.Errorf("cannot create DNS resolver: %w", err)
}
base := network.New(
resolver,
"",
conf.Network.Timeout.TCP.Get(0),
conf.Network.Timeout.HTTP.Get(0),
conf.Network.Timeout.Idle.Get(0),
)
proxyDialers := make([]mtglib.Network, len(conf.Network.Proxies))
for idx, v := range conf.Network.Proxies {
value, err := network.NewProxyNetwork(base, v.Get(nil))
if err != nil {
return nil, fmt.Errorf("cannot use %v for proxy url: %w", v.Get(nil), err)
}
proxyDialers[idx] = value
}
switch len(proxyDialers) {
case 0:
return base, nil
case 1:
return proxyDialers[0], nil
}
value, err := network.Join(proxyDialers...)
if err != nil {
panic(err)
}
return value, nil
}
func makeAntiReplayCache(conf *config.Config) mtglib.AntiReplayCache {
if !conf.Defense.AntiReplay.Enabled.Get(false) {
return antireplay.NewNoop()
}
return antireplay.NewStableBloomFilter(
conf.Defense.AntiReplay.MaxSize.Get(antireplay.DefaultStableBloomFilterMaxSize),
conf.Defense.AntiReplay.ErrorRate.Get(antireplay.DefaultStableBloomFilterErrorRate),
)
}
func makeIPBlocklist(conf config.ListConfig,
logger mtglib.Logger,
ntw mtglib.Network,
updateCallback ipblocklist.FireholUpdateCallback,
) (mtglib.IPBlocklist, error) {
if !conf.Enabled.Get(false) {
return ipblocklist.NewNoop(), nil
}
remoteURLs := []string{}
localFiles := []string{}
for _, v := range conf.URLs {
if v.IsRemote() {
remoteURLs = append(remoteURLs, v.String())
} else {
localFiles = append(localFiles, v.String())
}
}
blocklist, err := ipblocklist.NewFirehol(logger.Named("ipblockist"),
ntw,
conf.DownloadConcurrency.Get(1),
remoteURLs,
localFiles,
updateCallback)
if err != nil {
return nil, fmt.Errorf("incorrect parameters for firehol: %w", err)
}
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) {
factories := make([]events.ObserverFactory, 0, 2)
if conf.Stats.StatsD.Enabled.Get(false) {
statsdFactory, err := stats.NewStatsd(
conf.Stats.StatsD.Address.Get(""),
logger.Named("statsd"),
conf.Stats.StatsD.MetricPrefix.Get(stats.DefaultStatsdMetricPrefix),
conf.Stats.StatsD.TagFormat.Get(stats.DefaultStatsdTagFormat))
if err != nil {
return nil, fmt.Errorf("cannot build statsd observer: %w", err)
}
factories = append(factories, statsdFactory.Make)
}
if conf.Stats.Prometheus.Enabled.Get(false) {
prometheus := stats.NewPrometheus(
conf.Stats.Prometheus.MetricPrefix.Get(stats.DefaultMetricPrefix),
conf.Stats.Prometheus.HTTPPath.Get("/"),
)
listener, err := net.Listen("tcp", conf.Stats.Prometheus.BindTo.Get(""))
if err != nil {
return nil, fmt.Errorf("cannot start a listener for prometheus: %w", err)
}
go prometheus.Serve(listener) //nolint: errcheck
factories = append(factories, prometheus.Make)
}
if len(factories) > 0 {
return events.NewEventStream(factories), nil
}
return events.NewNoopStream(), nil
}
func runProxy(conf *config.Config, version string) error { //nolint: funlen
logger := makeLogger(conf)
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)
if err != nil {
return fmt.Errorf("cannot build network: %w", err)
}
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 {
return fmt.Errorf("cannot build ip blocklist: %w", err)
}
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 {
return fmt.Errorf("cannot build ip allowlist: %w", err)
}
doppelGangerURLs := make([]string, len(conf.Defense.Doppelganger.URLs))
for i, v := range conf.Defense.Doppelganger.URLs {
doppelGangerURLs[i] = v.String()
}
opts := mtglib.ProxyOpts{
Logger: logger,
Network: ntw,
AntiReplayCache: makeAntiReplayCache(conf),
IPBlocklist: blocklist,
IPAllowlist: allowlist,
EventStream: eventStream,
Secret: conf.Secret,
Concurrency: conf.GetConcurrency(mtglib.DefaultConcurrency),
DomainFrontingPort: conf.GetDomainFrontingPort(mtglib.DefaultDomainFrontingPort),
DomainFrontingIP: conf.GetDomainFrontingIP(nil),
DomainFrontingProxyProtocol: conf.GetDomainFrontingProxyProtocol(false),
PreferIP: conf.PreferIP.Get(mtglib.DefaultPreferIP),
AutoUpdate: conf.AutoUpdate.Get(false),
AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false),
TolerateTimeSkewness: conf.TolerateTimeSkewness.Value,
DoppelGangerURLs: doppelGangerURLs,
DoppelGangerPerRaid: conf.Defense.Doppelganger.Repeats.Get(mtglib.DoppelGangerPerRaid),
DoppelGangerEach: conf.Defense.Doppelganger.UpdateEach.Get(mtglib.DoppelGangerEach),
DoppelGangerDRS: conf.Defense.Doppelganger.DRS.Get(false),
}
proxy, err := mtglib.NewProxy(opts)
if err != nil {
return fmt.Errorf("cannot create a proxy: %w", err)
}
listener, err := utils.NewListener(conf.BindTo.Get(""), 0)
if err != nil {
return fmt.Errorf("cannot start proxy: %w", err)
}
if conf.ProxyProtocolListener.Get(false) {
listener = &proxyprotocol.ListenerAdapter{
Listener: proxyproto.Listener{
Listener: listener,
},
}
}
ctx := utils.RootContext()
go proxy.Serve(listener) //nolint: errcheck
<-ctx.Done()
listener.Close() //nolint: errcheck
proxy.Shutdown()
return nil
}
+96
View File
@@ -0,0 +1,96 @@
package cli
import (
"fmt"
"net"
"strconv"
"time"
"github.com/9seconds/mtg/v2/internal/config"
)
type SimpleRun struct {
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.'"`
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
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
DomainFrontingPort uint64 `kong:"name='domain-fronting-port',short='p',default='443',help='A port to access for domain fronting.'"` //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
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
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,funlen
conf := &config.Config{}
if err := conf.BindTo.Set(s.BindTo); err != nil {
return fmt.Errorf("incorrect bind-to parameter: %w", err)
}
if err := conf.Secret.Set(s.Secret); err != nil {
return fmt.Errorf("incorrect secret: %w", err)
}
if err := conf.Concurrency.Set(strconv.FormatUint(s.Concurrency, 10)); err != nil {
return fmt.Errorf("incorrect concurrency: %w", err)
}
if err := conf.PreferIP.Set(s.PreferIP); err != nil {
return fmt.Errorf("incorrect prefer-ip: %w", err)
}
if err := conf.DomainFrontingPort.Set(strconv.FormatUint(s.DomainFrontingPort, 10)); err != nil {
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 {
return fmt.Errorf("incorrect doh-ip: %w", err)
}
if err := conf.Network.Timeout.TCP.Set(s.Timeout.String()); err != nil {
return fmt.Errorf("incorrect timeout: %w", err)
}
if err := conf.Network.Timeout.HTTP.Set(s.Timeout.String()); err != nil {
return fmt.Errorf("incorrect timeout: %w", err)
}
if err := conf.Network.Timeout.Idle.Set(s.Timeout.String()); err != nil {
return fmt.Errorf("incorrect timeout: %w", err)
}
if err := conf.Defense.AntiReplay.MaxSize.Set(s.AntiReplayCacheSize); err != nil {
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.AllowFallbackOnUnknownDC.Value = true
conf.Defense.AntiReplay.Enabled.Value = true
if err := conf.Validate(); err != nil {
return fmt.Errorf("invalid result configuration: %w", err)
}
return runProxy(conf, version)
}
+51
View File
@@ -0,0 +1,51 @@
package cli
import (
"context"
"io"
"net"
"net/http"
"strings"
"github.com/9seconds/mtg/v2/essentials"
"github.com/9seconds/mtg/v2/mtglib"
)
func getIP(ntw mtglib.Network, protocol string) net.IP {
dialer := ntw.NativeDialer()
client := ntw.MakeHTTPClient(func(ctx context.Context, network, address string) (essentials.Conn, error) {
conn, err := dialer.DialContext(ctx, protocol, address)
if err != nil {
return nil, err
}
return essentials.WrapNetConn(conn), err
})
req, err := http.NewRequest(http.MethodGet, "https://ifconfig.co", nil) //nolint: noctx
if err != nil {
panic(err)
}
req.Header.Add("Accept", "text/plain")
resp, err := client.Do(req)
if err != nil {
return nil
}
if resp.StatusCode != http.StatusOK {
return nil
}
defer func() {
io.Copy(io.Discard, resp.Body) //nolint: errcheck
resp.Body.Close() //nolint: errcheck
}()
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil
}
return net.ParseIP(strings.TrimSpace(string(data)))
}
+96 -105
View File
@@ -4,32 +4,57 @@ import (
"bytes"
"encoding/json"
"fmt"
"net"
"net/url"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/pelletier/go-toml"
)
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 {
Debug bool `json:"debug"`
Secret mtglib.Secret `json:"secret"`
BindTo TypeHostPort `json:"bind-to"`
TCPBuffer TypeBytes `json:"tcp-buffer"`
PreferIP TypePreferIP `json:"prefer-ip"`
DomainFrontingPort TypePort `json:"domain-fronting-port"`
TolerateTimeSkewness TypeDuration `json:"tolerate-time-skewness"`
Concurrency uint `json:"concurrency"`
Defense struct {
Debug TypeBool `json:"debug"`
AllowFallbackOnUnknownDC TypeBool `json:"allowFallbackOnUnknownDc"`
Secret mtglib.Secret `json:"secret"`
BindTo TypeHostPort `json:"bindTo"`
ProxyProtocolListener TypeBool `json:"proxyProtocolListener"`
PreferIP TypePreferIP `json:"preferIp"`
AutoUpdate TypeBool `json:"autoUpdate"`
DomainFrontingPort TypePort `json:"domainFrontingPort"`
DomainFrontingIP TypeIP `json:"domainFrontingIp"`
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 {
Enabled bool `json:"enabled"`
MaxSize TypeBytes `json:"max-size"`
ErrorRate TypeErrorRate `json:"error-rate"`
} `json:"anti-replay"`
Blocklist struct {
Enabled bool `json:"enabled"`
DownloadConcurrency uint `json:"download-concurrency"`
URLs []TypeBlocklistURI `json:"urls"`
UpdateEach TypeDuration `json:"update-each"`
} `json:"blocklist"`
Optional
MaxSize TypeBytes `json:"maxSize"`
ErrorRate TypeErrorRate `json:"errorRate"`
} `json:"antiReplay"`
Blocklist ListConfig `json:"blocklist"`
Allowlist ListConfig `json:"allowlist"`
Doppelganger struct {
URLs []TypeHttpsURL `json:"urls"`
Repeats TypeConcurrency `json:"repeats_per_raid"`
UpdateEach TypeDuration `json:"raid_each"`
DRS TypeBool `json:"drs"`
} `json:"doppelganger"`
} `json:"defense"`
Network struct {
Timeout struct {
@@ -37,31 +62,72 @@ type Config struct {
HTTP TypeDuration `json:"http"`
Idle TypeDuration `json:"idle"`
} `json:"timeout"`
DOHIP TypeIP `json:"doh-ip"`
Proxies []TypeURL `json:"proxies"`
DOHIP TypeIP `json:"dohIp"`
DNS TypeDNSURI `json:"dns"`
Proxies []TypeProxyURL `json:"proxies"`
} `json:"network"`
Stats struct {
StatsD struct {
Enabled bool `json:"enabled"`
Optional
Address TypeHostPort `json:"address"`
MetricPrefix TypeMetricPrefix `json:"metric-prefix"`
TagFormat TypeStatsdTagFormat `json:"tag-format"`
MetricPrefix TypeMetricPrefix `json:"metricPrefix"`
TagFormat TypeStatsdTagFormat `json:"tagFormat"`
} `json:"statsd"`
Prometheus struct {
Enabled bool `json:"enabled"`
BindTo TypeHostPort `json:"bind-to"`
HTTPPath TypeHTTPPath `json:"http-path"`
MetricPrefix TypeMetricPrefix `json:"metric-prefix"`
Optional
BindTo TypeHostPort `json:"bindTo"`
HTTPPath TypeHTTPPath `json:"httpPath"`
MetricPrefix TypeMetricPrefix `json:"metricPrefix"`
} `json:"prometheus"`
} `json:"stats"`
}
func (c *Config) GetConcurrency(defaultValue uint) uint {
if concurrency := c.Concurrency.Get(0); concurrency != 0 {
return concurrency
}
return c.Concurrency.Get(defaultValue)
}
func (c *Config) GetDNS() *url.URL {
var dohURL *url.URL
if dohIP := c.Network.DOHIP.Get(nil); dohIP != nil {
dohURL, _ = url.Parse("https://" + dohIP.String())
}
return c.Network.DNS.Get(dohURL)
}
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 {
if !c.Secret.Valid() {
return fmt.Errorf("invalid secret %s", c.Secret.String())
}
if len(c.BindTo.HostValue(nil)) == 0 || c.BindTo.PortValue(0) == 0 {
if c.BindTo.Get("") == "" {
return fmt.Errorf("incorrect bind-to parameter %s", c.BindTo.String())
}
@@ -80,78 +146,3 @@ func (c *Config) String() string {
return buf.String()
}
type configRaw struct {
Debug bool `toml:"debug" json:"debug,omitempty"`
Secret string `toml:"secret" json:"secret"`
BindTo string `toml:"bind-to" json:"bind-to"`
TCPBuffer string `toml:"tcp-buffer" json:"tcp-buffer,omitempty"`
PreferIP string `toml:"prefer-ip" json:"prefer-ip,omitempty"`
DomainFrontingPort uint `toml:"domain-fronting-port" json:"domain-fronting-port,omitempty"`
TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerate-time-skewness,omitempty"`
Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"`
Defense struct {
AntiReplay struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
MaxSize string `toml:"max-size" json:"max-size,omitempty"`
ErrorRate float64 `toml:"error-rate" json:"error-rate,omitempty"`
} `toml:"anti-replay" json:"anti-replay,omitempty"`
Blocklist struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
DownloadConcurrency uint `toml:"download-concurrency" json:"download-concurrency,omitempty"`
URLs []string `toml:"urls" json:"urls,omitempty"`
UpdateEach string `toml:"update-each" json:"update-each,omitempty"`
} `toml:"blocklist" json:"blocklist,omitempty"`
} `toml:"defense" json:"defense,omitempty"`
Network struct {
Timeout struct {
TCP string `toml:"tcp" json:"tcp,omitempty"`
HTTP string `toml:"http" json:"http,omitempty"`
Idle string `toml:"idle" json:"idle,omitempty"`
} `toml:"timeout" json:"timeout,omitempty"`
DOHIP string `toml:"doh-ip" json:"doh-ip,omitempty"`
Proxies []string `toml:"proxies" json:"proxies,omitempty"`
} `toml:"network" json:"network,omitempty"`
Stats struct {
StatsD struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
Address string `toml:"address" json:"address,omitempty"`
MetricPrefix string `toml:"metric-prefix" json:"metric-prefix,omitempty"`
TagFormat string `toml:"tag-format" json:"tag-format,omitempty"`
} `toml:"statsd" json:"statsd,omitempty"`
Prometheus struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
BindTo string `toml:"bind-to" json:"bind-to,omitempty"`
HTTPPath string `toml:"http-path" json:"http-path,omitempty"`
MetricPrefix string `toml:"metric-prefix" json:"metric-prefix,omitempty"`
} `toml:"prometheus" json:"prometheus,omitempty"`
} `toml:"stats" json:"stats,omitempty"`
}
func Parse(rawData []byte) (*Config, error) {
rawConf := &configRaw{}
jsonBuf := &bytes.Buffer{}
conf := &Config{}
jsonEncoder := json.NewEncoder(jsonBuf)
jsonEncoder.SetEscapeHTML(false)
jsonEncoder.SetIndent("", "")
if err := toml.Unmarshal(rawData, rawConf); err != nil {
return nil, fmt.Errorf("cannot parse toml config: %w", err)
}
if err := jsonEncoder.Encode(rawConf); err != nil {
panic(err)
}
if err := json.NewDecoder(jsonBuf).Decode(conf); err != nil {
return nil, fmt.Errorf("cannot parse a config: %w", err)
}
if err := conf.Validate(); err != nil {
return nil, fmt.Errorf("cannot validate config: %w", err)
}
return conf, nil
}
+102
View File
@@ -0,0 +1,102 @@
package config
import (
"bytes"
"encoding/json"
"fmt"
"github.com/pelletier/go-toml/v2"
)
type tomlConfig struct {
Debug bool `toml:"debug" json:"debug,omitempty"`
AllowFallbackOnUnknownDC bool `toml:"allow-fallback-on-unknown-dc" json:"allowFallbackOnUnknownDc,omitempty"`
Secret string `toml:"secret" json:"secret"`
BindTo string `toml:"bind-to" json:"bindTo"`
ProxyProtocolListener bool `toml:"proxy-protocol-listener" json:"proxyProtocolListener"`
PreferIP string `toml:"prefer-ip" json:"preferIp,omitempty"`
AutoUpdate bool `toml:"auto-update" json:"autoUpdate,omitempty"`
DomainFrontingPort uint `toml:"domain-fronting-port" json:"domainFrontingPort,omitempty"`
DomainFrontingIP string `toml:"domain-fronting-ip" json:"domainFrontingIp,omitempty"`
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 {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
MaxSize string `toml:"max-size" json:"maxSize,omitempty"`
ErrorRate float64 `toml:"error-rate" json:"errorRate,omitempty"`
} `toml:"anti-replay" json:"antiReplay,omitempty"`
Blocklist 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:"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"`
Doppelganger struct {
URLs []string `toml:"urls" json:"urls,omitempty"`
Repeats uint `toml:"repeats-per-raid" json:"repeats_per_raid,omitempty"`
UpdateEach string `toml:"raid-each" json:"raid_each,omitempty"`
DRS bool `toml:"drs" json:"drs,omitempty"`
} `toml:"doppelganger" json:"doppelganger,omitempty"`
} `toml:"defense" json:"defense,omitempty"`
Network struct {
Timeout struct {
TCP string `toml:"tcp" json:"tcp,omitempty"`
HTTP string `toml:"http" json:"http,omitempty"`
Idle string `toml:"idle" json:"idle,omitempty"`
} `toml:"timeout" json:"timeout,omitempty"`
DOHIP string `toml:"doh-ip" json:"dohIp,omitempty"`
DNS string `toml:"dns" json:"dns,omitempty"`
Proxies []string `toml:"proxies" json:"proxies,omitempty"`
} `toml:"network" json:"network,omitempty"`
Stats struct {
StatsD struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
Address string `toml:"address" json:"address,omitempty"`
MetricPrefix string `toml:"metric-prefix" json:"metricPrefix,omitempty"`
TagFormat string `toml:"tag-format" json:"tagFormat,omitempty"`
} `toml:"statsd" json:"statsd,omitempty"`
Prometheus struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
BindTo string `toml:"bind-to" json:"bindTo,omitempty"`
HTTPPath string `toml:"http-path" json:"httpPath,omitempty"`
MetricPrefix string `toml:"metric-prefix" json:"metricPrefix,omitempty"`
} `toml:"prometheus" json:"prometheus,omitempty"`
} `toml:"stats" json:"stats,omitempty"`
}
func Parse(rawData []byte) (*Config, error) {
tomlConf := &tomlConfig{}
jsonBuf := &bytes.Buffer{}
conf := &Config{}
jsonEncoder := json.NewEncoder(jsonBuf)
jsonEncoder.SetEscapeHTML(false)
jsonEncoder.SetIndent("", "")
if err := toml.Unmarshal(rawData, tomlConf); err != nil {
return nil, fmt.Errorf("cannot parse toml config: %w", err)
}
if err := jsonEncoder.Encode(tomlConf); err != nil {
panic(err)
}
if err := json.NewDecoder(jsonBuf).Decode(conf); err != nil {
return nil, fmt.Errorf("cannot parse a config: %w", err)
}
return conf, nil
}
+41 -32
View File
@@ -8,61 +8,70 @@ import (
)
type TypeBlocklistURI struct {
value string
Value string
}
func (c *TypeBlocklistURI) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
text := string(data)
if filepath.IsAbs(text) {
if _, err := os.Stat(text); os.IsNotExist(err) {
return fmt.Errorf("filepath %s does not exist", text)
func (t *TypeBlocklistURI) Set(value string) error {
if stat, err := os.Stat(value); err == nil || os.IsExist(err) {
switch {
case stat.IsDir():
return fmt.Errorf("value is correct filepath but directory")
case stat.Mode().Perm()&0o400 == 0:
return fmt.Errorf("value is correct filepath but not readable")
}
c.value = text
value, err = filepath.Abs(value)
if err != nil {
return fmt.Errorf(
"value is correct filepath but cannot resolve absolute (%s): %w",
value, err)
}
t.Value = value
return nil
}
parsedURL, err := url.Parse(text)
parsedURL, err := url.Parse(value)
if err != nil {
return fmt.Errorf("incorrect url: %w", err)
return fmt.Errorf("incorrect url (%s): %w", value, err)
}
switch parsedURL.Scheme {
case "http", "https": // nolint: goconst
case "http", "https":
default:
return fmt.Errorf("unknown schema %s", parsedURL.Scheme)
return fmt.Errorf("unknown schema %s (%s)", parsedURL.Scheme, value)
}
if parsedURL.Host == "" {
return fmt.Errorf("incorrect url %s", text)
return fmt.Errorf("incorrect url %s", value)
}
c.value = parsedURL.String()
t.Value = parsedURL.String()
return nil
}
func (c TypeBlocklistURI) MarshalText() ([]byte, error) {
return []byte(c.value), nil
}
func (c TypeBlocklistURI) String() string {
return c.value
}
func (c TypeBlocklistURI) IsRemote() bool {
return !filepath.IsAbs(c.value)
}
func (c TypeBlocklistURI) Value(defaultValue string) string {
if c.value == "" {
func (t TypeBlocklistURI) Get(defaultValue string) string {
if t.Value == "" {
return defaultValue
}
return c.value
return t.Value
}
func (t TypeBlocklistURI) IsRemote() bool {
return !filepath.IsAbs(t.Value)
}
func (t *TypeBlocklistURI) UnmarshalText(data []byte) error {
return t.Set(string(data))
}
func (t TypeBlocklistURI) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeBlocklistURI) String() string {
return t.Value
}
+42 -106
View File
@@ -1,12 +1,10 @@
package config_test
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
@@ -20,42 +18,28 @@ type typeBlocklistURITestStruct struct {
type TypeBlocklistURITestSuite struct {
suite.Suite
directory string
absDirectory string
}
func (suite *TypeBlocklistURITestSuite) TestUnmarshalNil() {
typ := &config.TypeBlocklistURI{}
suite.NoError(typ.UnmarshalText(nil))
suite.Empty(typ.String())
}
func (suite *TypeBlocklistURITestSuite) SetupSuite() {
dir, _ := os.Getwd()
absDir, _ := filepath.Abs(dir)
func (suite *TypeBlocklistURITestSuite) TestUnknownSchema() {
typ := &config.TypeBlocklistURI{}
suite.Error(typ.UnmarshalText([]byte("gopher://lalala")))
}
func (suite *TypeBlocklistURITestSuite) TestEmptyHost() {
typ := &config.TypeBlocklistURI{}
suite.Error(typ.UnmarshalText([]byte("https:///path")))
}
func (suite *TypeBlocklistURITestSuite) TestIncorrectURL() {
typ := &config.TypeBlocklistURI{}
suite.Error(typ.UnmarshalText([]byte("h:/--")))
suite.directory = dir
suite.absDirectory = absDir
}
func (suite *TypeBlocklistURITestSuite) TestUnmarshalFail() {
rnd := make([]byte, 48)
rand.Read(rnd) // nolint: errcheck
unknownPath := base64.StdEncoding.EncodeToString(rnd)
testData := []string{
"1",
unknownPath,
"/" + unknownPath,
"http:/",
"gopher://lalalal",
"gopher://lalala",
"https:///paths",
"h:/=",
filepath.Join(suite.directory, "___"),
filepath.Join(suite.absDirectory, "___"),
suite.directory,
suite.absDirectory,
}
for _, v := range testData {
@@ -71,13 +55,12 @@ func (suite *TypeBlocklistURITestSuite) TestUnmarshalFail() {
}
func (suite *TypeBlocklistURITestSuite) TestUnmarshalOk() {
dir, _ := os.Getwd()
dir, _ = filepath.Abs(dir)
testData := []string{
"http://lalala",
filepath.Join(dir, "config.go"),
"https://lalala",
"https://lalala/path",
filepath.Join(suite.directory, "config.go"),
filepath.Join(suite.absDirectory, "config.go"),
}
for _, v := range testData {
@@ -92,77 +75,9 @@ func (suite *TypeBlocklistURITestSuite) TestUnmarshalOk() {
testStruct := &typeBlocklistURITestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.EqualValues(t, value, testStruct.Value.Value(""))
})
}
}
assert.EqualValues(t, value, testStruct.Value.Get(""))
func (suite *TypeBlocklistURITestSuite) TestMarshalOk() {
dir, _ := os.Getwd()
dir, _ = filepath.Abs(dir)
testData := []string{
"http://lalalal",
filepath.Join(dir, "config.go"),
}
for _, v := range testData {
name := v
data, err := json.Marshal(map[string]string{
"value": name,
})
suite.NoError(err)
suite.T().Run(name, func(t *testing.T) {
testStruct := &typeBlocklistURITestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, name, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, name, string(marshalled))
})
}
}
func (suite *TypeBlocklistURITestSuite) TestValue() {
testStruct := &typeBlocklistURITestStruct{}
suite.Equal("http://lalala", testStruct.Value.Value("http://lalala"))
data, err := json.Marshal(map[string]string{
"value": "http://blablabla",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.Equal("http://blablabla", testStruct.Value.Value(""))
}
func (suite *TypeBlocklistURITestSuite) TestIsRemote() {
dir, _ := os.Getwd()
dir, _ = filepath.Abs(dir)
testData := map[bool]string{
true: "http://lalalal",
false: filepath.Join(dir, "config.go"),
}
for k, v := range testData {
ok := k
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(strconv.FormatBool(ok), func(t *testing.T) {
testStruct := &typeBlocklistURITestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
if ok {
if strings.HasPrefix(value, "http") {
assert.True(t, testStruct.Value.IsRemote())
} else {
assert.False(t, testStruct.Value.IsRemote())
@@ -171,6 +86,27 @@ func (suite *TypeBlocklistURITestSuite) TestIsRemote() {
}
}
func (suite *TypeBlocklistURITestSuite) TestMarshalOk() {
testStruct := &typeBlocklistURITestStruct{
Value: config.TypeBlocklistURI{
Value: "http://some.url/with/path",
},
}
data, err := json.Marshal(testStruct)
suite.NoError(err)
suite.JSONEq(`{"value": "http://some.url/with/path"}`, string(data))
}
func (suite *TypeBlocklistURITestSuite) TestGet() {
value := config.TypeBlocklistURI{}
suite.Equal("/path", value.Get("/path"))
suite.NoError(value.Set("http://lalala.ru"))
suite.Equal("http://lalala.ru", value.Get("/path"))
suite.Equal("http://lalala.ru", value.Get(""))
}
func TestTypeBlocklistURI(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeBlocklistURITestSuite{})
+37
View File
@@ -0,0 +1,37 @@
package config
import (
"fmt"
"strconv"
)
type TypeBool struct {
Value bool
}
func (t *TypeBool) Set(data string) error {
parsed, err := strconv.ParseBool(data)
if err != nil {
return fmt.Errorf("incorrect bool value: %s", data)
}
t.Value = parsed
return nil
}
func (t TypeBool) Get(defaultValue bool) bool {
return t.Value || defaultValue
}
func (t *TypeBool) UnmarshalJSON(data []byte) error {
return t.Set(string(data))
}
func (t TypeBool) MarshalJSON() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeBool) String() string {
return strconv.FormatBool(t.Value)
}
+107
View File
@@ -0,0 +1,107 @@
package config_test
import (
"encoding/json"
"fmt"
"strconv"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeBoolTestStruct struct {
Value config.TypeBool `json:"value"`
}
type TypeBoolTestSuite struct {
suite.Suite
}
func (suite *TypeBoolTestSuite) TestUnmarshalFail() {
testData := []any{
"",
"np",
"нет",
int(10),
[]int{},
}
for _, v := range testData {
data, err := json.Marshal(map[string]any{
"value": v,
})
suite.NoError(err)
suite.T().Run(fmt.Sprintf("%v", v), func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typeBoolTestStruct{}))
})
}
}
func (suite *TypeBoolTestSuite) TestUnmarshalOk() {
testData := []bool{
true,
false,
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]bool{
"value": v,
})
suite.NoError(err)
suite.T().Run(strconv.FormatBool(v), func(t *testing.T) {
testStruct := &typeBoolTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
if value {
assert.True(t, testStruct.Value.Value)
} else {
assert.False(t, testStruct.Value.Value)
}
})
}
}
func (suite *TypeBoolTestSuite) TestMarshalOk() {
for _, v := range []bool{true, false} {
value := v
suite.T().Run(strconv.FormatBool(v), func(t *testing.T) {
testStruct := typeBoolTestStruct{
Value: config.TypeBool{
Value: value,
},
}
encodedJSON, err := json.Marshal(testStruct)
assert.NoError(t, err)
expectedJSON, err := json.Marshal(map[string]bool{
"value": value,
})
assert.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(encodedJSON))
})
}
}
func (suite *TypeBoolTestSuite) TestGet() {
value := config.TypeBool{}
suite.False(value.Get(false))
suite.True(value.Get(true))
value.Value = true
suite.True(value.Get(false))
suite.True(value.Get(true))
}
func TestTypeBool(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeBoolTestSuite{})
}
+29 -28
View File
@@ -7,48 +7,49 @@ import (
"github.com/alecthomas/units"
)
var typeBytesStringCleaner = strings.NewReplacer(" ", "", "\t", "", "IB", "iB")
type TypeBytes struct {
value units.Base2Bytes
Value units.Base2Bytes
}
func (c *TypeBytes) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
func (t *TypeBytes) Set(value string) error {
normalizedValue := typeBytesStringCleaner.Replace(strings.ToUpper(value))
normalizedData := strings.ToUpper(string(data))
normalizedData = strings.ReplaceAll(normalizedData, "IB", "iB")
value, err := units.ParseBase2Bytes(normalizedData)
parsedValue, err := units.ParseBase2Bytes(normalizedValue)
if err != nil {
return fmt.Errorf("incorrect bytes value: %w", err)
return fmt.Errorf("incorrect bytes value (%v): %w", value, err)
}
if value < 0 {
return fmt.Errorf("%d should be positive number", value)
if parsedValue < 0 {
return fmt.Errorf("bytes should be positive (%s)", value)
}
c.value = value
t.Value = parsedValue
return nil
}
func (c TypeBytes) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeBytes) String() string {
if c.value == 0 {
return ""
}
return strings.ToLower(c.value.String())
}
func (c TypeBytes) Value(defaultValue uint) uint {
if c.value == 0 {
func (t TypeBytes) Get(defaultValue uint) uint {
if t.Value == 0 {
return defaultValue
}
return uint(c.value)
return uint(t.Value)
}
func (t *TypeBytes) UnmarshalText(data []byte) error {
return t.Set(string(data))
}
func (t TypeBytes) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeBytes) String() string {
if t.Value == 0 {
return ""
}
return strings.ToLower(t.Value.String())
}
+11 -45
View File
@@ -17,19 +17,12 @@ type TypeBytesTestSuite struct {
suite.Suite
}
func (suite *TypeBytesTestSuite) TestUnmarshalNil() {
typ := &config.TypeBytes{}
suite.NoError(typ.UnmarshalText(nil))
suite.Empty(typ.String())
}
func (suite *TypeBytesTestSuite) TestUnmarshalFail() {
testData := []string{
"1m",
"1",
"-1kb",
"-1kib",
"-1QB",
}
for _, v := range testData {
@@ -65,53 +58,26 @@ func (suite *TypeBytesTestSuite) TestUnmarshalOk() {
testStruct := &typeBytesTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.EqualValues(t, value, testStruct.Value.Value(0))
assert.EqualValues(t, value, testStruct.Value.Get(0))
})
}
}
func (suite *TypeBytesTestSuite) TestMarshalOk() {
testData := []string{
"1b",
"1kib",
"2mib",
}
value := typeBytesTestStruct{}
suite.NoError(value.Value.Set("1kib"))
for _, v := range testData {
name := v
data, err := json.Marshal(map[string]string{
"value": name,
})
suite.NoError(err)
suite.T().Run(name, func(t *testing.T) {
testStruct := &typeBytesTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, name, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, name, string(marshalled))
})
}
data, err := json.Marshal(value)
suite.NoError(err)
suite.JSONEq(`{"value": "1kib"}`, string(data))
}
func (suite *TypeBytesTestSuite) TestValue() {
testStruct := &typeBytesTestStruct{}
func (suite *TypeBytesTestSuite) TestGet() {
value := config.TypeBytes{}
suite.EqualValues(1000, value.Get(1000))
suite.EqualValues(0, testStruct.Value.Value(0))
suite.EqualValues(1, testStruct.Value.Value(1))
data, err := json.Marshal(map[string]string{
"value": "1kb",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.EqualValues(1024, testStruct.Value.Value(0))
suite.EqualValues(1024, testStruct.Value.Value(1))
suite.NoError(value.Set("1mib"))
suite.EqualValues(1048576, value.Get(1000))
}
func TestTypeBytes(t *testing.T) {
+45
View File
@@ -0,0 +1,45 @@
package config
import (
"fmt"
"strconv"
)
type TypeConcurrency struct {
Value uint
}
func (t *TypeConcurrency) Set(value string) error {
concurrencyValue, err := strconv.ParseUint(value, 10, 16)
if err != nil {
return fmt.Errorf("value is not uint (%s): %w", value, err)
}
if concurrencyValue == 0 {
return fmt.Errorf("value should be >0 (%s)", value)
}
t.Value = uint(concurrencyValue)
return nil
}
func (t TypeConcurrency) Get(defaultValue uint) uint {
if t.Value == 0 {
return defaultValue
}
return t.Value
}
func (t *TypeConcurrency) UnmarshalJSON(data []byte) error {
return t.Set(string(data))
}
func (t TypeConcurrency) MarshalJSON() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeConcurrency) String() string {
return strconv.FormatUint(uint64(t.Value), 10)
}
+73
View File
@@ -0,0 +1,73 @@
package config_test
import (
"encoding/json"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeConcurrencyTestStruct struct {
Value config.TypeConcurrency `json:"value"`
}
type TypeConcurrencyTestSuite struct {
suite.Suite
}
func (suite *TypeConcurrencyTestSuite) TestUnmarshalFail() {
testData := []string{
"-1",
"0",
"0.0",
"1.0",
"1.1",
".",
"some_value",
}
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, &typeConcurrencyTestStruct{}))
})
}
}
func (suite *TypeConcurrencyTestSuite) TestUnmarshalOk() {
testStruct := &typeConcurrencyTestStruct{}
suite.NoError(json.Unmarshal([]byte(`{"value": 1}`), testStruct))
suite.EqualValues(1, testStruct.Value.Get(2))
}
func (suite *TypeConcurrencyTestSuite) TestMarshalOk() {
testStruct := &typeConcurrencyTestStruct{
Value: config.TypeConcurrency{
Value: 2,
},
}
data, err := json.Marshal(testStruct)
suite.NoError(err)
suite.JSONEq(`{"value": 2}`, string(data))
}
func (suite *TypeConcurrencyTestSuite) TestGet() {
value := config.TypeConcurrency{}
suite.EqualValues(1, value.Get(1))
value.Value = 3
suite.EqualValues(3, value.Get(1))
}
func TestTypeConcurrency(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeConcurrencyTestSuite{})
}
+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{})
}
+69
View File
@@ -0,0 +1,69 @@
package config
import (
"fmt"
"net"
"net/url"
)
type TypeDNSURI struct {
Value *url.URL
}
func (t *TypeDNSURI) Set(value string) error {
parsed, err := url.Parse(value)
if err != nil {
return fmt.Errorf("value is not URI: %w", err)
}
if parsed.Host == "" {
parsed.Host = parsed.Path
parsed.Path = ""
parsed.Scheme = "udp"
}
switch parsed.Scheme {
case "https", "tls":
case "udp":
if ip := net.ParseIP(parsed.Hostname()); ip == nil {
return fmt.Errorf("simple DNS must IP address: %s", parsed.Hostname())
}
default:
return fmt.Errorf("unsupported DNS type %s", parsed.Scheme)
}
if parsed.Scheme != "https" && parsed.Path != "" {
return fmt.Errorf("path is supported only for DoH: %s", parsed)
}
if parsed.User != nil {
return fmt.Errorf("used info is not supported: %s", parsed.User.String())
}
t.Value = parsed
return nil
}
func (t *TypeDNSURI) Get(defaultValue *url.URL) *url.URL {
if t.Value != nil {
return t.Value
}
return defaultValue
}
func (t *TypeDNSURI) UnmarshalText(data []byte) error {
return t.Set(string(data))
}
func (t TypeDNSURI) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeDNSURI) String() string {
if t.Value == nil {
return ""
}
return t.Value.String()
}
+117
View File
@@ -0,0 +1,117 @@
package config_test
import (
"encoding/json"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeDNSURITestStruct struct {
Value config.TypeDNSURI `json:"value"`
}
type TypeDNSURITestSuite struct {
suite.Suite
}
func (suite *TypeDNSURITestSuite) TestUnmarshalFail() {
testData := []string{
"xx",
"ppar",
"",
"dns://hahaha",
"udp://xcxxcv",
"udp://1.1.1.1/xcv",
"1.1.1.1/xxx",
"tls://dns/xx",
"tls://1.1.1.1/xx",
"https://user:password@1.1.1.1",
"tls://user:password@1.1.1.1",
"udp://user:password@1.1.1.1",
}
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, &typeDNSURITestStruct{}))
})
}
}
func (suite *TypeDNSURITestSuite) TestUnmarshalOk() {
testData := []string{
"1.1.1.1",
"tls://1.1.1.1",
"tls://dns.google",
"https://1.1.1.1",
"https://1.1.1.1/dns-query",
"https://dns.google",
"https://dns.google/dns-query",
"udp://1.1.1.1",
}
for _, v := range testData {
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeDNSURITestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
if v == "1.1.1.1" {
v = "udp://" + v
}
assert.Equal(t, v, testStruct.Value.String())
})
}
}
func (suite *TypeDNSURITestSuite) TestMarshalOk() {
testData := []string{
"tls://1.1.1.1",
"tls://dns.google",
"https://1.1.1.1",
"https://1.1.1.1/dns-query",
}
for _, v := range testData {
suite.T().Run(v, func(t *testing.T) {
testStruct := &typePreferIPTestStruct{
Value: config.TypePreferIP{
Value: v,
},
}
encodedJSON, err := json.Marshal(testStruct)
assert.NoError(t, err)
expectedJSON, err := json.Marshal(map[string]string{
"value": v,
})
assert.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(encodedJSON))
})
}
}
func (suite *TypeDNSURITestSuite) TestGet() {
value := config.TypeDNSURI{}
suite.Nil(value.Get(nil))
suite.NoError(value.Set("tls://1.1.1.1"))
suite.NotNil(value.Get(nil))
}
func TestDNSURI(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeDNSURITestSuite{})
}
+29 -22
View File
@@ -6,41 +6,48 @@ import (
"time"
)
var typeDurationStringCleaner = strings.NewReplacer(" ", "", "\t", "")
type TypeDuration struct {
value time.Duration
Value time.Duration
}
func (c *TypeDuration) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
dur, err := time.ParseDuration(strings.ToLower(string(data)))
func (t *TypeDuration) Set(value string) error {
parsedValue, err := time.ParseDuration(
typeDurationStringCleaner.Replace(strings.ToLower(value)))
if err != nil {
return fmt.Errorf("incorrect duration: %w", err)
return fmt.Errorf("incorrect duration (%s): %w", value, err)
}
if dur < 0 {
return fmt.Errorf("%s should be positive duration", dur)
if parsedValue < 0 {
return fmt.Errorf("duration has to be a positive: %s", value)
}
c.value = dur
t.Value = parsedValue
return nil
}
func (c TypeDuration) MarshalText() ([]byte, error) {
return []byte(c.value.String()), nil
}
func (c TypeDuration) String() string {
return c.value.String()
}
func (c TypeDuration) Value(defaultValue time.Duration) time.Duration {
if c.value == 0 {
func (t TypeDuration) Get(defaultValue time.Duration) time.Duration {
if t.Value == 0 {
return defaultValue
}
return c.value
return t.Value
}
func (t *TypeDuration) UnmarshalText(data []byte) error {
return t.Set(string(data))
}
func (t TypeDuration) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeDuration) String() string {
if t.Value == 0 {
return ""
}
return t.Value.String()
}
+33 -41
View File
@@ -18,18 +18,12 @@ type TypeDurationTestSuite struct {
suite.Suite
}
func (suite *TypeDurationTestSuite) TestUnmarshalNil() {
typ := &config.TypeDuration{}
suite.NoError(typ.UnmarshalText(nil))
suite.EqualValues(0, typ.Value(0))
}
func (suite *TypeDurationTestSuite) TestUnmarshalFail() {
testData := []string{
"1t",
"1",
"-1s",
"-1h",
"1 seconds ago",
"1s ago",
"",
}
for _, v := range testData {
@@ -47,8 +41,11 @@ func (suite *TypeDurationTestSuite) TestUnmarshalFail() {
func (suite *TypeDurationTestSuite) TestUnmarshalOk() {
testData := map[string]time.Duration{
"1s": time.Second,
"1m": time.Minute,
"2h1s": 2*time.Hour + time.Second,
"0": 0 * time.Second,
"0s": 0 * time.Second,
"1\tM": time.Minute,
"1H": time.Hour,
"1 h": time.Hour,
}
for k, v := range testData {
@@ -63,53 +60,48 @@ func (suite *TypeDurationTestSuite) TestUnmarshalOk() {
testStruct := &typeDurationTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, value, testStruct.Value.Value(0))
assert.Equal(t, value, testStruct.Value.Value)
})
}
}
func (suite *TypeDurationTestSuite) TestMarshalOk() {
testData := []string{
"1s",
"1m0s",
"2h0m1s",
testData := map[string]string{
"1s": "1s",
"0": "",
"0s": "",
"0ms": "",
"1 H": "1h0m0s",
}
for _, v := range testData {
name := v
for k, v := range testData {
value := k
expected := v
data, err := json.Marshal(map[string]string{
"value": name,
})
suite.NoError(err)
suite.T().Run(name, func(t *testing.T) {
suite.T().Run(value, func(t *testing.T) {
testStruct := &typeDurationTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, name, testStruct.Value.String())
assert.NoError(t, testStruct.Value.Set(value))
marshalled, err := testStruct.Value.MarshalText()
data, err := json.Marshal(testStruct)
assert.NoError(t, err)
assert.Equal(t, name, string(marshalled))
expectedJSON, err := json.Marshal(map[string]string{
"value": expected,
})
assert.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(data))
})
}
}
func (suite *TypeDurationTestSuite) TestValue() {
testStruct := &typeDurationTestStruct{}
func (suite *TypeDurationTestSuite) TestGet() {
value := config.TypeDuration{}
suite.Equal(time.Second, value.Get(time.Second))
suite.EqualValues(0, testStruct.Value.Value(0))
suite.Equal(time.Second, testStruct.Value.Value(time.Second))
data, err := json.Marshal(map[string]string{
"value": "1s",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.Equal(time.Second, testStruct.Value.Value(0))
suite.Equal(time.Second, testStruct.Value.Value(time.Minute))
value.Value = 3 * time.Second
suite.Equal(3*time.Second, value.Get(time.Hour))
}
func TestTypeDuration(t *testing.T) {
+22 -18
View File
@@ -8,36 +8,40 @@ import (
const typeErrorRateIgnoreLess = 1e-8
type TypeErrorRate struct {
value float64
Value float64
}
func (c *TypeErrorRate) UnmarshalJSON(data []byte) error {
value, err := strconv.ParseFloat(string(data), 64)
func (t *TypeErrorRate) Set(value string) error {
parsedValue, err := strconv.ParseFloat(value, 64)
if err != nil {
return fmt.Errorf("incorrect float value: %w", err)
return fmt.Errorf("value is not a float (%s): %w", value, err)
}
if value <= 0 || value >= 100 {
return fmt.Errorf("%f should be 0 < x < 100", value)
if parsedValue <= 0.0 || parsedValue >= 100.0 {
return fmt.Errorf("value should be 0 < x < 100 (%s)", value)
}
c.value = value
t.Value = parsedValue
return nil
}
func (c *TypeErrorRate) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeErrorRate) String() string {
return strconv.FormatFloat(c.value, 'f', -1, 64)
}
func (c TypeErrorRate) Value(defaultValue float64) float64 {
if c.value < typeErrorRateIgnoreLess {
func (t TypeErrorRate) Get(defaultValue float64) float64 {
if t.Value < typeErrorRateIgnoreLess {
return defaultValue
}
return c.value
return t.Value
}
func (t *TypeErrorRate) UnmarshalJSON(data []byte) error {
return t.Set(string(data))
}
func (t TypeErrorRate) MarshalJSON() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeErrorRate) String() string {
return strconv.FormatFloat(t.Value, 'f', -1, 64)
}
+33 -77
View File
@@ -2,7 +2,6 @@ package config_test
import (
"encoding/json"
"strconv"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
@@ -19,104 +18,61 @@ type TypeErrorRateTestSuite struct {
}
func (suite *TypeErrorRateTestSuite) TestUnmarshalFail() {
testData := []float64{
1000,
-100,
-0.0001,
testData := []string{
"",
"1s",
"1,",
"1,2",
".",
"3.4.5",
"3.5.",
".3.5",
"some word",
"1e2",
"-1.0",
}
for _, v := range testData {
data, err := json.Marshal(map[string]float64{
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(strconv.FormatFloat(v, 'f', -1, 64), func(t *testing.T) {
suite.T().Run(v, func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typeErrorRateTestStruct{}))
})
}
data, err := json.Marshal(map[string]string{
"value": "hello",
})
suite.NoError(err)
suite.Error(json.Unmarshal(data, &typeErrorRateTestStruct{}))
}
func (suite *TypeErrorRateTestSuite) TestUnmarshalOk() {
testData := []float64{
1,
55.5,
0.0001,
1e-6,
}
data, err := json.Marshal(map[string]float64{
"value": 1.0,
})
suite.NoError(err)
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]float64{
"value": v,
})
suite.NoError(err)
suite.T().Run(strconv.FormatFloat(v, 'f', -1, 64), func(t *testing.T) {
testStruct := &typeErrorRateTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.InEpsilon(t, value, testStruct.Value.Value(0), 1e-10)
})
}
testStruct := &typeErrorRateTestStruct{}
suite.NoError(json.Unmarshal(data, testStruct))
suite.InEpsilon(1.0, testStruct.Value.Value, 1e-10)
}
func (suite *TypeErrorRateTestSuite) TestMarshalOk() {
testData := []float64{
1,
55.5,
0.0001,
1e-6,
testStruct := typeErrorRateTestStruct{
Value: config.TypeErrorRate{
Value: 1.01,
},
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]float64{
"value": v,
})
suite.NoError(err)
suite.T().Run(strconv.FormatFloat(v, 'f', -1, 64), func(t *testing.T) {
testStruct := &typeErrorRateTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
parsed, err := strconv.ParseFloat(testStruct.Value.String(), 64)
assert.NoError(t, err)
assert.InEpsilon(t, value, parsed, 1e-10)
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
parsed, err = strconv.ParseFloat(string(marshalled), 64)
assert.NoError(t, err)
assert.InEpsilon(t, value, parsed, 1e-10)
})
}
encodedJSON, err := json.Marshal(testStruct)
suite.NoError(err)
suite.JSONEq(`{"value": 1.01}`, string(encodedJSON))
}
func (suite *TypeErrorRateTestSuite) TestValue() {
testStruct := &typeErrorRateTestStruct{}
func (suite *TypeErrorRateTestSuite) TestGet() {
value := config.TypeErrorRate{}
suite.InEpsilon(1.0, value.Get(1.0), 1e-10)
suite.InEpsilon(1, testStruct.Value.Value(1), 1e-10)
suite.InEpsilon(2, testStruct.Value.Value(2), 1e-10)
data, err := json.Marshal(map[string]float64{
"value": 1,
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.InEpsilon(1, testStruct.Value.Value(2), 1e-10)
suite.InEpsilon(1, testStruct.Value.Value(3), 1e-10)
value.Value = 5.0
suite.InEpsilon(5.0, value.Get(1.0), 1e-10)
}
func TestTypeErrorRate(t *testing.T) {
+37 -41
View File
@@ -7,61 +7,57 @@ import (
)
type TypeHostPort struct {
host TypeIP
port TypePort
Value string
Host string
Port uint
}
func (c *TypeHostPort) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
text := string(data)
host, port, err := net.SplitHostPort(text)
func (t *TypeHostPort) Set(value string) error {
host, port, err := net.SplitHostPort(value)
if err != nil {
return fmt.Errorf("incorrect host:port syntax: %w", err)
return fmt.Errorf("incorrect host:port value (%v): %w", value, err)
}
if port == "" {
return fmt.Errorf("port in %s host:port pair cannot be empty", text)
portValue, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return fmt.Errorf("incorrect port number (%v): %w", value, err)
}
if err := c.port.UnmarshalJSON([]byte(port)); err != nil {
return fmt.Errorf("incorrect port in host:port: %w", err)
if portValue == 0 {
return fmt.Errorf("incorrect port number (%s)", value)
}
if err := c.host.UnmarshalText([]byte(host)); err != nil {
return fmt.Errorf("incorrect host: %w", err)
if host == "" {
return fmt.Errorf("empty host: %s", value)
}
if net.ParseIP(host) == nil {
return fmt.Errorf("host is not an IP address: %s", value)
}
t.Value = net.JoinHostPort(host, port)
t.Port = uint(portValue)
t.Host = host
return nil
}
func (c TypeHostPort) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeHostPort) String() string {
return c.Value(net.IP{}, 0)
}
func (c TypeHostPort) HostValue(defaultValue net.IP) net.IP {
return c.host.Value(defaultValue)
}
func (c TypeHostPort) PortValue(defaultValue uint) uint {
return c.port.Value(defaultValue)
}
func (c TypeHostPort) Value(defaultHostValue net.IP, defaultPortValue uint) string {
host := c.HostValue(defaultHostValue)
port := c.PortValue(defaultPortValue)
hostStr := ""
if len(host) > 0 {
hostStr = host.String()
func (t TypeHostPort) Get(defaultValue string) string {
if t.Value == "" {
return defaultValue
}
return net.JoinHostPort(hostStr, strconv.Itoa(int(port)))
return t.Value
}
func (t *TypeHostPort) UnmarshalText(data []byte) error {
return t.Set(string(data))
}
func (t TypeHostPort) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeHostPort) String() string {
return t.Value
}
+21 -48
View File
@@ -2,7 +2,6 @@ package config_test
import (
"encoding/json"
"net"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
@@ -20,11 +19,13 @@ type TypeHostPortTestSuite struct {
func (suite *TypeHostPortTestSuite) TestUnmarshalFail() {
testData := []string{
"10.0.0.10:aaa",
"10.0.0.10:",
":",
"xxx",
"xxx:80",
":800",
"127.0.0.1:8000000",
"12...:80",
"",
"localhost",
"google.com:",
}
for _, v := range testData {
@@ -41,9 +42,8 @@ func (suite *TypeHostPortTestSuite) TestUnmarshalFail() {
func (suite *TypeHostPortTestSuite) TestUnmarshalOk() {
testData := []string{
"10.0.0.10:80",
"0.0.0.0:80",
":8000",
"127.0.0.1:80",
"10.0.0.10:6553",
}
for _, v := range testData {
@@ -56,57 +56,30 @@ func (suite *TypeHostPortTestSuite) TestUnmarshalOk() {
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeHostPortTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.EqualValues(t, value, testStruct.Value.Value(nil, 0))
assert.Equal(t, value, testStruct.Value.Value)
})
}
}
func (suite *TypeHostPortTestSuite) TestMarshalOk() {
testData := []string{
"10.0.0.10:80",
"0.0.0.0:80",
":8000",
testStruct := typeHostPortTestStruct{
Value: config.TypeHostPort{
Value: "127.0.0.1:8000",
},
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeHostPortTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, value, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, value, string(marshalled))
})
}
data, err := json.Marshal(testStruct)
suite.NoError(err)
suite.JSONEq(`{"value": "127.0.0.1:8000"}`, string(data))
}
func (suite *TypeHostPortTestSuite) TestValue() {
testStruct := &typeHostPortTestStruct{}
func (suite *TypeHostPortTestSuite) TestGet() {
value := config.TypeHostPort{}
suite.Equal("127.0.0.1:9000", value.Get("127.0.0.1:9000"))
suite.EqualValues("127.0.0.1:80",
testStruct.Value.Value(net.ParseIP("127.0.0.1"), 80))
suite.EqualValues("127.1.0.1:80",
testStruct.Value.Value(net.ParseIP("127.1.0.1"), 80))
data, err := json.Marshal(map[string]string{
"value": "127.0.0.1:80",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.EqualValues("127.0.0.1:80", testStruct.Value.Value(nil, 0))
suite.EqualValues("127.0.0.1:80", testStruct.Value.Value(net.ParseIP("10.0.0.10"), 3000))
value.Value = "127.0.0.1:80"
suite.Equal("127.0.0.1:80", value.Get("127.0.0.1:9000"))
}
func TestTypeHostPort(t *testing.T) {
+18 -20
View File
@@ -3,33 +3,31 @@ package config
import "strings"
type TypeHTTPPath struct {
value string
Value string
}
func (c *TypeHTTPPath) UnmarshalText(data []byte) error {
if len(data) > 0 {
c.value = "/" + strings.Trim(string(data), "/")
}
func (t *TypeHTTPPath) Set(value string) error {
t.Value = "/" + strings.Trim(value, "/")
return nil
}
func (c TypeHTTPPath) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeHTTPPath) String() string {
if c.value == "" {
return "/"
}
return c.value
}
func (c TypeHTTPPath) Value(defaultValue string) string {
if c.value == "" {
func (t TypeHTTPPath) Get(defaultValue string) string {
if t.Value == "" {
return defaultValue
}
return c.value
return t.Value
}
func (t *TypeHTTPPath) UnmarshalText(data []byte) error {
return t.Set(string(data))
}
func (t TypeHTTPPath) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeHTTPPath) String() string {
return t.Value
}
+24 -48
View File
@@ -17,72 +17,48 @@ type TypeHTTPPathTestSuite struct {
suite.Suite
}
func (suite *TypeHTTPPathTestSuite) TestUnmarshal() {
testData := []string{
"/hello",
"hello",
"hello/",
"/hello/",
func (suite *TypeHTTPPathTestSuite) TestUnmarshalOk() {
testData := map[string]string{
"": "/",
"/": "/",
"/path": "/path",
"path": "/path",
}
for _, v := range testData {
for k, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
"value": k,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
suite.T().Run(k, func(t *testing.T) {
testStruct := &typeHTTPPathTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, "/hello", testStruct.Value.Value(""))
assert.Equal(t, value, testStruct.Value.Get(""))
})
}
}
func (suite *TypeHTTPPathTestSuite) TestMarshalOk() {
testData := map[string]string{
"": "/",
"/hello": "/hello",
"/hello/": "/hello",
"hello/": "/hello",
"hello": "/hello",
value := typeHTTPPathTestStruct{
Value: config.TypeHTTPPath{
Value: "/path",
},
}
for k, v := range testData {
toPass := k
compareWith := v
data, err := json.Marshal(map[string]string{
"value": toPass,
})
suite.NoError(err)
suite.T().Run(toPass, func(t *testing.T) {
testStruct := &typeHTTPPathTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, compareWith, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, compareWith, string(marshalled))
})
}
data, err := json.Marshal(value)
suite.NoError(err)
suite.JSONEq(`{"value": "/path"}`, string(data))
}
func (suite *TypeHTTPPathTestSuite) TestValue() {
testStruct := &typeHTTPPathTestStruct{}
func (suite *TypeHTTPPathTestSuite) TestGet() {
value := config.TypeHTTPPath{}
suite.Equal("/hello", value.Get("/hello"))
suite.Equal("/hello", testStruct.Value.Value("/hello"))
data, err := json.Marshal(map[string]string{
"value": "/map",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.Equal("/map", testStruct.Value.Value("/hello"))
suite.NoError(value.Set("/lalala"))
suite.Equal("/lalala", value.Get("/hello"))
}
func TestTypeHTTPPath(t *testing.T) {
+53
View File
@@ -0,0 +1,53 @@
package config
import (
"fmt"
"net/url"
)
type TypeHttpsURL struct {
Value *url.URL
}
func (t *TypeHttpsURL) Set(value string) error {
parsedURL, err := url.Parse(value)
if err != nil {
return fmt.Errorf("value is not correct URL (%s): %w", value, err)
}
if parsedURL.Host == "" {
return fmt.Errorf("url has to have a schema: %s", value)
}
if parsedURL.Scheme != "https" {
return fmt.Errorf("unsupported schema: %s", parsedURL.Scheme)
}
t.Value = parsedURL
return nil
}
func (t *TypeHttpsURL) Get(defaultValue *url.URL) *url.URL {
if t.Value == nil {
return defaultValue
}
return t.Value
}
func (t *TypeHttpsURL) UnmarshalText(data []byte) error {
return t.Set(string(data))
}
func (t TypeHttpsURL) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeHttpsURL) String() string {
if t.Value == nil {
return ""
}
return t.Value.String()
}
+100
View File
@@ -0,0 +1,100 @@
package config_test
import (
"encoding/json"
"net/url"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeHttpsURLTestStruct struct {
Value config.TypeHttpsURL `json:"value"`
}
type HttpsURLTestSuite struct {
suite.Suite
}
func (suite *HttpsURLTestSuite) TestUnmarshalFail() {
testData := []string{
"",
"https://",
"://lala",
"/path",
"http://example.com",
"socks5://example.com",
}
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, &typeHttpsURLTestStruct{}))
})
}
}
func (suite *HttpsURLTestSuite) TestUnmarshalOk() {
testData := map[string]string{
"https://example.com": "https://example.com",
"https://example.com:8443": "https://example.com:8443",
"https://example.com/path?q=1": "https://example.com/path?q=1",
"https://user:pass@example.com": "https://user:pass@example.com",
}
for k, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": k,
})
suite.NoError(err)
suite.T().Run(k, func(t *testing.T) {
testStruct := &typeHttpsURLTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
parsed, _ := url.Parse(value)
assert.Equal(t, parsed.Scheme, testStruct.Value.Get(nil).Scheme)
assert.Equal(t, parsed.Host, testStruct.Value.Get(nil).Host)
assert.Equal(t, parsed.RawQuery, testStruct.Value.Get(nil).RawQuery)
assert.Equal(t, parsed.Path, testStruct.Value.Get(nil).Path)
})
}
}
func (suite *HttpsURLTestSuite) TestMarshalOk() {
parsed, _ := url.Parse("https://example.com/path?q=1")
testStruct := &typeHttpsURLTestStruct{
Value: config.TypeHttpsURL{
Value: parsed,
},
}
encodedJSON, err := json.Marshal(testStruct)
suite.NoError(err)
suite.JSONEq(`{"value": "https://example.com/path?q=1"}`,
string(encodedJSON))
}
func (suite *HttpsURLTestSuite) TestGet() {
emptyURL := &url.URL{}
value := config.TypeHttpsURL{}
suite.Equal(emptyURL, value.Get(emptyURL))
value.Value = &url.URL{}
suite.Equal(value.Value, value.Get(emptyURL))
}
func TestTypeHttpsURL(t *testing.T) {
t.Parallel()
suite.Run(t, &HttpsURLTestSuite{})
}
+24 -24
View File
@@ -6,40 +6,40 @@ import (
)
type TypeIP struct {
value net.IP
Value net.IP
}
func (c *TypeIP) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
ip := net.ParseIP(string(data))
func (t *TypeIP) Set(value string) error {
ip := net.ParseIP(value)
if ip == nil {
return fmt.Errorf("incorrect ip address: %s", string(data))
return fmt.Errorf("incorret ip %s", value)
}
c.value = ip
t.Value = ip
return nil
}
func (c *TypeIP) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeIP) String() string {
if len(c.value) > 0 {
return c.value.String()
}
return ""
}
func (c TypeIP) Value(defaultValue net.IP) net.IP {
if c.value == nil {
func (t *TypeIP) Get(defaultValue net.IP) net.IP {
if len(t.Value) == 0 {
return defaultValue
}
return c.value
return t.Value
}
func (t *TypeIP) UnmarshalText(data []byte) error {
return t.Set(string(data))
}
func (t TypeIP) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeIP) String() string {
if len(t.Value) == 0 {
return ""
}
return t.Value.String()
}
+34 -45
View File
@@ -20,10 +20,11 @@ type TypeIPTestSuite struct {
func (suite *TypeIPTestSuite) TestUnmarshalFail() {
testData := []string{
"0.0.10",
"10.0.0.10:",
"xxx:80",
"2001:0db8:85a3:0000:0000:8a2e:4",
"",
"....",
"0...",
"300.200.200.800",
"[]",
}
for _, v := range testData {
@@ -39,74 +40,62 @@ func (suite *TypeIPTestSuite) TestUnmarshalFail() {
}
func (suite *TypeIPTestSuite) TestUnmarshalOk() {
testData := []string{
"0.0.0.0",
"10.0.0.10",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
testData := map[string]string{
"2001:0db8:85a3:0000:0000:8a2e:0370:7334": "2001:db8:85a3::8a2e:370:7334",
"127.0.0.1": "127.0.0.1",
}
for _, v := range testData {
value := v
for k, v := range testData {
expected := v
data, err := json.Marshal(map[string]string{
"value": v,
"value": k,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
suite.T().Run(k, func(t *testing.T) {
testStruct := &typeIPTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t,
net.ParseIP(value).String(),
testStruct.Value.Value(nil).String())
assert.Equal(t, expected, testStruct.Value.Get(nil).String())
})
}
}
func (suite *TypeIPTestSuite) TestMarshalOk() {
testData := []string{
"0.0.0.0",
"10.0.0.10",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"2001:db8:85a3::8a2e:370:7334",
"127.0.0.1",
}
for _, v := range testData {
value := net.ParseIP(v).String()
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
value := v
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeIPTestStruct{}
testStruct := &typeIPTestStruct{
Value: config.TypeIP{
Value: net.ParseIP(value),
},
}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, value, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
encodedJSON, err := json.Marshal(testStruct)
assert.NoError(t, err)
assert.Equal(t, value, string(marshalled))
expectedJSON, err := json.Marshal(map[string]string{
"value": value,
})
assert.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(encodedJSON))
})
}
}
func (suite *TypeIPTestSuite) TestValue() {
testStruct := &typeIPTestStruct{}
suite.Empty(testStruct.Value.String())
func (suite *TypeIPTestSuite) TestGet() {
value := config.TypeIP{}
suite.Equal("127.0.0.1", value.Get(net.ParseIP("127.0.0.1")).String())
suite.Nil(testStruct.Value.Value(nil))
suite.Equal("127.1.0.1", testStruct.Value.Value(net.ParseIP("127.1.0.1")).String())
data, err := json.Marshal(map[string]string{
"value": "127.0.0.1",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.Equal("127.0.0.1", testStruct.Value.Value(nil).String())
suite.Equal("127.0.0.1", testStruct.Value.Value(net.ParseIP("10.0.0.10")).String())
suite.NoError(value.Set("127.0.0.2"))
suite.Equal("127.0.0.2", value.Get(net.ParseIP("127.0.0.1")).String())
}
func TestTypeIP(t *testing.T) {
+20 -21
View File
@@ -6,36 +6,35 @@ import (
)
type TypeMetricPrefix struct {
value string
Value string
}
func (c *TypeMetricPrefix) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
func (t *TypeMetricPrefix) Set(value string) error {
if ok, err := regexp.MatchString("^[a-z0-9]+$", value); !ok || err != nil {
return fmt.Errorf("incorrect metric prefix %s: %w", value, err)
}
prefix := string(data)
if ok, err := regexp.MatchString("^[a-z0-9]+$", prefix); !ok || err != nil {
return fmt.Errorf("incorrect metric prefix: %s", prefix)
}
c.value = prefix
t.Value = value
return nil
}
func (c TypeMetricPrefix) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeMetricPrefix) String() string {
return c.value
}
func (c TypeMetricPrefix) Value(defaultValue string) string {
if c.value == "" {
func (t TypeMetricPrefix) Get(defaultValue string) string {
if t.Value == "" {
return defaultValue
}
return c.value
return t.Value
}
func (t *TypeMetricPrefix) UnmarshalText(data []byte) error {
return t.Set(string(data))
}
func (t TypeMetricPrefix) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeMetricPrefix) String() string {
return t.Value
}
+20 -65
View File
@@ -17,18 +17,13 @@ type TypeMetricPrefixTestSuite struct {
suite.Suite
}
func (suite *TypeMetricPrefixTestSuite) TestUnmarshalNil() {
typ := &config.TypeMetricPrefix{}
suite.NoError(typ.UnmarshalText(nil))
suite.Empty(typ.String())
}
func (suite *TypeMetricPrefixTestSuite) TestUnmarshalFail() {
testData := []string{
"aaa.aaa",
"aaa-bbb",
"aaa:ccc",
"metric prefix",
"",
"-",
"hello/world",
"lala*",
"++sdf++",
}
for _, v := range testData {
@@ -44,69 +39,29 @@ func (suite *TypeMetricPrefixTestSuite) TestUnmarshalFail() {
}
func (suite *TypeMetricPrefixTestSuite) TestUnmarshalOk() {
testData := []string{
"mtg",
"mtg111",
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeMetricPrefixTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, value, testStruct.Value.Value(""))
})
}
testStruct := &typeMetricPrefixTestStruct{}
suite.NoError(json.Unmarshal([]byte(`{"value": "mtg"}`), testStruct))
suite.Equal("mtg", testStruct.Value.Get("lalala"))
}
func (suite *TypeMetricPrefixTestSuite) TestMarshalOk() {
testData := []string{
"mtg",
"mtg111",
testStruct := &typeMetricPrefixTestStruct{
Value: config.TypeMetricPrefix{
Value: "mtg",
},
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeMetricPrefixTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, value, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, value, string(marshalled))
})
}
data, err := json.Marshal(testStruct)
suite.NoError(err)
suite.JSONEq(`{"value": "mtg"}`, string(data))
}
func (suite *TypeMetricPrefixTestSuite) TestValue() {
testStruct := &typeMetricPrefixTestStruct{}
func (suite *TypeMetricPrefixTestSuite) TestGet() {
value := config.TypeMetricPrefix{}
suite.Equal("lalala", value.Get("lalala"))
suite.Equal("mtg", testStruct.Value.Value("mtg"))
suite.Equal("vvv", testStruct.Value.Value("vvv"))
data, err := json.Marshal(map[string]string{
"value": "aaa",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.Equal("aaa", testStruct.Value.Value("mtg"))
suite.Equal("aaa", testStruct.Value.Value("vvv"))
value.Value = "mtg"
suite.Equal("mtg", value.Get("lalala"))
}
func TestTypeMetricPrefix(t *testing.T) {
+22 -22
View File
@@ -6,40 +6,40 @@ import (
)
type TypePort struct {
value uint
Value uint
}
func (c *TypePort) UnmarshalJSON(data []byte) error {
if len(data) == 0 {
return nil
}
intValue, err := strconv.ParseUint(string(data), 10, 64)
func (t *TypePort) Set(value string) error {
portValue, err := strconv.ParseUint(value, 10, 16)
if err != nil {
return fmt.Errorf("port number is not a number: %w", err)
return fmt.Errorf("incorrect port number (%v): %w", value, err)
}
if intValue == 0 || intValue >= 65536 {
return fmt.Errorf("port number should be 0 < portNo < 65536: %d", intValue)
if portValue == 0 {
return fmt.Errorf("incorrect port number (%s)", value)
}
c.value = uint(intValue)
t.Value = uint(portValue)
return nil
}
func (c *TypePort) MarshalJSON() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypePort) String() string {
return strconv.Itoa(int(c.value))
}
func (c TypePort) Value(defaultValue uint) uint {
if c.value == 0 {
func (t TypePort) Get(defaultValue uint) uint {
if t.Value == 0 {
return defaultValue
}
return c.value
return t.Value
}
func (t *TypePort) UnmarshalJSON(data []byte) error {
return t.Set(string(data))
}
func (t TypePort) MarshalJSON() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypePort) String() string {
return strconv.Itoa(int(t.Value))
}
+24 -70
View File
@@ -2,7 +2,6 @@ package config_test
import (
"encoding/json"
"strconv"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
@@ -18,97 +17,52 @@ type TypePortTestSuite struct {
suite.Suite
}
func (suite *TypePortTestSuite) TestUnmarshalNil() {
typ := &config.TypePort{}
suite.NoError(typ.UnmarshalJSON(nil))
suite.Equal("0", typ.String())
}
func (suite *TypePortTestSuite) TestUnmarshalFail() {
testData := []int{
-1,
1_000_000,
testData := []string{
"",
"port",
"0",
"-1",
"1.5",
"70000",
}
for _, v := range testData {
data, err := json.Marshal(map[string]int{
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(strconv.Itoa(v), func(t *testing.T) {
suite.T().Run(v, func(t *testing.T) {
assert.Error(t, json.Unmarshal(data, &typePortTestStruct{}))
})
}
}
func (suite *TypePortTestSuite) TestUnmarshalOk() {
testData := []int{
1,
1_000,
65535,
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]int{
"value": v,
})
suite.NoError(err)
suite.T().Run(strconv.Itoa(v), func(t *testing.T) {
testStruct := &typePortTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.EqualValues(t, value, testStruct.Value.Value(0))
})
}
testStruct := &typePortTestStruct{}
suite.NoError(json.Unmarshal([]byte(`{"value": 5}`), testStruct))
suite.EqualValues(5, testStruct.Value.Value)
}
func (suite *TypePortTestSuite) TestMarshalOk() {
testData := map[string]int{
"1": 1,
"1000": 1000,
"65535": 65535,
testStruct := &typePortTestStruct{
Value: config.TypePort{
Value: 10,
},
}
for k, v := range testData {
name := k
value := v
data, err := json.Marshal(map[string]int{
"value": value,
})
suite.NoError(err)
suite.T().Run(name, func(t *testing.T) {
testStruct := &typePortTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, name, testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, name, string(marshalled))
})
}
data, err := json.Marshal(testStruct)
suite.NoError(err)
suite.JSONEq(`{"value":10}`, string(data))
}
func (suite *TypePortTestSuite) TestValue() {
testStruct := &typePortTestStruct{}
func (suite *TypePortTestSuite) TestGet() {
value := config.TypePort{}
suite.EqualValues(10, value.Get(10))
suite.EqualValues(0, testStruct.Value.Value(0))
suite.EqualValues(1, testStruct.Value.Value(1))
data, err := json.Marshal(map[string]int{
"value": 5,
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.EqualValues(5, testStruct.Value.Value(0))
suite.EqualValues(5, testStruct.Value.Value(1))
value.Value = 100
suite.EqualValues(100, value.Get(10))
}
func TestTypePort(t *testing.T) {
+38 -26
View File
@@ -6,45 +6,57 @@ import (
)
const (
// TypePreferIPPreferIPv4 states that you prefer to use IPv4 addresses
// but IPv6 is also possible.
TypePreferIPPreferIPv4 = "prefer-ipv4"
// TypePreferIPPreferIPv6 states that you prefer to use IPv6 addresses
// but IPv4 is also possible.
TypePreferIPPreferIPv6 = "prefer-ipv6"
TypePreferOnlyIPv4 = "only-ipv4"
TypePreferOnlyIPv6 = "only-ipv6"
// TypePreferOnlyIPv4 states that you prefer to use IPv4 addresses
// only.
TypePreferOnlyIPv4 = "only-ipv4"
// TypePreferOnlyIPv6 states that you prefer to use IPv6 addresses
// only.
TypePreferOnlyIPv6 = "only-ipv6"
)
type TypePreferIP struct {
value string
Value string
}
func (c *TypePreferIP) UnmarshalText(data []byte) error {
if len(data) == 0 {
func (t *TypePreferIP) Set(value string) error {
value = strings.ToLower(value)
switch value {
case TypePreferIPPreferIPv4, TypePreferIPPreferIPv6,
TypePreferOnlyIPv4, TypePreferOnlyIPv6:
t.Value = value
return nil
}
text := strings.ToLower(string(data))
switch text {
case TypePreferIPPreferIPv4, TypePreferIPPreferIPv6, TypePreferOnlyIPv4, TypePreferOnlyIPv6:
c.value = text
default:
return fmt.Errorf("incorrect prefer-ip value: %s", string(data))
return fmt.Errorf("unsupported ip preference: %s", value)
}
return nil
}
func (c TypePreferIP) MarshalText() ([]byte, error) {
return []byte(c.value), nil
}
func (c *TypePreferIP) String() string {
return c.value
}
func (c *TypePreferIP) Value(defaultValue string) string {
if c.value == "" {
func (t *TypePreferIP) Get(defaultValue string) string {
if t.Value == "" {
return defaultValue
}
return c.value
return t.Value
}
func (t *TypePreferIP) UnmarshalText(data []byte) error {
return t.Set(string(data))
}
func (t TypePreferIP) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypePreferIP) String() string {
return t.Value
}
+29 -58
View File
@@ -18,18 +18,12 @@ type TypePreferIPTestSuite struct {
suite.Suite
}
func (suite *TypePreferIPTestSuite) TestUnmarshalNil() {
typ := &config.TypePreferIP{}
suite.NoError(typ.UnmarshalText(nil))
suite.Empty(typ.String())
}
func (suite *TypePreferIPTestSuite) TestUnmarshalFail() {
testData := []string{
"p",
"ipv4",
"onlyipv4",
"ipv6prefer",
"",
"prefer",
"preferipv4",
config.TypePreferIPPreferIPv4 + "_",
}
for _, v := range testData {
@@ -50,14 +44,10 @@ func (suite *TypePreferIPTestSuite) TestUnmarshalOk() {
config.TypePreferIPPreferIPv6,
config.TypePreferOnlyIPv4,
config.TypePreferOnlyIPv6,
strings.ToUpper(config.TypePreferIPPreferIPv4),
strings.ToUpper(config.TypePreferIPPreferIPv6),
strings.ToUpper(config.TypePreferOnlyIPv4),
strings.ToUpper(config.TypePreferOnlyIPv6),
strings.ToLower(config.TypePreferIPPreferIPv4),
strings.ToLower(config.TypePreferIPPreferIPv6),
strings.ToLower(config.TypePreferOnlyIPv4),
strings.ToLower(config.TypePreferOnlyIPv6),
strings.ToTitle(config.TypePreferOnlyIPv4),
strings.ToTitle(config.TypePreferOnlyIPv6),
strings.ToTitle(config.TypePreferIPPreferIPv4),
strings.ToTitle(config.TypePreferIPPreferIPv6),
}
for _, v := range testData {
@@ -70,11 +60,8 @@ func (suite *TypePreferIPTestSuite) TestUnmarshalOk() {
suite.T().Run(v, func(t *testing.T) {
testStruct := &typePreferIPTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.EqualValues(t,
strings.ToLower(value),
testStruct.Value.Value(config.TypePreferIPPreferIPv4))
assert.Equal(t, strings.ToLower(value), testStruct.Value.Value)
})
}
}
@@ -85,55 +72,39 @@ func (suite *TypePreferIPTestSuite) TestMarshalOk() {
config.TypePreferIPPreferIPv6,
config.TypePreferOnlyIPv4,
config.TypePreferOnlyIPv6,
strings.ToUpper(config.TypePreferIPPreferIPv4),
strings.ToUpper(config.TypePreferIPPreferIPv6),
strings.ToUpper(config.TypePreferOnlyIPv4),
strings.ToUpper(config.TypePreferOnlyIPv6),
strings.ToLower(config.TypePreferIPPreferIPv4),
strings.ToLower(config.TypePreferIPPreferIPv6),
strings.ToLower(config.TypePreferOnlyIPv4),
strings.ToLower(config.TypePreferOnlyIPv6),
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typePreferIPTestStruct{}
testStruct := &typePreferIPTestStruct{
Value: config.TypePreferIP{
Value: value,
},
}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, strings.ToLower(value), testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
encodedJSON, err := json.Marshal(testStruct)
assert.NoError(t, err)
assert.Equal(t, strings.ToLower(value), string(marshalled))
expectedJSON, err := json.Marshal(map[string]string{
"value": value,
})
assert.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(encodedJSON))
})
}
}
func (suite *TypePreferIPTestSuite) TestValue() {
testStruct := &typePreferIPTestStruct{}
func (suite *TypePreferIPTestSuite) TestGet() {
value := config.TypePreferIP{}
suite.Equal(config.TypePreferIPPreferIPv4,
value.Get(config.TypePreferIPPreferIPv4))
suite.EqualValues(config.TypePreferIPPreferIPv4,
testStruct.Value.Value(config.TypePreferIPPreferIPv4))
suite.EqualValues(config.TypePreferIPPreferIPv6,
testStruct.Value.Value(config.TypePreferIPPreferIPv6))
data, err := json.Marshal(map[string]string{
"value": config.TypePreferOnlyIPv4,
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.EqualValues(config.TypePreferOnlyIPv4,
testStruct.Value.Value(config.TypePreferOnlyIPv6))
suite.EqualValues(config.TypePreferOnlyIPv4,
testStruct.Value.Value(config.TypePreferIPPreferIPv6))
suite.NoError(value.Set(config.TypePreferIPPreferIPv6))
suite.Equal(config.TypePreferIPPreferIPv6,
value.Get(config.TypePreferIPPreferIPv4))
}
func TestTypePreferIP(t *testing.T) {
+65
View File
@@ -0,0 +1,65 @@
package config
import (
"fmt"
"net"
"net/url"
)
const typeProxyURLDefaultSOCKS5Port = "1080"
type TypeProxyURL struct {
Value *url.URL
}
func (t *TypeProxyURL) Set(value string) error {
parsedURL, err := url.Parse(value)
if err != nil {
return fmt.Errorf("value is not correct URL (%s): %w", value, err)
}
if parsedURL.Host == "" {
return fmt.Errorf("url has to have a schema: %s", value)
}
switch parsedURL.Scheme {
case "socks5", "socks5h":
default:
return fmt.Errorf("unsupported schema: %s", parsedURL.Scheme)
}
if _, _, err := net.SplitHostPort(parsedURL.Host); err != nil {
parsedURL.Host = net.JoinHostPort(
parsedURL.Host,
typeProxyURLDefaultSOCKS5Port,
)
}
t.Value = parsedURL
return nil
}
func (t *TypeProxyURL) Get(defaultValue *url.URL) *url.URL {
if t.Value == nil {
return defaultValue
}
return t.Value
}
func (t *TypeProxyURL) UnmarshalText(data []byte) error {
return t.Set(string(data))
}
func (t TypeProxyURL) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
func (t TypeProxyURL) String() string {
if t.Value == nil {
return ""
}
return t.Value.String()
}
+96
View File
@@ -0,0 +1,96 @@
package config_test
import (
"encoding/json"
"net/url"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeProxyURLTestStruct struct {
Value config.TypeProxyURL `json:"value"`
}
type ProxyURLTestSuite struct {
suite.Suite
}
func (suite *ProxyURLTestSuite) TestUnmarshalFail() {
testData := []string{
"",
"socks5://",
"://lala",
"/path",
}
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, &typeProxyURLTestStruct{}))
})
}
}
func (suite *ProxyURLTestSuite) TestUnmarshalOk() {
testData := map[string]string{
"socks5://127.0.0.1/?open_threshold=1": "socks5://127.0.0.1:1080/?open_threshold=1",
"socks5://127.0.0.1:80": "socks5://127.0.0.1:80",
}
for k, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": k,
})
suite.NoError(err)
suite.T().Run(k, func(t *testing.T) {
testStruct := &typeProxyURLTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
parsed, _ := url.Parse(value)
assert.Equal(t, parsed.Scheme, testStruct.Value.Get(nil).Scheme)
assert.Equal(t, parsed.Host, testStruct.Value.Get(nil).Host)
assert.Equal(t, parsed.RawQuery, testStruct.Value.Get(nil).RawQuery)
assert.Equal(t, parsed.Path, testStruct.Value.Get(nil).Path)
})
}
}
func (suite *ProxyURLTestSuite) TestMarshalOk() {
parsed, _ := url.Parse("socks5://127.0.0.1:1080?open_threshold=1")
testStruct := &typeProxyURLTestStruct{
Value: config.TypeProxyURL{
Value: parsed,
},
}
encodedJSON, err := json.Marshal(testStruct)
suite.NoError(err)
suite.JSONEq(`{"value": "socks5://127.0.0.1:1080?open_threshold=1"}`,
string(encodedJSON))
}
func (suite *ProxyURLTestSuite) TestGet() {
emptyURL := &url.URL{}
value := config.TypeProxyURL{}
suite.Equal(emptyURL, value.Get(emptyURL))
value.Value = &url.URL{}
suite.Equal(value.Value, value.Get(emptyURL))
}
func TestTypeProxyURL(t *testing.T) {
t.Parallel()
suite.Run(t, &ProxyURLTestSuite{})
}
+34 -25
View File
@@ -6,44 +6,53 @@ import (
)
const (
// TypeStatsdTagFormatInfluxdb defines a tag format compatible with
// InfluxDB.
TypeStatsdTagFormatInfluxdb = "influxdb"
TypeStatsdTagFormatDatadog = "datadog"
// TypeStatsdTagFormatDatadog defines a tag format compatible with
// DataDog.
TypeStatsdTagFormatDatadog = "datadog"
// TypeStatsdTagFormatGraphite defines a tag format compatible with
// Graphite.
TypeStatsdTagFormatGraphite = "graphite"
)
type TypeStatsdTagFormat struct {
value string
Value string
}
func (c *TypeStatsdTagFormat) UnmarshalText(data []byte) error {
if len(data) == 0 {
func (t *TypeStatsdTagFormat) Set(value string) error {
lowercasedValue := strings.ToLower(value)
switch lowercasedValue {
case TypeStatsdTagFormatDatadog, TypeStatsdTagFormatInfluxdb,
TypeStatsdTagFormatGraphite:
t.Value = lowercasedValue
return nil
}
text := strings.ToLower(string(data))
switch text {
case TypeStatsdTagFormatInfluxdb, TypeStatsdTagFormatDatadog, TypeStatsdTagFormatGraphite:
c.value = text
default:
return fmt.Errorf("incorrect tag format value: %s", string(data))
return fmt.Errorf("unknown tag format %s", value)
}
return nil
}
func (c TypeStatsdTagFormat) MarshalText() ([]byte, error) {
return []byte(c.value), nil
}
func (c *TypeStatsdTagFormat) String() string {
return c.value
}
func (c *TypeStatsdTagFormat) Value(defaultValue string) string {
if c.value == "" {
func (t TypeStatsdTagFormat) Get(defaultValue string) string {
if t.Value == "" {
return defaultValue
}
return c.value
return t.Value
}
func (t *TypeStatsdTagFormat) UnmarshalText(data []byte) error {
return t.Set(string(data))
}
func (t *TypeStatsdTagFormat) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
func (t *TypeStatsdTagFormat) String() string {
return t.Value
}
+31 -59
View File
@@ -14,22 +14,14 @@ type typeStatsdTagFormatTestStruct struct {
Value config.TypeStatsdTagFormat `json:"value"`
}
type TypeStatsdTagFormatTestSuite struct {
type StatsdTagFormatTestSuite struct {
suite.Suite
}
func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalNil() {
typ := &config.TypeStatsdTagFormat{}
suite.NoError(typ.UnmarshalText(nil))
suite.Equal("lalala", typ.Value("lalala"))
}
func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalFail() {
func (suite *StatsdTagFormatTestSuite) TestUnmarshalFail() {
testData := []string{
"p",
"ipv4",
"onlyipv4",
"ipv6prefer",
"",
"dogdog",
}
for _, v := range testData {
@@ -44,17 +36,14 @@ func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalFail() {
}
}
func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalOk() {
func (suite *StatsdTagFormatTestSuite) TestUnmarshalOk() {
testData := []string{
config.TypeStatsdTagFormatDatadog,
config.TypeStatsdTagFormatInfluxdb,
config.TypeStatsdTagFormatGraphite,
strings.ToUpper(config.TypeStatsdTagFormatDatadog),
config.TypeStatsdTagFormatDatadog,
strings.ToUpper(config.TypeStatsdTagFormatInfluxdb),
strings.ToUpper(config.TypeStatsdTagFormatGraphite),
strings.ToLower(config.TypeStatsdTagFormatDatadog),
strings.ToLower(config.TypeStatsdTagFormatInfluxdb),
strings.ToLower(config.TypeStatsdTagFormatGraphite),
strings.ToUpper(config.TypeStatsdTagFormatDatadog),
}
for _, v := range testData {
@@ -67,70 +56,53 @@ func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalOk() {
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeStatsdTagFormatTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.EqualValues(t,
strings.ToLower(value),
testStruct.Value.Value(config.TypeStatsdTagFormatDatadog))
assert.Equal(t, strings.ToLower(value), testStruct.Value.Value)
})
}
}
func (suite *TypeStatsdTagFormatTestSuite) TestMarshalOk() {
func (suite *StatsdTagFormatTestSuite) TestMarshalOk() {
testData := []string{
config.TypeStatsdTagFormatDatadog,
config.TypeStatsdTagFormatInfluxdb,
config.TypeStatsdTagFormatGraphite,
strings.ToUpper(config.TypeStatsdTagFormatDatadog),
strings.ToUpper(config.TypeStatsdTagFormatInfluxdb),
strings.ToUpper(config.TypeStatsdTagFormatGraphite),
strings.ToLower(config.TypeStatsdTagFormatDatadog),
strings.ToLower(config.TypeStatsdTagFormatInfluxdb),
strings.ToLower(config.TypeStatsdTagFormatGraphite),
config.TypeStatsdTagFormatDatadog,
}
for _, v := range testData {
value := v
data, err := json.Marshal(map[string]string{
"value": v,
})
suite.NoError(err)
suite.T().Run(v, func(t *testing.T) {
testStruct := &typeStatsdTagFormatTestStruct{}
testStruct := &typeStatsdTagFormatTestStruct{
Value: config.TypeStatsdTagFormat{
Value: value,
},
}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, strings.ToLower(value), testStruct.Value.String())
marshalled, err := testStruct.Value.MarshalText()
encodedJSON, err := json.Marshal(testStruct)
assert.NoError(t, err)
assert.Equal(t, strings.ToLower(value), string(marshalled))
expectedJSON, err := json.Marshal(map[string]string{
"value": value,
})
assert.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(encodedJSON))
})
}
}
func (suite *TypeStatsdTagFormatTestSuite) TestValue() {
testStruct := &typePreferIPTestStruct{}
func (suite *StatsdTagFormatTestSuite) TestGet() {
value := config.TypeStatsdTagFormat{}
suite.Equal(config.TypeStatsdTagFormatDatadog,
value.Get(config.TypeStatsdTagFormatDatadog))
suite.EqualValues(config.TypePreferIPPreferIPv4,
testStruct.Value.Value(config.TypePreferIPPreferIPv4))
suite.EqualValues(config.TypePreferIPPreferIPv6,
testStruct.Value.Value(config.TypePreferIPPreferIPv6))
data, err := json.Marshal(map[string]string{
"value": config.TypePreferOnlyIPv4,
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.EqualValues(config.TypePreferOnlyIPv4,
testStruct.Value.Value(config.TypePreferOnlyIPv6))
suite.EqualValues(config.TypePreferOnlyIPv4,
testStruct.Value.Value(config.TypePreferIPPreferIPv6))
suite.NoError(value.Set(config.TypeStatsdTagFormatInfluxdb))
suite.Equal(config.TypeStatsdTagFormatInfluxdb,
value.Get(config.TypeStatsdTagFormatDatadog))
}
func TestTypeStatsdTagFormat(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeStatsdTagFormatTestSuite{})
suite.Run(t, &StatsdTagFormatTestSuite{})
}
-71
View File
@@ -1,71 +0,0 @@
package config
import (
"fmt"
"net"
"net/url"
)
type TypeURL struct {
value *url.URL
}
func (c *TypeURL) UnmarshalText(data []byte) error { // nolint: cyclop
if len(data) == 0 {
return nil
}
value, err := url.Parse(string(data))
if err != nil {
return fmt.Errorf("incorrect URL: %w", err)
}
switch value.Scheme {
case "http", "https", "socks5":
case "":
return fmt.Errorf("url %s has to have a schema", value)
default:
return fmt.Errorf("unsupported schema %s", value.Scheme)
}
if value.Host == "" {
return fmt.Errorf("url %s has to have a host", value)
}
if _, _, err := net.SplitHostPort(value.Host); err != nil {
switch value.Scheme {
case "http":
value.Host = net.JoinHostPort(value.Host, "80")
case "https":
value.Host = net.JoinHostPort(value.Host, "443")
case "socks5":
value.Host = net.JoinHostPort(value.Host, "1080")
default:
return fmt.Errorf("cannot set a default port for %s", value)
}
}
c.value = value
return nil
}
func (c *TypeURL) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
func (c TypeURL) String() string {
if c.value == nil {
return ""
}
return c.value.String()
}
func (c TypeURL) Value(defaultValue *url.URL) *url.URL {
if c.value == nil {
return defaultValue
}
return c.value
}
-107
View File
@@ -1,107 +0,0 @@
package config_test
import (
"encoding/json"
"net/url"
"testing"
"github.com/9seconds/mtg/v2/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type typeURLTestStruct struct {
Value config.TypeURL `json:"value"`
}
type TypeURLTestSuite struct {
suite.Suite
}
func (suite *TypeURLTestSuite) TestUnmarshalNil() {
u, _ := url.Parse("https://google.com")
typ := &config.TypeURL{}
suite.NoError(typ.UnmarshalText(nil))
suite.Empty(typ.String())
suite.Equal("https://google.com", typ.Value(u).String())
}
func (suite *TypeURLTestSuite) TestUnmarshalFail() {
testData := []string{
"http:/aaa.com",
"ipv4",
"111",
"://111",
"http://aaa.com:xxx",
"gopher://aaa.com:888",
"gopher://aaa.com",
}
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, &typeURLTestStruct{}))
})
}
}
func (suite *TypeURLTestSuite) TestUnmarshalOk() {
testData := map[string]string{
"https://10.0.0.10:80": "https://10.0.0.10:80",
"https://10.0.0.10:443": "https://10.0.0.10",
"http://10.0.0.10:8": "http://10.0.0.10:8",
"http://10.0.0.10:80": "http://10.0.0.10",
"socks5://10.0.0.10:1080": "socks5://10.0.0.10",
"socks5://10.0.0.10:888": "socks5://10.0.0.10:888",
}
for k, v := range testData {
expected := k
actual := v
data, err := json.Marshal(map[string]string{
"value": actual,
})
suite.NoError(err)
suite.T().Run(actual, func(t *testing.T) {
testStruct := &typeURLTestStruct{}
assert.NoError(t, json.Unmarshal(data, testStruct))
assert.Equal(t, expected, testStruct.Value.Value(nil).String())
marshalled, err := testStruct.Value.MarshalText()
assert.NoError(t, err)
assert.Equal(t, expected, string(marshalled))
})
}
}
func (suite *TypeURLTestSuite) TestValue() {
testStruct := &typeURLTestStruct{}
u1, _ := url.Parse("https://10.0.0.10:80")
u2, _ := url.Parse("https://10.1.0.10:80")
suite.Equal("https://10.0.0.10:80", testStruct.Value.Value(u1).String())
suite.Equal("https://10.1.0.10:80", testStruct.Value.Value(u2).String())
data, err := json.Marshal(map[string]string{
"value": "http://127.0.0.1:80",
})
suite.NoError(err)
suite.NoError(json.Unmarshal(data, testStruct))
suite.Equal("http://127.0.0.1:80", testStruct.Value.Value(u1).String())
suite.Equal("http://127.0.0.1:80", testStruct.Value.Value(u2).String())
}
func TestTypeURL(t *testing.T) {
t.Parallel()
suite.Run(t, &TypeURLTestSuite{})
}
+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)
go func() {
io.Copy(buf, reader) // nolint: errcheck
io.Copy(buf, reader) //nolint: errcheck
close(closeChan)
}()
@@ -35,7 +35,7 @@ func captureOutput(filefp **os.File, callback func()) string {
callback()
writer.Close()
writer.Close() //nolint: errcheck
<-closeChan
return strings.TrimSpace(buf.String())
+12 -6
View File
@@ -5,6 +5,7 @@ import (
"net"
"net/http"
"github.com/9seconds/mtg/v2/essentials"
"github.com/stretchr/testify/mock"
)
@@ -12,19 +13,24 @@ type MtglibNetworkMock struct {
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)
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)
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) NativeDialer() *net.Dialer {
return m.Called().Get(0).(*net.Dialer)
}
func (m *MtglibNetworkMock) MakeHTTPClient(dialFunc func(ctx context.Context,
network, address string) (net.Conn, error)) *http.Client {
return m.Called(dialFunc).Get(0).(*http.Client)
network, address string) (essentials.Conn, error),
) *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"
)
type NetConnMock struct {
type EssentialsConnMock struct {
mock.Mock
}
func (n *NetConnMock) Read(b []byte) (int, error) {
func (n *EssentialsConnMock) Read(b []byte) (int, error) {
args := n.Called(b)
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)
return args.Int(0), args.Error(1)
}
func (n *NetConnMock) Close() error {
return n.Called().Error(0) // nolint: wrapcheck
func (n *EssentialsConnMock) Close() error {
return n.Called().Error(0) //nolint: wrapcheck
}
func (n *NetConnMock) LocalAddr() net.Addr {
return n.Called().Get(0).(net.Addr)
func (n *EssentialsConnMock) CloseRead() error {
return n.Called().Error(0) //nolint: wrapcheck
}
func (n *NetConnMock) RemoteAddr() net.Addr {
return n.Called().Get(0).(net.Addr)
func (n *EssentialsConnMock) CloseWrite() error {
return n.Called().Error(0) //nolint: wrapcheck
}
func (n *NetConnMock) SetDeadline(t time.Time) error {
return n.Called(t).Error(0) // nolint: wrapcheck
func (n *EssentialsConnMock) LocalAddr() net.Addr {
return n.Called().Get(0).(net.Addr) //nolint: forcetypeassert
}
func (n *NetConnMock) SetReadDeadline(t time.Time) error {
return n.Called(t).Error(0) // nolint: wrapcheck
func (n *EssentialsConnMock) RemoteAddr() net.Addr {
return n.Called().Get(0).(net.Addr) //nolint: forcetypeassert
}
func (n *NetConnMock) SetWriteDeadline(t time.Time) error {
return n.Called(t).Error(0) // nolint: wrapcheck
func (n *EssentialsConnMock) SetDeadline(t time.Time) error {
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
}
+19
View File
@@ -0,0 +1,19 @@
package utils
import "net/url"
func MakeQRCodeURL(data string) string {
values := url.Values{}
values.Set("qzone", "4")
values.Set("format", "svg")
values.Set("data", data)
rv := url.URL{
Scheme: "https",
Host: "api.qrserver.com",
Path: "/v1/create-qr-code",
RawQuery: values.Encode(),
}
return rv.String()
}
+31
View File
@@ -0,0 +1,31 @@
package utils_test
import (
"net/url"
"strings"
"testing"
"github.com/9seconds/mtg/v2/internal/utils"
"github.com/stretchr/testify/suite"
)
type MakeQRCodeURLTestSuite struct {
suite.Suite
}
func (suite *MakeQRCodeURLTestSuite) TestSomeData() {
value := utils.MakeQRCodeURL("some data")
parsed, err := url.Parse(value)
suite.NoError(err)
suite.Equal("some data", parsed.Query().Get("data"))
suite.Equal("svg", parsed.Query().Get("format"))
suite.Equal("api.qrserver.com", strings.TrimPrefix(parsed.Host, "www."))
suite.Equal("v1/create-qr-code", strings.Trim(parsed.Path, "/"))
}
func TestMakeQRCodeURL(t *testing.T) {
t.Parallel()
suite.Run(t, &MakeQRCodeURLTestSuite{})
}
+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
}
+26
View File
@@ -0,0 +1,26 @@
package utils
import (
"fmt"
"os"
"github.com/9seconds/mtg/v2/internal/config"
)
func ReadConfig(path string) (*config.Config, error) {
content, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("cannot read config file: %w", err)
}
conf, err := config.Parse(content)
if err != nil {
return nil, fmt.Errorf("cannot parse config: %w", err)
}
if err := conf.Validate(); err != nil {
return nil, fmt.Errorf("invalid config: %w", err)
}
return conf, nil
}
+55
View File
@@ -0,0 +1,55 @@
package utils_test
import (
"path/filepath"
"testing"
"github.com/9seconds/mtg/v2/internal/utils"
"github.com/stretchr/testify/suite"
)
type ReadConfigTestSuite struct {
suite.Suite
}
func (suite *ReadConfigTestSuite) GetConfigPath(filename string) string {
return filepath.Join("testdata", filename)
}
func (suite *ReadConfigTestSuite) TestReadMinimal() {
conf, err := utils.ReadConfig(suite.GetConfigPath("minimal.toml"))
suite.NoError(err)
suite.NoError(conf.Validate())
suite.Equal("0.0.0.0:80", conf.BindTo.Get(""))
suite.Equal("7mqFMMq3P2Tvvt_rPx5qhmFnb29nbGUuY29t", conf.Secret.Base64())
}
func (suite *ReadConfigTestSuite) TestReadAbsentFile() {
_, err := utils.ReadConfig(suite.GetConfigPath("unknown.file"))
suite.Error(err)
}
func (suite *ReadConfigTestSuite) TestBrokenFile() {
_, err := utils.ReadConfig(suite.GetConfigPath("broken.toml"))
suite.Error(err)
}
func (suite *ReadConfigTestSuite) TestMissedBindTo() {
_, err := utils.ReadConfig(suite.GetConfigPath("missed-bindto.toml"))
suite.Error(err)
}
func (suite *ReadConfigTestSuite) TestMissedSecret() {
_, err := utils.ReadConfig(suite.GetConfigPath("missed-secret.toml"))
suite.Error(err)
}
func (suite *ReadConfigTestSuite) TestEmpty() {
_, err := utils.ReadConfig(suite.GetConfigPath("empty.toml"))
suite.Error(err)
}
func TestReadConfig(t *testing.T) {
t.Parallel()
suite.Run(t, &ReadConfigTestSuite{})
}
-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
package utils
+1
View File
@@ -1,3 +1,4 @@
//go:build windows
// +build windows
package utils
+1
View File
@@ -0,0 +1 @@
11
View File
+2
View File
@@ -0,0 +1,2 @@
secret = "7mqFMMq3P2Tvvt_rPx5qhmFnb29nbGUuY29t"

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