Преглед изворни кода

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

tags/v2.0.1
9seconds пре 5 година
родитељ
комит
fec60083a3
100 измењених фајлова са 5045 додато и 1960 уклоњено
  1. 4
    0
      .codecov.yml
  2. 1
    3
      .github/workflows/ci.yaml
  3. 1
    0
      .gitignore
  4. 1
    2
      .golangci.toml
  5. 2
    3
      Dockerfile
  6. 22
    7
      Makefile
  7. 251
    194
      README.md
  8. 0
    36
      antireplay/cache.go
  9. 13
    26
      antireplay/init.go
  10. 0
    8
      antireplay/nilcache.go
  11. 14
    0
      antireplay/noop.go
  12. 26
    0
      antireplay/noop_test.go
  13. 51
    0
      antireplay/stable_bloom_filter.go
  14. 26
    0
      antireplay/stable_bloom_filter_test.go
  15. 0
    26
      cli/generate.go
  16. 0
    101
      cli/proxy.go
  17. 0
    43
      cli/utils.go
  18. 0
    312
      config/config.go
  19. 0
    78
      config/global_ips.go
  20. 0
    99
      config/urls.go
  21. 0
    6
      conntypes/acks.go
  22. 0
    5
      conntypes/dc.go
  23. 0
    24
      conntypes/id.go
  24. 0
    3
      conntypes/packet.go
  25. 0
    22
      conntypes/protocol.go
  26. 0
    29
      conntypes/type.go
  27. 0
    14
      conntypes/wrap_interfaces.go
  28. 0
    41
      conntypes/wrap_packet_ack_interfaces.go
  29. 0
    51
      conntypes/wrap_packet_interfaces.go
  30. 0
    56
      conntypes/wrap_stream_interfaces.go
  31. 108
    0
      events/event_stream.go
  32. 220
    0
      events/event_stream_test.go
  33. 69
    0
      events/init.go
  34. 46
    0
      events/init_test.go
  35. 149
    0
      events/multi_observer.go
  36. 33
    0
      events/noop.go
  37. 78
    0
      events/noop_test.go
  38. 186
    0
      example.config.toml
  39. 0
    122
      faketls/client_protocol.go
  40. 0
    73
      faketls/cloak.go
  41. 0
    30
      faketls/consts.go
  42. 25
    19
      go.mod
  43. 107
    43
      go.sum
  44. 0
    172
      hub/connection.go
  45. 0
    70
      hub/connection_list.go
  46. 0
    40
      hub/hub.go
  47. 0
    24
      hub/init.go
  48. 0
    7
      hub/interface.go
  49. 0
    90
      hub/mux.go
  50. 0
    79
      hub/proxy_conn.go
  51. 192
    0
      internal/cli/access.go
  52. 197
    0
      internal/cli/access_test.go
  53. 81
    0
      internal/cli/base.go
  54. 33
    0
      internal/cli/base_internal_test.go
  55. 10
    0
      internal/cli/cli.go
  56. 24
    0
      internal/cli/generate_secret.go
  57. 51
    0
      internal/cli/generate_secret_test.go
  58. 37
    0
      internal/cli/init_test.go
  59. 167
    0
      internal/cli/proxy.go
  60. 2
    0
      internal/cli/testdata/minimal.toml
  61. 157
    0
      internal/config/config.go
  62. 54
    0
      internal/config/config_test.go
  63. 1
    0
      internal/config/testdata/broken.toml
  64. 2
    0
      internal/config/testdata/minimal.toml
  65. 1
    0
      internal/config/testdata/only_secret.toml
  66. 68
    0
      internal/config/type_blocklist_uri.go
  67. 177
    0
      internal/config/type_blocklist_uri_test.go
  68. 54
    0
      internal/config/type_bytes.go
  69. 120
    0
      internal/config/type_bytes_test.go
  70. 46
    0
      internal/config/type_duration.go
  71. 118
    0
      internal/config/type_duration_test.go
  72. 43
    0
      internal/config/type_error_rate.go
  73. 125
    0
      internal/config/type_error_rate_test.go
  74. 67
    0
      internal/config/type_hostport.go
  75. 115
    0
      internal/config/type_hostport_test.go
  76. 35
    0
      internal/config/type_http_path.go
  77. 91
    0
      internal/config/type_http_path_test.go
  78. 45
    0
      internal/config/type_ip.go
  79. 115
    0
      internal/config/type_ip_test.go
  80. 41
    0
      internal/config/type_metric_prefix.go
  81. 115
    0
      internal/config/type_metric_prefix_test.go
  82. 45
    0
      internal/config/type_port.go
  83. 117
    0
      internal/config/type_port_test.go
  84. 50
    0
      internal/config/type_prefer_ip.go
  85. 142
    0
      internal/config/type_prefer_ip_test.go
  86. 49
    0
      internal/config/type_statsd_tag_format.go
  87. 136
    0
      internal/config/type_statsd_tag_format_test.go
  88. 71
    0
      internal/config/type_url.go
  89. 107
    0
      internal/config/type_url_test.go
  90. 42
    0
      internal/testlib/capture_output.go
  91. 7
    0
      internal/testlib/events_observer_mock.go
  92. 11
    0
      internal/testlib/mtglib_antireplay_cache_mock.go
  93. 30
    0
      internal/testlib/mtglib_network_mock.go
  94. 48
    0
      internal/testlib/net_conn_mock.go
  95. 0
    0
      internal/utils/rlimit.go
  96. 0
    0
      internal/utils/rlimit_windows.go
  97. 1
    1
      internal/utils/root_context.go
  98. 1
    1
      internal/utils/root_context_windows.go
  99. 371
    0
      ipblocklist/firehol.go
  100. 0
    0
      ipblocklist/firehol_test.go

+ 4
- 0
.codecov.yml Прегледај датотеку

@@ -0,0 +1,4 @@
1
+---
2
+
3
+fixes:
4
+  - "github.com/9seconds/mtg/v2/::"

+ 1
- 3
.github/workflows/ci.yaml Прегледај датотеку

@@ -30,8 +30,6 @@ jobs:
30 30
     strategy:
31 31
       matrix:
32 32
         go_version:
33
-          - ~1.14
34
-          - ~1.15
35 33
           - ^1.16
36 34
     steps:
37 35
       - name: Checkout
@@ -72,7 +70,7 @@ jobs:
72 70
       - name: Run linter
73 71
         uses: golangci/golangci-lint-action@v2
74 72
         with:
75
-          version: v1.37.1
73
+          version: v1.39.0
76 74
 
77 75
   docker:
78 76
     name: Docker

+ 1
- 0
.gitignore Прегледај датотеку

@@ -9,3 +9,4 @@ mtg
9 9
 vendor/
10 10
 ccbuilds/
11 11
 .bin/
12
+coverage.txt

+ 1
- 2
.golangci.toml Прегледај датотеку

@@ -3,11 +3,10 @@ concurrency = 4
3 3
 deadline = "2m"
4 4
 tests = true
5 5
 skip-dirs = ["vendor"]
6
-skip-files = ["version.go"]
7 6
 
8 7
 [output]
9 8
 format = "colored-line-number"
10 9
 
11 10
 [linters]
12 11
 enable-all = true
13
-disable = ["gochecknoglobals", "gas", "gomnd", "goerr113", "exhaustivestruct"]
12
+disable = ["gochecknoglobals", "gas", "goerr113", "exhaustivestruct"]

+ 2
- 3
Dockerfile Прегледај датотеку

@@ -24,9 +24,8 @@ RUN set -x \
24 24
 FROM scratch
25 25
 
26 26
 ENTRYPOINT ["/mtg"]
27
-ENV MTG_BIND=0.0.0.0:3128 \
28
-    MTG_STATS_BIND=0.0.0.0:3129
29
-EXPOSE 3128 3129
27
+CMD ["run", "/config.toml"]
30 28
 
31 29
 COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
32 30
 COPY --from=build /app/mtg /mtg
31
+COPY --from=build /app/example.config.toml /config.toml

+ 22
- 7
Makefile Прегледај датотеку

@@ -4,12 +4,12 @@ APP_NAME     := $(IMAGE_NAME)
4 4
 
5 5
 CC_BINARIES  := $(shell bash -c "echo -n $(APP_NAME)-{linux,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}")
6 6
 
7
-GOLANGCI_LINT_VERSION := v1.37.1
7
+GOLANGCI_LINT_VERSION := v1.39.0
8 8
 
9 9
 VERSION_GO         := $(shell go version)
10 10
 VERSION_DATE       := $(shell date -Ru)
11 11
 VERSION_TAG        := $(shell git describe --tags --always)
12
-COMMON_BUILD_FLAGS := -mod=readonly -ldflags="-s -w -X 'main.version=$(VERSION_TAG) ($(VERSION_GO)) [$(VERSION_DATE)]'"
12
+COMMON_BUILD_FLAGS := -trimpath -mod=readonly -ldflags="-extldflags '-static' -s -w -X 'main.version=$(VERSION_TAG) ($(VERSION_GO)) [$(VERSION_DATE)]'"
13 13
 
14 14
 GOBIN  := $(ROOT_DIR)/.bin
15 15
 GOTOOL := env "GOBIN=$(GOBIN)" "PATH=$(ROOT_DIR)/.bin:$(PATH)"
@@ -27,7 +27,11 @@ $(APP_NAME): build
27 27
 
28 28
 .PHONY: static
29 29
 static:
30
-	@env CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)"
30
+	@env CGO_ENABLED=0 GOOS=linux go build \
31
+		$(COMMON_BUILD_FLAGS) \
32
+		-tags netgo \
33
+		-a \
34
+		-o "$(APP_NAME)"
31 35
 
32 36
 $(APP_NAME)-%: GOOS=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f1 -d-)
33 37
 $(APP_NAME)-%: GOARCH=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f2 -d-)
@@ -35,6 +39,8 @@ $(APP_NAME)-%: ccbuilds
35 39
 	@env "GOOS=$(GOOS)" "GOARCH=$(GOARCH)" \
36 40
 		go build \
37 41
 		$(COMMON_BUILD_FLAGS) \
42
+		-tags netgo \
43
+		-a \
38 44
 		-o "./ccbuilds/$(APP_NAME)-$(GOOS)-$(GOARCH)"
39 45
 
40 46
 .PHONY: ccbuilds
@@ -44,13 +50,17 @@ ccbuilds:
44 50
 vendor: go.mod go.sum
45 51
 	@$(MOD_ON) go mod vendor
46 52
 
53
+.PHONY: fmt
54
+fmt:
55
+	@$(GOTOOL) gofumpt -w -s -extra "$(ROOT_DIR)"
56
+
47 57
 .PHONY: test
48 58
 test:
49 59
 	@go test -v ./...
50 60
 
51 61
 .PHONY: citest
52 62
 citest:
53
-	@go test -coverprofile=coverage.txt -covermode=atomic -race -v ./...
63
+	@go test -coverprofile=coverage.txt -covermode=atomic -parallel 2 -race -v ./...
54 64
 
55 65
 .PHONY: crosscompile
56 66
 crosscompile: $(CC_BINARIES)
@@ -74,7 +84,7 @@ doc:
74 84
 	@$(GOTOOL) godoc -http 0.0.0.0:10000
75 85
 
76 86
 .PHONY: install-tools
77
-install-tools: install-tools-lint install-tools-godoc
87
+install-tools: install-tools-lint install-tools-godoc install-tools-gofumpt
78 88
 
79 89
 .PHONY: install-tools-lint
80 90
 install-tools-lint:
@@ -87,6 +97,11 @@ install-tools-godoc:
87 97
 	@mkdir -p "$(GOBIN)" || true && \
88 98
 		$(GOTOOL) go get -u golang.org/x/tools/cmd/godoc
89 99
 
100
+.PHONY: install-tools-gofumpt
101
+install-tools-gofumpt:
102
+	@mkdir -p "$(GOBIN)" || true && \
103
+		$(GOTOOL) go get -u mvdan.cc/gofumpt
104
+
90 105
 .PHONY: update-deps
91
-upgrade-deps:
92
-	$go get -u && go mod tidy
106
+update-deps:
107
+	@go get -u && go mod tidy

+ 251
- 194
README.md Прегледај датотеку

@@ -1,14 +1,16 @@
1 1
 # mtg
2 2
 
