Bladeren bron

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

tags/v2.1.6
9seconds 4 jaren geleden
bovenliggende
commit
c07e3d573d

+ 41
- 4
.github/workflows/ci.yaml Bestand weergeven

@@ -38,11 +38,11 @@ jobs:
38 38
   test:
39 39
     name: Test
40 40
     runs-on: ubuntu-latest
41
-    timeout-minutes: 5
41
+    timeout-minutes: 10
42 42
     strategy:
43 43
       matrix:
44 44
         go_version:
45
-          - ^1.17
45
+          - ^1.18
46 46
     steps:
47 47
       - name: Checkout
48 48
         uses: actions/checkout@v2
@@ -69,6 +69,38 @@ jobs:
69 69
         with:
70 70
           file: ./coverage.txt
71 71
 
72
+  fuzz:
73
+    name: Fuzzing
74
+    runs-on: ubuntu-latest
75
+    timeout-minutes: 20
76
+    steps:
77
+      - name: Checkout
78
+        uses: actions/checkout@v2
79
+        with:
80
+          submodules: recursive
81
+
82
+      - name: Setup Go
83
+        uses: actions/setup-go@v2
84
+        with:
85
+          go-version: ^1.18
86
+
87
+      - name: Cache fuzz results
88
+        uses: actions/cache@v2
89
+        with:
90
+          path: ~/.cache/go-build/fuzz
91
+          key: ${{ runner.os }}-go-${{ hashFiles('**/*_fuzz_test.go', '**/*_fuzz_internal_test.go') }}
92
+          restore-keys: ${{ runner.os }}-go-
93
+
94
+      - name: Cache dependencies
95
+        uses: actions/cache@v2
96
+        with:
97
+          path: ~/go/pkg/mod
98
+          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
99
+          restore-keys: ${{ runner.os }}-go-
100
+
101
+      - name: Run fuzzing
102
+        run: make -j4 fuzz
103
+
72 104
   lint:
73 105
     name: Lint
74 106
     runs-on: ubuntu-latest
@@ -79,10 +111,15 @@ jobs:
79 111
         with:
80 112
           submodules: recursive
81 113
 
114
+      - name: Setup Go
115
+        uses: actions/setup-go@v2
116
+        with:
117
+          go-version: ^1.18
118
+
82 119
       - name: Run linter
83
-        uses: golangci/golangci-lint-action@v2
120
+        uses: golangci/golangci-lint-action@v3
84 121
         with:
85
-          version: v1.44.2
122
+          version: v1.45.0
86 123
 
87 124
   docker:
88 125
     name: Docker

+ 1
- 1
.golangci.toml Bestand weergeven

@@ -9,4 +9,4 @@ format = "colored-line-number"
9 9
 
10 10
 [linters]
11 11
 enable-all = true
12
-disable = ["ireturn", "varnamelen", "gochecknoglobals", "gas", "goerr113", "exhaustivestruct", "containedctx"]
12
+disable = ["thelper", "ireturn", "varnamelen", "gochecknoglobals", "gas", "goerr113", "exhaustivestruct", "containedctx"]

+ 1
- 1
Dockerfile Bestand weergeven

@@ -1,7 +1,7 @@
1 1
 ###############################################################################
2 2
 # BUILD STAGE
3 3
 
4
-FROM golang:1.17-alpine AS build
4
+FROM golang:1.18-alpine AS build
5 5
 
6 6
 RUN set -x \
7 7
   && apk --no-cache --update add \

+ 30
- 7
Makefile Bestand weergeven

@@ -2,12 +2,12 @@ ROOT_DIR     := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
2 2
 IMAGE_NAME   := mtg
3 3
 APP_NAME     := $(IMAGE_NAME)
4 4
 
5
-GOLANGCI_LINT_VERSION := v1.44.2
5
+GOLANGCI_LINT_VERSION := v1.45.0
6 6
 
7
-VERSION_GO         := $(shell go version)
8
-VERSION_DATE       := $(shell date -Ru)
9
-VERSION_TAG        := $(shell git describe --tags --always)
10
-COMMON_BUILD_FLAGS := -trimpath -mod=readonly -ldflags="-extldflags '-static' -s -w -X 'main.version=$(VERSION_TAG) ($(VERSION_GO)) [$(VERSION_DATE)]'"
7
+VERSION            := $(shell git describe --exact-match HEAD 2>/dev/null || git describe --tags --always)
8
+COMMON_BUILD_FLAGS := -trimpath -mod=readonly -ldflags="-extldflags '-static' -s -w -X 'main.version=$(VERSION)'"
9
+
10
+FUZZ_FLAGS := -fuzztime=120s
11 11
 
12 12
 GOBIN  := $(ROOT_DIR)/.bin
13 13
 GOTOOL := env "GOBIN=$(GOBIN)" "PATH=$(ROOT_DIR)/.bin:$(PATH)"
@@ -78,7 +78,7 @@ install-tools: install-tools-lint install-tools-godoc install-tools-gofumpt inst
78 78
 
79 79
 .PHONY: install-tools-lint
80 80
 install-tools-lint: .bin
81
-	@curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh \
81
+	@curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \
82 82
 		| bash -s -- -b "$(GOBIN)" "$(GOLANGCI_LINT_VERSION)"
83 83
 
84 84
 .PHONY: install-tools-godoc
@@ -95,4 +95,27 @@ install-tools-goreleaser: .bin
95 95
 
96 96
 .PHONY: update-deps
97 97
 update-deps:
98
-	@go get -u && go mod tidy -go=1.17
98
+	@go get -u && go mod tidy -go=1.18
99
+
100
+.PHONY: fuzz
101
+fuzz: fuzz-ClientHello fuzz-ServerGenerateHandshakeFrame fuzz-ClientHandshake fuzz-ServerReceive fuzz-ServerSend
102
+
103
+.PHONY: fuzz-ClientHello
104
+fuzz-ClientHello:
105
+	@go test -fuzz=FuzzClientHello $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/faketls"
106
+
107
+.PHONY: fuzz-ServerGenerateHandshakeFrame
108
+fuzz-ServerGenerateHandshakeFrame:
109
+	@go test -fuzz=FuzzServerGenerateHandshakeFrame $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
110
+
111
+.PHONY: fuzz-ClientHandshake
112
+fuzz-ClientHandshake:
113
+	@go test -fuzz=FuzzClientHandshake $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
114
+
115
+.PHONY: fuzz-ServerReceive
116
+fuzz-ServerReceive:
117
+	@go test -fuzz=FuzzServerReceive $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
118
+
119
+.PHONY: fuzz-ServerSend
120
+fuzz-ServerSend:
121
+	@go test -fuzz=FuzzServerSend $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"

+ 6
- 2
README.md Bestand weergeven

