REPOSITORY / ScuroNeko/mtg

Compare commits

DIFF REPOSITORY

Compare commits

...
179 Commits
Author SHA1 Message Date
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 0882d45d6a Update dependencies 2021-05-05 11:47:57 +03:00
9seconds 86c6541e0d Small typo 2021-05-05 11:47:12 +03:00
Sergey ArkhipovandGitHub cc1f60c4a6 Merge pull request #187 from 9seconds/trimpath
Add trimpath for better build reproducibility
2021-05-05 11:44:28 +03:00
9seconds 700f71bb31 Add trimpath for better build reproducibility 2021-05-05 11:23:02 +03:00
Sergey ArkhipovandGitHub 097b52c8ef Make values in tag meaning more consistent 2021-04-12 18:53:23 +03:00
Sergey ArkhipovandGitHub c3ffd7a307 Merge pull request #184 from 9seconds/e2e-integration-test
Add a real integration test
2021-04-12 10:41:19 +03:00
9seconds fe3b0f3ca5 Add a real integration test 2021-04-12 10:29:19 +03:00
Sergey ArkhipovandGitHub 2273a0e4af Merge pull request #183 from 9seconds/test-stage
Add option to use telegram test dcs
2021-04-09 17:14:08 +03:00
9seconds 115510985a Add option to use telegram test dcs 2021-04-09 16:45:37 +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
180 changed files with 4785 additions and 3598 deletions
+55 -6
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,11 +38,11 @@ jobs:
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 5
timeout-minutes: 10
strategy:
matrix:
go_version:
- ^1.16
- ^1.19
steps:
- name: Checkout
uses: actions/checkout@v2
@@ -57,6 +69,38 @@ jobs:
with:
file: ./coverage.txt
fuzz:
name: Fuzzing
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: recursive
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ^1.18
- name: Cache fuzz results
uses: actions/cache@v2
with:
path: ~/.cache/go-build/fuzz
key: ${{ runner.os }}-go-${{ hashFiles('**/*_fuzz_test.go', '**/*_fuzz_internal_test.go') }}
restore-keys: ${{ runner.os }}-go-
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- name: Run fuzzing
run: make -j4 fuzz
lint:
name: Lint
runs-on: ubuntu-latest
@@ -67,15 +111,20 @@ jobs:
with:
submodules: recursive
- name: Run linter
uses: golangci/golangci-lint-action@v2
- name: Setup Go
uses: actions/setup-go@v2
with:
version: v1.39.0
go-version: ^1.19
- name: Run linter
uses: golangci/golangci-lint-action@v3
with:
version: v1.48.0
docker:
name: Docker
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v2
+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:
+1
View File
@@ -10,3 +10,4 @@ vendor/
ccbuilds/
.bin/
coverage.txt
dist/
+11 -1
View File
@@ -9,4 +9,14 @@ format = "colored-line-number"
[linters]
enable-all = true
disable = ["gochecknoglobals", "gas", "goerr113", "exhaustivestruct"]
disable = [
"containedctx",
"exhaustivestruct",
"exhaustruct",
"gas",
"gochecknoglobals",
"goerr113",
"ireturn",
"thelper",
"varnamelen",
]
+65
View File
@@ -0,0 +1,65 @@
---
project_name: mtg
before:
hooks:
- go mod tidy
- go generate ./...
builds:
- binary: '{{ .ProjectName }}'
goos:
- darwin
- freebsd
- linux
- netbsd
- openbsd
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: freebsd
goarch: arm64
- goos: netbsd
goarch: arm64
- goos: openbsd
goarch: arm64
archives:
- name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
format: tar.gz
wrap_in_directory: true
format_overrides:
- goos: windows
format: zip
files:
- LICENSE
- README.md
- SECURITY.md
gomod:
proxy: true
snapshot:
name_template: '{{ .Version }}'
checksum:
name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt'
source:
enabled: true
name_template: '{{ .ProjectName }}-sources'
+1 -1
View File
@@ -1,7 +1,7 @@
###############################################################################
# BUILD STAGE
FROM golang:1.16-alpine AS build
FROM golang:1.19-alpine AS build
RUN set -x \
&& apk --no-cache --update add \
+50 -36
View File
@@ -2,14 +2,12 @@ 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.48.0
GOLANGCI_LINT_VERSION := v1.39.0
VERSION := $(shell git describe --exact-match HEAD 2>/dev/null || git describe --tags --always)
COMMON_BUILD_FLAGS := -trimpath -mod=readonly -ldflags="-extldflags '-static' -s -w -X 'main.version=$(VERSION)'"
VERSION_GO := $(shell go version)
VERSION_DATE := $(shell date -Ru)
VERSION_TAG := $(shell git describe --tags --always)
COMMON_BUILD_FLAGS := -mod=readonly -ldflags="-extldflags '-static' -s -w -X 'main.version=$(VERSION_TAG) ($(VERSION_GO)) [$(VERSION_DATE)]'"
FUZZ_FLAGS := -fuzztime=120s
GOBIN := $(ROOT_DIR)/.bin
GOTOOL := env "GOBIN=$(GOBIN)" "PATH=$(ROOT_DIR)/.bin:$(PATH)"
@@ -33,26 +31,15 @@ static:
-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
.bin:
@mkdir -p "$(GOBIN)" || true
.PHONY: fmt
fmt:
@$(GOTOOL) gofumpt -w -s -extra "$(ROOT_DIR)"
@$(GOTOOL) gofumpt -w -extra "$(ROOT_DIR)"
.PHONY: test
test:
@@ -62,9 +49,6 @@ test:
citest:
@go test -coverprofile=coverage.txt -covermode=atomic -parallel 2 -race -v ./...
.PHONY: crosscompile
crosscompile: $(CC_BINARIES)
.PHONY: clean
clean:
@git clean -xfd && \
@@ -75,6 +59,12 @@ clean:
lint:
@$(GOTOOL) golangci-lint run
.PHONY: release
release:
@$(GOTOOL) goreleaser release --snapshot --rm-dist && \
find "$(ROOT_DIR)/dist" -type d | grep -vP "dist$$" | xargs -r rm -rf && \
rm -f "$(ROOT_DIR)/dist/config.yaml"
.PHONY: docker
docker:
@docker build --pull -t "$(IMAGE_NAME)" "$(ROOT_DIR)"
@@ -84,24 +74,48 @@ doc:
@$(GOTOOL) godoc -http 0.0.0.0:10000
.PHONY: install-tools
install-tools: install-tools-lint install-tools-godoc install-tools-gofumpt
install-tools: install-tools-lint install-tools-godoc install-tools-gofumpt install-tools-goreleaser
.PHONY: install-tools-lint
install-tools-lint:
@mkdir -p "$(GOBIN)" || true && \
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh \
install-tools-lint: .bin
@curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.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
install-tools-godoc: .bin
@$(GOTOOL) go install golang.org/x/tools/cmd/godoc@latest
.PHONY: install-tools-gofumpt
install-tools-gofumpt:
@mkdir -p "$(GOBIN)" || true && \
$(GOTOOL) go get -u mvdan.cc/gofumpt
install-tools-gofumpt: .bin
@$(GOTOOL) go install mvdan.cc/gofumpt@latest
.PHONY: goreleaser
install-tools-goreleaser: .bin
@$(GOTOOL) go install github.com/goreleaser/goreleaser@latest
.PHONY: update-deps
upgrade-deps:
$go get -u && go mod tidy
update-deps:
@go get -u && go mod tidy -go=1.18
.PHONY: fuzz
fuzz: fuzz-ClientHello fuzz-ServerGenerateHandshakeFrame fuzz-ClientHandshake fuzz-ServerReceive fuzz-ServerSend
.PHONY: fuzz-ClientHello
fuzz-ClientHello:
@go test -fuzz=FuzzClientHello $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/faketls"
.PHONY: fuzz-ServerGenerateHandshakeFrame
fuzz-ServerGenerateHandshakeFrame:
@go test -fuzz=FuzzServerGenerateHandshakeFrame $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
.PHONY: fuzz-ClientHandshake
fuzz-ClientHandshake:
@go test -fuzz=FuzzClientHandshake $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
.PHONY: fuzz-ServerReceive
fuzz-ServerReceive:
@go test -fuzz=FuzzServerReceive $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
.PHONY: fuzz-ServerSend
fuzz-ServerSend:
@go test -fuzz=FuzzServerSend $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
+78 -12
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)
@@ -190,13 +190,13 @@ 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
```
@@ -224,6 +224,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 +247,50 @@ 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.
### 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=9.9.9.9 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 +317,16 @@ 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
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
@@ -280,7 +338,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 +367,12 @@ $ mtg access /etc/mtg.toml
}
```
or if you are using docker:
```console
$ docker exec mtg-proxy /mtg access /config.toml
```
## Metrics
Out of the box, mtg works with
@@ -323,18 +387,20 @@ 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:
| Name | Values | Description |
|-------------|------------------------|-----------------------------------------------|
| ip_family | ipv4, ipv6 | A version of the IP protocol. |
| 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. |
| Name | Values | Description |
|-------------|----------------------------|-----------------------------------------------|
| ip_family | `ipv4`, `ipv6` | A version of the IP protocol. |
| 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) //nolint: gomnd
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)))
}
+26
View File
@@ -0,0 +1,26 @@
package essentials
import (
"io"
"net"
)
// CloseableReader is an [io.Reader] interface that can close its reading end.
type CloseableReader interface {
io.Reader
CloseRead() error
}
// CloseableWriter is an [io.Writer] that can close its writing end.
type CloseableWriter interface {
io.Writer
CloseWrite() error
}
// Conn is an extension of [net.Conn] that can close its ends. This mostly
// implies TCP connections.
type Conn interface {
net.Conn
CloseableReader
CloseableWriter
}
+6
View File
@@ -0,0 +1,6 @@
// This is a minimal package that contains _essentials_ of mtglib and its
// complimentary packages. This is mostly required to comply some interfaces
// between mtglib and its internals to avoid circular dependencies.
//
// This package should contain only bare minimum and mostly technical.
package essentials
+4 -2
View File
@@ -9,7 +9,7 @@ import (
"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
@@ -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()
}
+15
View File
@@ -130,6 +130,21 @@ func (m multiObserver) EventReplayAttack(evt mtglib.EventReplayAttack) {
wg.Wait()
}
func (m multiObserver) EventIPListSize(evt mtglib.EventIPListSize) {
wg := &sync.WaitGroup{}
wg.Add(len(m.observers))
for _, v := range m.observers {
go func(obs Observer) {
defer wg.Done()
obs.EventIPListSize(evt)
}(v)
}
wg.Wait()
}
func (m multiObserver) Shutdown() {
for _, v := range m.observers {
v.Shutdown()
+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)
}
})
}
+38 -2
View File
@@ -30,7 +30,9 @@ 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"
#
# Deprecated: this setting is no longer makes any effect.
# tcp-buffer = "4kb"
# Sometimes you want to enforce mtg to use some types of
# IP connectivity to Telegram. We have 4 modes:
@@ -56,6 +58,19 @@ 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
# network defines different network-related settings
[network]
# please be aware that mtg needs to do some external requests. For
@@ -155,12 +170,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
+43 -15
View File
@@ -1,30 +1,58 @@
module github.com/9seconds/mtg/v2
go 1.16
go 1.18
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 v0.6.1
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137
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.20.0 // indirect
github.com/rs/zerolog v1.21.0
github.com/panjf2000/ants/v2 v2.5.0
github.com/pelletier/go-toml v1.9.5
github.com/prometheus/client_golang v1.13.0
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rs/zerolog v1.27.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/stretchr/testify v1.7.2
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418
google.golang.org/protobuf v1.28.1 // indirect
)
require (
github.com/txthinking/socks5 v0.0.0-20220615051428-39268faee3e6
github.com/yl2chen/cidranger v1.0.2
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gotd/ige v0.1.5 // indirect
github.com/gotd/xor v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // 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.2.0 // indirect
github.com/txthinking/runnergroup v0.0.0-20220212043759-8da8edb7dae8 // indirect
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0 // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+367 -252
View File
@@ -1,163 +1,179 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
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/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/alecthomas/kong v0.6.1 h1:1kNhcFepkR+HmasQpbiKDLylIL8yh5B5y1zPp5bJimA=
github.com/alecthomas/kong v0.6.1/go.mod h1:JfHWDzLmbh/puW6I3V7uWenoh56YNVONW+w8eKeUr9I=
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
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/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/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/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/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
github.com/d4l3k/messagediff v1.2.1/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.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/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/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gotd/getdoc v0.6.2/go.mod h1:6Ul8cyaq+Aw0gVyuU+TKZPCmNjdlylzSIWVxjzI5t/U=
github.com/gotd/ige v0.1.5 h1:qITql4hZpqPM/DSeO5IVlxzXJxrs4ZZiKRwPif2K76U=
github.com/gotd/ige v0.1.5/go.mod h1:LbvqjUuGELVuHcKjfJZ2NPNzegICQU4eqrPAkXy0nTs=
github.com/gotd/neo v0.1.2 h1:hayFfKNSH5RP/L+KKKnsKMdDNCdhSfzVuo5TF4wvpa8=
github.com/gotd/neo v0.1.2/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ=
github.com/gotd/td v0.34.0 h1:BLIdpv2sxbCuWI2fyXCECRQuLJdnYk3VMZDRKNog/E8=
github.com/gotd/td v0.34.0/go.mod h1:4s/7cuEscdvZIecM/pVi5L3nTK+djSoVLAhgfk40OE4=
github.com/gotd/tl v0.4.0/go.mod h1:CMIcjPWFS4qxxJ+1Ce7U/ilbtPrkoVo/t8uhN5Y/D7c=
github.com/gotd/xor v0.1.0/go.mod h1:ZTmdgqf6SOHder8/MFp9CNkXIadzID5lIiaZxRZICH0=
github.com/gotd/xor v0.1.1 h1:LSPEeuf7noTo4fi4PrEsAaWXOSwjsY2e+IINPiR+c7s=
github.com/gotd/xor v0.1.1/go.mod h1:ZTmdgqf6SOHder8/MFp9CNkXIadzID5lIiaZxRZICH0=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k=
github.com/jarcoal/httpmock v1.0.8/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/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
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/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/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/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=
@@ -167,316 +183,415 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
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-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
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-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mccutchen/go-httpbin v1.1.1 h1:aEws49HEJEyXHLDnshQVswfUlCVoS8g6h9YaDyaW7RE=
github.com/mccutchen/go-httpbin v1.1.1/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/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/panjf2000/ants/v2 v2.5.0 h1:1rWGWSnxCsQBga+nQbA4/iY6VMeNoOIAM0ZWh9u3q2Q=
github.com/panjf2000/ants/v2 v2.5.0/go.mod h1:cU93usDlihJZ5CfRGNDYsiBYvoilLvBF5Qp/BT2GNRE=
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 v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
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/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_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
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.20.0 h1:pfeDeUdQcIxOMutNjCejsEFp7qeP+/iltHSSmLpE+hU=
github.com/prometheus/common v0.20.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
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/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/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/quasilyte/go-ruleguard/dsl v0.3.2/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smira/go-statsd v1.3.2 h1:1EeuzxNZ/TD9apbTOFSM9nulqfcsQFmT4u1A2DREabI=
github.com/smira/go-statsd v1.3.2/go.mod h1:1srXJ9/pbnN04G8f4F1jUzsGOnwkPKXciyqpewGlkC4=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/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/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
github.com/txthinking/runnergroup v0.0.0-20220212043759-8da8edb7dae8 h1:iYc+JnXtzv6sdMx9Q7OTKkDAn7FhDPDogcjeSfEQcLY=
github.com/txthinking/runnergroup v0.0.0-20220212043759-8da8edb7dae8/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
github.com/txthinking/socks5 v0.0.0-20220615051428-39268faee3e6 h1:8DkPbOq/EPxbD5VJajKuvssiYZJSrlpeetcGfrBoBVE=
github.com/txthinking/socks5 v0.0.0-20220615051428-39268faee3e6/go.mod h1:7NloQcrxaZYKURWph5HLxVDlIwMHJXCPkeWPtpftsIg=
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe h1:gMWxZxBFRAXqoGkwkYlPX2zvyyKNWJpxOxCrjqJkm5A=
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4=
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.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=
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.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
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/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
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.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=
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-20190605123033-f99c8df09eb5/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-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
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/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/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-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/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 h1:9vYwv7OjYaky/tlAeD7C4oC9EsPTlaFl1H2jS++V+ME=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d h1:W07d4xkoAUSNOkOzdzXCdFGxT7o2rW4q8M34tB2i//k=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
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/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.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.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
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.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
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=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+43 -51
View File
@@ -12,6 +12,11 @@ import (
"strconv"
"strings"
"sync"
"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"
)
type accessResponse struct {
@@ -26,65 +31,66 @@ 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
wg.Add(2) //nolint: gomnd
go func() {
defer wg.Done()
ip := cli.Access.PublicIPv4
ip := a.PublicIPv4
if ip == nil {
ip = c.getIP("tcp4")
ip = a.getIP(ntw, "tcp4")
}
if ip != nil {
ip = ip.To4()
}
resp.IPv4 = c.makeURLs(ip, cli)
resp.IPv4 = a.makeURLs(conf, ip)
}()
go func() {
defer wg.Done()
ip := cli.Access.PublicIPv6
ip := a.PublicIPv6
if ip == nil {
ip = c.getIP("tcp6")
ip = a.getIP(ntw, "tcp6")
}
if ip != nil {
ip = ip.To16()
}
resp.IPv6 = c.makeURLs(ip, cli)
resp.IPv6 = a.makeURLs(conf, ip)
}()
wg.Wait()
@@ -100,12 +106,12 @@ 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
func (a *Access) getIP(ntw mtglib.Network, protocol string) net.IP {
client := ntw.MakeHTTPClient(func(ctx context.Context, network, address string) (essentials.Conn, error) {
return ntw.DialContext(ctx, protocol, address) //nolint: wrapcheck
})
req, err := http.NewRequest(http.MethodGet, "https://ifconfig.co", nil) // nolint: noctx
req, err := http.NewRequest(http.MethodGet, "https://ifconfig.co", nil) //nolint: noctx
if err != nil {
panic(err)
}
@@ -122,7 +128,7 @@ func (c *Access) getIP(protocol string) net.IP {
}
defer func() {
io.Copy(io.Discard, resp.Body) // nolint: errcheck
io.Copy(io.Discard, resp.Body) //nolint: errcheck
resp.Body.Close()
}()
@@ -134,24 +140,24 @@ func (c *Access) getIP(protocol string) net.IP {
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 +177,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{})
}
+2 -1
View File
@@ -5,6 +5,7 @@ import "github.com/alecthomas/kong"
type CLI struct {
GenerateSecret GenerateSecret `kong:"cmd,help='Generate new proxy secret'"`
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'"`
}
+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)
}
+278
View File
@@ -0,0 +1,278 @@
package cli
import (
"context"
"fmt"
"net"
"net/url"
"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/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"
"github.com/9seconds/mtg/v2/stats"
"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) {
tcpTimeout := conf.Network.Timeout.TCP.Get(network.DefaultTimeout)
httpTimeout := conf.Network.Timeout.HTTP.Get(network.DefaultHTTPTimeout)
dohIP := conf.Network.DOHIP.Get(net.ParseIP(network.DefaultDOHHostname)).String()
userAgent := "mtg/" + version
baseDialer, err := network.NewDefaultDialer(tcpTimeout, 0)
if err != nil {
return nil, fmt.Errorf("cannot build a default dialer: %w", err)
}
if len(conf.Network.Proxies) == 0 {
return network.NewNetwork(baseDialer, userAgent, dohIP, httpTimeout) //nolint: wrapcheck
}
proxyURLs := make([]*url.URL, 0, len(conf.Network.Proxies))
for _, v := range conf.Network.Proxies {
if value := v.Get(nil); value != nil {
proxyURLs = append(proxyURLs, value)
}
}
if len(proxyURLs) == 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
}
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) //nolint: gomnd
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)
}
opts := mtglib.ProxyOpts{
Logger: logger,
Network: ntw,
AntiReplayCache: makeAntiReplayCache(conf),
IPBlocklist: blocklist,
IPAllowlist: allowlist,
EventStream: eventStream,
Secret: conf.Secret,
DomainFrontingPort: conf.DomainFrontingPort.Get(mtglib.DefaultDomainFrontingPort),
PreferIP: conf.PreferIP.Get(mtglib.DefaultPreferIP),
AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false),
TolerateTimeSkewness: conf.TolerateTimeSkewness.Value,
}
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)
}
ctx := utils.RootContext()
go proxy.Serve(listener) //nolint: errcheck
<-ctx.Done()
listener.Close()
proxy.Shutdown()
return nil
}
+89
View File
@@ -0,0 +1,89 @@
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
DOHIP net.IP `kong:"name='doh-ip',short='n',default='9.9.9.9',help='IP address of DNS-over-HTTP to use.'"` //nolint: lll
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 { //nolint: gomnd
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 { //nolint: gomnd
return fmt.Errorf("incorrect domain-fronting-port: %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)
}
+40 -105
View File
@@ -6,30 +6,38 @@ import (
"fmt"
"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"`
PreferIP TypePreferIP `json:"preferIp"`
DomainFrontingPort TypePort `json:"domainFrontingPort"`
TolerateTimeSkewness TypeDuration `json:"tolerateTimeSkewness"`
Concurrency TypeConcurrency `json:"concurrency"`
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"`
} `json:"defense"`
Network struct {
Timeout struct {
@@ -37,21 +45,23 @@ 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"`
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"`
}
@@ -61,7 +71,7 @@ func (c *Config) Validate() error {
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 +90,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
}
+86
View File
@@ -0,0 +1,86 @@
package config
import (
"bytes"
"encoding/json"
"fmt"
"github.com/pelletier/go-toml"
)
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"`
PreferIP string `toml:"prefer-ip" json:"preferIp,omitempty"`
DomainFrontingPort uint `toml:"domain-fronting-port" json:"domainFrontingPort,omitempty"`
TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerateTimeSkewness,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:"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"`
} `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"`
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 := []interface{}{
"",
"np",
"нет",
int(10),
[]int{},
}
for _, v := range testData {
data, err := json.Marshal(map[string]interface{}{
"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) //nolint: gomnd
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) //nolint: gomnd
}
+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{})
}
+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) //nolint: gomnd
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) //nolint: gomnd
}
+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) //nolint: gomnd
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) {
+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) //nolint: gomnd
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) {
+61
View File
@@ -0,0 +1,61 @@
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 corect URL (%s): %w", value, err)
}
if parsedURL.Host == "" {
return fmt.Errorf("url has to have a schema: %s", value)
}
if parsedURL.Scheme != "socks5" {
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{})
}
+1 -1
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)
}()
+8 -7
View File
@@ -2,9 +2,9 @@ package testlib
import (
"context"
"net"
"net/http"
"github.com/9seconds/mtg/v2/essentials"
"github.com/stretchr/testify/mock"
)
@@ -12,19 +12,20 @@ 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) 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()
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"
+1
View File
@@ -0,0 +1 @@
bind-to = "0.0.0.0:80"
+8
View File
@@ -0,0 +1,8 @@
// files defines a set of abstraction for 'files': an openable entities that
// could be read after.
//
// This is not a file on a filesystem of your local machine, it also can
// include "in memory" files or even remote ones, like HTTP endpoints. If you
// make a GET request to HTTP endpoint, then a body is readable and you can
// consider it as an openable file.
package files
+65
View File
@@ -0,0 +1,65 @@
package files
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
)
type httpFile struct {
http *http.Client
url string
}
func (h httpFile) Open(ctx context.Context) (io.ReadCloser, error) {
request, err := http.NewRequestWithContext(ctx, http.MethodGet, h.url, nil)
if err != nil {
panic(err)
}
response, err := h.http.Do(request)
if err != nil {
if response != nil {
io.Copy(io.Discard, response.Body) //nolint: errcheck
response.Body.Close()
}
return nil, fmt.Errorf("cannot get url %s: %w", h.url, err)
}
if response.StatusCode >= http.StatusBadRequest {
return nil, fmt.Errorf("unexpected status code %d", response.StatusCode)
}
return response.Body, nil
}
func (h httpFile) String() string {
return h.url
}
// NewHTTP returns a file abstraction for HTTP/HTTPS endpoint. You also need to
// provide a valid instance of [http.Client] to access it.
func NewHTTP(client *http.Client, endpoint string) (File, error) {
if client == nil {
return nil, ErrBadHTTPClient
}
parsed, err := url.Parse(endpoint)
if err != nil {
return nil, fmt.Errorf("incorrect url %s: %w", endpoint, err)
}
switch parsed.Scheme {
case "http", "https":
default:
return nil, fmt.Errorf("unsupported url %s", endpoint)
}
return httpFile{
http: client,
url: endpoint,
}, nil
}
+90
View File
@@ -0,0 +1,90 @@
package files_test
import (
"context"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/9seconds/mtg/v2/ipblocklist/files"
"github.com/stretchr/testify/suite"
)
type HTTPTestSuite struct {
suite.Suite
httpClient *http.Client
httpServer *httptest.Server
ctx context.Context
ctxCancel context.CancelFunc
}
func (suite *HTTPTestSuite) makeFile(path string) (files.File, error) {
return files.NewHTTP(suite.httpClient, suite.httpServer.URL+"/"+path) //nolint: wrapcheck
}
func (suite *HTTPTestSuite) SetupSuite() {
mux := http.NewServeMux()
mux.Handle("/", http.FileServer(http.Dir("testdata")))
suite.httpServer = httptest.NewServer(mux)
suite.httpClient = suite.httpServer.Client()
}
func (suite *HTTPTestSuite) SetupTest() {
suite.ctx, suite.ctxCancel = context.WithCancel(context.Background())
}
func (suite *HTTPTestSuite) TearDownTest() {
suite.ctxCancel()
suite.httpServer.CloseClientConnections()
}
func (suite *HTTPTestSuite) TearDownSuite() {
suite.httpServer.Close()
}
func (suite *HTTPTestSuite) TestBadURL() {
_, err := files.NewHTTP(suite.httpClient, "sdfsdf")
suite.Error(err)
}
func (suite *HTTPTestSuite) TestBadSchema() {
_, err := files.NewHTTP(suite.httpClient, "gopher://lala")
suite.Error(err)
}
func (suite *HTTPTestSuite) TestNilHTTPClient() {
_, err := files.NewHTTP(nil, "")
suite.Error(err)
}
func (suite *HTTPTestSuite) TestAbsentFile() {
file, err := suite.makeFile("absent")
suite.NoError(err)
_, err = file.Open(suite.ctx)
suite.Error(err)
}
func (suite *HTTPTestSuite) TestOk() {
file, err := suite.makeFile("readable")
suite.NoError(err)
readCloser, err := file.Open(suite.ctx)
suite.NoError(err)
defer readCloser.Close()
data, err := io.ReadAll(readCloser)
suite.NoError(err)
suite.Equal("Hooray!", strings.TrimSpace(string(data)))
}
func TestHTTP(t *testing.T) {
t.Parallel()
suite.Run(t, &HTTPTestSuite{})
}
+21
View File
@@ -0,0 +1,21 @@
package files
import (
"context"
"errors"
"io"
)
// ErrBadHTTPClient is returned if given HTTP client is initialized
// incorrectly.
var ErrBadHTTPClient = errors.New("incorrect http client")
// File is an abstraction for a entity that can be opened in some context.
type File interface {
// Open returns an readable entity for a file. It is important to not forget
// to close it after the usage.
Open(context.Context) (io.ReadCloser, error)
// String returns a short text description for the file
String() string
}
+31
View File
@@ -0,0 +1,31 @@
package files
import (
"context"
"fmt"
"io"
"os"
)
type localFile struct {
path string
}
func (l localFile) Open(ctx context.Context) (io.ReadCloser, error) {
return os.Open(l.path) //nolint: wrapcheck
}
func (l localFile) String() string {
return l.path
}
// NewLocal returns an openable File for a path on a local file system.
func NewLocal(path string) (File, error) {
if stat, err := os.Stat(path); os.IsNotExist(err) || stat.IsDir() || stat.Mode().Perm()&0o400 == 0 {
return nil, fmt.Errorf("%s is not a readable file", path)
}
return localFile{
path: path,
}, nil
}
+55
View File
@@ -0,0 +1,55 @@
package files_test
import (
"context"
"io"
"path/filepath"
"strings"
"testing"
"github.com/9seconds/mtg/v2/ipblocklist/files"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type LocalTestSuite struct {
suite.Suite
}
func (suite *LocalTestSuite) getLocalFile(name string) string {
return filepath.Join("testdata", name)
}
func (suite *LocalTestSuite) TestIncorrect() {
names := []string{
"absent",
"directory",
}
for _, v := range names {
value := v
suite.T().Run(v, func(t *testing.T) {
_, err := files.NewLocal(suite.getLocalFile(value))
assert.Error(t, err)
})
}
}
func (suite *LocalTestSuite) TestOk() {
file, err := files.NewLocal(suite.getLocalFile("readable"))
suite.NoError(err)
reader, err := file.Open(context.Background())
suite.NoError(err)
data, err := io.ReadAll(reader)
suite.NoError(err)
suite.Equal("Hooray!", strings.TrimSpace(string(data)))
}
func TestLocal(t *testing.T) {
t.Parallel()
suite.Run(t, &LocalTestSuite{})
}
+38
View File
@@ -0,0 +1,38 @@
package files
import (
"context"
"io"
"net"
"strings"
)
type memFile struct {
data string
}
func (m memFile) Open(ctx context.Context) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader(m.data)), nil
}
func (m memFile) String() string {
return "mem"
}
// NewMem returns an openable file that is kept in RAM.
func NewMem(networks []*net.IPNet) File {
builder := strings.Builder{}
if len(networks) > 0 {
builder.WriteString(networks[0].String())
}
for i := 1; i < len(networks); i++ {
builder.WriteString("\n")
builder.WriteString(networks[i].String())
}
return memFile{
data: builder.String(),
}
}
+42
View File
@@ -0,0 +1,42 @@
package files_test
import (
"context"
"io"
"net"
"strings"
"testing"
"github.com/9seconds/mtg/v2/ipblocklist/files"
"github.com/stretchr/testify/suite"
)
type MemTestSuite struct {
suite.Suite
}
func (suite *MemTestSuite) TestOk() {
_, network1, _ := net.ParseCIDR("192.168.0.1/24")
_, network2, _ := net.ParseCIDR("2001:0db8:85a3:0000:0000:8a2e:0370:7334/36")
file := files.NewMem([]*net.IPNet{
network1,
network2,
})
reader, err := file.Open(context.Background())
suite.NoError(err)
data, err := io.ReadAll(reader)
suite.NoError(err)
strData := strings.TrimSpace(string(data))
suite.Contains(strData, "192.168.0.0/24")
suite.Contains(strData, "2001:db8:8000::/36")
}
func TestMem(t *testing.T) {
t.Parallel()
suite.Run(t, &MemTestSuite{})
}
View File
+1
View File
@@ -0,0 +1 @@
Hooray!
+129 -230
View File
@@ -4,57 +4,54 @@ import (
"bufio"
"context"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"regexp"
"strings"
"sync"
"time"
"github.com/9seconds/mtg/v2/ipblocklist/files"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/kentik/patricia"
"github.com/kentik/patricia/bool_tree"
"github.com/panjf2000/ants/v2"
"github.com/yl2chen/cidranger"
)
const (
fireholIPv4DefaultCIDR = 32
fireholIPv6DefaultCIDR = 128
var (
fireholRegexpComment = regexp.MustCompile(`\s*#.*?$`)
fireholIPv4DefaultCIDR = net.CIDRMask(32, 32) //nolint: gomnd
fireholIPv6DefaultCIDR = net.CIDRMask(128, 128) //nolint: gomnd
)
var fireholRegexpComment = regexp.MustCompile(`\s*#.*?$`)
// FireholUpdateCallback defines a signature of the callback that has to be
// execute when ip list is updated.
type FireholUpdateCallback func(context.Context, int)
// Firehol is IPBlocklist which uses lists from FireHOL:
// Firehol is [mtglib.IPBlocklist] which uses lists from FireHOL:
// https://iplists.firehol.org/
//
// It can use both local files and remote URLs. This is not necessary
// that blocklists should be taken from this website, we expect only
// compatible formats here.
// It can use both local files and remote URLs. This is not necessary that
// blocklists should be taken from this website, we expect only compatible
// formats here.
//
// Example of the format:
//
// # this is a comment
// # to ignore
// 127.0.0.1 # you can specify an IP
// 10.0.0.0/8 # or cidr
// # this is a comment
// # to ignore
// 127.0.0.1 # you can specify an IP
// 10.0.0.0/8 # or cidr
type Firehol struct {
ctx context.Context
ctxCancel context.CancelFunc
logger mtglib.Logger
ctx context.Context
ctxCancel context.CancelFunc
logger mtglib.Logger
updateMutex sync.RWMutex
rwMutex sync.RWMutex
updateCallback FireholUpdateCallback
ranger cidranger.Ranger
remoteURLs []string
localFiles []string
blocklists []files.File
httpClient *http.Client
workerPool *ants.Pool
treeV4 *bool_tree.TreeV4
treeV6 *bool_tree.TreeV6
}
// Shutdown stop a background update process.
@@ -68,20 +65,20 @@ func (f *Firehol) Contains(ip net.IP) bool {
return true
}
f.rwMutex.RLock()
defer f.rwMutex.RUnlock()
f.updateMutex.RLock()
defer f.updateMutex.RUnlock()
if ip4 := ip.To4(); ip4 != nil {
return f.containsIPv4(ip4)
ok, err := f.ranger.Contains(ip)
if err != nil {
f.logger.BindStr("ip", ip.String()).DebugError("Cannot check if ip is present", err)
}
return f.containsIPv6(ip.To16())
return ok && err == nil
}
// Run starts a background update process.
//
// This is a blocking method so you probably want to run it in a
// goroutine.
// This is a blocking method so you probably want to run it in a goroutine.
func (f *Firehol) Run(updateEach time.Duration) {
if updateEach == 0 {
updateEach = DefaultFireholUpdateEach
@@ -98,161 +95,67 @@ func (f *Firehol) Run(updateEach time.Duration) {
}
}()
if err := f.update(); err != nil {
f.logger.WarningError("cannot update blocklist", err)
} else {
f.logger.Info("blocklist was updated")
}
f.update()
for {
select {
case <-f.ctx.Done():
return
case <-ticker.C:
if err := f.update(); err != nil {
f.logger.WarningError("cannot update blocklist", err)
} else {
f.logger.Info("blocklist was updated")
}
f.update()
}
}
}
func (f *Firehol) containsIPv4(addr net.IP) bool {
ip := patricia.NewIPv4AddressFromBytes(addr, 32)
if ok, _, err := f.treeV4.FindDeepestTag(ip); ok && err == nil {
return true
}
return false
}
func (f *Firehol) containsIPv6(addr net.IP) bool {
ip := patricia.NewIPv6Address(addr, 128)
if ok, _, err := f.treeV6.FindDeepestTag(ip); ok && err == nil {
return true
}
return false
}
func (f *Firehol) update() error { // nolint: funlen, cyclop
func (f *Firehol) update() {
ctx, cancel := context.WithCancel(f.ctx)
defer cancel()
wg := &sync.WaitGroup{}
wg.Add(len(f.remoteURLs) + len(f.localFiles))
wg.Add(len(f.blocklists))
treeMutex := &sync.Mutex{}
v4tree := bool_tree.NewTreeV4()
v6tree := bool_tree.NewTreeV6()
mutex := &sync.Mutex{}
ranger := cidranger.NewPCTrieRanger()
errorChan := make(chan error, 1)
defer close(errorChan)
for _, v := range f.localFiles {
go func(filename string) {
for _, v := range f.blocklists {
go func(file files.File) {
defer wg.Done()
if err := f.updateLocalFile(ctx, filename, treeMutex, v4tree, v6tree); err != nil {
cancel()
f.logger.BindStr("filename", filename).WarningError("cannot update", err)
logger := f.logger.BindStr("filename", file.String())
select {
case errorChan <- err:
default:
}
fileContent, err := file.Open(ctx)
if err != nil {
logger.WarningError("update has failed", err)
return
}
defer fileContent.Close()
if err := f.updateFromFile(mutex, ranger, bufio.NewScanner(fileContent)); err != nil {
logger.WarningError("update has failed", err)
}
}(v)
}
for _, v := range f.remoteURLs {
value := v
f.workerPool.Submit(func() { // nolint: errcheck
defer wg.Done()
if err := f.updateRemoteURL(ctx, value, treeMutex, v4tree, v6tree); err != nil {
cancel()
f.logger.BindStr("url", value).WarningError("cannot update", err)
select {
case errorChan <- err:
default:
}
}
})
}
wg.Wait()
select {
case err := <-errorChan:
return fmt.Errorf("cannot update trees: %w", err)
default:
f.updateMutex.Lock()
defer f.updateMutex.Unlock()
f.ranger = ranger
if f.updateCallback != nil {
f.updateCallback(ctx, ranger.Len())
}
f.rwMutex.Lock()
defer f.rwMutex.Unlock()
f.treeV4 = v4tree
f.treeV6 = v6tree
return nil
f.logger.Info("ip list was updated")
}
func (f *Firehol) updateLocalFile(ctx context.Context, filename string,
mutex sync.Locker,
v4tree *bool_tree.TreeV4, v6tree *bool_tree.TreeV6) error {
filefp, err := os.Open(filename)
if err != nil {
return fmt.Errorf("cannot open file: %w", err)
}
go func(ctx context.Context, closer io.Closer) {
<-ctx.Done()
closer.Close()
}(ctx, filefp)
defer filefp.Close()
return f.updateTrees(mutex, filefp, v4tree, v6tree)
}
func (f *Firehol) updateRemoteURL(ctx context.Context, url string,
mutex sync.Locker,
v4tree *bool_tree.TreeV4, v6tree *bool_tree.TreeV6) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return fmt.Errorf("cannot build a request: %w", err)
}
resp, err := f.httpClient.Do(req) // nolint: bodyclose
if err != nil {
return fmt.Errorf("cannot request a remote URL %s: %w", url, err)
}
go func(ctx context.Context, closer io.Closer) {
<-ctx.Done()
closer.Close()
}(ctx, resp.Body)
defer func(rc io.ReadCloser) {
io.Copy(io.Discard, rc) // nolint: errcheck
rc.Close()
}(resp.Body)
return f.updateTrees(mutex, resp.Body, v4tree, v6tree)
}
func (f *Firehol) updateTrees(mutex sync.Locker,
reader io.Reader,
v4tree *bool_tree.TreeV4,
v6tree *bool_tree.TreeV6) error {
scanner := bufio.NewScanner(reader)
func (f *Firehol) updateFromFile(mutex sync.Locker,
ranger cidranger.Ranger,
scanner *bufio.Scanner,
) error {
for scanner.Scan() {
text := scanner.Text()
text = fireholRegexpComment.ReplaceAllLiteralString(text, "")
@@ -262,94 +165,92 @@ func (f *Firehol) updateTrees(mutex sync.Locker,
continue
}
ip, cidr, err := f.updateParseLine(text)
ipnet, err := f.updateParseLine(text)
if err != nil {
return fmt.Errorf("cannot parse a line: %w", err)
}
if err := f.updateAddToTrees(ip, cidr, mutex, v4tree, v6tree); err != nil {
return fmt.Errorf("cannot add a node to the tree: %w", err)
mutex.Lock()
err = ranger.Insert(cidranger.NewBasicRangerEntry(*ipnet))
mutex.Unlock()
if err != nil {
return fmt.Errorf("cannot insert %v into ranger: %w", ipnet, err)
}
}
if scanner.Err() != nil {
return fmt.Errorf("cannot parse a response: %w", scanner.Err())
return fmt.Errorf("cannot parse a file: %w", scanner.Err())
}
return nil
}
func (f *Firehol) updateParseLine(text string) (net.IP, uint, error) {
_, ipnet, err := net.ParseCIDR(text)
if err != nil {
ipaddr := net.ParseIP(text)
if ipaddr == nil {
return nil, 0, fmt.Errorf("incorrect ip address %s", text)
}
ip4 := ipaddr.To4()
if ip4 != nil {
return ip4, fireholIPv4DefaultCIDR, nil
}
return ipaddr.To16(), fireholIPv6DefaultCIDR, nil
func (f *Firehol) updateParseLine(text string) (*net.IPNet, error) {
if _, ipnet, err := net.ParseCIDR(text); err == nil {
return ipnet, nil
}
ones, _ := ipnet.Mask.Size()
return ipnet.IP, uint(ones), nil
}
func (f *Firehol) updateAddToTrees(ip net.IP, cidr uint,
mutex sync.Locker,
v4tree *bool_tree.TreeV4, v6tree *bool_tree.TreeV6) error {
mutex.Lock()
defer mutex.Unlock()
if ip.To4() != nil {
addr := patricia.NewIPv4AddressFromBytes(ip, cidr)
if _, _, err := v4tree.Set(addr, true); err != nil {
return err // nolint: wrapcheck
}
} else {
addr := patricia.NewIPv6Address(ip, cidr)
if _, _, err := v6tree.Set(addr, true); err != nil {
return err // nolint: wrapcheck
}
ipaddr := net.ParseIP(text)
if ipaddr == nil {
return nil, fmt.Errorf("incorrect ip address %s", text)
}
return nil
mask := fireholIPv4DefaultCIDR
if ipaddr.To4() == nil {
mask = fireholIPv6DefaultCIDR
}
return &net.IPNet{
IP: ipaddr,
Mask: mask,
}, nil
}
// NewFirehol creates a new instance of FireHOL IP blocklist.
//
// This method does not start an update process so please execute Run
// when it is necessary.
// This method does not start an update process so please execute Run when it
// is necessary.
func NewFirehol(logger mtglib.Logger, network mtglib.Network,
downloadConcurrency uint,
remoteURLs []string,
localFiles []string) (*Firehol, error) {
for _, v := range remoteURLs {
parsed, err := url.Parse(v)
if err != nil {
return nil, fmt.Errorf("incorrect url %s: %w", v, err)
}
switch parsed.Scheme {
case "http", "https":
default:
return nil, fmt.Errorf("unsupported url %s", v)
}
}
urls []string,
localFiles []string,
updateCallback FireholUpdateCallback,
) (*Firehol, error) {
blocklists := []files.File{}
for _, v := range localFiles {
if stat, err := os.Stat(v); os.IsNotExist(err) || stat.IsDir() || stat.Mode().Perm()&0o400 == 0 {
return nil, fmt.Errorf("%s is not a readable file", v)
file, err := files.NewLocal(v)
if err != nil {
return nil, fmt.Errorf("cannot create a local file %s: %w", v, err)
}
blocklists = append(blocklists, file)
}
httpClient := network.MakeHTTPClient(nil)
for _, v := range urls {
file, err := files.NewHTTP(httpClient, v)
if err != nil {
return nil, fmt.Errorf("cannot create a HTTP file %s: %w", v, err)
}
blocklists = append(blocklists, file)
}
return NewFireholFromFiles(logger, downloadConcurrency, blocklists, updateCallback)
}
// NewFirehol creates a new instance of FireHOL IP blocklist.
//
// This method creates this instances from a given list of files.
func NewFireholFromFiles(logger mtglib.Logger,
downloadConcurrency uint,
blocklists []files.File,
updateCallback FireholUpdateCallback,
) (*Firehol, error) {
if downloadConcurrency == 0 {
downloadConcurrency = DefaultFireholDownloadConcurrency
}
@@ -358,14 +259,12 @@ func NewFirehol(logger mtglib.Logger, network mtglib.Network,
ctx, cancel := context.WithCancel(context.Background())
return &Firehol{
ctx: ctx,
ctxCancel: cancel,
logger: logger.Named("firehol"),
httpClient: network.MakeHTTPClient(nil),
treeV4: bool_tree.NewTreeV4(),
treeV6: bool_tree.NewTreeV6(),
workerPool: workerPool,
remoteURLs: remoteURLs,
localFiles: localFiles,
ctx: ctx,
ctxCancel: cancel,
logger: logger.Named("firehol"),
ranger: cidranger.NewPCTrieRanger(),
workerPool: workerPool,
blocklists: blocklists,
updateCallback: updateCallback,
}, nil
}
+7 -5
View File
@@ -37,7 +37,7 @@ func (suite *FireholTestSuite) SetupSuite() {
defer filefp.Close()
io.Copy(w, filefp) // nolint: errcheck
io.Copy(w, filefp) //nolint: errcheck
})
suite.httpServer = httptest.NewServer(mux)
@@ -67,7 +67,8 @@ func (suite *FireholTestSuite) TearDownSuite() {
func (suite *FireholTestSuite) TestLocalFail() {
blocklist, err := ipblocklist.NewFirehol(logger.NewNoopLogger(),
suite.networkMock, 2,
nil, []string{filepath.Join("testdata", "broken_ipset.ipset")})
nil, []string{filepath.Join("testdata", "broken_ipset.ipset")},
nil)
suite.NoError(err)
@@ -85,7 +86,8 @@ func (suite *FireholTestSuite) TestLocalFail() {
func (suite *FireholTestSuite) TestLocalOk() {
blocklist, err := ipblocklist.NewFirehol(logger.NewNoopLogger(),
suite.networkMock, 2,
nil, []string{filepath.Join("testdata", "good_ipset.ipset")})
nil, []string{filepath.Join("testdata", "good_ipset.ipset")},
nil)
suite.NoError(err)
@@ -103,7 +105,7 @@ func (suite *FireholTestSuite) TestLocalOk() {
func (suite *FireholTestSuite) TestRemoteFail() {
blocklist, err := ipblocklist.NewFirehol(logger.NewNoopLogger(),
suite.networkMock, 2,
[]string{"https://google.com"}, nil)
[]string{"https://google.com"}, nil, nil)
suite.NoError(err)
@@ -127,7 +129,7 @@ func (suite *FireholTestSuite) TestMixed() {
suite.httpServer.URL,
}, []string{
filepath.Join("testdata", "good_ipset.ipset"),
})
}, nil)
suite.NoError(err)
+5 -5
View File
@@ -1,8 +1,8 @@
// Package ipblocklist contains default implementation of the
// IPBlocklist for mtg.
// [mtglib.IPBlocklist] for mtg.
//
// Please check documentation for mtglib.IPBlocklist interface to get an
// idea of this abstraction.
// Please check documentation for [mtglib.IPBlocklist] interface to get an idea
// of this abstraction.
package ipblocklist
import "time"
@@ -12,7 +12,7 @@ const (
// concurrent downloads of ip blocklists for Firehol.
DefaultFireholDownloadConcurrency = 1
// DefaultFireholUpdateEach defines a default time period when
// Firehol requests updates of the blocklists.
// DefaultFireholUpdateEach defines a default time period when Firehol
// requests updates of the blocklists.
DefaultFireholUpdateEach = 6 * time.Hour
)
+5 -3
View File
@@ -2,16 +2,18 @@ package ipblocklist
import (
"net"
"time"
"github.com/9seconds/mtg/v2/mtglib"
)
type noop struct{}
func (n noop) Contains(ip net.IP) bool { return false }
func (n noop) Contains(ip net.IP) bool { return false }
func (n noop) Run(updateEach time.Duration) {}
func (n noop) Shutdown() {}
// NewNoop returns a dummy ipblocklist which allows all incoming
// connections.
// NewNoop returns a dummy ipblocklist which allows all incoming connections.
func NewNoop() mtglib.IPBlocklist {
return noop{}
}
+7
View File
@@ -17,6 +17,13 @@ func (suite *NoopTestSuite) TestOp() {
suite.False(ipblocklist.NewNoop().Contains(net.ParseIP("10.0.0.10")))
}
func (suite *NoopTestSuite) TestRun() {
blocklist := ipblocklist.NewNoop()
blocklist.Run(0)
blocklist.Shutdown()
}
func TestNoop(t *testing.T) {
t.Parallel()
suite.Run(t, &NoopTestSuite{})

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