3
-Bullshit-free MTPROTO proxy for Telegram
3
+Highly-opionated (ex-bullshit-free) MTPROTO proxy for
4
+[Telegram](https://telegram.org/).
4 5
 
5
-[![Build Status](https://travis-ci.org/9seconds/mtg.svg?branch=master)](https://travis-ci.org/9seconds/mtg)
6
-[![Go Report Card](https://goreportcard.com/badge/github.com/9seconds/mtg)](https://goreportcard.com/report/github.com/9seconds/mtg)
7
-[![Docker Build Status](https://img.shields.io/docker/build/nineseconds/mtg.svg)](https://hub.docker.com/r/nineseconds/mtg/)
6
+[![CI](https://github.com/9seconds/mtg/actions/workflows/ci.yaml/badge.svg?branch=master)](https://github.com/9seconds/mtg/actions/workflows/ci.yaml)
7
+[![codecov](https://codecov.io/gh/9seconds/mtg/branch/master/graph/badge.svg?token=JfdDyGVpT4)](https://codecov.io/gh/9seconds/mtg)
8
+[![Go Reference](https://pkg.go.dev/badge/github.com/9seconds/mtg.svg)](https://pkg.go.dev/github.com/9seconds/mtg/v2)
8 9
 
9
-**Please see a guide on upgrading to 1.0 at the end of this README.**
10
+**If you use v1.0 or upgrade broke you proxy, please read the chapter
11
+[Version 2](#version-2)**
10 12
 
11
-# Rationale
13
+## Rationale
12 14
 
13 15
 There are several available proxies for Telegram MTPROTO available. Here
14 16
 are the most notable:
@@ -17,267 +19,322 @@ are the most notable:
17 19
 * [Python](https://github.com/alexbers/mtprotoproxy)
18 20
 * [Erlang](https://github.com/seriyps/mtproto_proxy)
19 21
 
20
-Almost all of them follow the way how official proxy was built. This
21
-includes support of multiple secrets, support of promoted channels, etc.
22
+You can use any of these. They work great and all implementations have
23
+feature parity now. This includes support of adtag, replay attack
24
+protection, domain fronting, faketls, and so on. mtg has a similar
25
+goal: to give a possibility to connect to Telegram in a restricted,
26
+censored environment. But it does it slightly differently in details
27
+that probably matter.
22 28
 
23
-mtg is an implementation in golang which is intended to be:
29
+* **Resource-efficient**
30
+
31
+  It has to be resource-efficient. It does not mean that you will see
32
+  the smallest memory usage. It means that it will try to use allocated
33
+  resources in zero-waste mode, reusing as much memory as possible and
34
+  so on.
24 35
 
25
-* **Lightweight**
26
-  It has to consume as few resources as possible but not by losing
27
-  maintainability.
28 36
 * **Easily deployable**
37
+
29 38
   I strongly believe that Telegram proxies should follow the way of
30
-  ShadowSocks: promoted channels is a strange way of doing business
31
-  I suppose. I think the only viable way is to have a proxy with
32
-  minimum configuration which should work everywhere.
39
+  [ShadowSocks](https://shadowsocks.org): promoted channels is a strange
40
+  way of doing business I suppose. I think the only viable way is to
41
+  have a proxy that can be restored anywhere easily.
42
+
33 43
 * **A single secret**
34
-  I think that multiple secrets solve no problems and just complexify
44
+
45
+  I think that multiple secrets solve no problems and just complex
35 46
   software. I also believe that in the case of throwout proxies, this
36
-  feature is a useless luxury.
37
-* **Minimum docker image size**
38
-  Official image is less than 3.5 megabytes. Literally.
39
-* **No management WebUI**
40
-  This is an implementation of a simple lightweight proxy. I won't do that.
47
+  the feature is a useless luxury.
41 48
 
42
-This proxy supports 2 modes of work: direct connection to Telegram and
43
-promoted channel mode. If you do not need promoted channels, I would
44
-recommend you to go with direct mode: this way is more robust.
49
+* **No adtag support**
45 50
 
46
-To run a proxy in direct mode, all you need to do is just provide a
47
-secret. If you do not provide ADTag as a second parameter, promoted
48
-channels mode won't be activated.
51
+  Please read [Version 2](#version-2) chapter.
49 52
 
50
-To get promoted channel, please contact
51
-[@MTProxybot](https://t.me/MTProxybot) and provide generated adtag as a
52
-second parameter.
53
+* **No management WebUI**
53 54
 
55
+  This is an implementation of a simple lightweight proxy. I won't do that.
54 56
 
55
-# Source code organization
57
+* **Proxy chaining**
58
+
59
+  mtg has the support of [SOCKS5](https://en.wikipedia.org/wiki/SOCKS)
60
+  proxies. So, in theory, you can run this proxy as a frontend
61
+  and route traffic via [v2ray](https://www.v2ray.com/),
62
+  [Gost](https://docs.ginuerzh.xyz/gost/),
63
+  [Trojan](https://trojan-gfw.github.io/trojan/), or any other project
64
+  you like.
65
+
66
+* **Native blocklist support**
67
+
68
+  Previously, this was delegated to the [FireHOL](https://firehol.org/)
69
+  project or similar ones which track attacks and publish a list of
70
+  potentially dangerous IPs. mtg has native support of such blocklists.
71
+
72
+* **Can be used as a library**
56 73
 
57
-There are 2 main branches:
74
+  mtg v2 was redesigned in a way so it can be embedded into your
75
+  software (written in Golang) with a minimum effort + you can replace
76
+  some parts with those you want.
58 77
 
59
-1. `master` branch contains potentially unstable features
60
-2. `stable` branch contains stable version. Usually you want to use this branch.
78
+### Version 2
61 79
 
62
-# How to build
80
+If you use version 1.x before, you are probably noticed some major
81
+backward non-compatible details:
63 82
 
64
-```console
65
-make
66
-```
83
+1. Configuration file
84
+2. Removed support of adtag
67 85
 
68
-If you want to build for another platform:
86
+For the configuration file, please check out the full example in this
87
+repository. It has a lot of comments and most of the options are
88
+optional. We do have only `secret` and `bind-to` sections mandatory.
89
+Other sections in the example configuration file are filled with default
90
+values.
69 91
 
70
-```console
71
-make crosscompile
72
-```
92
+Adtag support was removed completely. This was done to debloat mtg and
93
+keep it simple and obvious. Hopefully, this goal is achieved and the
94
+source code is clean and straightforward enough.
73 95
 
74
-If you want to build Docker image (called `mtg`):
96
+I always was quite skeptical about adtag. In my POV, a proxy as a fat
97
+big connectivity point for hundreds of clients is an illusion. If you
98
+work in a censored environment, the first thing that authority does is
99
+IP blocking. For us, it means, those big proxies that can benefit from
100
+having a pinned channel are going to be blocked in a minute.
75 101
 
76
-```console
77
-make docker
78
-```
102
+Proxy has to be intimate. It has to be shared within a small group as
103
+a family or maybe your college friends. It has to have a small number
104
+of connections and never publicly announced its presence. It has to fly
105
+under the radar. If the proxy is detected, you need to be able to give
106
+a rebirth on a new IP address as soon as possible. I do no think that
107
+having some special channel for such a use case makes any sense.
79 108
 
80
-# Docker image
109
+But other details like replay attack protection, domain fronting,
110
+accurate FakeTLS implementation, IP blacklisting, and proxy
111
+chaining matter here. If you work in censored perimeter like
112
+[GFW](https://en.wikipedia.org/wiki/Great_Firewall)-protected
113
+country, you probably want to have an MTPROTO proxy as
114
+a frontend that transports traffic via cloaked tunnels
115
+made by [Trojan](https://trojan-gfw.github.io/trojan/),
116
+[Shadowsocks](https://shadowsocks.org), [v2ray](https://www.v2ray.com/),
117
+or [Gost](https://docs.ginuerzh.xyz/gost/). That's why you have to have
118
+the support of chaining as a first-class citizen.
81 119
 
82
-Docker follows the same policy as the source code organization:
120
+Yes, this is possible and doable with optional adtag support. But the
121
+truth is that the MTPROTO proxy for Telegram is just a thing that either
122
+work as a normal client (direct mode) or doing some RPC calls in [TL
123
+language](https://core.telegram.org/mtproto/TL) (adtag support). I
124
+understand the intention of the developers and I understand that they
125
+were under high pressure fighting with [RKN](https://rkn.gov.ru/) and
126
+doing TON after that. Nothing is ideal. But for the proxy, it means that
127
+source code is full of complex non-trivial code which is required only
128
+to support a feature that we barely need.
83 129
 
84
-- `latest` mirrors the master branch
85
-- `stable` mirrors the stable branch
86
-- tags are for tagged releases
130
+So, to have a reasonable MTPROTO proxy, adtag support was removed. This
131
+is a rare chance in my career where software v2 debloats a previous
132
+version. It feels so good :)
87 133
 
88
-```console
89
-docker pull nineseconds/mtg:latest
90
-```
134
+### Version 1 and 2
91 135
 
92
-```console
93
-docker pull nineseconds/mtg:stable
94
-```
136
+I do continue to support both versions 1 and 2. But in a different mode.
95 137
 
96
-```console
97
-docker pull nineseconds/mtg:0.10
98
-```
138
+Version 1 is now officially in maintenance mode. It means that I won't
139
+make any new features or improvements there. You can consider a feature
140
+freeze there. No bugs are going to be fixed there except for critical
141
+ones. PRs are welcome though. The goal is to keep it working. It will
142
+get some periodical updates like updates to the new Golang version of
143
+dependencies version bump, but that's mostly it.
99 144
 
100
-# Ansible role
145
+**If you want to have mtg with _adtag support_, please use version 1**.
101 146
 
102
-You can find unofficial Ansible role for mtg here: https://github.com/rlex/ansible-role-mtg
103
-Also, there is another project on Ansible Galaxy: https://galaxy.ansible.com/ivansible/lin_mtproxy
147
+Version 2 is going to have all my love, active support, bug fixing, etc.
148
+It is under active development and maintenance.
104 149
 
105
-# Configuration
150
+This project has several main branches
106 151
 
107
-To run this tool you need to configure as less as possible. Telegram
108
-clients support 3 different secret types:
152
+1. [`master`](https://github.com/9seconds/mtg/tree/master) branch
153
+   contains a bleeding edge. It may potentially have some features
154
+   which will break your source code.
155
+2. [`stable`](https://github.com/9seconds/mtg/tree/stable) branch contains
156
+   dumps of a master branch when we consider it 'stable'. This is a
157
+   branch you probably want to pick.
158
+3. [`v2`](https://github.com/9seconds/mtg/tree/v2) has a development
159
+   of the v2.x version. In theory, it is the same as `master` but this
160
+   will change when we have v3.x.
161
+4. [`v1`](https://github.com/9seconds/mtg/tree/v1) has a version 1.x.
162
+
163
+## Getting started
109 164
 
110
-* Simple - basically, it is just a flow of frames ciphered by AES-CTR stream
111
-  cipher.
112
-* Secured - the same stream as simple but with some random noise to prevent
113
-  statistical analysis of traffic flow.
114
-* FakeTLS - this mode envelops telegram stream in TLS so it looks (in theory)
115
-  the same as any TLS1.3 traffic from DPI point of view.
165
+### Download a tool
166
+
167
+#### Download binaries
168
+
169
+Binaries can be downloaded from the release page. Also, you can download
170
+docker image.
116 171
 
117
-If you do not have preferences, go with FakeTLS or at least secured.
118
-Simple mode is a little bit naive and traffic flow can be easily
119
-identified as Telegram one.
172
+For the current version, please download like
120 173
 
121
-Unlike the rest of implementation, mtg is quite strict about the
122
-execution mode: if you run a proxy instance with FakeTLS secret, you
123
-can't connect to it with simple or secured clients. You can't connect
124
-to the proxy with secured secret with FakeTLS key. It forces one mode
125
-of working. So, unfortunately, there is no way how to connect to the
126
-deployed proxy with another secret (if you know how to construct and
127
-convert them). But at the same time, old clients can't connect so they
128
-won't expose the type of the service.
174
+```console
175
+docker pull nineseconds/mtg:2
176
+```
129 177
 
130
-First, you need to generate a secret:
178
+For version 1:
131 179
 
132 180
 ```console
133
-$ mtg generate-secret simple
134
-52a493bdfb90eea55739eabff2d92a14
181
+docker pull nineseconds/mtg:1
135 182
 ```
136 183
 
184
+You may also check both [Docker
185
+Hub](https://hub.docker.com/r/nineseconds/mtg/tags) and [Github
186
+Registry](https://github.com/users/9seconds/packages/container/package/mtg).
187
+Please do not choose `latest` or `stable` if you want to avoid
188
+surprises. Always choose some version tag.
189
+
190
+Also, if you have `go` installed, you can always download this tool with `go get`:
191
+
137 192
 ```console
138
-$ mtg generate-secret secured
139
-ddf05fb7acb549be047a7c585116581418
193
+go get github.com/9seconds/mtg/v2
140 194
 ```
141 195
 
196
+#### Build from sources
197
+
142 198
 ```console
143
-$ mtg generate-secret -c google.com tls
144
-ee852380f362a09343efb4690c4e17862e676f6f676c652e636f6d
199
+git clone https://github.com:9seconds/mtg.git
200
+cd mtg
201
+make static
145 202
 ```
146 203
 
147
-Or, if you prefer docker:
204
+or for the docker image:
148 205
 
149 206
 ```console
150
-$ docker run --rm nineseconds/mtg generate-secret tls -c bing.com
151
-eedf71035a8ed48a623d8e83e66aec4d0562696e672e636f6d
207
+make docker
152 208
 ```
153 209
 
154
-## Antireplay cache
210
+### Generate secret
155 211
 
156
-To prevent replay attacks, we have internal storage of first frames
157
-messages for connected clients. These frames are generated randomly
158
-by design and we have the negligible possibility of duplication
159
-(probability is 1/(2^64)) but it could be quite effective to prevent
160
-replays.
212
+If you already have a secret in Base64 format or that, which starts with `ee`,
213
+you can skip this chapter. Otherwise:
161 214
 
162
-It is possible to disable this cache. To do that, please explicitly set
163
-its size to 0.
215
+```console
216
+$ mtg generate-secret google.com
217
+7ibaERuTSGPH1RdztfYnN4tnb29nbGUuY29t
218
+```
164 219
 
220
+or
165 221
 
166
-## FakeTLS
222
+```console
223
+$ mtg generate-secret --hex google.com
224
+ee473ce5d4958eb5f968c87680a23854a0676f6f676c652e636f6d
225
+```
167 226
 
168
-If you run this a proxy in faketls mode, this proxy will try to hide
169
-itself cloaking a host provided as a part of the generated secret. It
170
-means that if you cloak google.com then you can curl this proxy and
171
-you'll get a google.com response back.
227
+This secret is a keystone for a proxy and your password for a client.
228
+You need to keep it secured.
172 229
 
173
-mtg proxies L3 traffic. In other words, only TCP, without interfering in
174
-TLS, HTTP or any other high-level protocol.
230
+We recommend choosing a hostname wisely. Here we have a _google.com_
231
+but in reality, all providers can easily detect that this is not a
232
+Google. Google has a list of networks it officially uses and your IP
233
+address won't probably belong to it. It is a great idea to hide behind
234
+some domain that has some relation to this IP address.
175 235
 
236
+For example, you've bought a VPS from [Digital
237
+Ocean](https://www.digitalocean.com/). Then it might be a good idea to
238
+generate a secret for _digitalocean.com_ then.
176 239
 
177
-## Environment variables
240
+### Prepare a configuration file
178 241
 
179
-It is possible to configure this tool using environment variables. You
180
-can configure any flag but not secret or adtag. Here is the list of
181
-supported environment variables:
242
+Please checkout an example configuration file. All options except of
243
+`secret` and `bind-to` are optional. You can safely have this minimal
244
+configuration file:
182 245
 
183
-| Environment variable          | Corresponding flags          | Default value                     | Description                                                                                                                                                                                                                                                                     |
184
-|-------------------------------|------------------------------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
185
-| `MTG_DEBUG`                   | `-d`, `--debug`              | `false`                           | Run in debug mode. Usually, you need to run in this mode  only if you develop this tool or its maintainer is asking you to provide  logs with such verbosity.                                                                                                                   |
186
-| `MTG_VERBOSE`                 | `-v`, `--verbose`            | `false`                           | Run in verbose mode. This is way less chatty than debug mode.                                                                                                                                                                                                                   |
187
-| `MTG_BIND`                    | `-b`, `--bind`               | `0.0.0.0:3128`                    | Which host/port pair should we bind to (listen on).                                                                                                                                                                                                                             |
188
-| `MTG_IPV4`                    | `-4`, `--public-ipv4`        | [Autodetect](https://ifconfig.co) | IPv4 address:port of this proxy. This is required if you NAT your proxy or run it in a docker container. In that case, you absolutely need to specify public IPv4 address of the proxy, otherwise either URLs will be broken or proxy could not access Telegram middle proxies. |
189
-| `MTG_IPV6`                    | `-6`, `--public-ipv6`        | [Autodetect](https://ifconfig.co) | IPv6 address:port of this proxy. This is required if you NAT your proxy or run it in a docker container. In that case, you absolutely need to specify public IPv6 address of the proxy, otherwise either URLs will be broken or proxy could not access Telegram middle proxies. |
190
-| `MTG_STATS_BIND`              | `-t`, `--stats-bind`         | `127.0.0.1:3129`                  | Which hist:port should we bind the internal statistics HTTP server (Prometheus).                                                                                                                                                                                                |
191
-| `MTG_STATS_NAMESPACE`         | `--stats-namespace`          | `mtg`                             | Which namespace should be used for prometheus metrics.                                                                                                                                                                                                                          |
192
-| `MTG_STATSD_ADDR`             | `--statsd-addr`              |                                   | host:port of statsd service. No defaults, by default we do not send anything there.                                                                                                                                                                                             |
193
-| `MTG_STATSD_PREFIX`           | `--statsd-prefix`            | `mtg`                             | Which bucket prefix we should use. For example, if you set `mtg`, then metric `traffic.ingress` would be send as `mtg.traffic.ingress`.                                                                                                                                         |
194
-| `MTG_STATSD_TAGS_FORMAT`      | `--statsd-tags-format`       |                                   | Which tags format we should use. By default, we are using default vanilla statsd tags format but if you want to send directly to InfluxDB or Datadog, please specify it there. Possible options are `influxdb` and `datadog`.                                                   |
195
-| `MTG_STATSD_TAGS`             | `--statsd-tags`              |                                   | Which tags should we send to statsd with our metrics. Please specify them as `key=value` pairs.                                                                                                                                                                                 |
196
-| `MTG_BUFFER_WRITE`            | `-w`, `--write-buffer`       | `32KB`                            | The size of TCP write buffer in bytes. Write buffer is the buffer for messages which are going from client to Telegram.                                                                                                                                                         |
197
-| `MTG_BUFFER_READ`             | `-r`, `--read-buffer`        | `32KB`                            | The size of TCP read buffer in bytes. Read buffer is the buffer for messages from Telegram to client.                                                                                                                                                                           |
198
-| `MTG_ANTIREPLAY_MAXSIZE`      | `--anti-replay-max-size`     | `128MB`                           | Max size of antireplay cache.                                                                                                                                                                                                                                                   |
199
-| `MTG_CLOAK_PORT`              | `--cloak-port`               | `443`                             | Which port we should use to connect to cloaked host in FakeTLS mode.                                                                                                                                                                                                            |
200
-| `MTG_MULTIPLEX_PERCONNECTION` | `--multiplex-per-connection` | `50`                              | How many client connections can share a single Telegram connection in adtag mode                                                                                                                                                                                                |
201
-| `MTG_NTP_SERVERS`             | `--ntp-server`               | default pool                      | A list of NTP servers to use.                                                                                                                                                                                                                                                   |
202
-| `MTG_PREFER_DIRECT_IP`        | `--prefer-ip`                | `ipv6`                            | Which IP protocol to prefer if possible. Works mostly in direct mode.                                                                                                                                                                                                           |
246
+```toml
247
+secret = "ee473ce5d4958eb5f968c87680a23854a0676f6f676c652e636f6d"
248
+bind-to = "0.0.0.0:443"
249
+```
203 250
 
251
+This is enough to run the whole application. All other
252
+options already have sensible defaults for the app at almost any scale.
204 253
 
205
-Usually you want to modify only read/write buffer sizes. If you feel
206
-that proxy is slow, try to increase both sizes giving more priority to
207
-read buffer.
254
+Oh, the configuration is done in [TOML format](https://toml.io/en/).
208 255
 
209
-Unfortunately, MTPROTO proxy protocol does not allow us to use splice
210
-or any other neat tricks how to eliminate the need of copying data into
211
-userspace.
256
+### Run a proxy
212 257
 
213
-# How to run the tool
258
+Put a binary and a config into your webserver. Just for example,
259
+a binary goes to `/usr/local/bin/mtg` and configuration to `/etc/mtg.toml`.
214 260
 
215
-Now run the tool:
261
+Now you can create a systemd unit:
216 262
 
217 263
 ```console
218
-$ mtg run <secret>
264
+$ cat /etc/systemd/system/mtg.service
265
+[Unit]
266
+Description=mtg
267
+
268
+[Service]
269
+ExecStart=/usr/local/bin/mtg run /etc/mtg.toml
270
+Restart=always
271
+RestartSec=3
272
+
273
+[Install]
274
+WantedBy=multi-user.target
275
+$ sudo systemctl daemon-reload
276
+$ sudo systemctl enable mtg
277
+$ sudo systemctl start mtg
219 278
 ```
220 279
 
221
-How to run the tool with ADTag:
280
+or you can run a docker image
222 281
 
223 282
 ```console
224
-$ mtg run <secret> <adtag>
283
+docker run -d -v /etc/mtg.toml:/config.toml -p 443:3128 --restart=unless-stopped nineseconds/mtg:2
225 284
 ```
226 285
 
227
-This tool will listen on port 3128 by default with the given secret.
286
+where _443_ is a host port (a port you want to connect to from a
287
+client), and _3128_ is the one you have in your config in the `bind-to`
288
+section.
228 289
 
290
+### Access a proxy
229 291
 
230
-# oneliner to run this proxy
231
-
232
-Please ensure that docker is installed. After that just execute
292
+Now you can generate some useful links:
233 293
 
234 294
 ```console
235
-curl -sfL --compressed https://raw.githubusercontent.com/9seconds/mtg/master/run.sh | bash
295
+$ mtg access /etc/mtg.toml
296
+{
297
+  "ipv4": {
298
+    "ip": "x.y.z.a",
299
+    "port": 3128,
300
+    "tg_url": "tg://proxy?...",
301
+    "tg_qrcode": "https://api.qrserver.com/v1/create-qr-code?data...",
302
+    "tme_url": "https://t.me/proxy?...",
303
+    "tme_qrcode": "https://api.qrserver.com/v1/create-qr-code?data..."
304
+  },
305
+  "secret": {
306
+    "hex": "...",
307
+    "base64": "..."
308
+  }
309
+}
236 310
 ```
237 311
 
238
-
239
-# statsd integration
240
-
241
-mtg provides an integration with statsd, you can enable it with command
242
-line interface. To enable it, you have to provide IP address of statsd
243
-service.
244
-
245
-Out of the box, mtg supports 2 additional dialects: [InfluxDB](https://www.influxdata.com/blog/getting-started-with-sending-statsd-metrics-to-telegraf-influxdb/)
246
-and [Datadog](https://docs.datadoghq.com/developers/dogstatsd/).
247
-
248
-All metrics are gauges. Here is the list of metrics and their meaning:
249
-
250
-| Metric name            | Unit    | Description                                |
251
-|------------------------|---------|--------------------------------------------|
252
-| `connections`          | number  | The number of active connections.          |
253
-| `telegram_connections` | number  | The number of active telegram connections. |
254
-| `crashes`              | number  | An amount of crashes in client handlers.   |
255
-| `traffic.egress`       | bytes   | Traffic from the start of application.     |
256
-| `replay_attacks`       | number  | The number of prevented replay attacks.    |
257
-
258
-All metrics are prefixed with given prefix. Default prefix is `mtg`.
259
-Also, metrics provide tags (ipv4/ipv6, dc indexes etc).
260
-
261
-
262
-# Prometheus integration
263
-
264
-[Prometheus](https://prometheus.io) integration comes out of
265
-the box, you do not need to setup anything special.
266
-
267
-
268
-# Upgrade to 1.0
269
-
270
-Version 1.0 breaks compatibility with previous versions so please read
271
-this chapter carefully:
272
-
273
-1. mtg now uses subcommands. Please use `mtg run` instead of just
274
-   `mtg` to run a proxy.
275
-2. Options which set host and port separately were removed in a
276
-   favor of fused `host:port` options.
277
-3. Own stats server was removed. Prometheus endpoint is moved to
278
-   default stats endpoint.
279
-4. It is possible to connect to this proxy only with a secret which
280
-   was used to run it. So, no backward compatibility of clients.
281
-5. Multiplexing involves connectivity with middle proxies and involves
282
-   the most complex code path of this proxy. To avoid potential bugs,
283
-   we still recommend using direct mode.
312
+## Metrics
313
+
314
+Out of the box, mtg works with
315
+[statsd](https://github.com/statsd/statsd) and
316
+[Prometheus](https://prometheus.io/). Please check configuration file
317
+example to get how to set this integration up.
318
+
319
+Here goes a list of metrics with their types but without a prefix.
320
+
321
+| Name                        | Type    | Tags                             | Description                                                                                |
322
+|-----------------------------|---------|----------------------------------|--------------------------------------------------------------------------------------------|
323
+| client_connections          | gauge   | `ip_family`                      | Count of processing client connections.                                                    |
324
+| telegram_connections        | gauge   | `telegram_ip`, `dc`              | Count of connections to Telegram servers.                                                  |
325
+| domain_fronting_connections | gauge   | `ip_family`                      | Count of connections to fronting domain.                                                   |
326
+| telegram_traffic            | counter | `telegram_ip`, `dc`, `direction` | Count of bytes, transmitted to/from Telegram.                                              |
327
+| domain_fronting_traffic     | counter | `direction`                      | Count of bytes, transmitted to/from fronting domain.                                       |
328
+| domain_fronting             | counter | –                                | Count of domain fronting events.                                                           |
329
+| concurrency_limited         | counter | –                                | Count of events, when client connection was rejected due to concurrency limit.             |
330
+| ip_blocklisted              | counter | –                                | Count of events when client connection was rejected because IP was found in the blacklist. |
331
+| replay_attacks              | counter | –                                | Count of detected replay attacks.                                                          |
332
+
333
+Tag meaning:
334
+
335
+| Name        | Values                     | Description                                   |
336
+|-------------|----------------------------|-----------------------------------------------|
337
+| ip_family   | `ipv4`, `ipv6`             | A version of the IP protocol.                 |
338
+| dc          |                            | A number of the Telegram DC for a connection. |
339
+| telegram_ip |                            | IP address of the Telegram server.            |
340
+| direction   | `to_client`, `from_client` | A direction of the traffic flow.              |

+ 0
- 36
antireplay/cache.go Прегледај датотеку

@@ -1,36 +0,0 @@
1
-package antireplay
2
-
3
-import "github.com/VictoriaMetrics/fastcache"
4
-
5
-var (
6
-	prefixObfuscated2 = []byte{0x00}
7
-	prefixTLS         = []byte{0x01}
8
-)
9
-
10
-type cache struct {
11
-	data *fastcache.Cache
12
-}
13
-
14
-func (c cache) AddObfuscated2(data []byte) {
15
-	c.data.Set(keyObfuscated2(data), nil)
16
-}
17
-
18
-func (c cache) AddTLS(data []byte) {
19
-	c.data.Set(keyTLS(data), nil)
20
-}
21
-
22
-func (c cache) HasObfuscated2(data []byte) bool {
23
-	return c.data.Has(keyObfuscated2(data))
24
-}
25
-
26
-func (c cache) HasTLS(data []byte) bool {
27
-	return c.data.Has(keyTLS(data))
28
-}
29
-
30
-func keyObfuscated2(data []byte) []byte {
31
-	return append(prefixObfuscated2, data...)
32
-}
33
-
34
-func keyTLS(data []byte) []byte {
35
-	return append(prefixTLS, data...)
36
-}

+ 13
- 26
antireplay/init.go Прегледај датотеку

@@ -1,30 +1,17 @@
1
+// Antireplay package has cache implementations that are effective
2
+// against replay attacks.
3
+//
4
+// To understand more about replay attacks, please read documentation
5
+// for mtglib.AntiReplayCache interface. This package has a list of some
6
+// implementations of this interface.
1 7
 package antireplay
2 8
 
3
-import (
4
-	"sync"
9
+const (
10
+	// DefaultStableBloomFilterMaxSize is a recommended byte size for a
11
+	// stable bloom filter.
12
+	DefaultStableBloomFilterMaxSize = 1024 * 1024 // 1MiB
5 13
 
6
-	"github.com/9seconds/mtg/config"
7
-	"github.com/VictoriaMetrics/fastcache"
14
+	// DefaultStableBloomFilterErrorRate is a recommended default error
15
+	// rate for a stable bloom filter.
16
+	DefaultStableBloomFilterErrorRate = 0.001
8 17
 )
9
-
10
-type CacheInterface interface {
11
-	AddObfuscated2([]byte)
12
-	AddTLS([]byte)
13
-	HasObfuscated2([]byte) bool
14
-	HasTLS([]byte) bool
15
-}
16
-
17
-var (
18
-	Cache    CacheInterface
19
-	initOnce sync.Once
20
-)
21
-
22
-func Init() {
23
-	initOnce.Do(func() {
24
-		if config.C.AntiReplayMaxSize == 0 {
25
-			Cache = nilCache{}
26
-		} else {
27
-			Cache = cache{fastcache.New(config.C.AntiReplayMaxSize)}
28
-		}
29
-	})
30
-}

+ 0
- 8
antireplay/nilcache.go Прегледај датотеку

@@ -1,8 +0,0 @@
1
-package antireplay
2
-
3
-type nilCache struct{}
4
-
5
-func (n nilCache) AddObfuscated2(_ []byte)      {}
6
-func (n nilCache) AddTLS(_ []byte)              {}
7
-func (n nilCache) HasObfuscated2(_ []byte) bool { return false }
8
-func (n nilCache) HasTLS(_ []byte) bool         { return false }

+ 14
- 0
antireplay/noop.go Прегледај датотеку

@@ -0,0 +1,14 @@
1
+package antireplay
2
+
3
+import "github.com/9seconds/mtg/v2/mtglib"
4
+
5
+type noop struct{}
6
+
7
+func (n noop) SeenBefore(_ []byte) bool { return false }
8
+
9
+// NewNoop returns an implementation that does nothing. A corresponding
10
+// method always returns false, so this cache accepts everything you
11
+// pass to it.
12
+func NewNoop() mtglib.AntiReplayCache {
13
+	return noop{}
14
+}

+ 26
- 0
antireplay/noop_test.go Прегледај датотеку

@@ -0,0 +1,26 @@
1
+package antireplay_test
2
+
3
+import (
4
+	"testing"
5
+
6
+	"github.com/9seconds/mtg/v2/antireplay"
7
+	"github.com/stretchr/testify/suite"
8
+)
9
+
10
+type NoopTestSuite struct {
11
+	suite.Suite
12
+}
13
+
14
+func (suite *NoopTestSuite) TestOp() {
15
+	filter := antireplay.NewNoop()
16
+
17
+	suite.False(filter.SeenBefore([]byte{1, 2, 3}))
18
+	suite.False(filter.SeenBefore([]byte{4, 5, 6}))
19
+	suite.False(filter.SeenBefore([]byte{1, 2, 3}))
20
+	suite.False(filter.SeenBefore([]byte{4, 5, 6}))
21
+}
22
+
23
+func TestNoop(t *testing.T) {
24
+	t.Parallel()
25
+	suite.Run(t, &NoopTestSuite{})
26
+}

+ 51
- 0
antireplay/stable_bloom_filter.go Прегледај датотеку

@@ -0,0 +1,51 @@
1
+package antireplay
2
+
3
+import (
4
+	"sync"
5
+
6
+	"github.com/9seconds/mtg/v2/mtglib"
7
+	"github.com/OneOfOne/xxhash"
8
+	boom "github.com/tylertreat/BoomFilters"
9
+)
10
+
11
+type stableBloomFilter struct {
12
+	filter boom.StableBloomFilter
13
+	mutex  sync.Mutex
14
+}
15
+
16
+func (s *stableBloomFilter) SeenBefore(digest []byte) bool {
17
+	s.mutex.Lock()
18
+	defer s.mutex.Unlock()
19
+
20
+	return s.filter.TestAndAdd(digest)
21
+}
22
+
23
+// NewStableBloomFilter returns an implementation of AntiReplayCache
24
+// based on stable bloom filter.
25
+//
26
+// http://webdocs.cs.ualberta.ca/~drafiei/papers/DupDet06Sigmod.pdf
27
+//
28
+// The basic idea of a stable bloom filter is quite simple: each time
29
+// when you set a new element, you randomly reset P elements. There is a
30
+// hardcore math which proves that if you choose this P correctly, you
31
+// can maintain the same error rate for a stream of elements.
32
+//
33
+// byteSize is the number of bytes you want to give to a bloom filter.
34
+// errorRate is desired false-positive error rate. If you want to use
35
+// default values, please pass 0 for byteSize and <0 for errorRate.
36
+func NewStableBloomFilter(byteSize uint, errorRate float64) mtglib.AntiReplayCache {
37
+	if byteSize == 0 {
38
+		byteSize = DefaultStableBloomFilterMaxSize
39
+	}
40
+
41
+	if errorRate < 0 {
42
+		errorRate = DefaultStableBloomFilterErrorRate
43
+	}
44
+
45
+	sf := boom.NewDefaultStableBloomFilter(byteSize*8, errorRate) // nolint: gomnd
46
+	sf.SetHash(xxhash.New64())
47
+
48
+	return &stableBloomFilter{
49
+		filter: *sf,
50
+	}
51
+}

+ 26
- 0
antireplay/stable_bloom_filter_test.go Прегледај датотеку

@@ -0,0 +1,26 @@
1
+package antireplay_test
2
+
3
+import (
4
+	"testing"
5
+
6
+	"github.com/9seconds/mtg/v2/antireplay"
7
+	"github.com/stretchr/testify/suite"
8
+)
9
+
10
+type StableBloomFilterTestSuite struct {
11
+	suite.Suite
12
+}
13
+
14
+func (suite *StableBloomFilterTestSuite) TestOp() {
15
+	filter := antireplay.NewStableBloomFilter(500, 0.001)
16
+
17
+	suite.False(filter.SeenBefore([]byte{1, 2, 3}))
18
+	suite.False(filter.SeenBefore([]byte{4, 5, 6}))
19
+	suite.True(filter.SeenBefore([]byte{1, 2, 3}))
20
+	suite.True(filter.SeenBefore([]byte{4, 5, 6}))
21
+}
22
+
23
+func TestStableBloomFilter(t *testing.T) {
24
+	t.Parallel()
25
+	suite.Run(t, &StableBloomFilterTestSuite{})
26
+}

+ 0
- 26
cli/generate.go Прегледај датотеку

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

+ 0
- 101
cli/proxy.go Прегледај датотеку

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

+ 0
- 43
cli/utils.go Прегледај датотеку

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

+ 0
- 312
config/config.go Прегледај датотеку

@@ -1,312 +0,0 @@
1
-package config
2
-
3
-import (
4
-	"bytes"
5
-	"context"
6
-	"encoding/json"
7
-	"errors"
8
-	"fmt"
9
-	"math"
10
-	"net"
11
-
12
-	"github.com/alecthomas/units"
13
-	statsd "github.com/smira/go-statsd"
14
-	"go.uber.org/zap"
15
-)
16
-
17
-type SecretMode uint8
18
-
19
-func (s SecretMode) String() string {
20
-	switch s {
21
-	case SecretModeSimple:
22
-		return "simple"
23
-	case SecretModeSecured:
24
-		return "secured"
25
-	case SecretModeTLS:
26
-		return "tls"
27
-	}
28
-
29
-	return "tls"
30
-}
31
-
32
-const (
33
-	SecretModeSimple SecretMode = iota
34
-	SecretModeSecured
35
-	SecretModeTLS
36
-)
37
-
38
-type PreferIP uint8
39
-
40
-const (
41
-	PreferIPv4 PreferIP = iota
42
-	PreferIPv6
43
-)
44
-
45
-const SimpleSecretLength = 16
46
-
47
-type OptionType uint8
48
-
49
-const (
50
-	OptionTypeDebug OptionType = iota
51
-	OptionTypeVerbose
52
-
53
-	OptionTypePreferIP
54
-
55
-	OptionTypeBind
56
-	OptionTypePublicIPv4
57
-	OptionTypePublicIPv6
58
-
59
-	OptionTypeStatsBind
60
-	OptionTypeStatsNamespace
61
-	OptionTypeStatsdAddress
62
-	OptionTypeStatsdTagsFormat
63
-	OptionTypeStatsdTags
64
-
65
-	OptionTypeWriteBufferSize
66
-	OptionTypeReadBufferSize
67
-
68
-	OptionTypeCloakPort
69
-
70
-	OptionTypeAntiReplayMaxSize
71
-
72
-	OptionTypeMultiplexPerConnection
73
-
74
-	OptionTypeNTPServers
75
-
76
-	OptionTypeSecret
77
-	OptionTypeAdtag
78
-)
79
-
80
-type Config struct {
81
-	Bind             *net.TCPAddr      `json:"bind"`
82
-	PublicIPv4       *net.TCPAddr      `json:"public_ipv4"`
83
-	PublicIPv6       *net.TCPAddr      `json:"public_ipv6"`
84
-	StatsBind        *net.TCPAddr      `json:"stats_bind"`
85
-	StatsdAddr       *net.TCPAddr      `json:"stats_addr"`
86
-	StatsdTagsFormat *statsd.TagFormat `json:"statsd_tags_format"`
87
-
88
-	StatsNamespace string            `json:"stats_namespace"`
89
-	CloakHost      string            `json:"cloak_host"`
90
-	StatsdTags     map[string]string `json:"statsd_tags"`
91
-
92
-	WriteBuffer int `json:"write_buffer"`
93
-	ReadBuffer  int `json:"read_buffer"`
94
-	CloakPort   int `json:"cloak_port"`
95
-
96
-	AntiReplayMaxSize int `json:"anti_replay_max_size"`
97
-
98
-	MultiplexPerConnection int `json:"multiplex_per_connection"`
99
-
100
-	Debug      bool       `json:"debug"`
101
-	Verbose    bool       `json:"verbose"`
102
-	SecretMode SecretMode `json:"secret_mode"`
103
-	PreferIP   PreferIP   `json:"prefer_ip"`
104
-	NTPServers []string   `json:"ntp_servers"`
105
-
106
-	Secret []byte `json:"secret"`
107
-	AdTag  []byte `json:"adtag"`
108
-}
109
-
110
-func (c *Config) ClientReadBuffer() int {
111
-	return c.ReadBuffer
112
-}
113
-
114
-func (c *Config) ClientWriteBuffer() int {
115
-	return c.WriteBuffer
116
-}
117
-
118
-func (c *Config) MiddleProxyMode() bool {
119
-	return len(c.AdTag) > 0
120
-}
121
-
122
-func (c *Config) ProxyReadBuffer() int {
123
-	value := c.ReadBuffer
124
-
125
-	if c.MiddleProxyMode() {
126
-		value = c.adjustProxyValue(value)
127
-	}
128
-
129
-	return value
130
-}
131
-
132
-func (c *Config) ProxyWriteBuffer() int {
133
-	value := c.WriteBuffer
134
-
135
-	if c.MiddleProxyMode() {
136
-		value = c.adjustProxyValue(value)
137
-	}
138
-
139
-	return value
140
-}
141
-
142
-func (c *Config) adjustProxyValue(value int) int {
143
-	if c.MultiplexPerConnection == 0 {
144
-		return value
145
-	}
146
-
147
-	fvalue := float64(value)
148
-
149
-	newValue := fvalue * 2 * math.Log(float64(c.MultiplexPerConnection))
150
-	newValue = math.Ceil(newValue)
151
-	newValue = math.Max(fvalue, newValue)
152
-
153
-	return int(newValue)
154
-}
155
-
156
-type Opt struct {
157
-	Option OptionType
158
-	Value  interface{}
159
-}
160
-
161
-var C = Config{}
162
-
163
-func Init(options ...Opt) error { // nolint: gocyclo, funlen, cyclop
164
-	for _, opt := range options {
165
-		switch opt.Option {
166
-		case OptionTypeDebug:
167
-			C.Debug = opt.Value.(bool)
168
-		case OptionTypeVerbose:
169
-			C.Verbose = opt.Value.(bool)
170
-		case OptionTypePreferIP:
171
-			value := opt.Value.(string)
172
-			switch value {
173
-			case "ipv4":
174
-				C.PreferIP = PreferIPv4
175
-			case "ipv6":
176
-				C.PreferIP = PreferIPv6
177
-			default:
178
-				return fmt.Errorf("incorrect direct IP mode %s", value)
179
-			}
180
-		case OptionTypeBind:
181
-			C.Bind = opt.Value.(*net.TCPAddr)
182
-		case OptionTypePublicIPv4:
183
-			C.PublicIPv4 = opt.Value.(*net.TCPAddr)
184
-			if C.PublicIPv4 == nil {
185
-				C.PublicIPv4 = &net.TCPAddr{}
186
-			}
187
-		case OptionTypePublicIPv6:
188
-			C.PublicIPv6 = opt.Value.(*net.TCPAddr)
189
-			if C.PublicIPv6 == nil {
190
-				C.PublicIPv6 = &net.TCPAddr{}
191
-			}
192
-		case OptionTypeStatsBind:
193
-			C.StatsBind = opt.Value.(*net.TCPAddr)
194
-		case OptionTypeStatsNamespace:
195
-			C.StatsNamespace = opt.Value.(string)
196
-		case OptionTypeStatsdAddress:
197
-			C.StatsdAddr = opt.Value.(*net.TCPAddr)
198
-		case OptionTypeStatsdTagsFormat:
199
-			value := opt.Value.(string)
200
-			switch value {
201
-			case "datadog":
202
-				C.StatsdTagsFormat = statsd.TagFormatDatadog
203
-			case "influxdb":
204
-				C.StatsdTagsFormat = statsd.TagFormatInfluxDB
205
-			default:
206
-				return fmt.Errorf("incorrect statsd tag %s", value)
207
-			}
208
-		case OptionTypeStatsdTags:
209
-			C.StatsdTags = opt.Value.(map[string]string)
210
-		case OptionTypeWriteBufferSize:
211
-			C.WriteBuffer = int(opt.Value.(units.Base2Bytes))
212
-		case OptionTypeReadBufferSize:
213
-			C.ReadBuffer = int(opt.Value.(units.Base2Bytes))
214
-		case OptionTypeCloakPort:
215
-			C.CloakPort = int(opt.Value.(uint16))
216
-		case OptionTypeAntiReplayMaxSize:
217
-			C.AntiReplayMaxSize = int(opt.Value.(units.Base2Bytes))
218
-		case OptionTypeMultiplexPerConnection:
219
-			C.MultiplexPerConnection = int(opt.Value.(uint))
220
-		case OptionTypeNTPServers:
221
-			C.NTPServers = opt.Value.([]string)
222
-			if len(C.NTPServers) == 0 {
223
-				return errors.New("ntp server list is empty")
224
-			}
225
-		case OptionTypeSecret:
226
-			C.Secret = opt.Value.([]byte)
227
-		case OptionTypeAdtag:
228
-			C.AdTag = opt.Value.([]byte)
229
-		default:
230
-			return fmt.Errorf("unknown tag %v", opt.Option)
231
-		}
232
-	}
233
-
234
-	switch {
235
-	case len(C.Secret) == 1+SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xdd}):
236
-		C.SecretMode = SecretModeSecured
237
-		C.Secret = bytes.TrimPrefix(C.Secret, []byte{0xdd})
238
-	case len(C.Secret) > SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xee}):
239
-		C.SecretMode = SecretModeTLS
240
-		secret := bytes.TrimPrefix(C.Secret, []byte{0xee})
241
-		C.Secret = secret[:SimpleSecretLength]
242
-		C.CloakHost = string(secret[SimpleSecretLength:])
243
-	case len(C.Secret) == SimpleSecretLength:
244
-		C.SecretMode = SecretModeSimple
245
-	default:
246
-		return errors.New("incorrect secret")
247
-	}
248
-
249
-	if C.MultiplexPerConnection == 0 {
250
-		return errors.New("cannot use 0 clients per connection for multiplexing")
251
-	}
252
-
253
-	if C.CloakHost != "" {
254
-		if _, err := net.LookupHost(C.CloakHost); err != nil {
255
-			zap.S().Warnw("Cannot resolve address of host", "hostname", C.CloakHost, "error", err)
256
-		}
257
-	}
258
-
259
-	return nil
260
-}
261
-
262
-func InitPublicAddress(ctx context.Context) error {
263
-	if C.PublicIPv4.Port == 0 {
264
-		C.PublicIPv4.Port = C.Bind.Port
265
-	}
266
-
267
-	if C.PublicIPv6.Port == 0 {
268
-		C.PublicIPv6.Port = C.Bind.Port
269
-	}
270
-
271
-	foundAddress := C.PublicIPv4.IP != nil || C.PublicIPv6.IP != nil
272
-
273
-	if C.PublicIPv4.IP == nil {
274
-		ip, err := getGlobalIPv4(ctx)
275
-		if err != nil {
276
-			zap.S().Warnw("Cannot resolve public address", "error", err)
277
-		} else {
278
-			C.PublicIPv4.IP = ip
279
-			foundAddress = true
280
-		}
281
-	}
282
-
283
-	if C.PublicIPv6.IP == nil {
284
-		ip, err := getGlobalIPv6(ctx)
285
-		if err != nil {
286
-			zap.S().Warnw("Cannot resolve public address", "error", err)
287
-		} else {
288
-			C.PublicIPv6.IP = ip
289
-			foundAddress = true
290
-		}
291
-	}
292
-
293
-	if !foundAddress {
294
-		return errors.New("cannot resolve any public address")
295
-	}
296
-
297
-	return nil
298
-}
299
-
300
-func Printable() interface{} {
301
-	data, err := json.Marshal(C)
302
-	if err != nil {
303
-		panic(err)
304
-	}
305
-
306
-	rv := map[string]interface{}{}
307
-	if err := json.Unmarshal(data, &rv); err != nil {
308
-		panic(err)
309
-	}
310
-
311
-	return rv
312
-}

+ 0
- 78
config/global_ips.go Прегледај датотеку

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

+ 0
- 99
config/urls.go Прегледај датотеку

@@ -1,99 +0,0 @@
1
-package config
2
-
3
-import (
4
-	"encoding/hex"
5
-	"net"
6
-	"net/url"
7
-	"strconv"
8
-)
9
-
10
-type URLs struct {
11
-	TG        string `json:"tg_url"`
12
-	TMe       string `json:"tme_url"`
13
-	TGQRCode  string `json:"tg_qrcode"`
14
-	TMeQRCode string `json:"tme_qrcode"`
15
-}
16
-
17
-type IPURLs struct {
18
-	IPv4      *URLs  `json:"ipv4,omitempty"`
19
-	IPv6      *URLs  `json:"ipv6,omitempty"`
20
-	BotSecret string `json:"secret_for_mtproxybot"`
21
-}
22
-
23
-func GetURLs() (urls IPURLs) {
24
-	secret := ""
25
-
26
-	switch C.SecretMode {
27
-	case SecretModeSimple:
28
-		secret = hex.EncodeToString(C.Secret)
29
-	case SecretModeSecured:
30
-		secret = "dd" + hex.EncodeToString(C.Secret)
31
-	case SecretModeTLS:
32
-		secret = "ee" + hex.EncodeToString(C.Secret) + hex.EncodeToString([]byte(C.CloakHost))
33
-	}
34
-
35
-	if C.PublicIPv4.IP != nil {
36
-		urls.IPv4 = makeURLs(C.PublicIPv4, secret)
37
-	}
38
-
39
-	if C.PublicIPv6.IP != nil {
40
-		urls.IPv6 = makeURLs(C.PublicIPv6, secret)
41
-	}
42
-
43
-	urls.BotSecret = hex.EncodeToString(C.Secret)
44
-
45
-	return urls
46
-}
47
-
48
-func makeURLs(addr *net.TCPAddr, secret string) *URLs {
49
-	urls := &URLs{}
50
-
51
-	values := url.Values{}
52
-	values.Set("server", addr.IP.String())
53
-	values.Set("port", strconv.Itoa(addr.Port))
54
-	values.Set("secret", secret)
55
-
56
-	return &URLs{
57
-		TG:        makeTGURL(values),
58
-		TMe:       makeTMeURL(values),
59
-		TGQRCode:  makeQRCodeURL(urls.TG),
60
-		TMeQRCode: makeQRCodeURL(urls.TMe),
61
-	}
62
-}
63
-
64
-func makeTGURL(values url.Values) string {
65
-	tgURL := url.URL{
66
-		Scheme:   "tg",
67
-		Host:     "proxy",
68
-		RawQuery: values.Encode(),
69
-	}
70
-
71
-	return tgURL.String()
72
-}
73
-
74
-func makeTMeURL(values url.Values) string {
75
-	tMeURL := url.URL{
76
-		Scheme:   "https",
77
-		Host:     "t.me",
78
-		Path:     "proxy",
79
-		RawQuery: values.Encode(),
80
-	}
81
-
82
-	return tMeURL.String()
83
-}
84
-
85
-func makeQRCodeURL(data string) string {
86
-	qr := url.URL{
87
-		Scheme: "https",
88
-		Host:   "api.qrserver.com",
89
-		Path:   "v1/create-qr-code",
90
-	}
91
-
92
-	values := url.Values{}
93
-	values.Set("qzone", "4")
94
-	values.Set("format", "svg")
95
-	values.Set("data", data)
96
-	qr.RawQuery = values.Encode()
97
-
98
-	return qr.String()
99
-}

+ 0
- 6
conntypes/acks.go Прегледај датотеку

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

+ 0
- 5
conntypes/dc.go Прегледај датотеку

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

+ 0
- 24
conntypes/id.go Прегледај датотеку

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

+ 0
- 3
conntypes/packet.go Прегледај датотеку

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

+ 0
- 22
conntypes/protocol.go Прегледај датотеку

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

+ 0
- 29
conntypes/type.go Прегледај датотеку

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

+ 0
- 14
conntypes/wrap_interfaces.go Прегледај датотеку

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

+ 0
- 41
conntypes/wrap_packet_ack_interfaces.go Прегледај датотеку

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

+ 0
- 51
conntypes/wrap_packet_interfaces.go Прегледај датотеку

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

+ 0
- 56
conntypes/wrap_stream_interfaces.go Прегледај датотеку

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

+ 108
- 0
events/event_stream.go Прегледај датотеку

@@ -0,0 +1,108 @@
1
+package events
2
+
3
+import (
4
+	"context"
5
+	"math/rand"
6
+	"runtime"
7
+
8
+	"github.com/9seconds/mtg/v2/mtglib"
9
+	"github.com/OneOfOne/xxhash"
10
+)
11
+
12
+// EventStream is a default implementation of the mtglib.EventStream
13
+// interface.
14
+//
15
+// EventStream manages a set of goroutines, observers. Main
16
+// responsibility of the event stream is to route an event to relevant
17
+// observer based on some hash so each observer will have all events
18
+// which belong to some stream id.
19
+//
20
+// Thus, EventStream can spawn many observers.
21
+type EventStream struct {
22
+	ctx       context.Context
23
+	ctxCancel context.CancelFunc
24
+	chans     []chan mtglib.Event
25
+}
26
+
27
+// Send starts delivering of the message to observer with respect to a
28
+// given context If context is closed, message could be not delivered.
29
+func (e EventStream) Send(ctx context.Context, evt mtglib.Event) {
30
+	var chanNo uint32
31
+
32
+	if streamID := evt.StreamID(); streamID != "" {
33
+		chanNo = xxhash.ChecksumString32(streamID)
34
+	} else {
35
+		chanNo = rand.Uint32()
36
+	}
37
+
38
+	select {
39
+	case <-ctx.Done():
40
+	case <-e.ctx.Done():
41
+	case e.chans[int(chanNo)%len(e.chans)] <- evt:
42
+	}
43
+}
44
+
45
+// Shutdown stops an event stream pipeline.
46
+func (e EventStream) Shutdown() {
47
+	e.ctxCancel()
48
+}
49
+
50
+// NewEventStream builds a new default event stream.
51
+//
52
+// If you give an empty array of observers, then NoopObserver is going
53
+// to be used. If you give many observers, then they will process a
54
+// message concurrently.
55
+func NewEventStream(observerFactories []ObserverFactory) EventStream {
56
+	if len(observerFactories) == 0 {
57
+		observerFactories = append(observerFactories, NewNoopObserver)
58
+	}
59
+
60
+	ctx, cancel := context.WithCancel(context.Background())
61
+	rv := EventStream{
62
+		ctx:       ctx,
63
+		ctxCancel: cancel,
64
+		chans:     make([]chan mtglib.Event, runtime.NumCPU()),
65
+	}
66
+
67
+	for i := 0; i < runtime.NumCPU(); i++ {
68
+		rv.chans[i] = make(chan mtglib.Event, 1)
69
+
70
+		if len(observerFactories) == 1 {
71
+			go eventStreamProcessor(ctx, rv.chans[i], observerFactories[0]())
72
+		} else {
73
+			go eventStreamProcessor(ctx, rv.chans[i], newMultiObserver(observerFactories))
74
+		}
75
+	}
76
+
77
+	return rv
78
+}
79
+
80
+func eventStreamProcessor(ctx context.Context, eventChan <-chan mtglib.Event, observer Observer) { // nolint: cyclop
81
+	defer observer.Shutdown()
82
+
83
+	for {
84
+		select {
85
+		case <-ctx.Done():
86
+			return
87
+		case evt := <-eventChan:
88
+			switch typedEvt := evt.(type) {
89
+			case mtglib.EventTraffic:
90
+				observer.EventTraffic(typedEvt)
91
+			case mtglib.EventStart:
92
+				observer.EventStart(typedEvt)
93
+			case mtglib.EventFinish:
94
+				observer.EventFinish(typedEvt)
95
+			case mtglib.EventConnectedToDC:
96
+				observer.EventConnectedToDC(typedEvt)
97
+			case mtglib.EventDomainFronting:
98
+				observer.EventDomainFronting(typedEvt)
99
+			case mtglib.EventIPBlocklisted:
100
+				observer.EventIPBlocklisted(typedEvt)
101
+			case mtglib.EventConcurrencyLimited:
102
+				observer.EventConcurrencyLimited(typedEvt)
103
+			case mtglib.EventReplayAttack:
104
+				observer.EventReplayAttack(typedEvt)
105
+			}
106
+		}
107
+	}
108
+}

+ 220
- 0
events/event_stream_test.go Прегледај датотеку

@@ -0,0 +1,220 @@
1
+package events_test
2
+
3
+import (
4
+	"context"
5
+	"net"
6
+	"testing"
7
+	"time"
8
+
9
+	"github.com/9seconds/mtg/v2/events"
10
+	"github.com/9seconds/mtg/v2/mtglib"
11
+	"github.com/stretchr/testify/mock"
12
+	"github.com/stretchr/testify/suite"
13
+)
14
+
15
+type EventStreamTestSuite struct {
16
+	suite.Suite
17
+
18
+	ctx           context.Context
19
+	ctxCancel     context.CancelFunc
20
+	observerMock1 *ObserverMock
21
+	observerMock2 *ObserverMock
22
+	stream        events.EventStream
23
+}
24
+
25
+func (suite *EventStreamTestSuite) SetupTest() {
26
+	suite.ctx, suite.ctxCancel = context.WithCancel(context.Background())
27
+
28
+	suite.observerMock1 = &ObserverMock{}
29
+	suite.observerMock2 = &ObserverMock{}
30
+
31
+	suite.observerMock1.On("Shutdown")
32
+	suite.observerMock2.On("Shutdown")
33
+
34
+	factories := make([]events.ObserverFactory, 2)
35
+	factories[0] = func() events.Observer { return suite.observerMock1 }
36
+	factories[1] = func() events.Observer { return suite.observerMock2 }
37
+
38
+	suite.stream = events.NewEventStream(factories)
39
+}
40
+
41
+func (suite *EventStreamTestSuite) TestEventStart() {
42
+	evt := mtglib.NewEventStart("connID", net.ParseIP("10.0.0.1"))
43
+
44
+	for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
45
+		v.
46
+			On("EventStart", mock.Anything).
47
+			Once().
48
+			Run(func(args mock.Arguments) {
49
+				caught, ok := args.Get(0).(mtglib.EventStart)
50
+
51
+				suite.True(ok)
52
+				suite.Equal(evt.RemoteIP.String(), caught.RemoteIP.String())
53
+				suite.Equal(evt.StreamID(), caught.StreamID())
54
+				suite.Equal(evt.Timestamp(), caught.Timestamp())
55
+			})
56
+	}
57
+
58
+	suite.stream.Send(suite.ctx, evt)
59
+	time.Sleep(100 * time.Millisecond)
60
+}
61
+
62
+func (suite *EventStreamTestSuite) TestEventConnectedToDC() {
63
+	evt := mtglib.NewEventConnectedToDC("connID", net.ParseIP("10.0.0.1"), 3)
64
+
65
+	for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
66
+		v.
67
+			On("EventConnectedToDC", mock.Anything).
68
+			Once().
69
+			Run(func(args mock.Arguments) {
70
+				caught, ok := args.Get(0).(mtglib.EventConnectedToDC)
71
+
72
+				suite.True(ok)
73
+				suite.Equal(evt.RemoteIP.String(), caught.RemoteIP.String())
74
+				suite.Equal(evt.StreamID(), caught.StreamID())
75
+				suite.Equal(evt.DC, caught.DC)
76
+				suite.Equal(evt.Timestamp(), caught.Timestamp())
77
+			})
78
+	}
79
+
80
+	suite.stream.Send(suite.ctx, evt)
81
+	time.Sleep(100 * time.Millisecond)
82
+}
83
+
84
+func (suite *EventStreamTestSuite) TestEventDomainFronting() {
85
+	evt := mtglib.NewEventDomainFronting("connID")
86
+
87
+	for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
88
+		v.
89
+			On("EventDomainFronting", mock.Anything).
90
+			Once().
91
+			Run(func(args mock.Arguments) {
92
+				caught, ok := args.Get(0).(mtglib.EventDomainFronting)
93
+
94
+				suite.True(ok)
95
+				suite.Equal(evt.StreamID(), caught.StreamID())
96
+				suite.Equal(evt.Timestamp(), caught.Timestamp())
97
+			})
98
+	}
99
+
100
+	suite.stream.Send(suite.ctx, evt)
101
+	time.Sleep(100 * time.Millisecond)
102
+}
103
+
104
+func (suite *EventStreamTestSuite) TestEventTraffic() {
105
+	evt := mtglib.NewEventTraffic("connID", 1024, true)
106
+
107
+	for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
108
+		v.
109
+			On("EventTraffic", mock.Anything).
110
+			Once().
111
+			Run(func(args mock.Arguments) {
112
+				caught, ok := args.Get(0).(mtglib.EventTraffic)
113
+
114
+				suite.True(ok)
115
+				suite.Equal(evt.StreamID(), caught.StreamID())
116
+				suite.Equal(evt.Timestamp(), caught.Timestamp())
117
+				suite.Equal(evt.Traffic, caught.Traffic)
118
+				suite.Equal(evt.IsRead, caught.IsRead)
119
+			})
120
+	}
121
+
122
+	suite.stream.Send(suite.ctx, evt)
123
+	time.Sleep(100 * time.Millisecond)
124
+}
125
+
126
+func (suite *EventStreamTestSuite) TestEventFinish() {
127
+	evt := mtglib.NewEventFinish("connID")
128
+
129
+	for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
130
+		v.
131
+			On("EventFinish", mock.Anything).
132
+			Once().
133
+			Run(func(args mock.Arguments) {
134
+				caught, ok := args.Get(0).(mtglib.EventFinish)
135
+
136
+				suite.True(ok)
137
+				suite.Equal(evt.StreamID(), caught.StreamID())
138
+				suite.Equal(evt.Timestamp(), caught.Timestamp())
139
+			})
140
+	}
141
+
142
+	suite.stream.Send(suite.ctx, evt)
143
+	time.Sleep(100 * time.Millisecond)
144
+}
145
+
146
+func (suite *EventStreamTestSuite) TestEventConcurrencyLimited() {
147
+	evt := mtglib.NewEventConcurrencyLimited()
148
+
149
+	for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
150
+		v.
151
+			On("EventConcurrencyLimited", mock.Anything).
152
+			Once().
153
+			Run(func(args mock.Arguments) {
154
+				caught, ok := args.Get(0).(mtglib.EventConcurrencyLimited)
155
+
156
+				suite.True(ok)
157
+				suite.Equal(evt.Timestamp(), caught.Timestamp())
158
+				suite.Empty(evt.StreamID())
159
+			})
160
+	}
161
+
162
+	suite.stream.Send(suite.ctx, evt)
163
+	time.Sleep(100 * time.Millisecond)
164
+}
165
+
166
+func (suite *EventStreamTestSuite) TestEventIPBlocklisted() {
167
+	evt := mtglib.NewEventIPBlocklisted(net.ParseIP("10.0.0.10"))
168
+
169
+	for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
170
+		v.
171
+			On("EventIPBlocklisted", mock.Anything).
172
+			Once().
173
+			Run(func(args mock.Arguments) {
174
+				caught, ok := args.Get(0).(mtglib.EventIPBlocklisted)
175
+
176
+				suite.True(ok)
177
+				suite.Equal(evt.StreamID(), caught.StreamID())
178
+				suite.Equal(evt.Timestamp(), caught.Timestamp())
179
+				suite.Equal(evt.RemoteIP.String(), caught.RemoteIP.String())
180
+			})
181
+	}
182
+
183
+	suite.stream.Send(suite.ctx, evt)
184
+	time.Sleep(100 * time.Millisecond)
185
+}
186
+
187
+func (suite *EventStreamTestSuite) TestEventReplayAttack() {
188
+	evt := mtglib.NewEventReplayAttack("CONNID")
189
+
190
+	for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
191
+		v.
192
+			On("EventReplayAttack", mock.Anything).
193
+			Once().
194
+			Run(func(args mock.Arguments) {
195
+				caught, ok := args.Get(0).(mtglib.EventReplayAttack)
196
+
197
+				suite.True(ok)
198
+				suite.Equal(evt.StreamID(), caught.StreamID())
199
+				suite.Equal(evt.Timestamp(), caught.Timestamp())
200
+			})
201
+	}
202
+
203
+	suite.stream.Send(suite.ctx, evt)
204
+	time.Sleep(100 * time.Millisecond)
205
+}
206
+
207
+func (suite *EventStreamTestSuite) TearDownTest() {
208
+	suite.stream.Shutdown()
209
+	suite.ctxCancel()
210
+
211
+	time.Sleep(100 * time.Millisecond)
212
+
213
+	suite.observerMock1.AssertExpectations(suite.T())
214
+	suite.observerMock2.AssertExpectations(suite.T())
215
+}
216
+
217
+func TestEventStream(t *testing.T) {
218
+	t.Parallel()
219
+	suite.Run(t, &EventStreamTestSuite{})
220
+}

+ 69
- 0
events/init.go Прегледај датотеку

@@ -0,0 +1,69 @@
1
+// Events has a default implementations of EventStream for mtglib.
2
+//
3
+// Please see documentation for mtglib.EventStream interface to get an
4
+// idea of such an abstraction. This package has implementations for the
5
+// default event stream.
6
+//
7
+// Default event stream has a list of its own concepts. First, all it
8
+// does is a routing of messages to known observers. It takes an event,
9
+// defines its type and pass this message to a method of the observer.
10
+//
11
+// There might be many observers, but default event stream has a
12
+// guarantee though. It uses StreamID as a sharding key and guarantees
13
+// that a message with the same StreamID will be devlivered to the same
14
+// observer instance. So, each producer is guarateed to get all relevant
15
+// messages related to the same session. It is not possible that it will
16
+// get EventFinish if it has not seen EventStart for that session yet.
17
+package events
18
+
19
+import "github.com/9seconds/mtg/v2/mtglib"
20
+
21
+// Observer is an instance that listens for the incoming events.
22
+//
23
+// As it is said in the package description, the default event stream
24
+// guarantees that all events with the same StreamID are going to be
25
+// routed to the same instance of the observer. So, there is no need
26
+// to synchronize information about streams between many observers
27
+// instances, they can have their local storage.
28
+type Observer interface {
29
+	// EventStart reacts on incoming mtglib.EventStart event.
30
+	EventStart(mtglib.EventStart)
31
+
32
+	// EventFinish reacts on incoming mtglib.EventFinish event.
33
+	EventFinish(mtglib.EventFinish)
34
+
35
+	// EventConnectedToDC reacts on incoming mtglib.EventConnectedToDC
36
+	// event.
37
+	EventConnectedToDC(mtglib.EventConnectedToDC)
38
+
39
+	// EventDomainFronting reacts on incoming mtglib.EventDomainFronting
40
+	// event.
41
+	EventDomainFronting(mtglib.EventDomainFronting)
42
+
43
+	// EventTraffic reacts on incoming mtglib.EventTraffic event.
44
+	EventTraffic(mtglib.EventTraffic)
45
+
46
+	// EventConcurrencyLimited reacts on incoming
47
+	// mtglib.EventConcurrencyLimited event.
48
+	EventConcurrencyLimited(mtglib.EventConcurrencyLimited)
49
+
50
+	// EventIPBlocklisted reacts on incoming mtglib.EventIPBlocklisted event.
51
+	EventIPBlocklisted(mtglib.EventIPBlocklisted)
52
+
53
+	// EventReplayAttack reacts on incoming mtglib.EventReplayAttack event.
54
+	EventReplayAttack(mtglib.EventReplayAttack)
55
+
56
+	// Shutdown stop observer. Default event stream guarantees:
57
+	//   1. If shutdown is executed, it is executed only once
58
+	//   2. Observer won't receieve any new message after this
59
+	//      function call.
60
+	Shutdown()
61
+}
62
+
63
+// ObserverFactory creates a new instance of the observer.
64
+//
65
+// Default event stream creates a small set of goroutines to manage
66
+// incoming messages. Each message is routed to an appropriate observer
67
+// based on a sharding key, stream id. So, it is possible that an
68
+// instance of mtg will have many observer instances, not a single one.
69
+type ObserverFactory func() Observer

+ 46
- 0
events/init_test.go Прегледај датотеку

@@ -0,0 +1,46 @@
1
+package events_test
2
+
3
+import (
4
+	"github.com/9seconds/mtg/v2/mtglib"
5
+	"github.com/stretchr/testify/mock"
6
+)
7
+
8
+type ObserverMock struct {
9
+	mock.Mock
10
+}
11
+
12
+func (o *ObserverMock) EventStart(evt mtglib.EventStart) {
13
+	o.Called(evt)
14
+}
15
+
16
+func (o *ObserverMock) EventConnectedToDC(evt mtglib.EventConnectedToDC) {
17
+	o.Called(evt)
18
+}
19
+
20
+func (o *ObserverMock) EventDomainFronting(evt mtglib.EventDomainFronting) {
21
+	o.Called(evt)
22
+}
23
+
24
+func (o *ObserverMock) EventTraffic(evt mtglib.EventTraffic) {
25
+	o.Called(evt)
26
+}
27
+
28
+func (o *ObserverMock) EventFinish(evt mtglib.EventFinish) {
29
+	o.Called(evt)
30
+}
31
+
32
+func (o *ObserverMock) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimited) {
33
+	o.Called(evt)
34
+}
35
+
36
+func (o *ObserverMock) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
37
+	o.Called(evt)
38
+}
39
+
40
+func (o *ObserverMock) EventReplayAttack(evt mtglib.EventReplayAttack) {
41
+	o.Called(evt)
42
+}
43
+
44
+func (o *ObserverMock) Shutdown() {
45
+	o.Called()
46
+}

+ 149
- 0
events/multi_observer.go Прегледај датотеку

@@ -0,0 +1,149 @@
1
+package events
2
+
3
+import (
4
+	"sync"
5
+
6
+	"github.com/9seconds/mtg/v2/mtglib"
7
+)
8
+
9
+type multiObserver struct {
10
+	observers []Observer
11
+}
12
+
13
+func (m multiObserver) EventStart(evt mtglib.EventStart) {
14
+	wg := &sync.WaitGroup{}
15
+	wg.Add(len(m.observers))
16
+
17
+	for _, v := range m.observers {
18
+		go func(obs Observer) {
19
+			defer wg.Done()
20
+
21
+			obs.EventStart(evt)
22
+		}(v)
23
+	}
24
+
25
+	wg.Wait()
26
+}
27
+
28
+func (m multiObserver) EventConnectedToDC(evt mtglib.EventConnectedToDC) {
29
+	wg := &sync.WaitGroup{}
30
+	wg.Add(len(m.observers))
31
+
32
+	for _, v := range m.observers {
33
+		go func(obs Observer) {
34
+			defer wg.Done()
35
+
36
+			obs.EventConnectedToDC(evt)
37
+		}(v)
38
+	}
39
+
40
+	wg.Wait()
41
+}
42
+
43
+func (m multiObserver) EventDomainFronting(evt mtglib.EventDomainFronting) {
44
+	wg := &sync.WaitGroup{}
45
+	wg.Add(len(m.observers))
46
+
47
+	for _, v := range m.observers {
48
+		go func(obs Observer) {
49
+			defer wg.Done()
50
+
51
+			obs.EventDomainFronting(evt)
52
+		}(v)
53
+	}
54
+
55
+	wg.Wait()
56
+}
57
+
58
+func (m multiObserver) EventTraffic(evt mtglib.EventTraffic) {
59
+	wg := &sync.WaitGroup{}
60
+	wg.Add(len(m.observers))
61
+
62
+	for _, v := range m.observers {
63
+		go func(obs Observer) {
64
+			defer wg.Done()
65
+
66
+			obs.EventTraffic(evt)
67
+		}(v)
68
+	}
69
+
70
+	wg.Wait()
71
+}
72
+
73
+func (m multiObserver) EventFinish(evt mtglib.EventFinish) {
74
+	wg := &sync.WaitGroup{}
75
+	wg.Add(len(m.observers))
76
+
77
+	for _, v := range m.observers {
78
+		go func(obs Observer) {
79
+			defer wg.Done()
80
+
81
+			obs.EventFinish(evt)
82
+		}(v)
83
+	}
84
+
85
+	wg.Wait()
86
+}
87
+
88
+func (m multiObserver) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimited) {
89
+	wg := &sync.WaitGroup{}
90
+	wg.Add(len(m.observers))
91
+
92
+	for _, v := range m.observers {
93
+		go func(obs Observer) {
94
+			defer wg.Done()
95
+
96
+			obs.EventConcurrencyLimited(evt)
97
+		}(v)
98
+	}
99
+
100
+	wg.Wait()
101
+}
102
+
103
+func (m multiObserver) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
104
+	wg := &sync.WaitGroup{}
105
+	wg.Add(len(m.observers))
106
+
107
+	for _, v := range m.observers {
108
+		go func(obs Observer) {
109
+			defer wg.Done()
110
+
111
+			obs.EventIPBlocklisted(evt)
112
+		}(v)
113
+	}
114
+
115
+	wg.Wait()
116
+}
117
+
118
+func (m multiObserver) EventReplayAttack(evt mtglib.EventReplayAttack) {
119
+	wg := &sync.WaitGroup{}
120
+	wg.Add(len(m.observers))
121
+
122
+	for _, v := range m.observers {
123
+		go func(obs Observer) {
124
+			defer wg.Done()
125
+
126
+			obs.EventReplayAttack(evt)
127
+		}(v)
128
+	}
129
+
130
+	wg.Wait()
131
+}
132
+
133
+func (m multiObserver) Shutdown() {
134
+	for _, v := range m.observers {
135
+		v.Shutdown()
136
+	}
137
+}
138
+
139
+func newMultiObserver(factories []ObserverFactory) Observer {
140
+	observers := make([]Observer, len(factories))
141
+
142
+	for i, v := range factories {
143
+		observers[i] = v()
144
+	}
145
+
146
+	return multiObserver{
147
+		observers: observers,
148
+	}
149
+}

+ 33
- 0
events/noop.go Прегледај датотеку

@@ -0,0 +1,33 @@
1
+package events
2
+
3
+import (
4
+	"context"
5
+
6
+	"github.com/9seconds/mtg/v2/mtglib"
7
+)
8
+
9
+type noop struct{}
10
+
11
+func (n noop) Send(ctx context.Context, evt mtglib.Event) {}
12
+
13
+// NewNoopStream creates a stream which discards each message.
14
+func NewNoopStream() mtglib.EventStream {
15
+	return noop{}
16
+}
17
+
18
+type noopObserver struct{}
19
+
20
+func (n noopObserver) EventStart(_ mtglib.EventStart)                           {}
21
+func (n noopObserver) EventConnectedToDC(_ mtglib.EventConnectedToDC)           {}
22
+func (n noopObserver) EventDomainFronting(_ mtglib.EventDomainFronting)         {}
23
+func (n noopObserver) EventTraffic(_ mtglib.EventTraffic)                       {}
24
+func (n noopObserver) EventFinish(_ mtglib.EventFinish)                         {}
25
+func (n noopObserver) EventConcurrencyLimited(_ mtglib.EventConcurrencyLimited) {}
26
+func (n noopObserver) EventIPBlocklisted(_ mtglib.EventIPBlocklisted)           {}
27
+func (n noopObserver) EventReplayAttack(_ mtglib.EventReplayAttack)             {}
28
+func (n noopObserver) Shutdown()                                                {}
29
+
30
+// NewNoopObserver creates an observer which discards each message.
31
+func NewNoopObserver() Observer {
32
+	return noopObserver{}
33
+}

+ 78
- 0
events/noop_test.go Прегледај датотеку

@@ -0,0 +1,78 @@
1
+package events_test
2
+
3
+import (
4
+	"context"
5
+	"net"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/events"
9
+	"github.com/9seconds/mtg/v2/mtglib"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type NoopTestSuite struct {
14
+	suite.Suite
15
+
16
+	testData map[string]mtglib.Event
17
+	ctx      context.Context
18
+}
19
+
20
+func (suite *NoopTestSuite) SetupSuite() {
21
+	suite.testData = map[string]mtglib.Event{
22
+		"start":               mtglib.NewEventStart("connID", net.ParseIP("127.0.0.1")),
23
+		"connected-to-dc":     mtglib.NewEventConnectedToDC("connID", net.ParseIP("127.1.0.1"), 2),
24
+		"domain-fronting":     mtglib.NewEventDomainFronting("connID"),
25
+		"traffic":             mtglib.NewEventTraffic("connID", 1000, true),
26
+		"finish":              mtglib.NewEventFinish("connID"),
27
+		"concurrency-limited": mtglib.NewEventConcurrencyLimited(),
28
+		"ip-blacklisted":      mtglib.NewEventIPBlocklisted(net.ParseIP("10.0.0.10")),
29
+		"replay-attack":       mtglib.NewEventReplayAttack("connID"),
30
+	}
31
+	suite.ctx = context.Background()
32
+}
33
+
34
+func (suite *NoopTestSuite) TestStream() {
35
+	stream := events.NewNoopStream()
36
+
37
+	for name, v := range suite.testData {
38
+		value := v
39
+
40
+		suite.T().Run(name, func(t *testing.T) {
41
+			stream.Send(suite.ctx, value)
42
+		})
43
+	}
44
+}
45
+
46
+func (suite *NoopTestSuite) TestObserver() {
47
+	observer := events.NewNoopObserver()
48
+
49
+	for name, v := range suite.testData {
50
+		value := v
51
+
52
+		suite.T().Run(name, func(t *testing.T) {
53
+			switch typedEvt := value.(type) {
54
+			case mtglib.EventStart:
55
+				observer.EventStart(typedEvt)
56
+			case mtglib.EventConnectedToDC:
57
+				observer.EventConnectedToDC(typedEvt)
58
+			case mtglib.EventDomainFronting:
59
+				observer.EventDomainFronting(typedEvt)
60
+			case mtglib.EventFinish:
61
+				observer.EventFinish(typedEvt)
62
+			case mtglib.EventConcurrencyLimited:
63
+				observer.EventConcurrencyLimited(typedEvt)
64
+			case mtglib.EventIPBlocklisted:
65
+				observer.EventIPBlocklisted(typedEvt)
66
+			case mtglib.EventReplayAttack:
67
+				observer.EventReplayAttack(typedEvt)
68
+			}
69
+		})
70
+	}
71
+
72
+	observer.Shutdown()
73
+}
74
+
75
+func TestNoop(t *testing.T) {
76
+	t.Parallel()
77
+	suite.Run(t, &NoopTestSuite{})
78
+}

+ 186
- 0
example.config.toml Прегледај датотеку

@@ -0,0 +1,186 @@
1
+# This is an example of the configuration file for mtg. You actually can
2
+# run mtg with it. It starts a proxy on all interfaces with a secret
3
+# ee367a189aee18fa31c190054efd4a8e9573746f726167652e676f6f676c65617069732e636f6d
4
+#
5
+# It has all possible options with default values. So, a real world
6
+# configuration file should contain only those options you are going to
7
+# use. You do not need to enumerate all of them. In other words, each
8
+# option here has a default value. If you comment a key-value pair, it
9
+# should not make any effect.
10
+#
11
+# stats is the only exception.
12
+
13
+# Debug starts application in debug mode. It starts to be quite verbose
14
+# in output. Actually, the idea is that you run it in debug mode only if
15
+# you have any issue.
16
+debug = true
17
+
18
+# A secret. Please remember that mtg supports only FakeTLS mode, legacy
19
+# simple and secured mode are prohibited. For you it means that secret
20
+# should either be base64-encoded or starts with ee.
21
+secret = "ee367a189aee18fa31c190054efd4a8e9573746f726167652e676f6f676c65617069732e636f6d"
22
+
23
+# Host:port pair to run proxy on.
24
+bind-to = "0.0.0.0:3128"
25
+
26
+# Defines how many concurrent connections are allowed to this proxy.
27
+# All other incoming connections are going to be dropped.
28
+concurrency = 8192
29
+
30
+# A size of user-space buffer for TCP to use. Since we do 2 connections,
31
+# then we have tcp-buffer * (4 + 2) per each connection: read/write for
32
+# each connection + 2 copy buffers to pump the data between sockets.
33
+tcp-buffer = "4kb"
34
+
35
+# Sometimes you want to enforce mtg to use some types of
36
+# IP connectivity to Telegram. We have 4 modes:
37
+#   - prefer-ipv6:
38
+#     We can use both ipv4 and ipv6 but ipv6 has a preference
39
+#   - prefer-ipv4:
40
+#     We can use both ipv4 and ipv6 but ipv4 has a preference
41
+#   - only-ipv6:
42
+#     Only ipv6 connectivity is used
43
+#   - only-ipv4:
44
+#     Only ipv4 connectivity is used
45
+prefer-ip = "prefer-ipv6"
46
+
47
+# FakeTLS uses domain fronting protection. So it needs to know a port to
48
+# access.
49
+domain-fronting-port = 443
50
+
51
+# FakeTLS can compare timestamps to prevent probes. Each message has
52
+# encrypted timestamp. So, mtg can compare this timestamp and decide if
53
+# we need to proceed with connection or not.
54
+#
55
+# Sometimes time can be skewed so we accept all messages within a
56
+# time range of this parameter.
57
+tolerate-time-skewness = "5s"
58
+
59
+# network defines different network-related settings
60
+[network]
61
+# please be aware that mtg needs to do some external requests. For
62
+# example, if you do not pass public ips, it will request your public ip
63
+# address from some external service.
64
+#
65
+# As for 2.0, if you set a public-ip on your own, mtg won't issue any
66
+# network requests except of those required for Telegram.
67
+#
68
+# so, in order of doing them, it needs to do DNS lookup. mtg ignores DNS
69
+# resolver of the operating system and uses DOH instead. This is a host
70
+# it has to access.
71
+#
72
+# By default we use Quad9.
73
+doh-ip = "9.9.9.9"
74
+
75
+# mtg can work via proxies (for now, we support only socks5). Proxy
76
+# configuration is done via list. So, you can specify many proxies
77
+# there.
78
+#
79
+# Actually, if you supply an empty list, then no proxies are going to be
80
+# used. If you supply a single proxy, then mtg will use it exclusively.
81
+# If you supply >= 2, then mtg will load balance between them.
82
+#
83
+# If you add an empty string here, this is an equivalent of 'plain network',
84
+# with no proxy usage.
85
+#
86
+# Proxy configuration is done via ordinary URI schema:
87
+#
88
+#     socks5://user:password@host:port?open_threshold=5&half_open_timeout=1m&reset_failures_timeout=10s
89
+#
90
+# Only socks5 proxy is used. user/password is optional. As you can
91
+# see, you can specify some parameters in GET query. These parameters
92
+# configure circuit breaker.
93
+#
94
+# open_threshold means a number of errors which should happen so we stop
95
+# use a proxy.
96
+#
97
+# half_open_timeout means a time period (in Golang duration notation)
98
+# after which we can retry with this proxy
99
+#
100
+# reset_failures_timeout means a time period when we flush out errors
101
+# when circuit breaker in closed state.
102
+#
103
+# Please see https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker
104
+# on details about circuit breakers.
105
+proxies = [
106
+    # "socks5://user:password@host:port?open_threshold=5&half_open_timeout=1m&reset_failures_timeout=10s"
107
+]
108
+
109
+# network timeouts define different settings for timeouts. tcp timeout
110
+# define a global timeout on establishing of network connections. idle
111
+# means a timeout on pumping data between sockset when nothing is
112
+# happening.
113
+#
114
+# please be noticed that handshakes have no timeouts intentionally. You can
115
+# find a reasoning here:
116
+# https://www.ndss-symposium.org/wp-content/uploads/2020/02/23087-paper.pdf
117
+[network.timeout]
118
+tcp = "5s"
119
+http = "10s"
120
+idle = "1m"
121
+
122
+# Some countries do active probing on Telegram connections. This technique
123
+# allows to protect from such effort.
124
+#
125
+# mtg has a cache of some connection fingerprints. Actually, first bytes
126
+# of each connection. So, it stores them in some in-memory LRU+TTL cache.
127
+# You can configure this cache here.
128
+[defense.anti-replay]
129
+# You can enable/disable this feature.
130
+enabled = true
131
+# max size of such a cache. Please be aware that this number is
132
+# approximate we try hard to store data quite dense but it is possible
133
+# that we can go over this limit for 10-20% under some conditions and
134
+# architectures.
135
+max-size = "1mib"
136
+# we use stable bloom filters for anti-replay cache. This helps
137
+# to maintain a desired error ratio.
138
+error-rate = 0.001
139
+
140
+# You can protect proxies by using different blocklists. If client has
141
+# ip from the given range, we do not try to do a proper handshake. We
142
+# actually route it to fronting domain. So, this client will never ever
143
+# have a chance to use mtg to access Telegram.
144
+#
145
+# Please remember that blocklists are initialized in async way. So,
146
+# when you start a proxy, blocklists are empty, they are populated and
147
+# processed in backgrounds. An error in any URL is ignored.
148
+[defense.blocklist]
149
+# You can enable/disable this feature.
150
+enabled = true
151
+# This is a limiter for concurrency. In order to protect website
152
+# from overloading, we download files in this number of threads.
153
+download-concurrency = 2
154
+# A list of URLs in FireHOL format (https://iplists.firehol.org/)
155
+# You can provider links here (starts with https:// or http://) or
156
+# path to a local file, but in this case it should be absolute.
157
+urls = [
158
+    # "https://iplists.firehol.org/files/firehol_level1.netset",
159
+    # "/local.file"
160
+]
161
+# How often do we need to update a blocklist set.
162
+update-each = "24h"
163
+
164
+# statsd statistics integration.
165
+[stats.statsd]
166
+# enabled/disabled
167
+enabled = false
168
+# host:port for UDP endpoint of statsd
169
+address = "127.0.0.1:8888"
170
+# prefix of metric for statsd
171
+metric-prefix = "mtg"
172
+# tag format to use
173
+# supported values are 'datadog', 'influxdb' and 'graphite'
174
+# default format is graphite.
175
+tag-format = "datadog"
176
+
177
+# prometheus metrics integration.
178
+[stats.prometheus]
179
+# enabled/disabled
180
+enabled = true
181
+# host:port where to start http server for endpoint
182
+bind-to = "127.0.0.1:3129"
183
+# prefix of http path
184
+http-path = "/"
185
+# prefix for metrics for prometheus
186
+metric-prefix = "mtg"

+ 0
- 122
faketls/client_protocol.go Прегледај датотеку

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

+ 0
- 73
faketls/cloak.go Прегледај датотеку

@@ -1,73 +0,0 @@
1
-package faketls
2
-
3
-import (
4
-	"context"
5
-	"io"
6
-	"sync"
7
-	"time"
8
-
9
-	"github.com/9seconds/mtg/wrappers/rwc"
10
-)
11
-
12
-const (
13
-	cloakLastActivityTimeout = 5 * time.Second
14
-	cloakMaxTimeout          = 30 * time.Second
15
-)
16
-
17
-func cloak(one, another io.ReadWriteCloser) {
18
-	defer func() {
19
-		one.Close()
20
-		another.Close()
21
-	}()
22
-
23
-	channelPing := make(chan struct{}, 1)
24
-	ctx, cancel := context.WithCancel(context.Background())
25
-	one = rwc.NewPing(ctx, one, channelPing)
26
-	another = rwc.NewPing(ctx, another, channelPing)
27
-	wg := &sync.WaitGroup{}
28
-
29
-	wg.Add(2)
30
-
31
-	go cloakPipe(one, another, wg)
32
-
33
-	go cloakPipe(another, one, wg)
34
-
35
-	go func() {
36
-		wg.Wait()
37
-		cancel()
38
-	}()
39
-
40
-	go func() {
41
-		lastActivityTimer := time.NewTimer(cloakLastActivityTimeout)
42
-		defer lastActivityTimer.Stop()
43
-
44
-		maxTimer := time.NewTimer(cloakMaxTimeout)
45
-		defer maxTimer.Stop()
46
-
47
-		for {
48
-			select {
49
-			case <-channelPing:
50
-				lastActivityTimer.Stop()
51
-				lastActivityTimer = time.NewTimer(cloakLastActivityTimeout)
52
-			case <-ctx.Done():
53
-				return
54
-			case <-lastActivityTimer.C:
55
-				cancel()
56
-
57
-				return
58
-			case <-maxTimer.C:
59
-				cancel()
60
-
61
-				return
62
-			}
63
-		}
64
-	}()
65
-
66
-	<-ctx.Done()
67
-}
68
-
69
-func cloakPipe(one io.Writer, another io.Reader, wg *sync.WaitGroup) {
70
-	defer wg.Done()
71
-
72
-	io.Copy(one, another) // nolint: errcheck
73
-}

+ 0
- 30
faketls/consts.go Прегледај датотеку

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

+ 25
- 19
go.mod Прегледај датотеку

@@ -1,25 +1,31 @@
1
-module github.com/9seconds/mtg
1
+module github.com/9seconds/mtg/v2
2 2
 
3
-go 1.13
3
+go 1.16
4 4
 
5 5
 require (
6
-	github.com/VictoriaMetrics/fastcache v1.5.7
6
+	github.com/OneOfOne/xxhash v1.2.8
7
+	github.com/alecthomas/kong v0.2.16
7 8
 	github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15
8
-	github.com/beevik/ntp v0.3.0
9
-	github.com/golang/snappy v0.0.3 // indirect
10
-	github.com/prometheus/client_golang v1.9.0
11
-	github.com/prometheus/common v0.18.0 // indirect
12
-	github.com/prometheus/procfs v0.6.0 // indirect
9
+	github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
10
+	github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6
11
+	github.com/d4l3k/messagediff v1.2.1 // indirect
12
+	github.com/golang/protobuf v1.5.2 // indirect
13
+	github.com/gotd/td v0.34.0
14
+	github.com/jarcoal/httpmock v1.0.8
15
+	github.com/kentik/patricia v0.0.0-20201202224819-f9447a6e25f1
16
+	github.com/libp2p/go-reuseport v0.0.2
17
+	github.com/mccutchen/go-httpbin v1.1.1
18
+	github.com/panjf2000/ants/v2 v2.4.4
19
+	github.com/pelletier/go-toml v1.9.0
20
+	github.com/prometheus/client_golang v1.10.0
21
+	github.com/prometheus/common v0.23.0 // indirect
22
+	github.com/rs/zerolog v1.21.0
13 23
 	github.com/smira/go-statsd v1.3.2
14
-	go.uber.org/multierr v1.6.0 // indirect
15
-	go.uber.org/zap v1.16.0
16
-	golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
17
-	golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
18
-	golang.org/x/mod v0.4.1 // indirect
19
-	golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
20
-	golang.org/x/sys v0.0.0-20210303074136-134d130e1a04
21
-	golang.org/x/tools v0.1.0 // indirect
22
-	google.golang.org/protobuf v1.25.0 // indirect
23
-	gopkg.in/alecthomas/kingpin.v2 v2.2.6
24
-	honnef.co/go/tools v0.0.1-2020.1.3 // indirect
24
+	github.com/stretchr/objx v0.3.0 // indirect
25
+	github.com/stretchr/testify v1.7.0
26
+	github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43
27
+	github.com/xeipuuv/gojsonschema v1.2.0
28
+	golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e
29
+	golang.org/x/net v0.0.0-20210505024714-0287a6fb4125
30
+	golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6
25 31
 )

+ 107
- 43
go.sum Прегледај датотеку

@@ -3,40 +3,47 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
3 3
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
4 4
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
5 5
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
6
+github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
7
+github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
8
+github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
6 9
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
7 10
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
8
-github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw=
9
-github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8=
10 11
 github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
11 12
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
13
+github.com/alecthomas/kong v0.2.16 h1:F232CiYSn54Tnl1sJGTeHmx4vJDNLVP2b9yCVMOQwHQ=
14
+github.com/alecthomas/kong v0.2.16/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
12 15
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
13
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
14 16
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
15 17
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
16 18
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
17 19
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
18 20
 github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4=
19 21
 github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
20
-github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
21
-github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
22
+github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
23
+github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
22 24
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
23 25
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
24 26
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
25 27
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
26 28
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
29
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
30
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
27 31
 github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
28 32
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
29 33
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
30 34
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
31
-github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
32
-github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
35
+github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
36
+github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
33 37
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
34 38
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
35 39
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
36 40
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
37 41
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
38 42
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
43
+github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
39 44
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
45
+github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
46
+github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
40 47
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
41 48
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
42 49
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -46,9 +53,12 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z
46 53
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
47 54
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
48 55
 github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
56
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
49 57
 github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
50 58
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
51 59
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
60
+github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
61
+github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
52 62
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
53 63
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
54 64
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -72,6 +82,7 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO
72 82
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
73 83
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
74 84
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
85
+github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
75 86
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
76 87
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
77 88
 github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
@@ -90,23 +101,21 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
90 101
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
91 102
 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
92 103
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
93
-github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
94 104
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
95
-github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
96 105
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
106
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
107
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
108
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
97 109
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
98
-github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
99
-github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
100
-github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
101 110
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
102 111
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
103 112
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
104 113
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
105 114
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
106 115
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
107
-github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
108
-github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
109 116
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
117
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
118
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
110 119
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
111 120
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
112 121
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -115,6 +124,17 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
115 124
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
116 125
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
117 126
 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
127
+github.com/gotd/getdoc v0.6.2/go.mod h1:6Ul8cyaq+Aw0gVyuU+TKZPCmNjdlylzSIWVxjzI5t/U=
128
+github.com/gotd/ige v0.1.5 h1:qITql4hZpqPM/DSeO5IVlxzXJxrs4ZZiKRwPif2K76U=
129
+github.com/gotd/ige v0.1.5/go.mod h1:LbvqjUuGELVuHcKjfJZ2NPNzegICQU4eqrPAkXy0nTs=
130
+github.com/gotd/neo v0.1.2 h1:hayFfKNSH5RP/L+KKKnsKMdDNCdhSfzVuo5TF4wvpa8=
131
+github.com/gotd/neo v0.1.2/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ=
132
+github.com/gotd/td v0.34.0 h1:BLIdpv2sxbCuWI2fyXCECRQuLJdnYk3VMZDRKNog/E8=
133
+github.com/gotd/td v0.34.0/go.mod h1:4s/7cuEscdvZIecM/pVi5L3nTK+djSoVLAhgfk40OE4=
134
+github.com/gotd/tl v0.4.0/go.mod h1:CMIcjPWFS4qxxJ+1Ce7U/ilbtPrkoVo/t8uhN5Y/D7c=
135
+github.com/gotd/xor v0.1.0/go.mod h1:ZTmdgqf6SOHder8/MFp9CNkXIadzID5lIiaZxRZICH0=
136
+github.com/gotd/xor v0.1.1 h1:LSPEeuf7noTo4fi4PrEsAaWXOSwjsY2e+IINPiR+c7s=
137
+github.com/gotd/xor v0.1.1/go.mod h1:ZTmdgqf6SOHder8/MFp9CNkXIadzID5lIiaZxRZICH0=
118 138
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
119 139
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
120 140
 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
@@ -142,6 +162,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
142 162
 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
143 163
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
144 164
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
165
+github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k=
166
+github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
145 167
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
146 168
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
147 169
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -152,6 +174,10 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/
152 174
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
153 175
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
154 176
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
177
+github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
178
+github.com/k0kubun/pp v2.4.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
179
+github.com/kentik/patricia v0.0.0-20201202224819-f9447a6e25f1 h1:D7qhJP3R49ZjUzpzKQ6B2H3lgejPs6DTO5gRomhhOpE=
180
+github.com/kentik/patricia v0.0.0-20201202224819-f9447a6e25f1/go.mod h1:2OfLA+0esiUJpwMjrH39pEk79cb8MvGTBS9YlZpejJ4=
155 181
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
156 182
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
157 183
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -162,15 +188,22 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
162 188
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
163 189
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
164 190
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
191
+github.com/libp2p/go-reuseport v0.0.2 h1:XSG94b1FJfGA01BUrT82imejHQyTxO4jEWqheyCXYvU=
192
+github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ=
165 193
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
166 194
 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
167 195
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
168 196
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
197
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
169 198
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
170 199
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
200
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
201
+github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
171 202
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
172 203
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
173 204
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
205
+github.com/mccutchen/go-httpbin v1.1.1 h1:aEws49HEJEyXHLDnshQVswfUlCVoS8g6h9YaDyaW7RE=
206
+github.com/mccutchen/go-httpbin v1.1.1/go.mod h1:fhpOYavp5g2K74XDl/ao2y4KvhqVtKlkg1e+0UaQv7I=
174 207
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
175 208
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
176 209
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -208,11 +241,16 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ
208 241
 github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
209 242
 github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
210 243
 github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
244
+github.com/panjf2000/ants/v2 v2.4.4 h1:kebk2KSiXHGeiYS6b+w2RqNN5+IKoqlBNd7cuC7MvQI=
245
+github.com/panjf2000/ants/v2 v2.4.4/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
211 246
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
212 247
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
248
+github.com/pelletier/go-toml v1.9.0 h1:NOd0BRdOKpPf0SxkL3HxSQOG7rNh+4kl6PHcBPFs7Q0=
249
+github.com/pelletier/go-toml v1.9.0/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
213 250
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
214 251
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
215 252
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
253
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
216 254
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
217 255
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
218 256
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -226,8 +264,8 @@ github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod
226 264
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
227 265
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
228 266
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
229
-github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU=
230
-github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
267
+github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg=
268
+github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
231 269
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
232 270
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
233 271
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -239,24 +277,31 @@ github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
239 277
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
240 278
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
241 279
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
242
-github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
243
-github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y=
244 280
 github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
281
+github.com/prometheus/common v0.23.0 h1:GXWvPYuTUenIa+BhOq/x+L/QZzCqASkVRny5KTlPDGM=
282
+github.com/prometheus/common v0.23.0/go.mod h1:H6QK/N6XVT42whUeIdI3dp36w49c+/iMDk7UAI2qm7Q=
245 283
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
246 284
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
247 285
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
248 286
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
249 287
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
250
-github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
251 288
 github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
252 289
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
290
+github.com/quasilyte/go-ruleguard/dsl v0.3.2/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
253 291
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
254 292
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
255 293
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
294
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
295
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
296
+github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM=
297
+github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
256 298
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
257 299
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
258 300
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
259 301
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
302
+github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
303
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
304
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
260 305
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
261 306
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
262 307
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -274,14 +319,29 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3
274 319
 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
275 320
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
276 321
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
322
+github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
323
+github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
324
+github.com/stretchr/testify v1.1.5-0.20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
277 325
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
278 326
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
279
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
280 327
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
328
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
329
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
330
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
331
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
281 332
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
333
+github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43 h1:QEePdg0ty2r0t1+qwfZmQ4OOl/MB2UXIeJSpIZv56lg=
334
+github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43/go.mod h1:OYRfF6eb5wY9VRFkXJH8FFBi3plw2v+giaIu7P054pM=
282 335
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
283 336
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
337
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
338
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
339
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
340
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
341
+github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
342
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
284 343
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
344
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
285 345
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
286 346
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
287 347
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
@@ -298,6 +358,7 @@ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+
298 358
 go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
299 359
 go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
300 360
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
361
+go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
301 362
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
302 363
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
303 364
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
@@ -310,21 +371,20 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
310 371
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
311 372
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
312 373
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
313
-golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
314
-golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
374
+golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI=
375
+golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
315 376
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
316 377
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
317 378
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
318 379
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
319 380
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
381
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
320 382
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
321
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
322
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
323 383
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
324 384
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
385
+golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
325 386
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
326
-golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
327
-golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
387
+golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
328 388
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
329 389
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
330 390
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -341,10 +401,12 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
341 401
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
342 402
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
343 403
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
404
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
344 405
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
345 406
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
346
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
347 407
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
408
+golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 h1:Ugb8sMTWuWRC3+sz5WeN/4kejDx9BvIwnPUiJBjJE+8=
409
+golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
348 410
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
349 411
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
350 412
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -353,7 +415,9 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
353 415
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
354 416
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
355 417
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
418
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
356 419
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
420
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
357 421
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
358 422
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
359 423
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -364,12 +428,14 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
364 428
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
365 429
 golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
366 430
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
431
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
432
+golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
367 433
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
368 434
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
369 435
 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
370 436
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
437
+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
371 438
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
372
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
373 439
 golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
374 440
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
375 441
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -377,16 +443,17 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
377 443
 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
378 444
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
379 445
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
380
-golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
381 446
 golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
382 447
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
383
-golang.org/x/sys v0.0.0-20210303074136-134d130e1a04 h1:cEhElsAv9LUt9ZUUocxzWe05oFLVd+AA2nstydTeI8g=
384
-golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
385
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
448
+golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
449
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
450
+golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6 h1:cdsMqa2nXzqlgs183pHxtvoVwU7CyzaCTAUOg94af4c=
451
+golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
386 452
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
387 453
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
388 454
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
389 455
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
456
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
390 457
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
391 458
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
392 459
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -402,9 +469,8 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw
402 469
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
403 470
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
404 471
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
405
-golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
406 472
 golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
407
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
473
+golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
408 474
 golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
409 475
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
410 476
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -421,7 +487,6 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn
421 487
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
422 488
 google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
423 489
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
424
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
425 490
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
426 491
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
427 492
 google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@@ -431,18 +496,15 @@ google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
431 496
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
432 497
 google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
433 498
 google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
434
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
435 499
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
436 500
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
437 501
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
438 502
 google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
439 503
 google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
440
-google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
441 504
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
442
-google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
443
-google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
444
-google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
445
-gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
505
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
506
+google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
507
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
446 508
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
447 509
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
448 510
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -460,13 +522,15 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
460 522
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
461 523
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
462 524
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
525
+gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
463 526
 gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
464 527
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
528
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
529
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
465 530
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
466 531
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
467 532
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
533
+honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
468 534
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
469
-honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
470
-honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
471 535
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
472 536
 sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

+ 0
- 172
hub/connection.go Прегледај датотеку

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

+ 0
- 70
hub/connection_list.go Прегледај датотеку

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

+ 0
- 40
hub/hub.go Прегледај датотеку

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

+ 0
- 24
hub/init.go Прегледај датотеку

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

+ 0
- 7
hub/interface.go Прегледај датотеку

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

+ 0
- 90
hub/mux.go Прегледај датотеку

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

+ 0
- 79
hub/proxy_conn.go Прегледај датотеку

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

+ 192
- 0
internal/cli/access.go Прегледај датотеку

@@ -0,0 +1,192 @@
1
+package cli
2
+
3
+import (
4
+	"context"
5
+	"encoding/json"
6
+	"fmt"
7
+	"io"
8
+	"net"
9
+	"net/http"
10
+	"net/url"
11
+	"os"
12
+	"strconv"
13
+	"strings"
14
+	"sync"
15
+)
16
+
17
+type accessResponse struct {
18
+	IPv4   *accessResponseURLs `json:"ipv4,omitempty"`
19
+	IPv6   *accessResponseURLs `json:"ipv6,omitempty"`
20
+	Secret struct {
21
+		Hex    string `json:"hex"`
22
+		Base64 string `json:"base64"`
23
+	} `json:"secret"`
24
+}
25
+
26
+type accessResponseURLs struct {
27
+	IP        net.IP `json:"ip"`
28
+	Port      uint   `json:"port"`
29
+	TgURL     string `json:"tg_url"`
30
+	TgQrCode  string `json:"tg_qrcode"`
31
+	TmeURL    string `json:"tme_url"`
32
+	TmeQrCode string `json:"tme_qrcode"`
33
+}
34
+
35
+type Access struct {
36
+	base
37
+
38
+	PublicIPv4 net.IP `kong:"help='Public IPv4 address for proxy. By default it is resolved via remote website',name='ipv4',short='i'"`   // nolint: lll
39
+	PublicIPv6 net.IP `kong:"help='Public IPv6 address for proxy. By default it is resolved via remote website',name='ipv6',short='I'"`   // nolint: lll
40
+	Port       uint   `kong:"help='Port number. Default port is taken from configuration file, bind-to parameter',type:'uint',short='p'"` // nolint: lll
41
+	Hex        bool   `kong:"help='Print secret in hex encoding.',short='x'"`
42
+}
43
+
44
+func (c *Access) Run(cli *CLI, version string) error {
45
+	if err := c.ReadConfig(version); err != nil {
46
+		return fmt.Errorf("cannot init config: %w", err)
47
+	}
48
+
49
+	return c.Execute(cli)
50
+}
51
+
52
+func (c *Access) Execute(cli *CLI) error {
53
+	resp := &accessResponse{}
54
+	resp.Secret.Base64 = c.Config.Secret.Base64()
55
+	resp.Secret.Hex = c.Config.Secret.Hex()
56
+
57
+	wg := &sync.WaitGroup{}
58
+	wg.Add(2) // nolint: gomnd
59
+
60
+	go func() {
61
+		defer wg.Done()
62
+
63
+		ip := cli.Access.PublicIPv4
64
+		if ip == nil {
65
+			ip = c.getIP("tcp4")
66
+		}
67
+
68
+		if ip != nil {
69
+			ip = ip.To4()
70
+		}
71
+
72
+		resp.IPv4 = c.makeURLs(ip, cli)
73
+	}()
74
+
75
+	go func() {
76
+		defer wg.Done()
77
+
78
+		ip := cli.Access.PublicIPv6
79
+		if ip == nil {
80
+			ip = c.getIP("tcp6")
81
+		}
82
+
83
+		if ip != nil {
84
+			ip = ip.To16()
85
+		}
86
+
87
+		resp.IPv6 = c.makeURLs(ip, cli)
88
+	}()
89
+
90
+	wg.Wait()
91
+
92
+	encoder := json.NewEncoder(os.Stdout)
93
+	encoder.SetEscapeHTML(false)
94
+	encoder.SetIndent("", "  ")
95
+
96
+	if err := encoder.Encode(resp); err != nil {
97
+		return fmt.Errorf("cannot dump access json: %w", err)
98
+	}
99
+
100
+	return nil
101
+}
102
+
103
+func (c *Access) getIP(protocol string) net.IP {
104
+	client := c.Network.MakeHTTPClient(func(ctx context.Context, network, address string) (net.Conn, error) {
105
+		return c.Network.DialContext(ctx, protocol, address) // nolint: wrapcheck
106
+	})
107
+
108
+	req, err := http.NewRequest(http.MethodGet, "https://ifconfig.co", nil) // nolint: noctx
109
+	if err != nil {
110
+		panic(err)
111
+	}
112
+
113
+	req.Header.Add("Accept", "text/plain")
114
+
115
+	resp, err := client.Do(req)
116
+	if err != nil {
117
+		return nil
118
+	}
119
+
120
+	if resp.StatusCode != http.StatusOK {
121
+		return nil
122
+	}
123
+
124
+	defer func() {
125
+		io.Copy(io.Discard, resp.Body) // nolint: errcheck
126
+		resp.Body.Close()
127
+	}()
128
+
129
+	data, err := io.ReadAll(resp.Body)
130
+	if err != nil {
131
+		return nil
132
+	}
133
+
134
+	return net.ParseIP(strings.TrimSpace(string(data)))
135
+}
136
+
137
+func (c *Access) makeURLs(ip net.IP, cli *CLI) *accessResponseURLs {
138
+	if ip == nil {
139
+		return nil
140
+	}
141
+
142
+	portNo := cli.Access.Port
143
+	if portNo == 0 {
144
+		portNo = c.Config.BindTo.PortValue(0)
145
+	}
146
+
147
+	values := url.Values{}
148
+	values.Set("server", ip.String())
149
+	values.Set("port", strconv.Itoa(int(portNo)))
150
+
151
+	if cli.Access.Hex {
152
+		values.Set("secret", c.Config.Secret.Hex())
153
+	} else {
154
+		values.Set("secret", c.Config.Secret.Base64())
155
+	}
156
+
157
+	urlQuery := values.Encode()
158
+
159
+	rv := &accessResponseURLs{
160
+		IP:   ip,
161
+		Port: portNo,
162
+		TgURL: (&url.URL{
163
+			Scheme:   "tg",
164
+			Host:     "proxy",
165
+			RawQuery: urlQuery,
166
+		}).String(),
167
+		TmeURL: (&url.URL{
168
+			Scheme:   "https",
169
+			Host:     "t.me",
170
+			Path:     "proxy",
171
+			RawQuery: urlQuery,
172
+		}).String(),
173
+	}
174
+	rv.TgQrCode = c.makeQRCode(rv.TgURL)
175
+	rv.TmeQrCode = c.makeQRCode(rv.TmeURL)
176
+
177
+	return rv
178
+}
179
+
180
+func (c *Access) makeQRCode(data string) string {
181
+	values := url.Values{}
182
+	values.Set("qzone", "4")
183
+	values.Set("format", "svg")
184
+	values.Set("data", data)
185
+
186
+	return (&url.URL{
187
+		Scheme:   "https",
188
+		Host:     "api.qrserver.com",
189
+		Path:     "v1/create-qr-code",
190
+		RawQuery: values.Encode(),
191
+	}).String()
192
+}

+ 197
- 0
internal/cli/access_test.go Прегледај датотеку

@@ -0,0 +1,197 @@
1
+package cli_test
2
+
3
+import (
4
+	"net"
5
+	"net/http"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config"
9
+	"github.com/9seconds/mtg/v2/internal/testlib"
10
+	"github.com/9seconds/mtg/v2/mtglib"
11
+	"github.com/jarcoal/httpmock"
12
+	"github.com/stretchr/testify/suite"
13
+	"github.com/xeipuuv/gojsonschema"
14
+)
15
+
16
+var accressResponseJSONSchema = func() *gojsonschema.Schema {
17
+	schema, err := gojsonschema.NewSchema(gojsonschema.NewStringLoader(`
18
+{
19
+    "type": "object",
20
+    "required": ["secret"],
21
+    "additionalProperties": true,
22
+    "properties": {
23
+        "secret": {
24
+            "type": "object",
25
+            "required": [
26
+                "hex",
27
+                "base64"
28
+            ],
29
+            "additionalProperties": false,
30
+            "properties": {
31
+                "hex": {
32
+                    "type": "string",
33
+                    "minLength": 34
34
+                },
35
+                "base64": {
36
+                    "type": "string",
37
+                    "minLength": 10
38
+                }
39
+            }
40
+        },
41
+        "ipv4": {
42
+            "$ref": "#/definitions/ip"
43
+        },
44
+        "ipv6": {
45
+            "$ref": "#/definitions/ip"
46
+        }
47
+    },
48
+    "definitions": {
49
+        "ip": {
50
+            "type": "object",
51
+            "required": [
52
+                "ip",
53
+                "port",
54
+                "tg_url",
55
+                "tg_qrcode",
56
+                "tme_url",
57
+                "tme_qrcode"
58
+            ],
59
+            "additionalProperties": false,
60
+            "properties": {
61
+                "ip": {
62
+                    "type": "string",
63
+                    "minLength": 1,
64
+                    "anyOf": [
65
+                        {
66
+                            "format": "ipv4"
67
+                        },
68
+                        {
69
+                            "format": "ipv6"
70
+                        }
71
+                    ]
72
+                },
73
+                "port": {
74
+                    "type": "integer",
75
+                    "multipleOf": 1.0,
76
+                    "exclusiveMinimum": 0,
77
+                    "exclusiveMaximum": 65536
78
+                },
79
+                "tg_url": {
80
+                    "type": "string",
81
+                    "minLength": 1,
82
+                    "format": "uri"
83
+                },
84
+                "tg_qrcode": {
85
+                    "type": "string",
86
+                    "minLength": 1,
87
+                    "format": "uri"
88
+                },
89
+                "tme_url": {
90
+                    "type": "string",
91
+                    "minLength": 1,
92
+                    "format": "uri"
93
+                },
94
+                "tme_qrcode": {
95
+                    "type": "string",
96
+                    "minLength": 1,
97
+                    "format": "uri"
98
+                }
99
+            }
100
+        }
101
+    }
102
+}
103
+    `))
104
+	if err != nil {
105
+		panic(err)
106
+	}
107
+
108
+	return schema
109
+}()
110
+
111
+type AccessTestSuite struct {
112
+	CommonTestSuite
113
+}
114
+
115
+func (suite *AccessTestSuite) SetupTest() {
116
+	suite.CommonTestSuite.SetupTest()
117
+
118
+	suite.cli.Access.Config = &config.Config{}
119
+	suite.cli.Access.Config.Secret = mtglib.GenerateSecret("google.com")
120
+	suite.cli.Access.Network = suite.networkMock
121
+
122
+	suite.NoError(
123
+		suite.cli.Access.Config.BindTo.UnmarshalText([]byte("0.0.0.0:80")))
124
+}
125
+
126
+func (suite *AccessTestSuite) TestGenerateNoCalls() {
127
+	suite.cli.Access.PublicIPv4 = net.ParseIP("10.0.0.10")
128
+	suite.cli.Access.PublicIPv6 = net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
129
+
130
+	output := testlib.CaptureStdout(func() {
131
+		suite.NoError(suite.cli.Access.Execute(suite.cli))
132
+	})
133
+
134
+	validated, err := accressResponseJSONSchema.Validate(
135
+		gojsonschema.NewStringLoader(output))
136
+	suite.NoError(err)
137
+	suite.Empty(validated.Errors())
138
+	suite.True(validated.Valid())
139
+
140
+	suite.Contains(output, "10.0.0.10")
141
+	suite.Contains(output, "2001:db8:85a3::8a2e:370:7334")
142
+	suite.Contains(output, "ipv4")
143
+	suite.Contains(output, "ipv6")
144
+	suite.Contains(output, suite.cli.Access.Config.Secret.Base64())
145
+	suite.Contains(output, suite.cli.Access.Config.Secret.Hex())
146
+}
147
+
148
+func (suite *AccessTestSuite) TestGenerateIPv4Call() {
149
+	suite.cli.Access.PublicIPv6 = net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
150
+
151
+	httpmock.RegisterResponder(http.MethodGet, "https://ifconfig.co",
152
+		httpmock.NewStringResponder(http.StatusOK, "10.11.12.13"))
153
+
154
+	output := testlib.CaptureStdout(func() {
155
+		suite.NoError(suite.cli.Access.Execute(suite.cli))
156
+	})
157
+
158
+	validated, err := accressResponseJSONSchema.Validate(
159
+		gojsonschema.NewStringLoader(output))
160
+	suite.NoError(err)
161
+	suite.Empty(validated.Errors())
162
+	suite.True(validated.Valid())
163
+
164
+	suite.Contains(output, "10.11.12.13")
165
+	suite.Contains(output, "2001:db8:85a3::8a2e:370:7334")
166
+	suite.Contains(output, "ipv4")
167
+	suite.Contains(output, "ipv6")
168
+	suite.Contains(output, suite.cli.Access.Config.Secret.Base64())
169
+	suite.Contains(output, suite.cli.Access.Config.Secret.Hex())
170
+}
171
+
172
+func (suite *AccessTestSuite) TestIPv4CallFail() {
173
+	suite.cli.Access.PublicIPv6 = net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
174
+
175
+	httpmock.RegisterResponder(http.MethodGet, "https://ifconfig.co",
176
+		httpmock.NewStringResponder(http.StatusForbidden, ""))
177
+
178
+	output := testlib.CaptureStdout(func() {
179
+		suite.NoError(suite.cli.Access.Execute(suite.cli))
180
+	})
181
+
182
+	validated, err := accressResponseJSONSchema.Validate(
183
+		gojsonschema.NewStringLoader(output))
184
+	suite.NoError(err)
185
+	suite.Empty(validated.Errors())
186
+	suite.True(validated.Valid())
187
+
188
+	suite.Contains(output, "2001:db8:85a3::8a2e:370:7334")
189
+	suite.NotContains(output, "ipv4")
190
+	suite.Contains(output, "ipv6")
191
+	suite.Contains(output, suite.cli.Access.Config.Secret.Base64())
192
+	suite.Contains(output, suite.cli.Access.Config.Secret.Hex())
193
+}
194
+
195
+func TestAccess(t *testing.T) { // nolint: paralleltest
196
+	suite.Run(t, &AccessTestSuite{})
197
+}

+ 81
- 0
internal/cli/base.go Прегледај датотеку

@@ -0,0 +1,81 @@
1
+package cli
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+	"net/url"
7
+	"os"
8
+
9
+	"github.com/9seconds/mtg/v2/internal/config"
10
+	"github.com/9seconds/mtg/v2/mtglib"
11
+	"github.com/9seconds/mtg/v2/network"
12
+)
13
+
14
+type base struct {
15
+	ConfigPath string `kong:"arg,required,type='existingfile',help='Path to the configuration file.',name='config-path'"` // nolint: lll
16
+
17
+	Network mtglib.Network `kong:"-"`
18
+	Config  *config.Config `kong:"-"`
19
+}
20
+
21
+func (b *base) ReadConfig(version string) error {
22
+	content, err := os.ReadFile(b.ConfigPath)
23
+	if err != nil {
24
+		return fmt.Errorf("cannot read config file: %w", err)
25
+	}
26
+
27
+	conf, err := config.Parse(content)
28
+	if err != nil {
29
+		return fmt.Errorf("cannot parse config: %w", err)
30
+	}
31
+
32
+	ntw, err := b.makeNetwork(conf, version)
33
+	if err != nil {
34
+		return fmt.Errorf("cannot build a network: %w", err)
35
+	}
36
+
37
+	b.Config = conf
38
+	b.Network = ntw
39
+
40
+	return nil
41
+}
42
+
43
+func (b *base) makeNetwork(conf *config.Config, version string) (mtglib.Network, error) {
44
+	tcpTimeout := conf.Network.Timeout.TCP.Value(network.DefaultTimeout)
45
+	httpTimeout := conf.Network.Timeout.HTTP.Value(network.DefaultHTTPTimeout)
46
+	dohIP := conf.Network.DOHIP.Value(net.ParseIP(network.DefaultDOHHostname)).String()
47
+	bufferSize := conf.TCPBuffer.Value(network.DefaultBufferSize)
48
+	userAgent := "mtg/" + version
49
+
50
+	baseDialer, err := network.NewDefaultDialer(tcpTimeout, int(bufferSize))
51
+	if err != nil {
52
+		return nil, fmt.Errorf("cannot build a default dialer: %w", err)
53
+	}
54
+
55
+	proxyURLs := make([]*url.URL, 0, len(conf.Network.Proxies))
56
+
57
+	for _, v := range conf.Network.Proxies {
58
+		if value := v.Value(nil); value != nil {
59
+			proxyURLs = append(proxyURLs, v.Value(nil))
60
+		}
61
+	}
62
+
63
+	switch len(proxyURLs) {
64
+	case 0:
65
+		return network.NewNetwork(baseDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
66
+	case 1:
67
+		socksDialer, err := network.NewSocks5Dialer(baseDialer, proxyURLs[0])
68
+		if err != nil {
69
+			return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
70
+		}
71
+
72
+		return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
73
+	}
74
+
75
+	socksDialer, err := network.NewLoadBalancedSocks5Dialer(baseDialer, proxyURLs)
76
+	if err != nil {
77
+		return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
78
+	}
79
+
80
+	return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
81
+}

+ 33
- 0
internal/cli/base_internal_test.go Прегледај датотеку

@@ -0,0 +1,33 @@
1
+package cli
2
+
3
+import (
4
+	"path/filepath"
5
+	"testing"
6
+
7
+	"github.com/stretchr/testify/suite"
8
+)
9
+
10
+type BaseTestSuite struct {
11
+	suite.Suite
12
+
13
+	b base
14
+}
15
+
16
+func (suite *BaseTestSuite) SetupTest() {
17
+	suite.b = base{}
18
+}
19
+
20
+func (suite *BaseTestSuite) TestReadConfigNok() {
21
+	suite.b.ConfigPath = filepath.Join("testdata", "unknown")
22
+	suite.Error(suite.b.ReadConfig("dev"))
23
+}
24
+
25
+func (suite *BaseTestSuite) TestReadConfig() {
26
+	suite.b.ConfigPath = filepath.Join("testdata", "minimal.toml")
27
+	suite.NoError(suite.b.ReadConfig("dev"))
28
+}
29
+
30
+func TestBase(t *testing.T) {
31
+	t.Parallel()
32
+	suite.Run(t, &BaseTestSuite{})
33
+}

+ 10
- 0
internal/cli/cli.go Прегледај датотеку

@@ -0,0 +1,10 @@
1
+package cli
2
+
3
+import "github.com/alecthomas/kong"
4
+
5
+type CLI struct {
6
+	GenerateSecret GenerateSecret   `kong:"cmd,help='Generate new proxy secret'"`
7
+	Access         Access           `kong:"cmd,help='Print access information.'"`
8
+	Run            Proxy            `kong:"cmd,help='Run proxy.'"`
9
+	Version        kong.VersionFlag `kong:"help='Print version.',short='v'"`
10
+}

+ 24
- 0
internal/cli/generate_secret.go Прегледај датотеку

@@ -0,0 +1,24 @@
1
+package cli
2
+
3
+import (
4
+	"fmt"
5
+
6
+	"github.com/9seconds/mtg/v2/mtglib"
7
+)
8
+
9
+type GenerateSecret struct {
10
+	HostName string `kong:"arg,required,help='Hostname to use for domain fronting.',name='hostname'"`
11
+	Hex      bool   `kong:"help='Print secret in hex encoding.',short='x'"`
12
+}
13
+
14
+func (c *GenerateSecret) Run(cli *CLI, _ string) error {
15
+	secret := mtglib.GenerateSecret(cli.GenerateSecret.HostName)
16
+
17
+	if cli.GenerateSecret.Hex {
18
+		fmt.Println(secret.Hex()) // nolint: forbidigo
19
+	} else {
20
+		fmt.Println(secret.Base64()) // nolint: forbidigo
21
+	}
22
+
23
+	return nil
24
+}

+ 51
- 0
internal/cli/generate_secret_test.go Прегледај датотеку

@@ -0,0 +1,51 @@
1
+package cli_test
2
+
3
+import (
4
+	"strings"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/testlib"
8
+	"github.com/9seconds/mtg/v2/mtglib"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type GenerateSecretTestSuite struct {
13
+	CommonTestSuite
14
+}
15
+
16
+func (suite *GenerateSecretTestSuite) SetupTest() {
17
+	suite.CommonTestSuite.SetupTest()
18
+
19
+	suite.cli.GenerateSecret.HostName = "google.com"
20
+}
21
+
22
+func (suite *GenerateSecretTestSuite) TestDefault() {
23
+	output := testlib.CaptureStdout(func() {
24
+		suite.NoError(suite.cli.GenerateSecret.Run(suite.cli, "dev"))
25
+	})
26
+	suite.True(strings.HasPrefix(output, "7"))
27
+
28
+	secret, err := mtglib.ParseSecret(output)
29
+	suite.NoError(err)
30
+	suite.True(secret.Valid())
31
+	suite.Equal("google.com", secret.Host)
32
+}
33
+
34
+func (suite *GenerateSecretTestSuite) TestHex() {
35
+	suite.cli.GenerateSecret.Hex = true
36
+
37
+	output := testlib.CaptureStdout(func() {
38
+		suite.NoError(suite.cli.GenerateSecret.Run(suite.cli, "dev"))
39
+	})
40
+	suite.True(strings.HasPrefix(output, "ee"))
41
+
42
+	secret, err := mtglib.ParseSecret(output)
43
+	suite.NoError(err)
44
+	suite.True(secret.Valid())
45
+	suite.Equal("google.com", secret.Host)
46
+}
47
+
48
+func TestGenerateSecret(t *testing.T) {
49
+	t.Parallel()
50
+	suite.Run(t, &GenerateSecretTestSuite{})
51
+}

+ 37
- 0
internal/cli/init_test.go Прегледај датотеку

@@ -0,0 +1,37 @@
1
+package cli_test
2
+
3
+import (
4
+	"net/http"
5
+
6
+	"github.com/9seconds/mtg/v2/internal/cli"
7
+	"github.com/9seconds/mtg/v2/internal/testlib"
8
+	"github.com/jarcoal/httpmock"
9
+	"github.com/stretchr/testify/mock"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type CommonTestSuite struct {
14
+	suite.Suite
15
+
16
+	cli         *cli.CLI
17
+	networkMock *testlib.MtglibNetworkMock
18
+	httpClient  *http.Client
19
+}
20
+
21
+func (suite *CommonTestSuite) SetupTest() {
22
+	suite.networkMock = &testlib.MtglibNetworkMock{}
23
+	suite.httpClient = &http.Client{}
24
+	suite.cli = &cli.CLI{}
25
+
26
+	httpmock.ActivateNonDefault(suite.httpClient)
27
+
28
+	suite.networkMock.
29
+		On("MakeHTTPClient", mock.Anything).
30
+		Maybe().
31
+		Return(suite.httpClient)
32
+}
33
+
34
+func (suite *CommonTestSuite) TearDownTest() {
35
+	suite.networkMock.AssertExpectations(suite.T())
36
+	httpmock.DeactivateAndReset()
37
+}

+ 167
- 0
internal/cli/proxy.go Прегледај датотеку

@@ -0,0 +1,167 @@
1
+package cli
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+	"os"
7
+
8
+	"github.com/9seconds/mtg/v2/antireplay"
9
+	"github.com/9seconds/mtg/v2/events"
10
+	"github.com/9seconds/mtg/v2/internal/utils"
11
+	"github.com/9seconds/mtg/v2/ipblocklist"
12
+	"github.com/9seconds/mtg/v2/logger"
13
+	"github.com/9seconds/mtg/v2/mtglib"
14
+	"github.com/9seconds/mtg/v2/stats"
15
+	"github.com/rs/zerolog"
16
+)
17
+
18
+type Proxy struct {
19
+	base
20
+}
21
+
22
+func (c *Proxy) Run(cli *CLI, version string) error {
23
+	if err := c.ReadConfig(version); err != nil {
24
+		return fmt.Errorf("cannot init config: %w", err)
25
+	}
26
+
27
+	return c.Execute()
28
+}
29
+
30
+func (c *Proxy) Execute() error {
31
+	zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
32
+	zerolog.TimestampFieldName = "timestamp"
33
+	zerolog.LevelFieldName = "level"
34
+
35
+	if c.Config.Debug {
36
+		zerolog.SetGlobalLevel(zerolog.DebugLevel)
37
+	} else {
38
+		zerolog.SetGlobalLevel(zerolog.WarnLevel)
39
+	}
40
+
41
+	ctx := utils.RootContext()
42
+	opts := mtglib.ProxyOpts{
43
+		Logger:          logger.NewZeroLogger(zerolog.New(os.Stdout).With().Timestamp().Logger()),
44
+		Network:         c.Network,
45
+		AntiReplayCache: antireplay.NewNoop(),
46
+		IPBlocklist:     ipblocklist.NewNoop(),
47
+		EventStream:     events.NewNoopStream(),
48
+
49
+		Secret:             c.Config.Secret,
50
+		BufferSize:         c.Config.TCPBuffer.Value(mtglib.DefaultBufferSize),
51
+		DomainFrontingPort: c.Config.DomainFrontingPort.Value(mtglib.DefaultDomainFrontingPort),
52
+		IdleTimeout:        c.Config.Network.Timeout.Idle.Value(mtglib.DefaultIdleTimeout),
53
+		PreferIP:           c.Config.PreferIP.Value(mtglib.DefaultPreferIP),
54
+	}
55
+
56
+	opts.Logger.BindStr("configuration", c.Config.String()).Debug("configuration")
57
+
58
+	c.setupAntiReplayCache(&opts)
59
+
60
+	if err := c.setupIPBlocklist(&opts); err != nil {
61
+		return fmt.Errorf("cannot setup ipblocklist: %w", err)
62
+	}
63
+
64
+	if err := c.setupEventStream(&opts); err != nil {
65
+		return fmt.Errorf("cannot setup event stream: %w", err)
66
+	}
67
+
68
+	proxy, err := mtglib.NewProxy(opts)
69
+	if err != nil {
70
+		return fmt.Errorf("cannot create a proxy: %w", err)
71
+	}
72
+
73
+	listener, err := net.Listen("tcp", c.Config.BindTo.String())
74
+	if err != nil {
75
+		return fmt.Errorf("cannot start proxy: %w", err)
76
+	}
77
+
78
+	go proxy.Serve(listener) // nolint: errcheck
79
+
80
+	<-ctx.Done()
81
+	listener.Close()
82
+	proxy.Shutdown()
83
+
84
+	return nil
85
+}
86
+
87
+func (c *Proxy) setupAntiReplayCache(opts *mtglib.ProxyOpts) {
88
+	if !c.Config.Defense.AntiReplay.Enabled {
89
+		return
90
+	}
91
+
92
+	opts.AntiReplayCache = antireplay.NewStableBloomFilter(
93
+		c.Config.Defense.AntiReplay.MaxSize.Value(antireplay.DefaultStableBloomFilterMaxSize),
94
+		c.Config.Defense.AntiReplay.ErrorRate.Value(antireplay.DefaultStableBloomFilterErrorRate),
95
+	)
96
+}
97
+
98
+func (c *Proxy) setupIPBlocklist(opts *mtglib.ProxyOpts) error {
99
+	if !c.Config.Defense.Blocklist.Enabled {
100
+		return nil
101
+	}
102
+
103
+	remoteURLs := []string{}
104
+	localFiles := []string{}
105
+
106
+	for _, v := range c.Config.Defense.Blocklist.URLs {
107
+		if v.IsRemote() {
108
+			remoteURLs = append(remoteURLs, v.String())
109
+		} else {
110
+			localFiles = append(localFiles, v.String())
111
+		}
112
+	}
113
+
114
+	firehol, err := ipblocklist.NewFirehol(opts.Logger.Named("ipblockist"),
115
+		c.Network,
116
+		c.Config.Defense.Blocklist.DownloadConcurrency,
117
+		remoteURLs,
118
+		localFiles)
119
+	if err != nil {
120
+		return err // nolint: wrapcheck
121
+	}
122
+
123
+	go firehol.Run(c.Config.Defense.Blocklist.UpdateEach.Value(ipblocklist.DefaultFireholUpdateEach))
124
+
125
+	opts.IPBlocklist = firehol
126
+
127
+	return nil
128
+}
129
+
130
+func (c *Proxy) setupEventStream(opts *mtglib.ProxyOpts) error {
131
+	factories := make([]events.ObserverFactory, 0, 2)
132
+
133
+	if c.Config.Stats.StatsD.Enabled {
134
+		statsdFactory, err := stats.NewStatsd(
135
+			c.Config.Stats.StatsD.Address.String(),
136
+			opts.Logger.Named("statsd"),
137
+			c.Config.Stats.StatsD.MetricPrefix.Value(stats.DefaultStatsdMetricPrefix),
138
+			c.Config.Stats.StatsD.TagFormat.Value(stats.DefaultStatsdTagFormat))
139
+		if err != nil {
140
+			return fmt.Errorf("cannot build statsd observer: %w", err)
141
+		}
142
+
143
+		factories = append(factories, statsdFactory.Make)
144
+	}
145
+
146
+	if c.Config.Stats.Prometheus.Enabled {
147
+		prometheus := stats.NewPrometheus(
148
+			c.Config.Stats.Prometheus.MetricPrefix.Value(stats.DefaultMetricPrefix),
149
+			c.Config.Stats.Prometheus.HTTPPath.Value("/"),
150
+		)
151
+
152
+		listener, err := net.Listen("tcp", c.Config.Stats.Prometheus.BindTo.String())
153
+		if err != nil {
154
+			return fmt.Errorf("cannot start a listener for prometheus: %w", err)
155
+		}
156
+
157
+		go prometheus.Serve(listener) // nolint: errcheck
158
+
159
+		factories = append(factories, prometheus.Make)
160
+	}
161
+
162
+	if len(factories) > 0 {
163
+		opts.EventStream = events.NewEventStream(factories)
164
+	}
165
+
166
+	return nil
167
+}

+ 2
- 0
internal/cli/testdata/minimal.toml Прегледај датотеку

@@ -0,0 +1,2 @@
1
+secret = "7mqFMMq3P2Tvvt_rPx5qhmFnb29nbGUuY29t"
2
+bind-to = "0.0.0.0:80"

+ 157
- 0
internal/config/config.go Прегледај датотеку

@@ -0,0 +1,157 @@
1
+package config
2
+
3
+import (
4
+	"bytes"
5
+	"encoding/json"
6
+	"fmt"
7
+
8
+	"github.com/9seconds/mtg/v2/mtglib"
9
+	"github.com/pelletier/go-toml"
10
+)
11
+
12
+type Config struct {
13
+	Debug                bool          `json:"debug"`
14
+	Secret               mtglib.Secret `json:"secret"`
15
+	BindTo               TypeHostPort  `json:"bind-to"`
16
+	TCPBuffer            TypeBytes     `json:"tcp-buffer"`
17
+	PreferIP             TypePreferIP  `json:"prefer-ip"`
18
+	DomainFrontingPort   TypePort      `json:"domain-fronting-port"`
19
+	TolerateTimeSkewness TypeDuration  `json:"tolerate-time-skewness"`
20
+	Concurrency          uint          `json:"concurrency"`
21
+	Defense              struct {
22
+		AntiReplay struct {
23
+			Enabled   bool          `json:"enabled"`
24
+			MaxSize   TypeBytes     `json:"max-size"`
25
+			ErrorRate TypeErrorRate `json:"error-rate"`
26
+		} `json:"anti-replay"`
27
+		Blocklist struct {
28
+			Enabled             bool               `json:"enabled"`
29
+			DownloadConcurrency uint               `json:"download-concurrency"`
30
+			URLs                []TypeBlocklistURI `json:"urls"`
31
+			UpdateEach          TypeDuration       `json:"update-each"`
32
+		} `json:"blocklist"`
33
+	} `json:"defense"`
34
+	Network struct {
35
+		Timeout struct {
36
+			TCP  TypeDuration `json:"tcp"`
37
+			HTTP TypeDuration `json:"http"`
38
+			Idle TypeDuration `json:"idle"`
39
+		} `json:"timeout"`
40
+		DOHIP   TypeIP    `json:"doh-ip"`
41
+		Proxies []TypeURL `json:"proxies"`
42
+	} `json:"network"`
43
+	Stats struct {
44
+		StatsD struct {
45
+			Enabled      bool                `json:"enabled"`
46
+			Address      TypeHostPort        `json:"address"`
47
+			MetricPrefix TypeMetricPrefix    `json:"metric-prefix"`
48
+			TagFormat    TypeStatsdTagFormat `json:"tag-format"`
49
+		} `json:"statsd"`
50
+		Prometheus struct {
51
+			Enabled      bool             `json:"enabled"`
52
+			BindTo       TypeHostPort     `json:"bind-to"`
53
+			HTTPPath     TypeHTTPPath     `json:"http-path"`
54
+			MetricPrefix TypeMetricPrefix `json:"metric-prefix"`
55
+		} `json:"prometheus"`
56
+	} `json:"stats"`
57
+}
58
+
59
+func (c *Config) Validate() error {
60
+	if !c.Secret.Valid() {
61
+		return fmt.Errorf("invalid secret %s", c.Secret.String())
62
+	}
63
+
64
+	if len(c.BindTo.HostValue(nil)) == 0 || c.BindTo.PortValue(0) == 0 {
65
+		return fmt.Errorf("incorrect bind-to parameter %s", c.BindTo.String())
66
+	}
67
+
68
+	return nil
69
+}
70
+
71
+func (c *Config) String() string {
72
+	buf := &bytes.Buffer{}
73
+	encoder := json.NewEncoder(buf)
74
+
75
+	encoder.SetEscapeHTML(false)
76
+
77
+	if err := encoder.Encode(c); err != nil {
78
+		panic(err)
79
+	}
80
+
81
+	return buf.String()
82
+}
83
+
84
+type configRaw struct {
85
+	Debug                bool   `toml:"debug" json:"debug,omitempty"`
86
+	Secret               string `toml:"secret" json:"secret"`
87
+	BindTo               string `toml:"bind-to" json:"bind-to"`
88
+	TCPBuffer            string `toml:"tcp-buffer" json:"tcp-buffer,omitempty"`
89
+	PreferIP             string `toml:"prefer-ip" json:"prefer-ip,omitempty"`
90
+	DomainFrontingPort   uint   `toml:"domain-fronting-port" json:"domain-fronting-port,omitempty"`
91
+	TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerate-time-skewness,omitempty"`
92
+	Concurrency          uint   `toml:"concurrency" json:"concurrency,omitempty"`
93
+	Defense              struct {
94
+		AntiReplay struct {
95
+			Enabled   bool    `toml:"enabled" json:"enabled,omitempty"`
96
+			MaxSize   string  `toml:"max-size" json:"max-size,omitempty"`
97
+			ErrorRate float64 `toml:"error-rate" json:"error-rate,omitempty"`
98
+		} `toml:"anti-replay" json:"anti-replay,omitempty"`
99
+		Blocklist struct {
100
+			Enabled             bool     `toml:"enabled" json:"enabled,omitempty"`
101
+			DownloadConcurrency uint     `toml:"download-concurrency" json:"download-concurrency,omitempty"`
102
+			URLs                []string `toml:"urls" json:"urls,omitempty"`
103
+			UpdateEach          string   `toml:"update-each" json:"update-each,omitempty"`
104
+		} `toml:"blocklist" json:"blocklist,omitempty"`
105
+	} `toml:"defense" json:"defense,omitempty"`
106
+	Network struct {
107
+		Timeout struct {
108
+			TCP  string `toml:"tcp" json:"tcp,omitempty"`
109
+			HTTP string `toml:"http" json:"http,omitempty"`
110
+			Idle string `toml:"idle" json:"idle,omitempty"`
111
+		} `toml:"timeout" json:"timeout,omitempty"`
112
+		DOHIP   string   `toml:"doh-ip" json:"doh-ip,omitempty"`
113
+		Proxies []string `toml:"proxies" json:"proxies,omitempty"`
114
+	} `toml:"network" json:"network,omitempty"`
115
+	Stats struct {
116
+		StatsD struct {
117
+			Enabled      bool   `toml:"enabled" json:"enabled,omitempty"`
118
+			Address      string `toml:"address" json:"address,omitempty"`
119
+			MetricPrefix string `toml:"metric-prefix" json:"metric-prefix,omitempty"`
120
+			TagFormat    string `toml:"tag-format" json:"tag-format,omitempty"`
121
+		} `toml:"statsd" json:"statsd,omitempty"`
122
+		Prometheus struct {
123
+			Enabled      bool   `toml:"enabled" json:"enabled,omitempty"`
124
+			BindTo       string `toml:"bind-to" json:"bind-to,omitempty"`
125
+			HTTPPath     string `toml:"http-path" json:"http-path,omitempty"`
126
+			MetricPrefix string `toml:"metric-prefix" json:"metric-prefix,omitempty"`
127
+		} `toml:"prometheus" json:"prometheus,omitempty"`
128
+	} `toml:"stats" json:"stats,omitempty"`
129
+}
130
+
131
+func Parse(rawData []byte) (*Config, error) {
132
+	rawConf := &configRaw{}
133
+	jsonBuf := &bytes.Buffer{}
134
+	conf := &Config{}
135
+
136
+	jsonEncoder := json.NewEncoder(jsonBuf)
137
+	jsonEncoder.SetEscapeHTML(false)
138
+	jsonEncoder.SetIndent("", "")
139
+
140
+	if err := toml.Unmarshal(rawData, rawConf); err != nil {
141
+		return nil, fmt.Errorf("cannot parse toml config: %w", err)
142
+	}
143
+
144
+	if err := jsonEncoder.Encode(rawConf); err != nil {
145
+		panic(err)
146
+	}
147
+
148
+	if err := json.NewDecoder(jsonBuf).Decode(conf); err != nil {
149
+		return nil, fmt.Errorf("cannot parse a config: %w", err)
150
+	}
151
+
152
+	if err := conf.Validate(); err != nil {
153
+		return nil, fmt.Errorf("cannot validate config: %w", err)
154
+	}
155
+
156
+	return conf, nil
157
+}

+ 54
- 0
internal/config/config_test.go Прегледај датотеку

@@ -0,0 +1,54 @@
1
+package config_test
2
+
3
+import (
4
+	"os"
5
+	"path/filepath"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type ConfigTestSuite struct {
13
+	suite.Suite
14
+}
15
+
16
+func (suite *ConfigTestSuite) ReadConfig(filename string) []byte {
17
+	data, err := os.ReadFile(filepath.Join("testdata", filename))
18
+	suite.NoError(err)
19
+
20
+	return data
21
+}
22
+
23
+func (suite *ConfigTestSuite) TestParseEmpty() {
24
+	_, err := config.Parse([]byte{})
25
+	suite.Error(err)
26
+}
27
+
28
+func (suite *ConfigTestSuite) TestParseBrokenToml() {
29
+	_, err := config.Parse(suite.ReadConfig("broken.toml"))
30
+	suite.Error(err)
31
+}
32
+
33
+func (suite *ConfigTestSuite) TestParseOnlySecret() {
34
+	_, err := config.Parse(suite.ReadConfig("only_secret.toml"))
35
+	suite.Error(err)
36
+}
37
+
38
+func (suite *ConfigTestSuite) TestParseMinimalConfig() {
39
+	conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
40
+	suite.NoError(err)
41
+	suite.Equal("7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t", conf.Secret.Base64())
42
+	suite.Equal("0.0.0.0:3128", conf.BindTo.String())
43
+}
44
+
45
+func (suite *ConfigTestSuite) TestString() {
46
+	conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
47
+	suite.NoError(err)
48
+	suite.NotEmpty(conf.String())
49
+}
50
+
51
+func TestConfig(t *testing.T) {
52
+	t.Parallel()
53
+	suite.Run(t, &ConfigTestSuite{})
54
+}

+ 1
- 0
internal/config/testdata/broken.toml Прегледај датотеку

@@ -0,0 +1 @@
1
+s = sdfsdfds

+ 2
- 0
internal/config/testdata/minimal.toml Прегледај датотеку

@@ -0,0 +1,2 @@
1
+secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
2
+bind-to = "0.0.0.0:3128"

+ 1
- 0
internal/config/testdata/only_secret.toml Прегледај датотеку

@@ -0,0 +1 @@
1
+secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"

+ 68
- 0
internal/config/type_blocklist_uri.go Прегледај датотеку

@@ -0,0 +1,68 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"net/url"
6
+	"os"
7
+	"path/filepath"
8
+)
9
+
10
+type TypeBlocklistURI struct {
11
+	value string
12
+}
13
+
14
+func (c *TypeBlocklistURI) UnmarshalText(data []byte) error {
15
+	if len(data) == 0 {
16
+		return nil
17
+	}
18
+
19
+	text := string(data)
20
+	if filepath.IsAbs(text) {
21
+		if _, err := os.Stat(text); os.IsNotExist(err) {
22
+			return fmt.Errorf("filepath %s does not exist", text)
23
+		}
24
+
25
+		c.value = text
26
+
27
+		return nil
28
+	}
29
+
30
+	parsedURL, err := url.Parse(text)
31
+	if err != nil {
32
+		return fmt.Errorf("incorrect url: %w", err)
33
+	}
34
+
35
+	switch parsedURL.Scheme {
36
+	case "http", "https": // nolint: goconst
37
+	default:
38
+		return fmt.Errorf("unknown schema %s", parsedURL.Scheme)
39
+	}
40
+
41
+	if parsedURL.Host == "" {
42
+		return fmt.Errorf("incorrect url %s", text)
43
+	}
44
+
45
+	c.value = parsedURL.String()
46
+
47
+	return nil
48
+}
49
+
50
+func (c TypeBlocklistURI) MarshalText() ([]byte, error) {
51
+	return []byte(c.value), nil
52
+}
53
+
54
+func (c TypeBlocklistURI) String() string {
55
+	return c.value
56
+}
57
+
58
+func (c TypeBlocklistURI) IsRemote() bool {
59
+	return !filepath.IsAbs(c.value)
60
+}
61
+
62
+func (c TypeBlocklistURI) Value(defaultValue string) string {
63
+	if c.value == "" {
64
+		return defaultValue
65
+	}
66
+
67
+	return c.value
68
+}

+ 177
- 0
internal/config/type_blocklist_uri_test.go Прегледај датотеку

@@ -0,0 +1,177 @@
1
+package config_test
2
+
3
+import (
4
+	"crypto/rand"
5
+	"encoding/base64"
6
+	"encoding/json"
7
+	"os"
8
+	"path/filepath"
9
+	"strconv"
10
+	"testing"
11
+
12
+	"github.com/9seconds/mtg/v2/internal/config"
13
+	"github.com/stretchr/testify/assert"
14
+	"github.com/stretchr/testify/suite"
15
+)
16
+
17
+type typeBlocklistURITestStruct struct {
18
+	Value config.TypeBlocklistURI `json:"value"`
19
+}
20
+
21
+type TypeBlocklistURITestSuite struct {
22
+	suite.Suite
23
+}
24
+
25
+func (suite *TypeBlocklistURITestSuite) TestUnmarshalNil() {
26
+	typ := &config.TypeBlocklistURI{}
27
+	suite.NoError(typ.UnmarshalText(nil))
28
+	suite.Empty(typ.String())
29
+}
30
+
31
+func (suite *TypeBlocklistURITestSuite) TestUnknownSchema() {
32
+	typ := &config.TypeBlocklistURI{}
33
+	suite.Error(typ.UnmarshalText([]byte("gopher://lalala")))
34
+}
35
+
36
+func (suite *TypeBlocklistURITestSuite) TestEmptyHost() {
37
+	typ := &config.TypeBlocklistURI{}
38
+	suite.Error(typ.UnmarshalText([]byte("https:///path")))
39
+}
40
+
41
+func (suite *TypeBlocklistURITestSuite) TestIncorrectURL() {
42
+	typ := &config.TypeBlocklistURI{}
43
+	suite.Error(typ.UnmarshalText([]byte("h:/--")))
44
+}
45
+
46
+func (suite *TypeBlocklistURITestSuite) TestUnmarshalFail() {
47
+	rnd := make([]byte, 48)
48
+
49
+	rand.Read(rnd) // nolint: errcheck
50
+
51
+	unknownPath := base64.StdEncoding.EncodeToString(rnd)
52
+
53
+	testData := []string{
54
+		"1",
55
+		unknownPath,
56
+		"/" + unknownPath,
57
+		"http:/",
58
+		"gopher://lalalal",
59
+	}
60
+
61
+	for _, v := range testData {
62
+		data, err := json.Marshal(map[string]string{
63
+			"value": v,
64
+		})
65
+		suite.NoError(err)
66
+
67
+		suite.T().Run(v, func(t *testing.T) {
68
+			assert.Error(t, json.Unmarshal(data, &typeBlocklistURITestStruct{}))
69
+		})
70
+	}
71
+}
72
+
73
+func (suite *TypeBlocklistURITestSuite) TestUnmarshalOk() {
74
+	dir, _ := os.Getwd()
75
+	dir, _ = filepath.Abs(dir)
76
+
77
+	testData := []string{
78
+		"http://lalala",
79
+		filepath.Join(dir, "config.go"),
80
+		"https://lalala",
81
+	}
82
+
83
+	for _, v := range testData {
84
+		value := v
85
+
86
+		data, err := json.Marshal(map[string]string{
87
+			"value": v,
88
+		})
89
+		suite.NoError(err)
90
+
91
+		suite.T().Run(v, func(t *testing.T) {
92
+			testStruct := &typeBlocklistURITestStruct{}
93
+
94
+			assert.NoError(t, json.Unmarshal(data, testStruct))
95
+			assert.EqualValues(t, value, testStruct.Value.Value(""))
96
+		})
97
+	}
98
+}
99
+
100
+func (suite *TypeBlocklistURITestSuite) TestMarshalOk() {
101
+	dir, _ := os.Getwd()
102
+	dir, _ = filepath.Abs(dir)
103
+
104
+	testData := []string{
105
+		"http://lalalal",
106
+		filepath.Join(dir, "config.go"),
107
+	}
108
+
109
+	for _, v := range testData {
110
+		name := v
111
+
112
+		data, err := json.Marshal(map[string]string{
113
+			"value": name,
114
+		})
115
+		suite.NoError(err)
116
+
117
+		suite.T().Run(name, func(t *testing.T) {
118
+			testStruct := &typeBlocklistURITestStruct{}
119
+
120
+			assert.NoError(t, json.Unmarshal(data, testStruct))
121
+			assert.Equal(t, name, testStruct.Value.String())
122
+
123
+			marshalled, err := testStruct.Value.MarshalText()
124
+			assert.NoError(t, err)
125
+			assert.Equal(t, name, string(marshalled))
126
+		})
127
+	}
128
+}
129
+
130
+func (suite *TypeBlocklistURITestSuite) TestValue() {
131
+	testStruct := &typeBlocklistURITestStruct{}
132
+
133
+	suite.Equal("http://lalala", testStruct.Value.Value("http://lalala"))
134
+
135
+	data, err := json.Marshal(map[string]string{
136
+		"value": "http://blablabla",
137
+	})
138
+	suite.NoError(err)
139
+	suite.NoError(json.Unmarshal(data, testStruct))
140
+
141
+	suite.Equal("http://blablabla", testStruct.Value.Value(""))
142
+}
143
+
144
+func (suite *TypeBlocklistURITestSuite) TestIsRemote() {
145
+	dir, _ := os.Getwd()
146
+	dir, _ = filepath.Abs(dir)
147
+
148
+	testData := map[bool]string{
149
+		true:  "http://lalalal",
150
+		false: filepath.Join(dir, "config.go"),
151
+	}
152
+
153
+	for k, v := range testData {
154
+		ok := k
155
+
156
+		data, err := json.Marshal(map[string]string{
157
+			"value": v,
158
+		})
159
+		suite.NoError(err)
160
+
161
+		suite.T().Run(strconv.FormatBool(ok), func(t *testing.T) {
162
+			testStruct := &typeBlocklistURITestStruct{}
163
+			assert.NoError(t, json.Unmarshal(data, testStruct))
164
+
165
+			if ok {
166
+				assert.True(t, testStruct.Value.IsRemote())
167
+			} else {
168
+				assert.False(t, testStruct.Value.IsRemote())
169
+			}
170
+		})
171
+	}
172
+}
173
+
174
+func TestTypeBlocklistURI(t *testing.T) {
175
+	t.Parallel()
176
+	suite.Run(t, &TypeBlocklistURITestSuite{})
177
+}

+ 54
- 0
internal/config/type_bytes.go Прегледај датотеку

@@ -0,0 +1,54 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"strings"
6
+
7
+	"github.com/alecthomas/units"
8
+)
9
+
10
+type TypeBytes struct {
11
+	value units.Base2Bytes
12
+}
13
+
14
+func (c *TypeBytes) UnmarshalText(data []byte) error {
15
+	if len(data) == 0 {
16
+		return nil
17
+	}
18
+
19
+	normalizedData := strings.ToUpper(string(data))
20
+	normalizedData = strings.ReplaceAll(normalizedData, "IB", "iB")
21
+
22
+	value, err := units.ParseBase2Bytes(normalizedData)
23
+	if err != nil {
24
+		return fmt.Errorf("incorrect bytes value: %w", err)
25
+	}
26
+
27
+	if value < 0 {
28
+		return fmt.Errorf("%d should be positive number", value)
29
+	}
30
+
31
+	c.value = value
32
+
33
+	return nil
34
+}
35
+
36
+func (c TypeBytes) MarshalText() ([]byte, error) {
37
+	return []byte(c.String()), nil
38
+}
39
+
40
+func (c TypeBytes) String() string {
41
+	if c.value == 0 {
42
+		return ""
43
+	}
44
+
45
+	return strings.ToLower(c.value.String())
46
+}
47
+
48
+func (c TypeBytes) Value(defaultValue uint) uint {
49
+	if c.value == 0 {
50
+		return defaultValue
51
+	}
52
+
53
+	return uint(c.value)
54
+}

+ 120
- 0
internal/config/type_bytes_test.go Прегледај датотеку

@@ -0,0 +1,120 @@
1
+package config_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/config"
8
+	"github.com/stretchr/testify/assert"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type typeBytesTestStruct struct {
13
+	Value config.TypeBytes `json:"value"`
14
+}
15
+
16
+type TypeBytesTestSuite struct {
17
+	suite.Suite
18
+}
19
+
20
+func (suite *TypeBytesTestSuite) TestUnmarshalNil() {
21
+	typ := &config.TypeBytes{}
22
+	suite.NoError(typ.UnmarshalText(nil))
23
+	suite.Empty(typ.String())
24
+}
25
+
26
+func (suite *TypeBytesTestSuite) TestUnmarshalFail() {
27
+	testData := []string{
28
+		"1m",
29
+		"1",
30
+		"-1kb",
31
+		"-1kib",
32
+		"-1QB",
33
+	}
34
+
35
+	for _, v := range testData {
36
+		data, err := json.Marshal(map[string]string{
37
+			"value": v,
38
+		})
39
+		suite.NoError(err)
40
+
41
+		suite.T().Run(v, func(t *testing.T) {
42
+			assert.Error(t, json.Unmarshal(data, &typeBytesTestStruct{}))
43
+		})
44
+	}
45
+}
46
+
47
+func (suite *TypeBytesTestSuite) TestUnmarshalOk() {
48
+	testData := map[string]uint{
49
+		"1b":   1,
50
+		"1kb":  1024,
51
+		"1kib": 1024,
52
+		"2mb":  2 * 1024 * 1024,
53
+		"2mib": 2 * 1024 * 1024,
54
+	}
55
+
56
+	for k, v := range testData {
57
+		value := v
58
+
59
+		data, err := json.Marshal(map[string]string{
60
+			"value": k,
61
+		})
62
+		suite.NoError(err)
63
+
64
+		suite.T().Run(k, func(t *testing.T) {
65
+			testStruct := &typeBytesTestStruct{}
66
+
67
+			assert.NoError(t, json.Unmarshal(data, testStruct))
68
+			assert.EqualValues(t, value, testStruct.Value.Value(0))
69
+		})
70
+	}
71
+}
72
+
73
+func (suite *TypeBytesTestSuite) TestMarshalOk() {
74
+	testData := []string{
75
+		"1b",
76
+		"1kib",
77
+		"2mib",
78
+	}
79
+
80
+	for _, v := range testData {
81
+		name := v
82
+
83
+		data, err := json.Marshal(map[string]string{
84
+			"value": name,
85
+		})
86
+		suite.NoError(err)
87
+
88
+		suite.T().Run(name, func(t *testing.T) {
89
+			testStruct := &typeBytesTestStruct{}
90
+
91
+			assert.NoError(t, json.Unmarshal(data, testStruct))
92
+			assert.Equal(t, name, testStruct.Value.String())
93
+
94
+			marshalled, err := testStruct.Value.MarshalText()
95
+			assert.NoError(t, err)
96
+			assert.Equal(t, name, string(marshalled))
97
+		})
98
+	}
99
+}
100
+
101
+func (suite *TypeBytesTestSuite) TestValue() {
102
+	testStruct := &typeBytesTestStruct{}
103
+
104
+	suite.EqualValues(0, testStruct.Value.Value(0))
105
+	suite.EqualValues(1, testStruct.Value.Value(1))
106
+
107
+	data, err := json.Marshal(map[string]string{
108
+		"value": "1kb",
109
+	})
110
+	suite.NoError(err)
111
+	suite.NoError(json.Unmarshal(data, testStruct))
112
+
113
+	suite.EqualValues(1024, testStruct.Value.Value(0))
114
+	suite.EqualValues(1024, testStruct.Value.Value(1))
115
+}
116
+
117
+func TestTypeBytes(t *testing.T) {
118
+	t.Parallel()
119
+	suite.Run(t, &TypeBytesTestSuite{})
120
+}

+ 46
- 0
internal/config/type_duration.go Прегледај датотеку

@@ -0,0 +1,46 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"strings"
6
+	"time"
7
+)
8
+
9
+type TypeDuration struct {
10
+	value time.Duration
11
+}
12
+
13
+func (c *TypeDuration) UnmarshalText(data []byte) error {
14
+	if len(data) == 0 {
15
+		return nil
16
+	}
17
+
18
+	dur, err := time.ParseDuration(strings.ToLower(string(data)))
19
+	if err != nil {
20
+		return fmt.Errorf("incorrect duration: %w", err)
21
+	}
22
+
23
+	if dur < 0 {
24
+		return fmt.Errorf("%s should be positive duration", dur)
25
+	}
26
+
27
+	c.value = dur
28
+
29
+	return nil
30
+}
31
+
32
+func (c TypeDuration) MarshalText() ([]byte, error) {
33
+	return []byte(c.value.String()), nil
34
+}
35
+
36
+func (c TypeDuration) String() string {
37
+	return c.value.String()
38
+}
39
+
40
+func (c TypeDuration) Value(defaultValue time.Duration) time.Duration {
41
+	if c.value == 0 {
42
+		return defaultValue
43
+	}
44
+
45
+	return c.value
46
+}

+ 118
- 0
internal/config/type_duration_test.go Прегледај датотеку

@@ -0,0 +1,118 @@
1
+package config_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"testing"
6
+	"time"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type typeDurationTestStruct struct {
14
+	Value config.TypeDuration `json:"value"`
15
+}
16
+
17
+type TypeDurationTestSuite struct {
18
+	suite.Suite
19
+}
20
+
21
+func (suite *TypeDurationTestSuite) TestUnmarshalNil() {
22
+	typ := &config.TypeDuration{}
23
+	suite.NoError(typ.UnmarshalText(nil))
24
+	suite.EqualValues(0, typ.Value(0))
25
+}
26
+
27
+func (suite *TypeDurationTestSuite) TestUnmarshalFail() {
28
+	testData := []string{
29
+		"1t",
30
+		"1",
31
+		"-1s",
32
+		"-1h",
33
+	}
34
+
35
+	for _, v := range testData {
36
+		data, err := json.Marshal(map[string]string{
37
+			"value": v,
38
+		})
39
+		suite.NoError(err)
40
+
41
+		suite.T().Run(v, func(t *testing.T) {
42
+			assert.Error(t, json.Unmarshal(data, &typeDurationTestStruct{}))
43
+		})
44
+	}
45
+}
46
+
47
+func (suite *TypeDurationTestSuite) TestUnmarshalOk() {
48
+	testData := map[string]time.Duration{
49
+		"1s":   time.Second,
50
+		"1m":   time.Minute,
51
+		"2h1s": 2*time.Hour + time.Second,
52
+	}
53
+
54
+	for k, v := range testData {
55
+		value := v
56
+
57
+		data, err := json.Marshal(map[string]string{
58
+			"value": k,
59
+		})
60
+		suite.NoError(err)
61
+
62
+		suite.T().Run(k, func(t *testing.T) {
63
+			testStruct := &typeDurationTestStruct{}
64
+
65
+			assert.NoError(t, json.Unmarshal(data, testStruct))
66
+			assert.Equal(t, value, testStruct.Value.Value(0))
67
+		})
68
+	}
69
+}
70
+
71
+func (suite *TypeDurationTestSuite) TestMarshalOk() {
72
+	testData := []string{
73
+		"1s",
74
+		"1m0s",
75
+		"2h0m1s",
76
+	}
77
+
78
+	for _, v := range testData {
79
+		name := v
80
+
81
+		data, err := json.Marshal(map[string]string{
82
+			"value": name,
83
+		})
84
+		suite.NoError(err)
85
+
86
+		suite.T().Run(name, func(t *testing.T) {
87
+			testStruct := &typeDurationTestStruct{}
88
+
89
+			assert.NoError(t, json.Unmarshal(data, testStruct))
90
+			assert.Equal(t, name, testStruct.Value.String())
91
+
92
+			marshalled, err := testStruct.Value.MarshalText()
93
+			assert.NoError(t, err)
94
+			assert.Equal(t, name, string(marshalled))
95
+		})
96
+	}
97
+}
98
+
99
+func (suite *TypeDurationTestSuite) TestValue() {
100
+	testStruct := &typeDurationTestStruct{}
101
+
102
+	suite.EqualValues(0, testStruct.Value.Value(0))
103
+	suite.Equal(time.Second, testStruct.Value.Value(time.Second))
104
+
105
+	data, err := json.Marshal(map[string]string{
106
+		"value": "1s",
107
+	})
108
+	suite.NoError(err)
109
+	suite.NoError(json.Unmarshal(data, testStruct))
110
+
111
+	suite.Equal(time.Second, testStruct.Value.Value(0))
112
+	suite.Equal(time.Second, testStruct.Value.Value(time.Minute))
113
+}
114
+
115
+func TestTypeDuration(t *testing.T) {
116
+	t.Parallel()
117
+	suite.Run(t, &TypeDurationTestSuite{})
118
+}

+ 43
- 0
internal/config/type_error_rate.go Прегледај датотеку

@@ -0,0 +1,43 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"strconv"
6
+)
7
+
8
+const typeErrorRateIgnoreLess = 1e-8
9
+
10
+type TypeErrorRate struct {
11
+	value float64
12
+}
13
+
14
+func (c *TypeErrorRate) UnmarshalJSON(data []byte) error {
15
+	value, err := strconv.ParseFloat(string(data), 64)
16
+	if err != nil {
17
+		return fmt.Errorf("incorrect float value: %w", err)
18
+	}
19
+
20
+	if value <= 0 || value >= 100 {
21
+		return fmt.Errorf("%f should be 0 < x < 100", value)
22
+	}
23
+
24
+	c.value = value
25
+
26
+	return nil
27
+}
28
+
29
+func (c *TypeErrorRate) MarshalText() ([]byte, error) {
30
+	return []byte(c.String()), nil
31
+}
32
+
33
+func (c TypeErrorRate) String() string {
34
+	return strconv.FormatFloat(c.value, 'f', -1, 64)
35
+}
36
+
37
+func (c TypeErrorRate) Value(defaultValue float64) float64 {
38
+	if c.value < typeErrorRateIgnoreLess {
39
+		return defaultValue
40
+	}
41
+
42
+	return c.value
43
+}

+ 125
- 0
internal/config/type_error_rate_test.go Прегледај датотеку

@@ -0,0 +1,125 @@
1
+package config_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"strconv"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type typeErrorRateTestStruct struct {
14
+	Value config.TypeErrorRate `json:"value"`
15
+}
16
+
17
+type TypeErrorRateTestSuite struct {
18
+	suite.Suite
19
+}
20
+
21
+func (suite *TypeErrorRateTestSuite) TestUnmarshalFail() {
22
+	testData := []float64{
23
+		1000,
24
+		-100,
25
+		-0.0001,
26
+	}
27
+
28
+	for _, v := range testData {
29
+		data, err := json.Marshal(map[string]float64{
30
+			"value": v,
31
+		})
32
+		suite.NoError(err)
33
+
34
+		suite.T().Run(strconv.FormatFloat(v, 'f', -1, 64), func(t *testing.T) {
35
+			assert.Error(t, json.Unmarshal(data, &typeErrorRateTestStruct{}))
36
+		})
37
+	}
38
+
39
+	data, err := json.Marshal(map[string]string{
40
+		"value": "hello",
41
+	})
42
+	suite.NoError(err)
43
+	suite.Error(json.Unmarshal(data, &typeErrorRateTestStruct{}))
44
+}
45
+
46
+func (suite *TypeErrorRateTestSuite) TestUnmarshalOk() {
47
+	testData := []float64{
48
+		1,
49
+		55.5,
50
+		0.0001,
51
+		1e-6,
52
+	}
53
+
54
+	for _, v := range testData {
55
+		value := v
56
+
57
+		data, err := json.Marshal(map[string]float64{
58
+			"value": v,
59
+		})
60
+		suite.NoError(err)
61
+
62
+		suite.T().Run(strconv.FormatFloat(v, 'f', -1, 64), func(t *testing.T) {
63
+			testStruct := &typeErrorRateTestStruct{}
64
+
65
+			assert.NoError(t, json.Unmarshal(data, testStruct))
66
+			assert.InEpsilon(t, value, testStruct.Value.Value(0), 1e-10)
67
+		})
68
+	}
69
+}
70
+
71
+func (suite *TypeErrorRateTestSuite) TestMarshalOk() {
72
+	testData := []float64{
73
+		1,
74
+		55.5,
75
+		0.0001,
76
+		1e-6,
77
+	}
78
+
79
+	for _, v := range testData {
80
+		value := v
81
+
82
+		data, err := json.Marshal(map[string]float64{
83
+			"value": v,
84
+		})
85
+		suite.NoError(err)
86
+
87
+		suite.T().Run(strconv.FormatFloat(v, 'f', -1, 64), func(t *testing.T) {
88
+			testStruct := &typeErrorRateTestStruct{}
89
+
90
+			assert.NoError(t, json.Unmarshal(data, testStruct))
91
+
92
+			parsed, err := strconv.ParseFloat(testStruct.Value.String(), 64)
93
+			assert.NoError(t, err)
94
+			assert.InEpsilon(t, value, parsed, 1e-10)
95
+
96
+			marshalled, err := testStruct.Value.MarshalText()
97
+			assert.NoError(t, err)
98
+
99
+			parsed, err = strconv.ParseFloat(string(marshalled), 64)
100
+			assert.NoError(t, err)
101
+			assert.InEpsilon(t, value, parsed, 1e-10)
102
+		})
103
+	}
104
+}
105
+
106
+func (suite *TypeErrorRateTestSuite) TestValue() {
107
+	testStruct := &typeErrorRateTestStruct{}
108
+
109
+	suite.InEpsilon(1, testStruct.Value.Value(1), 1e-10)
110
+	suite.InEpsilon(2, testStruct.Value.Value(2), 1e-10)
111
+
112
+	data, err := json.Marshal(map[string]float64{
113
+		"value": 1,
114
+	})
115
+	suite.NoError(err)
116
+	suite.NoError(json.Unmarshal(data, testStruct))
117
+
118
+	suite.InEpsilon(1, testStruct.Value.Value(2), 1e-10)
119
+	suite.InEpsilon(1, testStruct.Value.Value(3), 1e-10)
120
+}
121
+
122
+func TestTypeErrorRate(t *testing.T) {
123
+	t.Parallel()
124
+	suite.Run(t, &TypeErrorRateTestSuite{})
125
+}

+ 67
- 0
internal/config/type_hostport.go Прегледај датотеку

@@ -0,0 +1,67 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+	"strconv"
7
+)
8
+
9
+type TypeHostPort struct {
10
+	host TypeIP
11
+	port TypePort
12
+}
13
+
14
+func (c *TypeHostPort) UnmarshalText(data []byte) error {
15
+	if len(data) == 0 {
16
+		return nil
17
+	}
18
+
19
+	text := string(data)
20
+
21
+	host, port, err := net.SplitHostPort(text)
22
+	if err != nil {
23
+		return fmt.Errorf("incorrect host:port syntax: %w", err)
24
+	}
25
+
26
+	if port == "" {
27
+		return fmt.Errorf("port in %s host:port pair cannot be empty", text)
28
+	}
29
+
30
+	if err := c.port.UnmarshalJSON([]byte(port)); err != nil {
31
+		return fmt.Errorf("incorrect port in host:port: %w", err)
32
+	}
33
+
34
+	if err := c.host.UnmarshalText([]byte(host)); err != nil {
35
+		return fmt.Errorf("incorrect host: %w", err)
36
+	}
37
+
38
+	return nil
39
+}
40
+
41
+func (c TypeHostPort) MarshalText() ([]byte, error) {
42
+	return []byte(c.String()), nil
43
+}
44
+
45
+func (c TypeHostPort) String() string {
46
+	return c.Value(net.IP{}, 0)
47
+}
48
+
49
+func (c TypeHostPort) HostValue(defaultValue net.IP) net.IP {
50
+	return c.host.Value(defaultValue)
51
+}
52
+
53
+func (c TypeHostPort) PortValue(defaultValue uint) uint {
54
+	return c.port.Value(defaultValue)
55
+}
56
+
57
+func (c TypeHostPort) Value(defaultHostValue net.IP, defaultPortValue uint) string {
58
+	host := c.HostValue(defaultHostValue)
59
+	port := c.PortValue(defaultPortValue)
60
+
61
+	hostStr := ""
62
+	if len(host) > 0 {
63
+		hostStr = host.String()
64
+	}
65
+
66
+	return net.JoinHostPort(hostStr, strconv.Itoa(int(port)))
67
+}

+ 115
- 0
internal/config/type_hostport_test.go Прегледај датотеку

@@ -0,0 +1,115 @@
1
+package config_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"net"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type typeHostPortTestStruct struct {
14
+	Value config.TypeHostPort `json:"value"`
15
+}
16
+
17
+type TypeHostPortTestSuite struct {
18
+	suite.Suite
19
+}
20
+
21
+func (suite *TypeHostPortTestSuite) TestUnmarshalFail() {
22
+	testData := []string{
23
+		"10.0.0.10:aaa",
24
+		"10.0.0.10:",
25
+		":",
26
+		"xxx",
27
+		"xxx:80",
28
+	}
29
+
30
+	for _, v := range testData {
31
+		data, err := json.Marshal(map[string]string{
32
+			"value": v,
33
+		})
34
+		suite.NoError(err)
35
+
36
+		suite.T().Run(v, func(t *testing.T) {
37
+			assert.Error(t, json.Unmarshal(data, &typeHostPortTestStruct{}))
38
+		})
39
+	}
40
+}
41
+
42
+func (suite *TypeHostPortTestSuite) TestUnmarshalOk() {
43
+	testData := []string{
44
+		"10.0.0.10:80",
45
+		"0.0.0.0:80",
46
+		":8000",
47
+	}
48
+
49
+	for _, v := range testData {
50
+		value := v
51
+
52
+		data, err := json.Marshal(map[string]string{
53
+			"value": v,
54
+		})
55
+		suite.NoError(err)
56
+
57
+		suite.T().Run(v, func(t *testing.T) {
58
+			testStruct := &typeHostPortTestStruct{}
59
+
60
+			assert.NoError(t, json.Unmarshal(data, testStruct))
61
+			assert.EqualValues(t, value, testStruct.Value.Value(nil, 0))
62
+		})
63
+	}
64
+}
65
+
66
+func (suite *TypeHostPortTestSuite) TestMarshalOk() {
67
+	testData := []string{
68
+		"10.0.0.10:80",
69
+		"0.0.0.0:80",
70
+		":8000",
71
+	}
72
+
73
+	for _, v := range testData {
74
+		value := v
75
+
76
+		data, err := json.Marshal(map[string]string{
77
+			"value": v,
78
+		})
79
+		suite.NoError(err)
80
+
81
+		suite.T().Run(v, func(t *testing.T) {
82
+			testStruct := &typeHostPortTestStruct{}
83
+
84
+			assert.NoError(t, json.Unmarshal(data, testStruct))
85
+			assert.Equal(t, value, testStruct.Value.String())
86
+
87
+			marshalled, err := testStruct.Value.MarshalText()
88
+			assert.NoError(t, err)
89
+			assert.Equal(t, value, string(marshalled))
90
+		})
91
+	}
92
+}
93
+
94
+func (suite *TypeHostPortTestSuite) TestValue() {
95
+	testStruct := &typeHostPortTestStruct{}
96
+
97
+	suite.EqualValues("127.0.0.1:80",
98
+		testStruct.Value.Value(net.ParseIP("127.0.0.1"), 80))
99
+	suite.EqualValues("127.1.0.1:80",
100
+		testStruct.Value.Value(net.ParseIP("127.1.0.1"), 80))
101
+
102
+	data, err := json.Marshal(map[string]string{
103
+		"value": "127.0.0.1:80",
104
+	})
105
+	suite.NoError(err)
106
+	suite.NoError(json.Unmarshal(data, testStruct))
107
+
108
+	suite.EqualValues("127.0.0.1:80", testStruct.Value.Value(nil, 0))
109
+	suite.EqualValues("127.0.0.1:80", testStruct.Value.Value(net.ParseIP("10.0.0.10"), 3000))
110
+}
111
+
112
+func TestTypeHostPort(t *testing.T) {
113
+	t.Parallel()
114
+	suite.Run(t, &TypeHostPortTestSuite{})
115
+}

+ 35
- 0
internal/config/type_http_path.go Прегледај датотеку

@@ -0,0 +1,35 @@
1
+package config
2
+
3
+import "strings"
4
+
5
+type TypeHTTPPath struct {
6
+	value string
7
+}
8
+
9
+func (c *TypeHTTPPath) UnmarshalText(data []byte) error {
10
+	if len(data) > 0 {
11
+		c.value = "/" + strings.Trim(string(data), "/")
12
+	}
13
+
14
+	return nil
15
+}
16
+
17
+func (c TypeHTTPPath) MarshalText() ([]byte, error) {
18
+	return []byte(c.String()), nil
19
+}
20
+
21
+func (c TypeHTTPPath) String() string {
22
+	if c.value == "" {
23
+		return "/"
24
+	}
25
+
26
+	return c.value
27
+}
28
+
29
+func (c TypeHTTPPath) Value(defaultValue string) string {
30
+	if c.value == "" {
31
+		return defaultValue
32
+	}
33
+
34
+	return c.value
35
+}

+ 91
- 0
internal/config/type_http_path_test.go Прегледај датотеку

@@ -0,0 +1,91 @@
1
+package config_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/config"
8
+	"github.com/stretchr/testify/assert"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type typeHTTPPathTestStruct struct {
13
+	Value config.TypeHTTPPath `json:"value"`
14
+}
15
+
16
+type TypeHTTPPathTestSuite struct {
17
+	suite.Suite
18
+}
19
+
20
+func (suite *TypeHTTPPathTestSuite) TestUnmarshal() {
21
+	testData := []string{
22
+		"/hello",
23
+		"hello",
24
+		"hello/",
25
+		"/hello/",
26
+	}
27
+
28
+	for _, v := range testData {
29
+		data, err := json.Marshal(map[string]string{
30
+			"value": v,
31
+		})
32
+		suite.NoError(err)
33
+
34
+		suite.T().Run(v, func(t *testing.T) {
35
+			testStruct := &typeHTTPPathTestStruct{}
36
+
37
+			assert.NoError(t, json.Unmarshal(data, testStruct))
38
+			assert.Equal(t, "/hello", testStruct.Value.Value(""))
39
+		})
40
+	}
41
+}
42
+
43
+func (suite *TypeHTTPPathTestSuite) TestMarshalOk() {
44
+	testData := map[string]string{
45
+		"":        "/",
46
+		"/hello":  "/hello",
47
+		"/hello/": "/hello",
48
+		"hello/":  "/hello",
49
+		"hello":   "/hello",
50
+	}
51
+
52
+	for k, v := range testData {
53
+		toPass := k
54
+		compareWith := v
55
+
56
+		data, err := json.Marshal(map[string]string{
57
+			"value": toPass,
58
+		})
59
+		suite.NoError(err)
60
+
61
+		suite.T().Run(toPass, func(t *testing.T) {
62
+			testStruct := &typeHTTPPathTestStruct{}
63
+
64
+			assert.NoError(t, json.Unmarshal(data, testStruct))
65
+			assert.Equal(t, compareWith, testStruct.Value.String())
66
+
67
+			marshalled, err := testStruct.Value.MarshalText()
68
+			assert.NoError(t, err)
69
+			assert.Equal(t, compareWith, string(marshalled))
70
+		})
71
+	}
72
+}
73
+
74
+func (suite *TypeHTTPPathTestSuite) TestValue() {
75
+	testStruct := &typeHTTPPathTestStruct{}
76
+
77
+	suite.Equal("/hello", testStruct.Value.Value("/hello"))
78
+
79
+	data, err := json.Marshal(map[string]string{
80
+		"value": "/map",
81
+	})
82
+	suite.NoError(err)
83
+	suite.NoError(json.Unmarshal(data, testStruct))
84
+
85
+	suite.Equal("/map", testStruct.Value.Value("/hello"))
86
+}
87
+
88
+func TestTypeHTTPPath(t *testing.T) {
89
+	t.Parallel()
90
+	suite.Run(t, &TypeHTTPPathTestSuite{})
91
+}

+ 45
- 0
internal/config/type_ip.go Прегледај датотеку

@@ -0,0 +1,45 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+)
7
+
8
+type TypeIP struct {
9
+	value net.IP
10
+}
11
+
12
+func (c *TypeIP) UnmarshalText(data []byte) error {
13
+	if len(data) == 0 {
14
+		return nil
15
+	}
16
+
17
+	ip := net.ParseIP(string(data))
18
+	if ip == nil {
19
+		return fmt.Errorf("incorrect ip address: %s", string(data))
20
+	}
21
+
22
+	c.value = ip
23
+
24
+	return nil
25
+}
26
+
27
+func (c *TypeIP) MarshalText() ([]byte, error) {
28
+	return []byte(c.String()), nil
29
+}
30
+
31
+func (c TypeIP) String() string {
32
+	if len(c.value) > 0 {
33
+		return c.value.String()
34
+	}
35
+
36
+	return ""
37
+}
38
+
39
+func (c TypeIP) Value(defaultValue net.IP) net.IP {
40
+	if c.value == nil {
41
+		return defaultValue
42
+	}
43
+
44
+	return c.value
45
+}

+ 115
- 0
internal/config/type_ip_test.go Прегледај датотеку

@@ -0,0 +1,115 @@
1
+package config_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"net"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type typeIPTestStruct struct {
14
+	Value config.TypeIP `json:"value"`
15
+}
16
+
17
+type TypeIPTestSuite struct {
18
+	suite.Suite
19
+}
20
+
21
+func (suite *TypeIPTestSuite) TestUnmarshalFail() {
22
+	testData := []string{
23
+		"0.0.10",
24
+		"10.0.0.10:",
25
+		"xxx:80",
26
+		"2001:0db8:85a3:0000:0000:8a2e:4",
27
+	}
28
+
29
+	for _, v := range testData {
30
+		data, err := json.Marshal(map[string]string{
31
+			"value": v,
32
+		})
33
+		suite.NoError(err)
34
+
35
+		suite.T().Run(v, func(t *testing.T) {
36
+			assert.Error(t, json.Unmarshal(data, &typeIPTestStruct{}))
37
+		})
38
+	}
39
+}
40
+
41
+func (suite *TypeIPTestSuite) TestUnmarshalOk() {
42
+	testData := []string{
43
+		"0.0.0.0",
44
+		"10.0.0.10",
45
+		"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
46
+	}
47
+
48
+	for _, v := range testData {
49
+		value := v
50
+
51
+		data, err := json.Marshal(map[string]string{
52
+			"value": v,
53
+		})
54
+		suite.NoError(err)
55
+
56
+		suite.T().Run(v, func(t *testing.T) {
57
+			testStruct := &typeIPTestStruct{}
58
+
59
+			assert.NoError(t, json.Unmarshal(data, testStruct))
60
+			assert.Equal(t,
61
+				net.ParseIP(value).String(),
62
+				testStruct.Value.Value(nil).String())
63
+		})
64
+	}
65
+}
66
+
67
+func (suite *TypeIPTestSuite) TestMarshalOk() {
68
+	testData := []string{
69
+		"0.0.0.0",
70
+		"10.0.0.10",
71
+		"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
72
+	}
73
+
74
+	for _, v := range testData {
75
+		value := net.ParseIP(v).String()
76
+
77
+		data, err := json.Marshal(map[string]string{
78
+			"value": v,
79
+		})
80
+		suite.NoError(err)
81
+
82
+		suite.T().Run(v, func(t *testing.T) {
83
+			testStruct := &typeIPTestStruct{}
84
+
85
+			assert.NoError(t, json.Unmarshal(data, testStruct))
86
+			assert.Equal(t, value, testStruct.Value.String())
87
+
88
+			marshalled, err := testStruct.Value.MarshalText()
89
+			assert.NoError(t, err)
90
+			assert.Equal(t, value, string(marshalled))
91
+		})
92
+	}
93
+}
94
+
95
+func (suite *TypeIPTestSuite) TestValue() {
96
+	testStruct := &typeIPTestStruct{}
97
+	suite.Empty(testStruct.Value.String())
98
+
99
+	suite.Nil(testStruct.Value.Value(nil))
100
+	suite.Equal("127.1.0.1", testStruct.Value.Value(net.ParseIP("127.1.0.1")).String())
101
+
102
+	data, err := json.Marshal(map[string]string{
103
+		"value": "127.0.0.1",
104
+	})
105
+	suite.NoError(err)
106
+	suite.NoError(json.Unmarshal(data, testStruct))
107
+
108
+	suite.Equal("127.0.0.1", testStruct.Value.Value(nil).String())
109
+	suite.Equal("127.0.0.1", testStruct.Value.Value(net.ParseIP("10.0.0.10")).String())
110
+}
111
+
112
+func TestTypeIP(t *testing.T) {
113
+	t.Parallel()
114
+	suite.Run(t, &TypeIPTestSuite{})
115
+}

+ 41
- 0
internal/config/type_metric_prefix.go Прегледај датотеку

@@ -0,0 +1,41 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"regexp"
6
+)
7
+
8
+type TypeMetricPrefix struct {
9
+	value string
10
+}
11
+
12
+func (c *TypeMetricPrefix) UnmarshalText(data []byte) error {
13
+	if len(data) == 0 {
14
+		return nil
15
+	}
16
+
17
+	prefix := string(data)
18
+	if ok, err := regexp.MatchString("^[a-z0-9]+$", prefix); !ok || err != nil {
19
+		return fmt.Errorf("incorrect metric prefix: %s", prefix)
20
+	}
21
+
22
+	c.value = prefix
23
+
24
+	return nil
25
+}
26
+
27
+func (c TypeMetricPrefix) MarshalText() ([]byte, error) {
28
+	return []byte(c.String()), nil
29
+}
30
+
31
+func (c TypeMetricPrefix) String() string {
32
+	return c.value
33
+}
34
+
35
+func (c TypeMetricPrefix) Value(defaultValue string) string {
36
+	if c.value == "" {
37
+		return defaultValue
38
+	}
39
+
40
+	return c.value
41
+}

+ 115
- 0
internal/config/type_metric_prefix_test.go Прегледај датотеку

@@ -0,0 +1,115 @@
1
+package config_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"testing"
6
+
7
+	"github.com/9seconds/mtg/v2/internal/config"
8
+	"github.com/stretchr/testify/assert"
9
+	"github.com/stretchr/testify/suite"
10
+)
11
+
12
+type typeMetricPrefixTestStruct struct {
13
+	Value config.TypeMetricPrefix `json:"value"`
14
+}
15
+
16
+type TypeMetricPrefixTestSuite struct {
17
+	suite.Suite
18
+}
19
+
20
+func (suite *TypeMetricPrefixTestSuite) TestUnmarshalNil() {
21
+	typ := &config.TypeMetricPrefix{}
22
+	suite.NoError(typ.UnmarshalText(nil))
23
+	suite.Empty(typ.String())
24
+}
25
+
26
+func (suite *TypeMetricPrefixTestSuite) TestUnmarshalFail() {
27
+	testData := []string{
28
+		"aaa.aaa",
29
+		"aaa-bbb",
30
+		"aaa:ccc",
31
+		"metric prefix",
32
+	}
33
+
34
+	for _, v := range testData {
35
+		data, err := json.Marshal(map[string]string{
36
+			"value": v,
37
+		})
38
+		suite.NoError(err)
39
+
40
+		suite.T().Run(v, func(t *testing.T) {
41
+			assert.Error(t, json.Unmarshal(data, &typeMetricPrefixTestStruct{}))
42
+		})
43
+	}
44
+}
45
+
46
+func (suite *TypeMetricPrefixTestSuite) TestUnmarshalOk() {
47
+	testData := []string{
48
+		"mtg",
49
+		"mtg111",
50
+	}
51
+
52
+	for _, v := range testData {
53
+		value := v
54
+
55
+		data, err := json.Marshal(map[string]string{
56
+			"value": v,
57
+		})
58
+		suite.NoError(err)
59
+
60
+		suite.T().Run(v, func(t *testing.T) {
61
+			testStruct := &typeMetricPrefixTestStruct{}
62
+
63
+			assert.NoError(t, json.Unmarshal(data, testStruct))
64
+			assert.Equal(t, value, testStruct.Value.Value(""))
65
+		})
66
+	}
67
+}
68
+
69
+func (suite *TypeMetricPrefixTestSuite) TestMarshalOk() {
70
+	testData := []string{
71
+		"mtg",
72
+		"mtg111",
73
+	}
74
+
75
+	for _, v := range testData {
76
+		value := v
77
+
78
+		data, err := json.Marshal(map[string]string{
79
+			"value": v,
80
+		})
81
+		suite.NoError(err)
82
+
83
+		suite.T().Run(v, func(t *testing.T) {
84
+			testStruct := &typeMetricPrefixTestStruct{}
85
+
86
+			assert.NoError(t, json.Unmarshal(data, testStruct))
87
+			assert.Equal(t, value, testStruct.Value.String())
88
+
89
+			marshalled, err := testStruct.Value.MarshalText()
90
+			assert.NoError(t, err)
91
+			assert.Equal(t, value, string(marshalled))
92
+		})
93
+	}
94
+}
95
+
96
+func (suite *TypeMetricPrefixTestSuite) TestValue() {
97
+	testStruct := &typeMetricPrefixTestStruct{}
98
+
99
+	suite.Equal("mtg", testStruct.Value.Value("mtg"))
100
+	suite.Equal("vvv", testStruct.Value.Value("vvv"))
101
+
102
+	data, err := json.Marshal(map[string]string{
103
+		"value": "aaa",
104
+	})
105
+	suite.NoError(err)
106
+	suite.NoError(json.Unmarshal(data, testStruct))
107
+
108
+	suite.Equal("aaa", testStruct.Value.Value("mtg"))
109
+	suite.Equal("aaa", testStruct.Value.Value("vvv"))
110
+}
111
+
112
+func TestTypeMetricPrefix(t *testing.T) {
113
+	t.Parallel()
114
+	suite.Run(t, &TypeMetricPrefixTestSuite{})
115
+}

+ 45
- 0
internal/config/type_port.go Прегледај датотеку

@@ -0,0 +1,45 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"strconv"
6
+)
7
+
8
+type TypePort struct {
9
+	value uint
10
+}
11
+
12
+func (c *TypePort) UnmarshalJSON(data []byte) error {
13
+	if len(data) == 0 {
14
+		return nil
15
+	}
16
+
17
+	intValue, err := strconv.ParseUint(string(data), 10, 64)
18
+	if err != nil {
19
+		return fmt.Errorf("port number is not a number: %w", err)
20
+	}
21
+
22
+	if intValue == 0 || intValue >= 65536 {
23
+		return fmt.Errorf("port number should be 0 < portNo < 65536: %d", intValue)
24
+	}
25
+
26
+	c.value = uint(intValue)
27
+
28
+	return nil
29
+}
30
+
31
+func (c *TypePort) MarshalJSON() ([]byte, error) {
32
+	return []byte(c.String()), nil
33
+}
34
+
35
+func (c TypePort) String() string {
36
+	return strconv.Itoa(int(c.value))
37
+}
38
+
39
+func (c TypePort) Value(defaultValue uint) uint {
40
+	if c.value == 0 {
41
+		return defaultValue
42
+	}
43
+
44
+	return c.value
45
+}

+ 117
- 0
internal/config/type_port_test.go Прегледај датотеку

@@ -0,0 +1,117 @@
1
+package config_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"strconv"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type typePortTestStruct struct {
14
+	Value config.TypePort `json:"value"`
15
+}
16
+
17
+type TypePortTestSuite struct {
18
+	suite.Suite
19
+}
20
+
21
+func (suite *TypePortTestSuite) TestUnmarshalNil() {
22
+	typ := &config.TypePort{}
23
+	suite.NoError(typ.UnmarshalJSON(nil))
24
+	suite.Equal("0", typ.String())
25
+}
26
+
27
+func (suite *TypePortTestSuite) TestUnmarshalFail() {
28
+	testData := []int{
29
+		-1,
30
+		1_000_000,
31
+	}
32
+
33
+	for _, v := range testData {
34
+		data, err := json.Marshal(map[string]int{
35
+			"value": v,
36
+		})
37
+		suite.NoError(err)
38
+
39
+		suite.T().Run(strconv.Itoa(v), func(t *testing.T) {
40
+			assert.Error(t, json.Unmarshal(data, &typePortTestStruct{}))
41
+		})
42
+	}
43
+}
44
+
45
+func (suite *TypePortTestSuite) TestUnmarshalOk() {
46
+	testData := []int{
47
+		1,
48
+		1_000,
49
+		65535,
50
+	}
51
+
52
+	for _, v := range testData {
53
+		value := v
54
+
55
+		data, err := json.Marshal(map[string]int{
56
+			"value": v,
57
+		})
58
+		suite.NoError(err)
59
+
60
+		suite.T().Run(strconv.Itoa(v), func(t *testing.T) {
61
+			testStruct := &typePortTestStruct{}
62
+
63
+			assert.NoError(t, json.Unmarshal(data, testStruct))
64
+			assert.EqualValues(t, value, testStruct.Value.Value(0))
65
+		})
66
+	}
67
+}
68
+
69
+func (suite *TypePortTestSuite) TestMarshalOk() {
70
+	testData := map[string]int{
71
+		"1":     1,
72
+		"1000":  1000,
73
+		"65535": 65535,
74
+	}
75
+
76
+	for k, v := range testData {
77
+		name := k
78
+		value := v
79
+
80
+		data, err := json.Marshal(map[string]int{
81
+			"value": value,
82
+		})
83
+		suite.NoError(err)
84
+
85
+		suite.T().Run(name, func(t *testing.T) {
86
+			testStruct := &typePortTestStruct{}
87
+
88
+			assert.NoError(t, json.Unmarshal(data, testStruct))
89
+			assert.Equal(t, name, testStruct.Value.String())
90
+
91
+			marshalled, err := testStruct.Value.MarshalJSON()
92
+			assert.NoError(t, err)
93
+			assert.Equal(t, name, string(marshalled))
94
+		})
95
+	}
96
+}
97
+
98
+func (suite *TypePortTestSuite) TestValue() {
99
+	testStruct := &typePortTestStruct{}
100
+
101
+	suite.EqualValues(0, testStruct.Value.Value(0))
102
+	suite.EqualValues(1, testStruct.Value.Value(1))
103
+
104
+	data, err := json.Marshal(map[string]int{
105
+		"value": 5,
106
+	})
107
+	suite.NoError(err)
108
+	suite.NoError(json.Unmarshal(data, testStruct))
109
+
110
+	suite.EqualValues(5, testStruct.Value.Value(0))
111
+	suite.EqualValues(5, testStruct.Value.Value(1))
112
+}
113
+
114
+func TestTypePort(t *testing.T) {
115
+	t.Parallel()
116
+	suite.Run(t, &TypePortTestSuite{})
117
+}

+ 50
- 0
internal/config/type_prefer_ip.go Прегледај датотеку

@@ -0,0 +1,50 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"strings"
6
+)
7
+
8
+const (
9
+	TypePreferIPPreferIPv4 = "prefer-ipv4"
10
+	TypePreferIPPreferIPv6 = "prefer-ipv6"
11
+	TypePreferOnlyIPv4     = "only-ipv4"
12
+	TypePreferOnlyIPv6     = "only-ipv6"
13
+)
14
+
15
+type TypePreferIP struct {
16
+	value string
17
+}
18
+
19
+func (c *TypePreferIP) UnmarshalText(data []byte) error {
20
+	if len(data) == 0 {
21
+		return nil
22
+	}
23
+
24
+	text := strings.ToLower(string(data))
25
+
26
+	switch text {
27
+	case TypePreferIPPreferIPv4, TypePreferIPPreferIPv6, TypePreferOnlyIPv4, TypePreferOnlyIPv6:
28
+		c.value = text
29
+	default:
30
+		return fmt.Errorf("incorrect prefer-ip value: %s", string(data))
31
+	}
32
+
33
+	return nil
34
+}
35
+
36
+func (c TypePreferIP) MarshalText() ([]byte, error) {
37
+	return []byte(c.value), nil
38
+}
39
+
40
+func (c *TypePreferIP) String() string {
41
+	return c.value
42
+}
43
+
44
+func (c *TypePreferIP) Value(defaultValue string) string {
45
+	if c.value == "" {
46
+		return defaultValue
47
+	}
48
+
49
+	return c.value
50
+}

+ 142
- 0
internal/config/type_prefer_ip_test.go Прегледај датотеку

@@ -0,0 +1,142 @@
1
+package config_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"strings"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type typePreferIPTestStruct struct {
14
+	Value config.TypePreferIP `json:"value"`
15
+}
16
+
17
+type TypePreferIPTestSuite struct {
18
+	suite.Suite
19
+}
20
+
21
+func (suite *TypePreferIPTestSuite) TestUnmarshalNil() {
22
+	typ := &config.TypePreferIP{}
23
+	suite.NoError(typ.UnmarshalText(nil))
24
+	suite.Empty(typ.String())
25
+}
26
+
27
+func (suite *TypePreferIPTestSuite) TestUnmarshalFail() {
28
+	testData := []string{
29
+		"p",
30
+		"ipv4",
31
+		"onlyipv4",
32
+		"ipv6prefer",
33
+	}
34
+
35
+	for _, v := range testData {
36
+		data, err := json.Marshal(map[string]string{
37
+			"value": v,
38
+		})
39
+		suite.NoError(err)
40
+
41
+		suite.T().Run(v, func(t *testing.T) {
42
+			assert.Error(t, json.Unmarshal(data, &typePreferIPTestStruct{}))
43
+		})
44
+	}
45
+}
46
+
47
+func (suite *TypePreferIPTestSuite) TestUnmarshalOk() {
48
+	testData := []string{
49
+		config.TypePreferIPPreferIPv4,
50
+		config.TypePreferIPPreferIPv6,
51
+		config.TypePreferOnlyIPv4,
52
+		config.TypePreferOnlyIPv6,
53
+		strings.ToUpper(config.TypePreferIPPreferIPv4),
54
+		strings.ToUpper(config.TypePreferIPPreferIPv6),
55
+		strings.ToUpper(config.TypePreferOnlyIPv4),
56
+		strings.ToUpper(config.TypePreferOnlyIPv6),
57
+		strings.ToLower(config.TypePreferIPPreferIPv4),
58
+		strings.ToLower(config.TypePreferIPPreferIPv6),
59
+		strings.ToLower(config.TypePreferOnlyIPv4),
60
+		strings.ToLower(config.TypePreferOnlyIPv6),
61
+	}
62
+
63
+	for _, v := range testData {
64
+		value := v
65
+
66
+		data, err := json.Marshal(map[string]string{
67
+			"value": v,
68
+		})
69
+		suite.NoError(err)
70
+
71
+		suite.T().Run(v, func(t *testing.T) {
72
+			testStruct := &typePreferIPTestStruct{}
73
+
74
+			assert.NoError(t, json.Unmarshal(data, testStruct))
75
+			assert.EqualValues(t,
76
+				strings.ToLower(value),
77
+				testStruct.Value.Value(config.TypePreferIPPreferIPv4))
78
+		})
79
+	}
80
+}
81
+
82
+func (suite *TypePreferIPTestSuite) TestMarshalOk() {
83
+	testData := []string{
84
+		config.TypePreferIPPreferIPv4,
85
+		config.TypePreferIPPreferIPv6,
86
+		config.TypePreferOnlyIPv4,
87
+		config.TypePreferOnlyIPv6,
88
+		strings.ToUpper(config.TypePreferIPPreferIPv4),
89
+		strings.ToUpper(config.TypePreferIPPreferIPv6),
90
+		strings.ToUpper(config.TypePreferOnlyIPv4),
91
+		strings.ToUpper(config.TypePreferOnlyIPv6),
92
+		strings.ToLower(config.TypePreferIPPreferIPv4),
93
+		strings.ToLower(config.TypePreferIPPreferIPv6),
94
+		strings.ToLower(config.TypePreferOnlyIPv4),
95
+		strings.ToLower(config.TypePreferOnlyIPv6),
96
+	}
97
+
98
+	for _, v := range testData {
99
+		value := v
100
+
101
+		data, err := json.Marshal(map[string]string{
102
+			"value": v,
103
+		})
104
+		suite.NoError(err)
105
+
106
+		suite.T().Run(v, func(t *testing.T) {
107
+			testStruct := &typePreferIPTestStruct{}
108
+
109
+			assert.NoError(t, json.Unmarshal(data, testStruct))
110
+			assert.Equal(t, strings.ToLower(value), testStruct.Value.String())
111
+
112
+			marshalled, err := testStruct.Value.MarshalText()
113
+			assert.NoError(t, err)
114
+			assert.Equal(t, strings.ToLower(value), string(marshalled))
115
+		})
116
+	}
117
+}
118
+
119
+func (suite *TypePreferIPTestSuite) TestValue() {
120
+	testStruct := &typePreferIPTestStruct{}
121
+
122
+	suite.EqualValues(config.TypePreferIPPreferIPv4,
123
+		testStruct.Value.Value(config.TypePreferIPPreferIPv4))
124
+	suite.EqualValues(config.TypePreferIPPreferIPv6,
125
+		testStruct.Value.Value(config.TypePreferIPPreferIPv6))
126
+
127
+	data, err := json.Marshal(map[string]string{
128
+		"value": config.TypePreferOnlyIPv4,
129
+	})
130
+	suite.NoError(err)
131
+	suite.NoError(json.Unmarshal(data, testStruct))
132
+
133
+	suite.EqualValues(config.TypePreferOnlyIPv4,
134
+		testStruct.Value.Value(config.TypePreferOnlyIPv6))
135
+	suite.EqualValues(config.TypePreferOnlyIPv4,
136
+		testStruct.Value.Value(config.TypePreferIPPreferIPv6))
137
+}
138
+
139
+func TestTypePreferIP(t *testing.T) {
140
+	t.Parallel()
141
+	suite.Run(t, &TypePreferIPTestSuite{})
142
+}

+ 49
- 0
internal/config/type_statsd_tag_format.go Прегледај датотеку

@@ -0,0 +1,49 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"strings"
6
+)
7
+
8
+const (
9
+	TypeStatsdTagFormatInfluxdb = "influxdb"
10
+	TypeStatsdTagFormatDatadog  = "datadog"
11
+	TypeStatsdTagFormatGraphite = "graphite"
12
+)
13
+
14
+type TypeStatsdTagFormat struct {
15
+	value string
16
+}
17
+
18
+func (c *TypeStatsdTagFormat) UnmarshalText(data []byte) error {
19
+	if len(data) == 0 {
20
+		return nil
21
+	}
22
+
23
+	text := strings.ToLower(string(data))
24
+
25
+	switch text {
26
+	case TypeStatsdTagFormatInfluxdb, TypeStatsdTagFormatDatadog, TypeStatsdTagFormatGraphite:
27
+		c.value = text
28
+	default:
29
+		return fmt.Errorf("incorrect tag format value: %s", string(data))
30
+	}
31
+
32
+	return nil
33
+}
34
+
35
+func (c TypeStatsdTagFormat) MarshalText() ([]byte, error) {
36
+	return []byte(c.value), nil
37
+}
38
+
39
+func (c *TypeStatsdTagFormat) String() string {
40
+	return c.value
41
+}
42
+
43
+func (c *TypeStatsdTagFormat) Value(defaultValue string) string {
44
+	if c.value == "" {
45
+		return defaultValue
46
+	}
47
+
48
+	return c.value
49
+}

+ 136
- 0
internal/config/type_statsd_tag_format_test.go Прегледај датотеку

@@ -0,0 +1,136 @@
1
+package config_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"strings"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type typeStatsdTagFormatTestStruct struct {
14
+	Value config.TypeStatsdTagFormat `json:"value"`
15
+}
16
+
17
+type TypeStatsdTagFormatTestSuite struct {
18
+	suite.Suite
19
+}
20
+
21
+func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalNil() {
22
+	typ := &config.TypeStatsdTagFormat{}
23
+	suite.NoError(typ.UnmarshalText(nil))
24
+	suite.Equal("lalala", typ.Value("lalala"))
25
+}
26
+
27
+func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalFail() {
28
+	testData := []string{
29
+		"p",
30
+		"ipv4",
31
+		"onlyipv4",
32
+		"ipv6prefer",
33
+	}
34
+
35
+	for _, v := range testData {
36
+		data, err := json.Marshal(map[string]string{
37
+			"value": v,
38
+		})
39
+		suite.NoError(err)
40
+
41
+		suite.T().Run(v, func(t *testing.T) {
42
+			assert.Error(t, json.Unmarshal(data, &typeStatsdTagFormatTestStruct{}))
43
+		})
44
+	}
45
+}
46
+
47
+func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalOk() {
48
+	testData := []string{
49
+		config.TypeStatsdTagFormatDatadog,
50
+		config.TypeStatsdTagFormatInfluxdb,
51
+		config.TypeStatsdTagFormatGraphite,
52
+		strings.ToUpper(config.TypeStatsdTagFormatDatadog),
53
+		strings.ToUpper(config.TypeStatsdTagFormatInfluxdb),
54
+		strings.ToUpper(config.TypeStatsdTagFormatGraphite),
55
+		strings.ToLower(config.TypeStatsdTagFormatDatadog),
56
+		strings.ToLower(config.TypeStatsdTagFormatInfluxdb),
57
+		strings.ToLower(config.TypeStatsdTagFormatGraphite),
58
+	}
59
+
60
+	for _, v := range testData {
61
+		value := v
62
+
63
+		data, err := json.Marshal(map[string]string{
64
+			"value": v,
65
+		})
66
+		suite.NoError(err)
67
+
68
+		suite.T().Run(v, func(t *testing.T) {
69
+			testStruct := &typeStatsdTagFormatTestStruct{}
70
+
71
+			assert.NoError(t, json.Unmarshal(data, testStruct))
72
+			assert.EqualValues(t,
73
+				strings.ToLower(value),
74
+				testStruct.Value.Value(config.TypeStatsdTagFormatDatadog))
75
+		})
76
+	}
77
+}
78
+
79
+func (suite *TypeStatsdTagFormatTestSuite) TestMarshalOk() {
80
+	testData := []string{
81
+		config.TypeStatsdTagFormatDatadog,
82
+		config.TypeStatsdTagFormatInfluxdb,
83
+		config.TypeStatsdTagFormatGraphite,
84
+		strings.ToUpper(config.TypeStatsdTagFormatDatadog),
85
+		strings.ToUpper(config.TypeStatsdTagFormatInfluxdb),
86
+		strings.ToUpper(config.TypeStatsdTagFormatGraphite),
87
+		strings.ToLower(config.TypeStatsdTagFormatDatadog),
88
+		strings.ToLower(config.TypeStatsdTagFormatInfluxdb),
89
+		strings.ToLower(config.TypeStatsdTagFormatGraphite),
90
+	}
91
+
92
+	for _, v := range testData {
93
+		value := v
94
+
95
+		data, err := json.Marshal(map[string]string{
96
+			"value": v,
97
+		})
98
+		suite.NoError(err)
99
+
100
+		suite.T().Run(v, func(t *testing.T) {
101
+			testStruct := &typeStatsdTagFormatTestStruct{}
102
+
103
+			assert.NoError(t, json.Unmarshal(data, testStruct))
104
+			assert.Equal(t, strings.ToLower(value), testStruct.Value.String())
105
+
106
+			marshalled, err := testStruct.Value.MarshalText()
107
+			assert.NoError(t, err)
108
+			assert.Equal(t, strings.ToLower(value), string(marshalled))
109
+		})
110
+	}
111
+}
112
+
113
+func (suite *TypeStatsdTagFormatTestSuite) TestValue() {
114
+	testStruct := &typePreferIPTestStruct{}
115
+
116
+	suite.EqualValues(config.TypePreferIPPreferIPv4,
117
+		testStruct.Value.Value(config.TypePreferIPPreferIPv4))
118
+	suite.EqualValues(config.TypePreferIPPreferIPv6,
119
+		testStruct.Value.Value(config.TypePreferIPPreferIPv6))
120
+
121
+	data, err := json.Marshal(map[string]string{
122
+		"value": config.TypePreferOnlyIPv4,
123
+	})
124
+	suite.NoError(err)
125
+	suite.NoError(json.Unmarshal(data, testStruct))
126
+
127
+	suite.EqualValues(config.TypePreferOnlyIPv4,
128
+		testStruct.Value.Value(config.TypePreferOnlyIPv6))
129
+	suite.EqualValues(config.TypePreferOnlyIPv4,
130
+		testStruct.Value.Value(config.TypePreferIPPreferIPv6))
131
+}
132
+
133
+func TestTypeStatsdTagFormat(t *testing.T) {
134
+	t.Parallel()
135
+	suite.Run(t, &TypeStatsdTagFormatTestSuite{})
136
+}

+ 71
- 0
internal/config/type_url.go Прегледај датотеку

@@ -0,0 +1,71 @@
1
+package config
2
+
3
+import (
4
+	"fmt"
5
+	"net"
6
+	"net/url"
7
+)
8
+
9
+type TypeURL struct {
10
+	value *url.URL
11
+}
12
+
13
+func (c *TypeURL) UnmarshalText(data []byte) error { // nolint: cyclop
14
+	if len(data) == 0 {
15
+		return nil
16
+	}
17
+
18
+	value, err := url.Parse(string(data))
19
+	if err != nil {
20
+		return fmt.Errorf("incorrect URL: %w", err)
21
+	}
22
+
23
+	switch value.Scheme {
24
+	case "http", "https", "socks5":
25
+	case "":
26
+		return fmt.Errorf("url %s has to have a schema", value)
27
+	default:
28
+		return fmt.Errorf("unsupported schema %s", value.Scheme)
29
+	}
30
+
31
+	if value.Host == "" {
32
+		return fmt.Errorf("url %s has to have a host", value)
33
+	}
34
+
35
+	if _, _, err := net.SplitHostPort(value.Host); err != nil {
36
+		switch value.Scheme {
37
+		case "http":
38
+			value.Host = net.JoinHostPort(value.Host, "80")
39
+		case "https":
40
+			value.Host = net.JoinHostPort(value.Host, "443")
41
+		case "socks5":
42
+			value.Host = net.JoinHostPort(value.Host, "1080")
43
+		default:
44
+			return fmt.Errorf("cannot set a default port for %s", value)
45
+		}
46
+	}
47
+
48
+	c.value = value
49
+
50
+	return nil
51
+}
52
+
53
+func (c *TypeURL) MarshalText() ([]byte, error) {
54
+	return []byte(c.String()), nil
55
+}
56
+
57
+func (c TypeURL) String() string {
58
+	if c.value == nil {
59
+		return ""
60
+	}
61
+
62
+	return c.value.String()
63
+}
64
+
65
+func (c TypeURL) Value(defaultValue *url.URL) *url.URL {
66
+	if c.value == nil {
67
+		return defaultValue
68
+	}
69
+
70
+	return c.value
71
+}

+ 107
- 0
internal/config/type_url_test.go Прегледај датотеку

@@ -0,0 +1,107 @@
1
+package config_test
2
+
3
+import (
4
+	"encoding/json"
5
+	"net/url"
6
+	"testing"
7
+
8
+	"github.com/9seconds/mtg/v2/internal/config"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/suite"
11
+)
12
+
13
+type typeURLTestStruct struct {
14
+	Value config.TypeURL `json:"value"`
15
+}
16
+
17
+type TypeURLTestSuite struct {
18
+	suite.Suite
19
+}
20
+
21
+func (suite *TypeURLTestSuite) TestUnmarshalNil() {
22
+	u, _ := url.Parse("https://google.com")
23
+
24
+	typ := &config.TypeURL{}
25
+	suite.NoError(typ.UnmarshalText(nil))
26
+	suite.Empty(typ.String())
27
+	suite.Equal("https://google.com", typ.Value(u).String())
28
+}
29
+
30
+func (suite *TypeURLTestSuite) TestUnmarshalFail() {
31
+	testData := []string{
32
+		"http:/aaa.com",
33
+		"ipv4",
34
+		"111",
35
+		"://111",
36
+		"http://aaa.com:xxx",
37
+		"gopher://aaa.com:888",
38
+		"gopher://aaa.com",
39
+	}
40
+
41
+	for _, v := range testData {
42
+		data, err := json.Marshal(map[string]string{
43
+			"value": v,
44
+		})
45
+		suite.NoError(err)
46
+
47
+		suite.T().Run(v, func(t *testing.T) {
48
+			assert.Error(t, json.Unmarshal(data, &typeURLTestStruct{}))
49
+		})
50
+	}
51
+}
52
+
53
+func (suite *TypeURLTestSuite) TestUnmarshalOk() {
54
+	testData := map[string]string{
55
+		"https://10.0.0.10:80":    "https://10.0.0.10:80",
56
+		"https://10.0.0.10:443":   "https://10.0.0.10",
57
+		"http://10.0.0.10:8":      "http://10.0.0.10:8",
58
+		"http://10.0.0.10:80":     "http://10.0.0.10",
59
+		"socks5://10.0.0.10:1080": "socks5://10.0.0.10",
60
+		"socks5://10.0.0.10:888":  "socks5://10.0.0.10:888",
61
+	}
62
+
63
+	for k, v := range testData {
64
+		expected := k
65
+		actual := v
66
+
67
+		data, err := json.Marshal(map[string]string{
68
+			"value": actual,
69
+		})
70
+		suite.NoError(err)
71
+
72
+		suite.T().Run(actual, func(t *testing.T) {
73
+			testStruct := &typeURLTestStruct{}
74
+
75
+			assert.NoError(t, json.Unmarshal(data, testStruct))
76
+			assert.Equal(t, expected, testStruct.Value.Value(nil).String())
77
+
78
+			marshalled, err := testStruct.Value.MarshalText()
79
+			assert.NoError(t, err)
80
+			assert.Equal(t, expected, string(marshalled))
81
+		})
82
+	}
83
+}
84
+
85
+func (suite *TypeURLTestSuite) TestValue() {
86
+	testStruct := &typeURLTestStruct{}
87
+
88
+	u1, _ := url.Parse("https://10.0.0.10:80")
89
+	u2, _ := url.Parse("https://10.1.0.10:80")
90
+
91
+	suite.Equal("https://10.0.0.10:80", testStruct.Value.Value(u1).String())
92
+	suite.Equal("https://10.1.0.10:80", testStruct.Value.Value(u2).String())
93
+
94
+	data, err := json.Marshal(map[string]string{
95
+		"value": "http://127.0.0.1:80",
96
+	})
97
+	suite.NoError(err)
98
+	suite.NoError(json.Unmarshal(data, testStruct))
99
+
100
+	suite.Equal("http://127.0.0.1:80", testStruct.Value.Value(u1).String())
101
+	suite.Equal("http://127.0.0.1:80", testStruct.Value.Value(u2).String())
102
+}
103
+
104
+func TestTypeURL(t *testing.T) {
105
+	t.Parallel()
106
+	suite.Run(t, &TypeURLTestSuite{})
107
+}

+ 42
- 0
internal/testlib/capture_output.go Прегледај датотеку

@@ -0,0 +1,42 @@
1
+package testlib
2
+
3
+import (
4
+	"bytes"
5
+	"io"
6
+	"os"
7
+	"strings"
8
+)
9
+
10
+func CaptureStdout(callback func()) string {
11
+	return captureOutput(&os.Stdout, callback)
12
+}
13
+
14
+func CaptureStderr(callback func()) string {
15
+	return captureOutput(&os.Stderr, callback)
16
+}
17
+
18
+func captureOutput(filefp **os.File, callback func()) string {
19
+	oldFp := *filefp
20
+
21
+	defer func() {
22
+		*filefp = oldFp
23
+	}()
24
+
25
+	reader, writer, _ := os.Pipe()
26
+	buf := &bytes.Buffer{}
27
+	closeChan := make(chan bool)
28
+
29
+	go func() {
30
+		io.Copy(buf, reader) // nolint: errcheck
31
+		close(closeChan)
32
+	}()
33
+
34
+	*filefp = writer
35
+
36
+	callback()
37
+
38
+	writer.Close()
39
+	<-closeChan
40
+
41
+	return strings.TrimSpace(buf.String())
42
+}

+ 7
- 0
internal/testlib/events_observer_mock.go Прегледај датотеку

@@ -0,0 +1,7 @@
1
+package testlib
2
+
3
+import "github.com/stretchr/testify/mock"
4
+
5
+type EventsObserverMock struct {
6
+	mock.Mock
7
+}

+ 11
- 0
internal/testlib/mtglib_antireplay_cache_mock.go Прегледај датотеку

@@ -0,0 +1,11 @@
1
+package testlib
2
+
3
+import "github.com/stretchr/testify/mock"
4
+
5
+type MtglibAntiReplayCacheMock struct {
6
+	mock.Mock
7
+}
8
+
9
+func (m *MtglibAntiReplayCacheMock) SeenBefore(data []byte) bool {
10
+	return m.Called(data).Bool(0)
11
+}

+ 30
- 0
internal/testlib/mtglib_network_mock.go Прегледај датотеку

@@ -0,0 +1,30 @@
1
+package testlib
2
+
3
+import (
4
+	"context"
5
+	"net"
6
+	"net/http"
7
+
8
+	"github.com/stretchr/testify/mock"
9
+)
10
+
11
+type MtglibNetworkMock struct {
12
+	mock.Mock
13
+}
14
+
15
+func (m *MtglibNetworkMock) Dial(network, address string) (net.Conn, error) {
16
+	args := m.Called(network, address)
17
+
18
+	return args.Get(0).(net.Conn), args.Error(1) // nolint: wrapcheck
19
+}
20
+
21
+func (m *MtglibNetworkMock) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
22
+	args := m.Called(ctx, network, address)
23
+
24
+	return args.Get(0).(net.Conn), args.Error(1) // nolint: wrapcheck
25
+}
26
+
27
+func (m *MtglibNetworkMock) MakeHTTPClient(dialFunc func(ctx context.Context,
28
+	network, address string) (net.Conn, error)) *http.Client {
29
+	return m.Called(dialFunc).Get(0).(*http.Client)
30
+}

+ 48
- 0
internal/testlib/net_conn_mock.go Прегледај датотеку

@@ -0,0 +1,48 @@
1
+package testlib
2
+
3
+import (
4
+	"net"
5
+	"time"
6
+
7
+	"github.com/stretchr/testify/mock"
8
+)
9
+
10
+type NetConnMock struct {
11
+	mock.Mock
12
+}
13
+
14
+func (n *NetConnMock) Read(b []byte) (int, error) {
15
+	args := n.Called(b)
16
+
17
+	return args.Int(0), args.Error(1)
18
+}
19
+
20
+func (n *NetConnMock) Write(b []byte) (int, error) {
21
+	args := n.Called(b)
22
+
23
+	return args.Int(0), args.Error(1)
24
+}
25
+
26
+func (n *NetConnMock) Close() error {
27
+	return n.Called().Error(0) // nolint: wrapcheck
28
+}
29
+
30
+func (n *NetConnMock) LocalAddr() net.Addr {
31
+	return n.Called().Get(0).(net.Addr)
32
+}
33
+
34
+func (n *NetConnMock) RemoteAddr() net.Addr {
35
+	return n.Called().Get(0).(net.Addr)
36
+}
37
+
38
+func (n *NetConnMock) SetDeadline(t time.Time) error {
39
+	return n.Called(t).Error(0) // nolint: wrapcheck
40
+}
41
+
42
+func (n *NetConnMock) SetReadDeadline(t time.Time) error {
43
+	return n.Called(t).Error(0) // nolint: wrapcheck
44
+}
45
+
46
+func (n *NetConnMock) SetWriteDeadline(t time.Time) error {
47
+	return n.Called(t).Error(0) // nolint: wrapcheck
48
+}

utils/rlimit.go → internal/utils/rlimit.go Прегледај датотеку


utils/rlimit_windows.go → internal/utils/rlimit_windows.go Прегледај датотеку


utils/signal_context.go → internal/utils/root_context.go Прегледај датотеку

@@ -9,7 +9,7 @@ import (
9 9
 	"syscall"
10 10
 )
11 11
 
12
-func GetSignalContext() context.Context {
12
+func RootContext() context.Context {
13 13
 	ctx, cancel := context.WithCancel(context.Background())
14 14
 	sigChan := make(chan os.Signal, 1)
15 15
 

utils/signal_context_windows.go → internal/utils/root_context_windows.go Прегледај датотеку

@@ -8,7 +8,7 @@ import (
8 8
 	"os/signal"
9 9
 )
10 10
 
11
-func GetSignalContext() context.Context {
11
+func RootContext() context.Context {
12 12
 	ctx, cancel := context.WithCancel(context.Background())
13 13
 	sigChan := make(chan os.Signal, 1)
14 14
 

+ 371
- 0
ipblocklist/firehol.go Прегледај датотеку

@@ -0,0 +1,371 @@
1
+package ipblocklist
2
+
3
+import (
4
+	"bufio"
5
+	"context"
6
+	"fmt"
7
+	"io"
8
+	"net"
9
+	"net/http"
10
+	"net/url"
11
+	"os"
12
+	"regexp"
13
+	"strings"
14
+	"sync"
15
+	"time"
16
+
17
+	"github.com/9seconds/mtg/v2/mtglib"
18
+	"github.com/kentik/patricia"
19
+	"github.com/kentik/patricia/bool_tree"
20
+	"github.com/panjf2000/ants/v2"
21
+)
22
+
23
+const (
24
+	fireholIPv4DefaultCIDR = 32
25
+	fireholIPv6DefaultCIDR = 128
26
+)
27
+
28
+var fireholRegexpComment = regexp.MustCompile(`\s*#.*?$`)
29
+
30
+// Firehol is IPBlocklist which uses lists from FireHOL:
31
+// https://iplists.firehol.org/
32
+//
33
+// It can use both local files and remote URLs. This is not necessary
34
+// that blocklists should be taken from this website, we expect only
35
+// compatible formats here.
36
+//
37
+// Example of the format:
38
+//
39
+//     # this is a comment
40
+//     # to ignore
41
+//     127.0.0.1   # you can specify an IP
42
+//     10.0.0.0/8  # or cidr
43
+type Firehol struct {
44
+	ctx       context.Context
45
+	ctxCancel context.CancelFunc
46
+	logger    mtglib.Logger
47
+
48
+	rwMutex sync.RWMutex
49
+
50
+	remoteURLs []string
51
+	localFiles []string
52
+
53
+	httpClient *http.Client
54
+	workerPool *ants.Pool
55
+
56
+	treeV4 *bool_tree.TreeV4
57
+	treeV6 *bool_tree.TreeV6
58
+}
59
+
60
+// Shutdown stop a background update process.
61
+func (f *Firehol) Shutdown() {
62
+	f.ctxCancel()
63
+}
64
+
65
+// Contains is given IP list can be found in FireHOL blocklists.
66
+func (f *Firehol) Contains(ip net.IP) bool {
67
+	if ip == nil {
68
+		return true
69
+	}
70
+
71
+	f.rwMutex.RLock()
72
+	defer f.rwMutex.RUnlock()
73
+
74
+	if ip4 := ip.To4(); ip4 != nil {
75
+		return f.containsIPv4(ip4)
76
+	}
77
+
78
+	return f.containsIPv6(ip.To16())
79
+}
80
+
81
+// Run starts a background update process.
82
+//
83
+// This is a blocking method so you probably want to run it in a
84
+// goroutine.
85
+func (f *Firehol) Run(updateEach time.Duration) {
86
+	if updateEach == 0 {
87
+		updateEach = DefaultFireholUpdateEach
88
+	}
89
+
90
+	ticker := time.NewTicker(updateEach)
91
+
92
+	defer func() {
93
+		ticker.Stop()
94
+
95
+		select {
96
+		case <-ticker.C:
97
+		default:
98
+		}
99
+	}()
100
+
101
+	if err := f.update(); err != nil {
102
+		f.logger.WarningError("cannot update blocklist", err)
103
+	} else {
104
+		f.logger.Info("blocklist was updated")
105
+	}
106
+
107
+	for {
108
+		select {
109
+		case <-f.ctx.Done():
110
+			return
111
+		case <-ticker.C:
112
+			if err := f.update(); err != nil {
113
+				f.logger.WarningError("cannot update blocklist", err)
114
+			} else {
115
+				f.logger.Info("blocklist was updated")
116
+			}
117
+		}
118
+	}
119
+}
120
+
121
+func (f *Firehol) containsIPv4(addr net.IP) bool {
122
+	ip := patricia.NewIPv4AddressFromBytes(addr, 32)
123
+
124
+	if ok, _, err := f.treeV4.FindDeepestTag(ip); ok && err == nil {
125
+		return true
126
+	}
127
+
128
+	return false
129
+}
130
+
131
+func (f *Firehol) containsIPv6(addr net.IP) bool {
132
+	ip := patricia.NewIPv6Address(addr, 128)
133
+
134
+	if ok, _, err := f.treeV6.FindDeepestTag(ip); ok && err == nil {
135
+		return true
136
+	}
137
+
138
+	return false
139
+}
140
+
141
+func (f *Firehol) update() error { // nolint: funlen, cyclop
142
+	ctx, cancel := context.WithCancel(f.ctx)
143
+	defer cancel()
144
+
145
+	wg := &sync.WaitGroup{}
146
+	wg.Add(len(f.remoteURLs) + len(f.localFiles))
147
+
148
+	treeMutex := &sync.Mutex{}
149
+	v4tree := bool_tree.NewTreeV4()
150
+	v6tree := bool_tree.NewTreeV6()
151
+
152
+	errorChan := make(chan error, 1)
153
+	defer close(errorChan)
154
+
155
+	for _, v := range f.localFiles {
156
+		go func(filename string) {
157
+			defer wg.Done()
158
+
159
+			if err := f.updateLocalFile(ctx, filename, treeMutex, v4tree, v6tree); err != nil {
160
+				cancel()
161
+				f.logger.BindStr("filename", filename).WarningError("cannot update", err)
162
+
163
+				select {
164
+				case errorChan <- err:
165
+				default:
166
+				}
167
+			}
168
+		}(v)
169
+	}
170
+
171
+	for _, v := range f.remoteURLs {
172
+		value := v
173
+
174
+		f.workerPool.Submit(func() { // nolint: errcheck
175
+			defer wg.Done()
176
+
177
+			if err := f.updateRemoteURL(ctx, value, treeMutex, v4tree, v6tree); err != nil {
178
+				cancel()
179
+				f.logger.BindStr("url", value).WarningError("cannot update", err)
180
+
181
+				select {
182
+				case errorChan <- err:
183
+				default:
184
+				}
185
+			}
186
+		})
187
+	}
188
+
189
+	wg.Wait()
190
+
191
+	select {
192
+	case err := <-errorChan:
193
+		return fmt.Errorf("cannot update trees: %w", err)
194
+	default:
195
+	}
196
+
197
+	f.rwMutex.Lock()
198
+	defer f.rwMutex.Unlock()
199
+
200
+	f.treeV4 = v4tree
201
+	f.treeV6 = v6tree
202
+
203
+	return nil
204
+}
205
+
206
+func (f *Firehol) updateLocalFile(ctx context.Context, filename string,
207
+	mutex sync.Locker,
208
+	v4tree *bool_tree.TreeV4, v6tree *bool_tree.TreeV6) error {
209
+	filefp, err := os.Open(filename)
210
+	if err != nil {
211
+		return fmt.Errorf("cannot open file: %w", err)
212
+	}
213
+
214
+	go func(ctx context.Context, closer io.Closer) {
215
+		<-ctx.Done()
216
+		closer.Close()
217
+	}(ctx, filefp)
218
+
219
+	defer filefp.Close()
220
+
221
+	return f.updateTrees(mutex, filefp, v4tree, v6tree)
222
+}
223
+
224
+func (f *Firehol) updateRemoteURL(ctx context.Context, url string,
225
+	mutex sync.Locker,
226
+	v4tree *bool_tree.TreeV4, v6tree *bool_tree.TreeV6) error {
227
+	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
228
+	if err != nil {
229
+		return fmt.Errorf("cannot build a request: %w", err)
230
+	}
231
+
232
+	resp, err := f.httpClient.Do(req) // nolint: bodyclose
233
+	if err != nil {
234
+		return fmt.Errorf("cannot request a remote URL %s: %w", url, err)
235
+	}
236
+
237
+	go func(ctx context.Context, closer io.Closer) {
238
+		<-ctx.Done()
239
+		closer.Close()
240
+	}(ctx, resp.Body)
241
+
242
+	defer func(rc io.ReadCloser) {
243
+		io.Copy(io.Discard, rc) // nolint: errcheck
244
+		rc.Close()
245
+	}(resp.Body)
246
+
247
+	return f.updateTrees(mutex, resp.Body, v4tree, v6tree)
248
+}
249
+
250
+func (f *Firehol) updateTrees(mutex sync.Locker,
251
+	reader io.Reader,
252
+	v4tree *bool_tree.TreeV4,
253
+	v6tree *bool_tree.TreeV6) error {
254
+	scanner := bufio.NewScanner(reader)
255
+
256
+	for scanner.Scan() {
257
+		text := scanner.Text()
258
+		text = fireholRegexpComment.ReplaceAllLiteralString(text, "")
259
+		text = strings.TrimSpace(text)
260
+
261
+		if text == "" {
262
+			continue
263
+		}
264
+
265
+		ip, cidr, err := f.updateParseLine(text)
266
+		if err != nil {
267
+			return fmt.Errorf("cannot parse a line: %w", err)
268
+		}
269
+
270
+		if err := f.updateAddToTrees(ip, cidr, mutex, v4tree, v6tree); err != nil {
271
+			return fmt.Errorf("cannot add a node to the tree: %w", err)
272
+		}
273
+	}
274
+
275
+	if scanner.Err() != nil {
276
+		return fmt.Errorf("cannot parse a response: %w", scanner.Err())
277
+	}
278
+
279
+	return nil
280
+}
281
+
282
+func (f *Firehol) updateParseLine(text string) (net.IP, uint, error) {
283
+	_, ipnet, err := net.ParseCIDR(text)
284
+	if err != nil {
285
+		ipaddr := net.ParseIP(text)
286
+		if ipaddr == nil {
287
+			return nil, 0, fmt.Errorf("incorrect ip address %s", text)
288
+		}
289
+
290
+		ip4 := ipaddr.To4()
291
+		if ip4 != nil {
292
+			return ip4, fireholIPv4DefaultCIDR, nil
293
+		}
294
+
295
+		return ipaddr.To16(), fireholIPv6DefaultCIDR, nil
296
+	}
297
+
298
+	ones, _ := ipnet.Mask.Size()
299
+
300
+	return ipnet.IP, uint(ones), nil
301
+}
302
+
303
+func (f *Firehol) updateAddToTrees(ip net.IP, cidr uint,
304
+	mutex sync.Locker,
305
+	v4tree *bool_tree.TreeV4, v6tree *bool_tree.TreeV6) error {
306
+	mutex.Lock()
307
+	defer mutex.Unlock()
308
+
309
+	if ip.To4() != nil {
310
+		addr := patricia.NewIPv4AddressFromBytes(ip, cidr)
311
+
312
+		if _, _, err := v4tree.Set(addr, true); err != nil {
313
+			return err // nolint: wrapcheck
314
+		}
315
+	} else {
316
+		addr := patricia.NewIPv6Address(ip, cidr)
317
+
318
+		if _, _, err := v6tree.Set(addr, true); err != nil {
319
+			return err // nolint: wrapcheck
320
+		}
321
+	}
322
+
323
+	return nil
324
+}
325
+
326
+// NewFirehol creates a new instance of FireHOL IP blocklist.
327
+//
328
+// This method does not start an update process so please execute Run
329
+// when it is necessary.
330
+func NewFirehol(logger mtglib.Logger, network mtglib.Network,
331
+	downloadConcurrency uint,
332
+	remoteURLs []string,
333
+	localFiles []string) (*Firehol, error) {
334
+	for _, v := range remoteURLs {
335
+		parsed, err := url.Parse(v)
336
+		if err != nil {
337
+			return nil, fmt.Errorf("incorrect url %s: %w", v, err)
338
+		}
339
+
340
+		switch parsed.Scheme {
341
+		case "http", "https":
342
+		default:
343
+			return nil, fmt.Errorf("unsupported url %s", v)
344
+		}
345
+	}
346
+
347
+	for _, v := range localFiles {
348
+		if stat, err := os.Stat(v); os.IsNotExist(err) || stat.IsDir() || stat.Mode().Perm()&0o400 == 0 {
349
+			return nil, fmt.Errorf("%s is not a readable file", v)
350
+		}
351
+	}
352
+
353
+	if downloadConcurrency == 0 {
354
+		downloadConcurrency = DefaultFireholDownloadConcurrency
355
+	}
356
+
357
+	workerPool, _ := ants.NewPool(int(downloadConcurrency))
358
+	ctx, cancel := context.WithCancel(context.Background())
359
+
360
+	return &Firehol{
361
+		ctx:        ctx,
362
+		ctxCancel:  cancel,
363
+		logger:     logger.Named("firehol"),
364
+		httpClient: network.MakeHTTPClient(nil),
365
+		treeV4:     bool_tree.NewTreeV4(),
366
+		treeV6:     bool_tree.NewTreeV6(),
367
+		workerPool: workerPool,
368
+		remoteURLs: remoteURLs,
369
+		localFiles: localFiles,
370
+	}, nil
371
+}

+ 0
- 0
ipblocklist/firehol_test.go Прегледај датотеку


Неке датотеке нису приказане због велике количине промена

Loading…
Откажи
Сачувај