@@ -317,12 +317,16 @@ Now you can create a systemd unit:
317 317
 ```console
318 318
 $ cat /etc/systemd/system/mtg.service
319 319
 [Unit]
320
-Description=mtg
320
+Description=mtg - MTProto proxy server
321
+Documentation=https://github.com/9seconds/mtg
322
+After=network.target
321 323
 
322 324
 [Service]
323 325
 ExecStart=/usr/local/bin/mtg run /etc/mtg.toml
324 326
 Restart=always
325 327
 RestartSec=3
328
+DynamicUser=true
329
+AmbientCapabilities=CAP_NET_BIND_SERVICE
326 330
 
327 331
 [Install]
328 332
 WantedBy=multi-user.target
@@ -388,7 +392,7 @@ Here goes a list of metrics with their types but without a prefix.
388 392
 | domain_fronting_traffic     | counter | `direction`                      | Count of bytes, transmitted to/from fronting domain.                                       |
389 393
 | domain_fronting             | counter | –                                | Count of domain fronting events.                                                           |
390 394
 | concurrency_limited         | counter | –                                | Count of events, when client connection was rejected due to concurrency limit.             |
391
-| ip_blocklisted              | counter |                         | Count of events when client connection was rejected because IP was found in the blacklist. |
395
+| ip_blocklisted              | counter | `ip_list`                        | Count of events when client connection was rejected because IP was found in the blocklist. |
392 396
 | replay_attacks              | counter | –                                | Count of detected replay attacks.                                                          |
393 397
 
394 398
 Tag meaning:

+ 3
- 3
go.mod Bestand weergeven

@@ -1,6 +1,6 @@
1 1
 module github.com/9seconds/mtg/v2
2 2
 
3
-go 1.17
3
+go 1.18
4 4
 
5 5
 require (
6 6
 	github.com/OneOfOne/xxhash v1.2.8
@@ -23,9 +23,9 @@ require (
23 23
 	github.com/stretchr/objx v0.3.0 // indirect
24 24
 	github.com/stretchr/testify v1.7.0
25 25
 	github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43
26
-	golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70
26
+	golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
27 27
 	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
28
-	golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5
28
+	golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8
29 29
 	google.golang.org/protobuf v1.27.1 // indirect
30 30
 )
31 31
 

+ 4
- 5
go.sum Bestand weergeven

@@ -291,8 +291,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
291 291
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
292 292
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
293 293
 golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
294
-golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU=
295
-golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
294
+golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
295
+golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
296 296
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
297 297
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
298 298
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -416,11 +416,10 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
416 416
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
417 417
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
418 418
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
419
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
420 419
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
421 420
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
422
-golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
423
-golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
421
+golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
422
+golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
424 423
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
425 424
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
426 425
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

+ 53
- 20
internal/cli/run_proxy.go Bestand weergeven

@@ -12,11 +12,13 @@ import (
12 12
 	"github.com/9seconds/mtg/v2/internal/config"
13 13
 	"github.com/9seconds/mtg/v2/internal/utils"
14 14
 	"github.com/9seconds/mtg/v2/ipblocklist"
15
+	"github.com/9seconds/mtg/v2/ipblocklist/files"
15 16
 	"github.com/9seconds/mtg/v2/logger"
16 17
 	"github.com/9seconds/mtg/v2/mtglib"
17 18
 	"github.com/9seconds/mtg/v2/network"
18 19
 	"github.com/9seconds/mtg/v2/stats"
19 20
 	"github.com/rs/zerolog"
21
+	"github.com/yl2chen/cidranger"
20 22
 )
21 23
 
22 24
 func makeLogger(conf *config.Config) mtglib.Logger {
@@ -89,7 +91,8 @@ func makeAntiReplayCache(conf *config.Config) mtglib.AntiReplayCache {
89 91
 func makeIPBlocklist(conf config.ListConfig,
90 92
 	logger mtglib.Logger,
91 93
 	ntw mtglib.Network,
92
-	updateCallback ipblocklist.FireholUpdateCallback) (mtglib.IPBlocklist, error) {
94
+	updateCallback ipblocklist.FireholUpdateCallback,
95
+) (mtglib.IPBlocklist, error) {
93 96
 	if !conf.Enabled.Get(false) {
94 97
 		return ipblocklist.NewNoop(), nil
95 98
 	}
@@ -105,7 +108,7 @@ func makeIPBlocklist(conf config.ListConfig,
105 108
 		}
106 109
 	}
107 110
 
108
-	firehol, err := ipblocklist.NewFirehol(logger.Named("ipblockist"),
111
+	blocklist, err := ipblocklist.NewFirehol(logger.Named("ipblockist"),
109 112
 		ntw,
110 113
 		conf.DownloadConcurrency.Get(1),
111 114
 		remoteURLs,
@@ -115,9 +118,44 @@ func makeIPBlocklist(conf config.ListConfig,
115 118
 		return nil, fmt.Errorf("incorrect parameters for firehol: %w", err)
116 119
 	}
117 120
 
118
-	go firehol.Run(conf.UpdateEach.Get(ipblocklist.DefaultFireholUpdateEach))
121
+	go blocklist.Run(conf.UpdateEach.Get(ipblocklist.DefaultFireholUpdateEach))
119 122
 
120
-	return firehol, nil
123
+	return blocklist, nil
124
+}
125
+
126
+func makeIPAllowlist(conf config.ListConfig,
127
+	logger mtglib.Logger,
128
+	ntw mtglib.Network,
129
+	updateCallback ipblocklist.FireholUpdateCallback,
130
+) (allowlist mtglib.IPBlocklist, err error) {
131
+	if !conf.Enabled.Get(false) {
132
+		allowlist, err = ipblocklist.NewFireholFromFiles(
133
+			logger.Named("ipblocklist"),
134
+			1,
135
+			[]files.File{
136
+				files.NewMem([]*net.IPNet{
137
+					cidranger.AllIPv4,
138
+					cidranger.AllIPv6,
139
+				}),
140
+			},
141
+			updateCallback,
142
+		)
143
+
144
+		go allowlist.Run(conf.UpdateEach.Get(ipblocklist.DefaultFireholUpdateEach))
145
+	} else {
146
+		allowlist, err = makeIPBlocklist(
147
+			conf,
148
+			logger,
149
+			ntw,
150
+			updateCallback,
151
+		)
152
+	}
153
+
154
+	if err != nil {
155
+		return nil, fmt.Errorf("cannot build allowlist: %w", err)
156
+	}
157
+
158
+	return allowlist, nil
121 159
 }
122 160
 
123 161
 func makeEventStream(conf *config.Config, logger mtglib.Logger) (mtglib.EventStream, error) {
@@ -185,21 +223,16 @@ func runProxy(conf *config.Config, version string) error { // nolint: funlen
185 223
 		return fmt.Errorf("cannot build ip blocklist: %w", err)
186 224
 	}
187 225
 
188
-	var whitelist mtglib.IPBlocklist
189
-
190
-	if conf.Defense.Allowlist.Enabled.Get(false) {
191
-		whlist, err := makeIPBlocklist(
192
-			conf.Defense.Allowlist,
193
-			logger.Named("allowlist"),
194
-			ntw,
195
-			func(ctx context.Context, size int) {
196
-				eventStream.Send(ctx, mtglib.NewEventIPListSize(size, false))
197
-			})
198
-		if err != nil {
199
-			return fmt.Errorf("cannot build ip allowlist: %w", err)
200
-		}
201
-
202
-		whitelist = whlist
226
+	allowlist, err := makeIPAllowlist(
227
+		conf.Defense.Allowlist,
228
+		logger.Named("allowlist"),
229
+		ntw,
230
+		func(ctx context.Context, size int) {
231
+			eventStream.Send(ctx, mtglib.NewEventIPListSize(size, false))
232
+		},
233
+	)
234
+	if err != nil {
235
+		return fmt.Errorf("cannot build ip allowlist: %w", err)
203 236
 	}
204 237
 
205 238
 	opts := mtglib.ProxyOpts{
@@ -207,7 +240,7 @@ func runProxy(conf *config.Config, version string) error { // nolint: funlen
207 240
 		Network:         ntw,
208 241
 		AntiReplayCache: makeAntiReplayCache(conf),
209 242
 		IPBlocklist:     blocklist,
210
-		IPWhitelist:     whitelist,
243
+		IPAllowlist:     allowlist,
211 244
 		EventStream:     eventStream,
212 245
 
213 246
 		Secret:             conf.Secret,

+ 2
- 1
internal/testlib/mtglib_network_mock.go Bestand weergeven

@@ -25,6 +25,7 @@ func (m *MtglibNetworkMock) DialContext(ctx context.Context, network, address st
25 25
 }
26 26
 
27 27
 func (m *MtglibNetworkMock) MakeHTTPClient(dialFunc func(ctx context.Context,
28
-	network, address string) (essentials.Conn, error)) *http.Client {
28
+	network, address string) (essentials.Conn, error),
29
+) *http.Client {
29 30
 	return m.Called(dialFunc).Get(0).(*http.Client) // nolint: forcetypeassert
30 31
 }

+ 37
- 0
ipblocklist/files/mem.go Bestand weergeven

@@ -0,0 +1,37 @@
1
+package files
2
+
3
+import (
4
+	"context"
5
+	"io"
6
+	"net"
7
+	"strings"
8
+)
9
+
10
+type memFile struct {
11
+	data string
12
+}
13
+
14
+func (m memFile) Open(ctx context.Context) (io.ReadCloser, error) {
15
+	return io.NopCloser(strings.NewReader(m.data)), nil
16
+}
17
+
18
+func (m memFile) String() string {
19
+	return "mem"
20
+}
21
+
22
+func NewMem(networks []*net.IPNet) File {
23
+	builder := strings.Builder{}
24
+
25
+	if len(networks) > 0 {
26
+		builder.WriteString(networks[0].String())
27
+	}
28
+
29
+	for i := 1; i < len(networks); i++ {
30
+		builder.WriteString("\n")
31
+		builder.WriteString(networks[i].String())
32
+	}
33
+
34
+	return memFile{
35
+		data: builder.String(),
36
+	}
37
+}

+ 42
- 0
ipblocklist/files/mem_test.go Bestand weergeven

@@ -0,0 +1,42 @@
1
+package files_test
2
+
3
+import (
4
+	"context"
5
+	"io"
6
+	"net"
7
+	"strings"
8
+	"testing"
9
+
10
+	"github.com/9seconds/mtg/v2/ipblocklist/files"
11
+	"github.com/stretchr/testify/suite"
12
+)
13
+
14
+type MemTestSuite struct {
15
+	suite.Suite
16
+}
17
+
18
+func (suite *MemTestSuite) TestOk() {
19
+	_, network1, _ := net.ParseCIDR("192.168.0.1/24")
20
+	_, network2, _ := net.ParseCIDR("2001:0db8:85a3:0000:0000:8a2e:0370:7334/36")
21
+
22
+	file := files.NewMem([]*net.IPNet{
23
+		network1,
24
+		network2,
25
+	})
26
+
27
+	reader, err := file.Open(context.Background())
28
+	suite.NoError(err)
29
+
30
+	data, err := io.ReadAll(reader)
31
+	suite.NoError(err)
32
+
33
+	strData := strings.TrimSpace(string(data))
34
+
35
+	suite.Contains(strData, "192.168.0.0/24")
36
+	suite.Contains(strData, "2001:db8:8000::/36")
37
+}
38
+
39
+func TestMem(t *testing.T) {
40
+	t.Parallel()
41
+	suite.Run(t, &MemTestSuite{})
42
+}

+ 6
- 3
ipblocklist/firehol.go Bestand weergeven

@@ -155,7 +155,8 @@ func (f *Firehol) update() {
155 155
 
156 156
 func (f *Firehol) updateFromFile(mutex sync.Locker,
157 157
 	ranger cidranger.Ranger,
158
-	scanner *bufio.Scanner) error {
158
+	scanner *bufio.Scanner,
159
+) error {
159 160
 	for scanner.Scan() {
160 161
 		text := scanner.Text()
161 162
 		text = fireholRegexpComment.ReplaceAllLiteralString(text, "")
@@ -216,7 +217,8 @@ func NewFirehol(logger mtglib.Logger, network mtglib.Network,
216 217
 	downloadConcurrency uint,
217 218
 	urls []string,
218 219
 	localFiles []string,
219
-	updateCallback FireholUpdateCallback) (*Firehol, error) {
220
+	updateCallback FireholUpdateCallback,
221
+) (*Firehol, error) {
220 222
 	blocklists := []files.File{}
221 223
 
222 224
 	for _, v := range localFiles {
@@ -245,7 +247,8 @@ func NewFirehol(logger mtglib.Logger, network mtglib.Network,
245 247
 func NewFireholFromFiles(logger mtglib.Logger,
246 248
 	downloadConcurrency uint,
247 249
 	blocklists []files.File,
248
-	updateCallback FireholUpdateCallback) (*Firehol, error) {
250
+	updateCallback FireholUpdateCallback,
251
+) (*Firehol, error) {
249 252
 	if downloadConcurrency == 0 {
250 253
 		downloadConcurrency = DefaultFireholDownloadConcurrency
251 254
 	}

+ 29
- 0
main.go Bestand weergeven

@@ -9,7 +9,10 @@
9 9
 package main
10 10
 
11 11
 import (
12
+	"fmt"
12 13
 	"math/rand"
14
+	"runtime/debug"
15
+	"strconv"
13 16
 	"time"
14 17
 
15 18
 	"github.com/9seconds/mtg/v2/internal/cli"
@@ -26,6 +29,32 @@ func main() {
26 29
 		panic(err)
27 30
 	}
28 31
 
32
+	if buildInfo, ok := debug.ReadBuildInfo(); ok {
33
+		vcsCommit := "<no-commit>"
34
+		vcsDate := time.Now()
35
+		vcsDirty := ""
36
+
37
+		for _, setting := range buildInfo.Settings {
38
+			switch setting.Key {
39
+			case "vcs.time":
40
+				vcsDate, _ = time.Parse(time.RFC3339, setting.Value)
41
+			case "vcs.revision":
42
+				vcsCommit = setting.Value
43
+			case "vcs.modified":
44
+				if isDirty, _ := strconv.ParseBool(setting.Value); isDirty {
45
+					vcsDirty = " [dirty]"
46
+				}
47
+			}
48
+		}
49
+
50
+		version = fmt.Sprintf("%s (%s: %s on %s%s)",
51
+			version,
52
+			buildInfo.GoVersion,
53
+			vcsDate.Format(time.RFC3339),
54
+			vcsCommit,
55
+			vcsDirty)
56
+	}
57
+
29 58
 	cli := &cli.CLI{}
30 59
 	ctx := kong.Parse(cli, kong.Vars{
31 60
 		"version": version,

+ 16
- 2
mtglib/events.go Bestand weergeven

@@ -83,7 +83,8 @@ type EventConcurrencyLimited struct {
83 83
 type EventIPBlocklisted struct {
84 84
 	eventBase
85 85
 
86
-	RemoteIP net.IP
86
+	RemoteIP    net.IP
87
+	IsBlockList bool
87 88
 }
88 89
 
89 90
 // EventReplayAttack is emitted when mtg detects a replay attack on a
@@ -172,7 +173,20 @@ func NewEventIPBlocklisted(remoteIP net.IP) EventIPBlocklisted {
172 173
 		eventBase: eventBase{
173 174
 			timestamp: time.Now(),
174 175
 		},
175
-		RemoteIP: remoteIP,
176
+		RemoteIP:    remoteIP,
177
+		IsBlockList: true,
178
+	}
179
+}
180
+
181
+// NewEventIPAllowlisted creates a NewEventIPBlocklisted event with a mark that
182
+// it is supposed to be for allow list.
183
+func NewEventIPAllowlisted(remoteIP net.IP) EventIPBlocklisted {
184
+	return EventIPBlocklisted{
185
+		eventBase: eventBase{
186
+			timestamp: time.Now(),
187
+		},
188
+		RemoteIP:    remoteIP,
189
+		IsBlockList: false,
176 190
 	}
177 191
 }
178 192
 

+ 9
- 0
mtglib/events_test.go Bestand weergeven

@@ -60,6 +60,15 @@ func (suite *EventsTestSuite) TestEventIPBlocklisted() {
60 60
 
61 61
 	suite.Empty(evt.StreamID())
62 62
 	suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
63
+	suite.True(evt.IsBlockList)
64
+}
65
+
66
+func (suite *EventsTestSuite) TestEventIPAllowlisted() {
67
+	evt := mtglib.NewEventIPAllowlisted(net.ParseIP("10.0.0.10"))
68
+
69
+	suite.Empty(evt.StreamID())
70
+	suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
71
+	suite.False(evt.IsBlockList)
63 72
 }
64 73
 
65 74
 func (suite *EventsTestSuite) TestEventReplayAttack() {

+ 4
- 0
mtglib/init.go Bestand weergeven

@@ -48,6 +48,10 @@ var (
48 48
 	// create a proxy but ip blocklist instance is not defined.
49 49
 	ErrIPBlocklistIsNotDefined = errors.New("ip blocklist is not defined")
50 50
 
51
+	// ErrIPAllowlistIsNotDefined is returned if you are trying to
52
+	// create a proxy but ip allowlist instance is not defined.
53
+	ErrIPAllowlistIsNotDefined = errors.New("ip allowlist is not defined")
54
+
51 55
 	// ErrEventStreamIsNotDefined is returned if you are trying to create a
52 56
 	// proxy but event stream instance is not defined.
53 57
 	ErrEventStreamIsNotDefined = errors.New("event stream is not defined")

+ 21
- 0
mtglib/internal/faketls/client_hello_fuzz_test.go Bestand weergeven

@@ -0,0 +1,21 @@
1
+package faketls_test
2
+
3
+import (
4
+	"testing"
5
+
6
+	"github.com/9seconds/mtg/v2/mtglib/internal/faketls"
7
+	"github.com/stretchr/testify/require"
8
+)
9
+
10
+var FuzzClientHelloSecret = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
11
+
12
+func FuzzClientHello(f *testing.F) {
13
+	f.Add([]byte{1, 2, 3})
14
+
15
+	f.Fuzz(func(t *testing.T, frame []byte) {
16
+		_, err := faketls.ParseClientHello(FuzzClientHelloSecret, frame)
17
+
18
+		// a probability of having != err is almost negligible
19
+		require.Error(t, err)
20
+	})
21
+}

+ 1
- 1
mtglib/internal/faketls/init.go Bestand weergeven

@@ -19,7 +19,7 @@ const (
19 19
 
20 20
 	// ClientHelloMinLen is a minimal possible length of
21 21
 	// ClientHello record.
22
-	ClientHelloMinLen = 4
22
+	ClientHelloMinLen = 6
23 23
 
24 24
 	// WelcomePacketRandomOffset is an offset of random in ServerHello
25 25
 	// packet (including record envelope).

+ 32
- 0
mtglib/internal/obfuscated2/client_handshake_fuzz_internal_test.go Bestand weergeven

@@ -0,0 +1,32 @@
1
+package obfuscated2
2
+
3
+import (
4
+	"bytes"
5
+	"testing"
6
+
7
+	"github.com/stretchr/testify/require"
8
+)
9
+
10
+var FuzzClientHandshakeSecret = []byte{1, 2, 3}
11
+
12
+func FuzzClientHandshake(f *testing.F) {
13
+	f.Add([]byte{1, 2, 3})
14
+
15
+	f.Fuzz(func(t *testing.T, frame []byte) {
16
+		data := bytes.NewReader(frame)
17
+
18
+		if _, _, _, err := ClientHandshake(FuzzClientHandshakeSecret, data); err != nil {
19
+			return
20
+		}
21
+
22
+		handshake := clientHandhakeFrame{}
23
+		require.Len(t, frame, handshakeFrameLen)
24
+
25
+		copy(handshake.data[:], frame)
26
+
27
+		decryptor := handshake.decryptor(FuzzClientHandshakeSecret)
28
+		decryptor.XORKeyStream(handshake.data[:], handshake.data[:])
29
+
30
+		require.Equal(t, handshakeConnectionType, handshake.connectionType())
31
+	})
32
+}

+ 54
- 0
mtglib/internal/obfuscated2/init_test.go Bestand weergeven

@@ -1,12 +1,20 @@
1 1
 package obfuscated2_test
2 2
 
3 3
 import (
4
+	"bytes"
5
+	"crypto/aes"
6
+	"crypto/cipher"
4 7
 	"encoding/base64"
5 8
 	"encoding/json"
6 9
 	"fmt"
7 10
 	"os"
8 11
 	"path/filepath"
9 12
 	"strings"
13
+	"testing"
14
+
15
+	"github.com/9seconds/mtg/v2/internal/testlib"
16
+	"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
17
+	"github.com/stretchr/testify/require"
10 18
 )
11 19
 
12 20
 type snapshotBytes struct {
@@ -50,6 +58,14 @@ type SnapshotTestSuite struct {
50 58
 	snapshots map[string]*Obfuscated2Snapshot
51 59
 }
52 60
 
61
+type ServerHandshakeTestData struct {
62
+	connMock *testlib.EssentialsConnMock
63
+
64
+	proxyConn obfuscated2.Conn
65
+	encryptor cipher.Stream
66
+	decryptor cipher.Stream
67
+}
68
+
53 69
 func (suite *SnapshotTestSuite) IngestSnapshots(dirname, namePrefix string) error {
54 70
 	suite.snapshots = map[string]*Obfuscated2Snapshot{}
55 71
 
@@ -81,3 +97,41 @@ func (suite *SnapshotTestSuite) IngestSnapshots(dirname, namePrefix string) erro
81 97
 
82 98
 	return nil
83 99
 }
100
+
101
+func NewServerHandshakeTestData(t *testing.T) ServerHandshakeTestData {
102
+	buf := &bytes.Buffer{}
103
+	connMock := &testlib.EssentialsConnMock{}
104
+
105
+	handshakeEnc, handshakeDec, err := obfuscated2.ServerHandshake(buf)
106
+	require.NoError(t, err)
107
+
108
+	serverEncrypted := buf.Bytes()
109
+	decBlock, _ := aes.NewCipher(serverEncrypted[8 : 8+32])
110
+	decryptor := cipher.NewCTR(decBlock, serverEncrypted[8+32:8+32+16])
111
+
112
+	serverDecrypted := make([]byte, len(serverEncrypted))
113
+	decryptor.XORKeyStream(serverDecrypted, serverEncrypted)
114
+
115
+	require.Equal(t, "3d3d3Q",
116
+		base64.RawStdEncoding.EncodeToString(serverDecrypted[8+32+16:8+32+16+4]))
117
+
118
+	serverEncryptedReverted := make([]byte, len(serverEncrypted))
119
+
120
+	for i := 0; i < 32+16; i++ {
121
+		serverEncryptedReverted[8+i] = serverEncrypted[8+32+16-1-i]
122
+	}
123
+
124
+	encBlock, _ := aes.NewCipher(serverEncryptedReverted[8 : 8+32])
125
+	encryptor := cipher.NewCTR(encBlock, serverEncryptedReverted[8+32:8+32+16])
126
+
127
+	return ServerHandshakeTestData{
128
+		connMock: connMock,
129
+		proxyConn: obfuscated2.Conn{
130
+			Conn:      connMock,
131
+			Encryptor: handshakeEnc,
132
+			Decryptor: handshakeDec,
133
+		},
134
+		encryptor: encryptor,
135
+		decryptor: decryptor,
136
+	}
137
+}

+ 30
- 0
mtglib/internal/obfuscated2/server_handshake_fuzz_internal_test.go Bestand weergeven

@@ -0,0 +1,30 @@
1
+package obfuscated2
2
+
3
+import (
4
+	"encoding/binary"
5
+	"testing"
6
+
7
+	"github.com/stretchr/testify/assert"
8
+)
9
+
10
+func FuzzServerGenerateHandshakeFrame(f *testing.F) {
11
+	f.Fuzz(func(t *testing.T, arg int) {
12
+		frame := generateServerHanshakeFrame()
13
+
14
+		assert.NotEqualValues(t, 0xef, frame.data[0])
15
+
16
+		firstBytes := binary.LittleEndian.Uint32(frame.data[:4])
17
+		assert.NotEqualValues(t, 0x44414548, firstBytes)
18
+		assert.NotEqualValues(t, 0x54534f50, firstBytes)
19
+		assert.NotEqualValues(t, 0x20544547, firstBytes)
20
+		assert.NotEqualValues(t, 0x4954504f, firstBytes)
21
+		assert.NotEqualValues(t, 0xeeeeeeee, firstBytes)
22
+
23
+		assert.NotEqualValues(
24
+			t,
25
+			0,
26
+			frame.data[4]|frame.data[5]|frame.data[6]|frame.data[7])
27
+
28
+		assert.Equal(t, handshakeConnectionType, frame.connectionType())
29
+	})
30
+}

+ 58
- 0
mtglib/internal/obfuscated2/server_handshake_fuzz_test.go Bestand weergeven

@@ -0,0 +1,58 @@
1
+package obfuscated2_test
2
+
3
+import (
4
+	"testing"
5
+
6
+	"github.com/stretchr/testify/assert"
7
+	"github.com/stretchr/testify/mock"
8
+)
9
+
10
+func FuzzServerSend(f *testing.F) {
11
+	f.Add([]byte{1, 2, 3, 4, 5})
12
+
13
+	f.Fuzz(func(t *testing.T, data []byte) {
14
+		handshakeData := NewServerHandshakeTestData(t)
15
+
16
+		handshakeData.connMock.
17
+			On("Write", mock.Anything).
18
+			Return(len(data), nil).
19
+			Once().
20
+			Run(func(args mock.Arguments) {
21
+				message := make([]byte, len(data))
22
+				handshakeData.decryptor.XORKeyStream(message, args.Get(0).([]byte)) // nolint: forcetypeassert
23
+				assert.Equal(t, message, data)
24
+			})
25
+
26
+		n, err := handshakeData.proxyConn.Write(data)
27
+
28
+		assert.EqualValues(t, len(data), n)
29
+		assert.NoError(t, err)
30
+		handshakeData.connMock.AssertExpectations(t)
31
+	})
32
+}
33
+
34
+func FuzzServerReceive(f *testing.F) {
35
+	f.Add([]byte{1, 2, 3, 4, 5})
36
+
37
+	f.Fuzz(func(t *testing.T, data []byte) {
38
+		handshakeData := NewServerHandshakeTestData(t)
39
+		buffer := make([]byte, len(data))
40
+
41
+		handshakeData.connMock.
42
+			On("Read", mock.Anything).
43
+			Return(len(data), nil).
44
+			Once().
45
+			Run(func(args mock.Arguments) {
46
+				message := make([]byte, len(data))
47
+				handshakeData.encryptor.XORKeyStream(message, data)
48
+				copy(args.Get(0).([]byte), message) // nolint: forcetypeassert
49
+			})
50
+
51
+		n, err := handshakeData.proxyConn.Read(buffer)
52
+
53
+		assert.EqualValues(t, len(data), n)
54
+		assert.NoError(t, err)
55
+		assert.Equal(t, data, buffer)
56
+		handshakeData.connMock.AssertExpectations(t)
57
+	})
58
+}

+ 9
- 48
mtglib/internal/obfuscated2/server_handshake_test.go Bestand weergeven

@@ -1,14 +1,8 @@
1 1
 package obfuscated2_test
2 2
 
3 3
 import (
4
-	"bytes"
5
-	"crypto/aes"
6
-	"crypto/cipher"
7
-	"encoding/base64"
8 4
 	"testing"
9 5
 
10
-	"github.com/9seconds/mtg/v2/internal/testlib"
11
-	"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
12 6
 	"github.com/stretchr/testify/mock"
13 7
 	"github.com/stretchr/testify/suite"
14 8
 )
@@ -16,64 +10,31 @@ import (
16 10
 type ServerHandshakeTestSuite struct {
17 11
 	suite.Suite
18 12
 
19
-	connMock  *testlib.EssentialsConnMock
20
-	proxyConn obfuscated2.Conn
21
-	encryptor cipher.Stream
22
-	decryptor cipher.Stream
13
+	data ServerHandshakeTestData
23 14
 }
24 15
 
25 16
 func (suite *ServerHandshakeTestSuite) SetupTest() {
26
-	buf := &bytes.Buffer{}
27
-	suite.connMock = &testlib.EssentialsConnMock{}
28
-
29
-	encryptor, decryptor, err := obfuscated2.ServerHandshake(buf)
30
-	suite.NoError(err)
31
-
32
-	suite.proxyConn = obfuscated2.Conn{
33
-		Conn:      suite.connMock,
34
-		Encryptor: encryptor,
35
-		Decryptor: decryptor,
36
-	}
37
-
38
-	serverEncrypted := buf.Bytes()
39
-
40
-	decBlock, _ := aes.NewCipher(serverEncrypted[8 : 8+32])
41
-	suite.decryptor = cipher.NewCTR(decBlock, serverEncrypted[8+32:8+32+16])
42
-
43
-	serverDecrypted := make([]byte, len(serverEncrypted))
44
-	suite.decryptor.XORKeyStream(serverDecrypted, serverEncrypted)
45
-
46
-	suite.Equal("3d3d3Q",
47
-		base64.RawStdEncoding.EncodeToString(serverDecrypted[8+32+16:8+32+16+4]))
48
-
49
-	serverEncryptedReverted := make([]byte, len(serverEncrypted))
50
-
51
-	for i := 0; i < 32+16; i++ {
52
-		serverEncryptedReverted[8+i] = serverEncrypted[8+32+16-1-i]
53
-	}
54
-
55
-	encBlock, _ := aes.NewCipher(serverEncryptedReverted[8 : 8+32])
56
-	suite.encryptor = cipher.NewCTR(encBlock, serverEncryptedReverted[8+32:8+32+16])
17
+	suite.data = NewServerHandshakeTestData(suite.T())
57 18
 }
58 19
 
59 20
 func (suite *ServerHandshakeTestSuite) TearDownTest() {
60
-	suite.connMock.AssertExpectations(suite.T())
21
+	suite.data.connMock.AssertExpectations(suite.T())
61 22
 }
62 23
 
63 24
 func (suite *ServerHandshakeTestSuite) TestSendToTelegram() {
64 25
 	messageToTelegram := []byte{10, 11, 12, 13, 14, 'a'}
65 26
 
66
-	suite.connMock.
27
+	suite.data.connMock.
67 28
 		On("Write", mock.Anything).
68 29
 		Return(len(messageToTelegram), nil).
69 30
 		Once().
70 31
 		Run(func(args mock.Arguments) {
71 32
 			message := make([]byte, len(messageToTelegram))
72
-			suite.decryptor.XORKeyStream(message, args.Get(0).([]byte)) // nolint: forcetypeassert
33
+			suite.data.decryptor.XORKeyStream(message, args.Get(0).([]byte)) // nolint: forcetypeassert
73 34
 			suite.Equal(messageToTelegram, message)
74 35
 		})
75 36
 
76
-	n, err := suite.proxyConn.Write(messageToTelegram)
37
+	n, err := suite.data.proxyConn.Write(messageToTelegram)
77 38
 	suite.EqualValues(len(messageToTelegram), n)
78 39
 	suite.NoError(err)
79 40
 }
@@ -82,17 +43,17 @@ func (suite *ServerHandshakeTestSuite) TestRecieveFromTelegram() {
82 43
 	messageFromTelegram := []byte{10, 11, 12, 13, 14, 'a'}
83 44
 	buffer := make([]byte, len(messageFromTelegram))
84 45
 
85
-	suite.connMock.
46
+	suite.data.connMock.
86 47
 		On("Read", mock.Anything).
87 48
 		Return(len(messageFromTelegram), nil).
88 49
 		Once().
89 50
 		Run(func(args mock.Arguments) {
90 51
 			message := make([]byte, len(messageFromTelegram))
91
-			suite.encryptor.XORKeyStream(message, messageFromTelegram)
52
+			suite.data.encryptor.XORKeyStream(message, messageFromTelegram)
92 53
 			copy(args.Get(0).([]byte), message) // nolint: forcetypeassert
93 54
 		})
94 55
 
95
-	n, err := suite.proxyConn.Read(buffer)
56
+	n, err := suite.data.proxyConn.Read(buffer)
96 57
 	suite.EqualValues(len(messageFromTelegram), n)
97 58
 	suite.NoError(err)
98 59
 	suite.Equal(messageFromTelegram, buffer)

+ 7
- 10
mtglib/proxy.go Bestand weergeven

@@ -34,7 +34,7 @@ type Proxy struct {
34 34
 	network         Network
35 35
 	antiReplayCache AntiReplayCache
36 36
 	blocklist       IPBlocklist
37
-	whitelist       IPBlocklist
37
+	allowlist       IPBlocklist
38 38
 	eventStream     EventStream
39 39
 	logger          Logger
40 40
 }
@@ -91,7 +91,7 @@ func (p *Proxy) ServeConn(conn essentials.Conn) {
91 91
 }
92 92
 
93 93
 // Serve starts a proxy on a given listener.
94
-func (p *Proxy) Serve(listener net.Listener) error { // nolint: cyclop
94
+func (p *Proxy) Serve(listener net.Listener) error {
95 95
 	p.streamWaitGroup.Add(1)
96 96
 	defer p.streamWaitGroup.Done()
97 97
 
@@ -109,10 +109,10 @@ func (p *Proxy) Serve(listener net.Listener) error { // nolint: cyclop
109 109
 		ipAddr := conn.RemoteAddr().(*net.TCPAddr).IP // nolint: forcetypeassert
110 110
 		logger := p.logger.BindStr("ip", ipAddr.String())
111 111
 
112
-		if p.whitelist != nil && !p.whitelist.Contains(ipAddr) {
112
+		if !p.allowlist.Contains(ipAddr) {
113 113
 			conn.Close()
114
-			logger.Info("ip was rejected by whitelist")
115
-			p.eventStream.Send(p.ctx, NewEventIPBlocklisted(ipAddr))
114
+			logger.Info("ip was rejected by allowlist")
115
+			p.eventStream.Send(p.ctx, NewEventIPAllowlisted(ipAddr))
116 116
 
117 117
 			continue
118 118
 		}
@@ -145,10 +145,7 @@ func (p *Proxy) Shutdown() {
145 145
 	p.streamWaitGroup.Wait()
146 146
 	p.workerPool.Release()
147 147
 
148
-	if p.whitelist != nil {
149
-		p.whitelist.Shutdown()
150
-	}
151
-
148
+	p.allowlist.Shutdown()
152 149
 	p.blocklist.Shutdown()
153 150
 }
154 151
 
@@ -308,7 +305,7 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) {
308 305
 		network:                  opts.Network,
309 306
 		antiReplayCache:          opts.AntiReplayCache,
310 307
 		blocklist:                opts.IPBlocklist,
311
-		whitelist:                opts.IPWhitelist,
308
+		allowlist:                opts.IPAllowlist,
312 309
 		eventStream:              opts.EventStream,
313 310
 		logger:                   opts.getLogger("proxy"),
314 311
 		domainFrontingPort:       opts.getDomainFrontingPort(),

+ 4
- 2
mtglib/proxy_opts.go Bestand weergeven

@@ -28,10 +28,10 @@ type ProxyOpts struct {
28 28
 	// This is a mandatory setting.
29 29
 	IPBlocklist IPBlocklist
30 30
 
31
-	// IPWhitelist defines a whitelist of IPs to allow to use proxy.
31
+	// IPAllowlist defines a whitelist of IPs to allow to use proxy.
32 32
 	//
33 33
 	// This is an optional setting, ignored by default (no restrictions).
34
-	IPWhitelist IPBlocklist
34
+	IPAllowlist IPBlocklist
35 35
 
36 36
 	// EventStream defines an instance of event stream.
37 37
 	//
@@ -125,6 +125,8 @@ func (p ProxyOpts) valid() error {
125 125
 		return ErrAntiReplayCacheIsNotDefined
126 126
 	case p.IPBlocklist == nil:
127 127
 		return ErrIPBlocklistIsNotDefined
128
+	case p.IPAllowlist == nil:
129
+		return ErrIPAllowlistIsNotDefined
128 130
 	case p.EventStream == nil:
129 131
 		return ErrEventStreamIsNotDefined
130 132
 	case p.Logger == nil:

+ 25
- 0
mtglib/proxy_test.go Bestand weergeven

@@ -15,6 +15,7 @@ import (
15 15
 	"github.com/9seconds/mtg/v2/antireplay"
16 16
 	"github.com/9seconds/mtg/v2/events"
17 17
 	"github.com/9seconds/mtg/v2/ipblocklist"
18
+	"github.com/9seconds/mtg/v2/ipblocklist/files"
18 19
 	"github.com/9seconds/mtg/v2/logger"
19 20
 	"github.com/9seconds/mtg/v2/mtglib"
20 21
 	"github.com/9seconds/mtg/v2/network"
@@ -22,6 +23,7 @@ import (
22 23
 	"github.com/gotd/td/telegram/dcs"
23 24
 	"github.com/gotd/td/tg"
24 25
 	"github.com/stretchr/testify/suite"
26
+	"github.com/yl2chen/cidranger"
25 27
 )
26 28
 
27 29
 type ProxyTestSuite struct {
@@ -49,11 +51,26 @@ func (suite *ProxyTestSuite) SetupSuite() {
49 51
 	ntw, err := network.NewNetwork(dialer, "mtgtest", "1.1.1.1", 0)
50 52
 	suite.NoError(err)
51 53
 
54
+	allowlist, _ := ipblocklist.NewFireholFromFiles(
55
+		logger.NewNoopLogger(),
56
+		1,
57
+		[]files.File{
58
+			files.NewMem([]*net.IPNet{
59
+				cidranger.AllIPv4,
60
+				cidranger.AllIPv6,
61
+			}),
62
+		},
63
+		nil,
64
+	)
65
+
66
+	go allowlist.Run(time.Second)
67
+
52 68
 	suite.opts = &mtglib.ProxyOpts{
53 69
 		Secret:          mtglib.GenerateSecret("httpbin.org"),
54 70
 		Network:         ntw,
55 71
 		AntiReplayCache: antireplay.NewNoop(),
56 72
 		IPBlocklist:     ipblocklist.NewNoop(),
73
+		IPAllowlist:     allowlist,
57 74
 		EventStream:     events.NewNoopStream(),
58 75
 		Logger:          logger.NewNoopLogger(),
59 76
 		UseTestDCs:      true,
@@ -114,6 +131,14 @@ func (suite *ProxyTestSuite) TestCannotInitNoIPBlocklist() {
114 131
 	suite.Error(err)
115 132
 }
116 133
 
134
+func (suite *ProxyTestSuite) TestCannotInitNoIPAllowlist() {
135
+	opts := *suite.opts
136
+	opts.IPAllowlist = nil
137
+
138
+	_, err := mtglib.NewProxy(opts)
139
+	suite.Error(err)
140
+}
141
+
117 142
 func (suite *ProxyTestSuite) TestCannotInitNoEventStream() {
118 143
 	opts := *suite.opts
119 144
 	opts.EventStream = nil

+ 10
- 5
network/circuit_breaker.go Bestand weergeven

@@ -36,7 +36,8 @@ func (c *circuitBreakerDialer) Dial(network, address string) (essentials.Conn, e
36 36
 }
37 37
 
38 38
 func (c *circuitBreakerDialer) DialContext(ctx context.Context,
39
-	network, address string) (essentials.Conn, error) {
39
+	network, address string,
40
+) (essentials.Conn, error) {
40 41
 	switch atomic.LoadUint32(&c.state) {
41 42
 	case circuitBreakerStateClosed:
42 43
 		return c.doClosed(ctx, network, address)
@@ -48,7 +49,8 @@ func (c *circuitBreakerDialer) DialContext(ctx context.Context,
48 49
 }
49 50
 
50 51
 func (c *circuitBreakerDialer) doClosed(ctx context.Context,
51
-	network, address string) (essentials.Conn, error) {
52
+	network, address string,
53
+) (essentials.Conn, error) {
52 54
 	conn, err := c.Dialer.DialContext(ctx, network, address)
53 55
 
54 56
 	select {
@@ -80,7 +82,8 @@ func (c *circuitBreakerDialer) doClosed(ctx context.Context,
80 82
 }
81 83
 
82 84
 func (c *circuitBreakerDialer) doHalfOpened(ctx context.Context,
83
-	network, address string) (essentials.Conn, error) {
85
+	network, address string,
86
+) (essentials.Conn, error) {
84 87
 	if !atomic.CompareAndSwapUint32(&c.halfOpenAttempts, 0, 1) {
85 88
 		return nil, ErrCircuitBreakerOpened
86 89
 	}
@@ -174,14 +177,16 @@ func (c *circuitBreakerDialer) stopTimer(timerRef **time.Timer) {
174 177
 }
175 178
 
176 179
 func (c *circuitBreakerDialer) ensureTimer(timerRef **time.Timer,
177
-	timeout time.Duration, callback func()) {
180
+	timeout time.Duration, callback func(),
181
+) {
178 182
 	if *timerRef == nil {
179 183
 		*timerRef = time.AfterFunc(timeout, callback)
180 184
 	}
181 185
 }
182 186
 
183 187
 func newCircuitBreakerDialer(baseDialer Dialer,
184
-	openThreshold uint32, halfOpenTimeout, resetFailuresTimeout time.Duration) Dialer {
188
+	openThreshold uint32, halfOpenTimeout, resetFailuresTimeout time.Duration,
189
+) Dialer {
185 190
 	cb := &circuitBreakerDialer{
186 191
 		Dialer:               baseDialer,
187 192
 		stateMutexChan:       make(chan bool, 1),

+ 6
- 3
network/network.go Bestand weergeven

@@ -61,7 +61,8 @@ func (n *network) DialContext(ctx context.Context, protocol, address string) (es
61 61
 }
62 62
 
63 63
 func (n *network) MakeHTTPClient(dialFunc func(ctx context.Context,
64
-	network, address string) (essentials.Conn, error)) *http.Client {
64
+	network, address string) (essentials.Conn, error),
65
+) *http.Client {
65 66
 	if dialFunc == nil {
66 67
 		dialFunc = n.DialContext
67 68
 	}
@@ -123,7 +124,8 @@ func (n *network) dnsResolve(protocol, address string) ([]string, error) {
123 124
 // It brings simple DNS cache and DNS-Over-HTTPS when necessary.
124 125
 func NewNetwork(dialer Dialer,
125 126
 	userAgent, dohHostname string,
126
-	httpTimeout time.Duration) (mtglib.Network, error) {
127
+	httpTimeout time.Duration,
128
+) (mtglib.Network, error) {
127 129
 	switch {
128 130
 	case httpTimeout < 0:
129 131
 		return nil, fmt.Errorf("timeout should be positive number %s", httpTimeout)
@@ -146,7 +148,8 @@ func NewNetwork(dialer Dialer,
146 148
 
147 149
 func makeHTTPClient(userAgent string,
148 150
 	timeout time.Duration,
149
-	dialFunc func(ctx context.Context, network, address string) (essentials.Conn, error)) *http.Client {
151
+	dialFunc func(ctx context.Context, network, address string) (essentials.Conn, error),
152
+) *http.Client {
150 153
 	return &http.Client{
151 154
 		Timeout: timeout,
152 155
 		Transport: networkHTTPTransport{

+ 14
- 9
stats/prometheus.go Bestand weergeven

@@ -110,8 +110,13 @@ func (p prometheusProcessor) EventConcurrencyLimited(_ mtglib.EventConcurrencyLi
110 110
 	p.factory.metricConcurrencyLimited.Inc()
111 111
 }
112 112
 
113
-func (p prometheusProcessor) EventIPBlocklisted(_ mtglib.EventIPBlocklisted) {
114
-	p.factory.metricIPBlocklisted.Inc()
113
+func (p prometheusProcessor) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
114
+	tag := TagIPListBlock
115
+	if !evt.IsBlockList {
116
+		tag = TagIPListAllow
117
+	}
118
+
119
+	p.factory.metricIPBlocklisted.WithLabelValues(tag).Inc()
115 120
 }
116 121
 
117 122
 func (p prometheusProcessor) EventReplayAttack(_ mtglib.EventReplayAttack) {
@@ -150,10 +155,10 @@ type PrometheusFactory struct {
150 155
 
151 156
 	metricTelegramTraffic       *prometheus.CounterVec
152 157
 	metricDomainFrontingTraffic *prometheus.CounterVec
158
+	metricIPBlocklisted         *prometheus.CounterVec
153 159
 
154 160
 	metricDomainFronting     prometheus.Counter
155 161
 	metricConcurrencyLimited prometheus.Counter
156
-	metricIPBlocklisted      prometheus.Counter
157 162
 	metricReplayAttacks      prometheus.Counter
158 163
 }
159 164
 
@@ -223,6 +228,11 @@ func NewPrometheus(metricPrefix, httpPath string) *PrometheusFactory { // nolint
223 228
 			Name:      MetricDomainFrontingTraffic,
224 229
 			Help:      "Traffic which is generated talking with front domain.",
225 230
 		}, []string{TagDirection}),
231
+		metricIPBlocklisted: prometheus.NewCounterVec(prometheus.CounterOpts{
232
+			Namespace: metricPrefix,
233
+			Name:      MetricIPBlocklisted,
234
+			Help:      "A number of rejected sessions due to ip blocklisting.",
235
+		}, []string{TagIPList}),
226 236
 
227 237
 		metricDomainFronting: prometheus.NewCounter(prometheus.CounterOpts{
228 238
 			Namespace: metricPrefix,
@@ -234,11 +244,6 @@ func NewPrometheus(metricPrefix, httpPath string) *PrometheusFactory { // nolint
234 244
 			Name:      MetricConcurrencyLimited,
235 245
 			Help:      "A number of sessions that were rejected by concurrency limiter.",
236 246
 		}),
237
-		metricIPBlocklisted: prometheus.NewCounter(prometheus.CounterOpts{
238
-			Namespace: metricPrefix,
239
-			Name:      MetricIPBlocklisted,
240
-			Help:      "A number of rejected sessions due to ip blocklisting.",
241
-		}),
242 247
 		metricReplayAttacks: prometheus.NewCounter(prometheus.CounterOpts{
243 248
 			Namespace: metricPrefix,
244 249
 			Name:      MetricReplayAttacks,
@@ -253,10 +258,10 @@ func NewPrometheus(metricPrefix, httpPath string) *PrometheusFactory { // nolint
253 258
 
254 259
 	registry.MustRegister(factory.metricTelegramTraffic)
255 260
 	registry.MustRegister(factory.metricDomainFrontingTraffic)
261
+	registry.MustRegister(factory.metricIPBlocklisted)
256 262
 
257 263
 	registry.MustRegister(factory.metricDomainFronting)
258 264
 	registry.MustRegister(factory.metricConcurrencyLimited)
259
-	registry.MustRegister(factory.metricIPBlocklisted)
260 265
 	registry.MustRegister(factory.metricReplayAttacks)
261 266
 
262 267
 	return factory

+ 12
- 1
stats/prometheus_test.go Bestand weergeven

@@ -156,7 +156,18 @@ func (suite *PrometheusTestSuite) TestEventIPBlocklisted() {
156 156
 
157 157
 	data, err := suite.Get()
158 158
 	suite.NoError(err)
159
-	suite.Contains(data, `mtg_ip_blocklisted 1`)
159
+	suite.Contains(data, `mtg_ip_blocklisted{ip_list="blocklist"} 1`)
160
+}
161
+
162
+func (suite *PrometheusTestSuite) TestEventIPAllowlisted() {
163
+	suite.prometheus.EventIPBlocklisted(
164
+		mtglib.NewEventIPAllowlisted(net.ParseIP("2001:db8::68")))
165
+
166
+	time.Sleep(100 * time.Millisecond)
167
+
168
+	data, err := suite.Get()
169
+	suite.NoError(err)
170
+	suite.Contains(data, `mtg_ip_blocklisted{ip_list="allowlist"} 1`)
160 171
 }
161 172
 
162 173
 func (suite *PrometheusTestSuite) TestEventReplayAttack() {

+ 9
- 3
stats/statsd.go Bestand weergeven

@@ -113,8 +113,13 @@ func (s statsdProcessor) EventConcurrencyLimited(_ mtglib.EventConcurrencyLimite
113 113
 	s.client.Incr(MetricConcurrencyLimited, 1)
114 114
 }
115 115
 
116
-func (s statsdProcessor) EventIPBlocklisted(_ mtglib.EventIPBlocklisted) {
117
-	s.client.Incr(MetricIPBlocklisted, 1)
116
+func (s statsdProcessor) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
117
+	tag := TagIPListBlock
118
+	if !evt.IsBlockList {
119
+		tag = TagIPListAllow
120
+	}
121
+
122
+	s.client.Incr(MetricIPBlocklisted, 1, statsd.StringTag(TagIPList, tag))
118 123
 }
119 124
 
120 125
 func (s statsdProcessor) EventReplayAttack(_ mtglib.EventReplayAttack) {
@@ -171,7 +176,8 @@ func (s StatsdFactory) Make() events.Observer {
171 176
 //
172 177
 // Valid tagFormats are 'datadog', 'influxdb' and 'graphite'.
173 178
 func NewStatsd(address string, log logger.StdLikeLogger,
174
-	metricPrefix, tagFormat string) (StatsdFactory, error) {
179
+	metricPrefix, tagFormat string,
180
+) (StatsdFactory, error) {
175 181
 	options := []statsd.Option{
176 182
 		statsd.MetricPrefix(metricPrefix),
177 183
 		statsd.Logger(log),

+ 9
- 1
stats/statsd_test.go Bestand weergeven

@@ -186,7 +186,15 @@ func (suite *StatsdTestSuite) TestEventIPBlocklisted() {
186 186
 		mtglib.NewEventIPBlocklisted(net.ParseIP("10.0.0.10")))
187 187
 
188 188
 	time.Sleep(statsdSleepTime)
189
-	suite.Equal("mtg.ip_blocklisted:1|c", suite.statsdServer.String())
189
+	suite.Equal("mtg.ip_blocklisted:1|c|#ip_list:blocklist", suite.statsdServer.String())
190
+}
191
+
192
+func (suite *StatsdTestSuite) TestEventIPAllowlisted() {
193
+	suite.statsd.EventIPBlocklisted(
194
+		mtglib.NewEventIPAllowlisted(net.ParseIP("10.0.0.10")))
195
+
196
+	time.Sleep(statsdSleepTime)
197
+	suite.Equal("mtg.ip_blocklisted:1|c|#ip_list:allowlist", suite.statsdServer.String())
190 198
 }
191 199
 
192 200
 func (suite *StatsdTestSuite) TestEventReplayAttack() {

Laden…
Annuleren
Opslaan