mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 11:44:02 +03:00
Merge remote-tracking branch 'origin/master' into stable
This commit is contained in:
@@ -38,11 +38,11 @@ jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
go_version:
|
||||
- ^1.17
|
||||
- ^1.18
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -69,6 +69,38 @@ jobs:
|
||||
with:
|
||||
file: ./coverage.txt
|
||||
|
||||
fuzz:
|
||||
name: Fuzzing
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.18
|
||||
|
||||
- name: Cache fuzz results
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/go-build/fuzz
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/*_fuzz_test.go', '**/*_fuzz_internal_test.go') }}
|
||||
restore-keys: ${{ runner.os }}-go-
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: ${{ runner.os }}-go-
|
||||
|
||||
- name: Run fuzzing
|
||||
run: make -j4 fuzz
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
@@ -79,10 +111,15 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Run linter
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
version: v1.44.2
|
||||
go-version: ^1.18
|
||||
|
||||
- name: Run linter
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: v1.45.0
|
||||
|
||||
docker:
|
||||
name: Docker
|
||||
|
||||
+1
-1
@@ -9,4 +9,4 @@ format = "colored-line-number"
|
||||
|
||||
[linters]
|
||||
enable-all = true
|
||||
disable = ["ireturn", "varnamelen", "gochecknoglobals", "gas", "goerr113", "exhaustivestruct", "containedctx"]
|
||||
disable = ["thelper", "ireturn", "varnamelen", "gochecknoglobals", "gas", "goerr113", "exhaustivestruct", "containedctx"]
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
###############################################################################
|
||||
# BUILD STAGE
|
||||
|
||||
FROM golang:1.17-alpine AS build
|
||||
FROM golang:1.18-alpine AS build
|
||||
|
||||
RUN set -x \
|
||||
&& apk --no-cache --update add \
|
||||
|
||||
@@ -2,12 +2,12 @@ ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
IMAGE_NAME := mtg
|
||||
APP_NAME := $(IMAGE_NAME)
|
||||
|
||||
GOLANGCI_LINT_VERSION := v1.44.2
|
||||
GOLANGCI_LINT_VERSION := v1.45.0
|
||||
|
||||
VERSION_GO := $(shell go version)
|
||||
VERSION_DATE := $(shell date -Ru)
|
||||
VERSION_TAG := $(shell git describe --tags --always)
|
||||
COMMON_BUILD_FLAGS := -trimpath -mod=readonly -ldflags="-extldflags '-static' -s -w -X 'main.version=$(VERSION_TAG) ($(VERSION_GO)) [$(VERSION_DATE)]'"
|
||||
VERSION := $(shell git describe --exact-match HEAD 2>/dev/null || git describe --tags --always)
|
||||
COMMON_BUILD_FLAGS := -trimpath -mod=readonly -ldflags="-extldflags '-static' -s -w -X 'main.version=$(VERSION)'"
|
||||
|
||||
FUZZ_FLAGS := -fuzztime=120s
|
||||
|
||||
GOBIN := $(ROOT_DIR)/.bin
|
||||
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
|
||||
|
||||
.PHONY: install-tools-lint
|
||||
install-tools-lint: .bin
|
||||
@curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh \
|
||||
@curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \
|
||||
| bash -s -- -b "$(GOBIN)" "$(GOLANGCI_LINT_VERSION)"
|
||||
|
||||
.PHONY: install-tools-godoc
|
||||
@@ -95,4 +95,27 @@ install-tools-goreleaser: .bin
|
||||
|
||||
.PHONY: update-deps
|
||||
update-deps:
|
||||
@go get -u && go mod tidy -go=1.17
|
||||
@go get -u && go mod tidy -go=1.18
|
||||
|
||||
.PHONY: fuzz
|
||||
fuzz: fuzz-ClientHello fuzz-ServerGenerateHandshakeFrame fuzz-ClientHandshake fuzz-ServerReceive fuzz-ServerSend
|
||||
|
||||
.PHONY: fuzz-ClientHello
|
||||
fuzz-ClientHello:
|
||||
@go test -fuzz=FuzzClientHello $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/faketls"
|
||||
|
||||
.PHONY: fuzz-ServerGenerateHandshakeFrame
|
||||
fuzz-ServerGenerateHandshakeFrame:
|
||||
@go test -fuzz=FuzzServerGenerateHandshakeFrame $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
|
||||
|
||||
.PHONY: fuzz-ClientHandshake
|
||||
fuzz-ClientHandshake:
|
||||
@go test -fuzz=FuzzClientHandshake $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
|
||||
|
||||
.PHONY: fuzz-ServerReceive
|
||||
fuzz-ServerReceive:
|
||||
@go test -fuzz=FuzzServerReceive $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
|
||||
|
||||
.PHONY: fuzz-ServerSend
|
||||
fuzz-ServerSend:
|
||||
@go test -fuzz=FuzzServerSend $(FUZZ_FLAGS) "$(ROOT_DIR)/mtglib/internal/obfuscated2"
|
||||
|
||||
@@ -317,12 +317,16 @@ Now you can create a systemd unit:
|
||||
```console
|
||||
$ cat /etc/systemd/system/mtg.service
|
||||
[Unit]
|
||||
Description=mtg
|
||||
Description=mtg - MTProto proxy server
|
||||
Documentation=https://github.com/9seconds/mtg
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/mtg run /etc/mtg.toml
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
DynamicUser=true
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -388,7 +392,7 @@ Here goes a list of metrics with their types but without a prefix.
|
||||
| domain_fronting_traffic | counter | `direction` | Count of bytes, transmitted to/from fronting domain. |
|
||||
| domain_fronting | counter | – | Count of domain fronting events. |
|
||||
| concurrency_limited | counter | – | Count of events, when client connection was rejected due to concurrency limit. |
|
||||
| ip_blocklisted | counter | – | Count of events when client connection was rejected because IP was found in the blacklist. |
|
||||
| ip_blocklisted | counter | `ip_list` | Count of events when client connection was rejected because IP was found in the blocklist. |
|
||||
| replay_attacks | counter | – | Count of detected replay attacks. |
|
||||
|
||||
Tag meaning:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/9seconds/mtg/v2
|
||||
|
||||
go 1.17
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/OneOfOne/xxhash v1.2.8
|
||||
@@ -23,9 +23,9 @@ require (
|
||||
github.com/stretchr/objx v0.3.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43
|
||||
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
)
|
||||
|
||||
|
||||
@@ -291,8 +291,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU=
|
||||
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -416,11 +416,10 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
+53
-20
@@ -12,11 +12,13 @@ import (
|
||||
"github.com/9seconds/mtg/v2/internal/config"
|
||||
"github.com/9seconds/mtg/v2/internal/utils"
|
||||
"github.com/9seconds/mtg/v2/ipblocklist"
|
||||
"github.com/9seconds/mtg/v2/ipblocklist/files"
|
||||
"github.com/9seconds/mtg/v2/logger"
|
||||
"github.com/9seconds/mtg/v2/mtglib"
|
||||
"github.com/9seconds/mtg/v2/network"
|
||||
"github.com/9seconds/mtg/v2/stats"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/yl2chen/cidranger"
|
||||
)
|
||||
|
||||
func makeLogger(conf *config.Config) mtglib.Logger {
|
||||
@@ -89,7 +91,8 @@ func makeAntiReplayCache(conf *config.Config) mtglib.AntiReplayCache {
|
||||
func makeIPBlocklist(conf config.ListConfig,
|
||||
logger mtglib.Logger,
|
||||
ntw mtglib.Network,
|
||||
updateCallback ipblocklist.FireholUpdateCallback) (mtglib.IPBlocklist, error) {
|
||||
updateCallback ipblocklist.FireholUpdateCallback,
|
||||
) (mtglib.IPBlocklist, error) {
|
||||
if !conf.Enabled.Get(false) {
|
||||
return ipblocklist.NewNoop(), nil
|
||||
}
|
||||
@@ -105,7 +108,7 @@ func makeIPBlocklist(conf config.ListConfig,
|
||||
}
|
||||
}
|
||||
|
||||
firehol, err := ipblocklist.NewFirehol(logger.Named("ipblockist"),
|
||||
blocklist, err := ipblocklist.NewFirehol(logger.Named("ipblockist"),
|
||||
ntw,
|
||||
conf.DownloadConcurrency.Get(1),
|
||||
remoteURLs,
|
||||
@@ -115,9 +118,44 @@ func makeIPBlocklist(conf config.ListConfig,
|
||||
return nil, fmt.Errorf("incorrect parameters for firehol: %w", err)
|
||||
}
|
||||
|
||||
go firehol.Run(conf.UpdateEach.Get(ipblocklist.DefaultFireholUpdateEach))
|
||||
go blocklist.Run(conf.UpdateEach.Get(ipblocklist.DefaultFireholUpdateEach))
|
||||
|
||||
return firehol, nil
|
||||
return blocklist, nil
|
||||
}
|
||||
|
||||
func makeIPAllowlist(conf config.ListConfig,
|
||||
logger mtglib.Logger,
|
||||
ntw mtglib.Network,
|
||||
updateCallback ipblocklist.FireholUpdateCallback,
|
||||
) (allowlist mtglib.IPBlocklist, err error) {
|
||||
if !conf.Enabled.Get(false) {
|
||||
allowlist, err = ipblocklist.NewFireholFromFiles(
|
||||
logger.Named("ipblocklist"),
|
||||
1,
|
||||
[]files.File{
|
||||
files.NewMem([]*net.IPNet{
|
||||
cidranger.AllIPv4,
|
||||
cidranger.AllIPv6,
|
||||
}),
|
||||
},
|
||||
updateCallback,
|
||||
)
|
||||
|
||||
go allowlist.Run(conf.UpdateEach.Get(ipblocklist.DefaultFireholUpdateEach))
|
||||
} else {
|
||||
allowlist, err = makeIPBlocklist(
|
||||
conf,
|
||||
logger,
|
||||
ntw,
|
||||
updateCallback,
|
||||
)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot build allowlist: %w", err)
|
||||
}
|
||||
|
||||
return allowlist, nil
|
||||
}
|
||||
|
||||
func makeEventStream(conf *config.Config, logger mtglib.Logger) (mtglib.EventStream, error) {
|
||||
@@ -185,21 +223,16 @@ func runProxy(conf *config.Config, version string) error { // nolint: funlen
|
||||
return fmt.Errorf("cannot build ip blocklist: %w", err)
|
||||
}
|
||||
|
||||
var whitelist mtglib.IPBlocklist
|
||||
|
||||
if conf.Defense.Allowlist.Enabled.Get(false) {
|
||||
whlist, err := makeIPBlocklist(
|
||||
conf.Defense.Allowlist,
|
||||
logger.Named("allowlist"),
|
||||
ntw,
|
||||
func(ctx context.Context, size int) {
|
||||
eventStream.Send(ctx, mtglib.NewEventIPListSize(size, false))
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot build ip allowlist: %w", err)
|
||||
}
|
||||
|
||||
whitelist = whlist
|
||||
allowlist, err := makeIPAllowlist(
|
||||
conf.Defense.Allowlist,
|
||||
logger.Named("allowlist"),
|
||||
ntw,
|
||||
func(ctx context.Context, size int) {
|
||||
eventStream.Send(ctx, mtglib.NewEventIPListSize(size, false))
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot build ip allowlist: %w", err)
|
||||
}
|
||||
|
||||
opts := mtglib.ProxyOpts{
|
||||
@@ -207,7 +240,7 @@ func runProxy(conf *config.Config, version string) error { // nolint: funlen
|
||||
Network: ntw,
|
||||
AntiReplayCache: makeAntiReplayCache(conf),
|
||||
IPBlocklist: blocklist,
|
||||
IPWhitelist: whitelist,
|
||||
IPAllowlist: allowlist,
|
||||
EventStream: eventStream,
|
||||
|
||||
Secret: conf.Secret,
|
||||
|
||||
@@ -25,6 +25,7 @@ func (m *MtglibNetworkMock) DialContext(ctx context.Context, network, address st
|
||||
}
|
||||
|
||||
func (m *MtglibNetworkMock) MakeHTTPClient(dialFunc func(ctx context.Context,
|
||||
network, address string) (essentials.Conn, error)) *http.Client {
|
||||
network, address string) (essentials.Conn, error),
|
||||
) *http.Client {
|
||||
return m.Called(dialFunc).Get(0).(*http.Client) // nolint: forcetypeassert
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type memFile struct {
|
||||
data string
|
||||
}
|
||||
|
||||
func (m memFile) Open(ctx context.Context) (io.ReadCloser, error) {
|
||||
return io.NopCloser(strings.NewReader(m.data)), nil
|
||||
}
|
||||
|
||||
func (m memFile) String() string {
|
||||
return "mem"
|
||||
}
|
||||
|
||||
func NewMem(networks []*net.IPNet) File {
|
||||
builder := strings.Builder{}
|
||||
|
||||
if len(networks) > 0 {
|
||||
builder.WriteString(networks[0].String())
|
||||
}
|
||||
|
||||
for i := 1; i < len(networks); i++ {
|
||||
builder.WriteString("\n")
|
||||
builder.WriteString(networks[i].String())
|
||||
}
|
||||
|
||||
return memFile{
|
||||
data: builder.String(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package files_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/9seconds/mtg/v2/ipblocklist/files"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type MemTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *MemTestSuite) TestOk() {
|
||||
_, network1, _ := net.ParseCIDR("192.168.0.1/24")
|
||||
_, network2, _ := net.ParseCIDR("2001:0db8:85a3:0000:0000:8a2e:0370:7334/36")
|
||||
|
||||
file := files.NewMem([]*net.IPNet{
|
||||
network1,
|
||||
network2,
|
||||
})
|
||||
|
||||
reader, err := file.Open(context.Background())
|
||||
suite.NoError(err)
|
||||
|
||||
data, err := io.ReadAll(reader)
|
||||
suite.NoError(err)
|
||||
|
||||
strData := strings.TrimSpace(string(data))
|
||||
|
||||
suite.Contains(strData, "192.168.0.0/24")
|
||||
suite.Contains(strData, "2001:db8:8000::/36")
|
||||
}
|
||||
|
||||
func TestMem(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, &MemTestSuite{})
|
||||
}
|
||||
@@ -155,7 +155,8 @@ func (f *Firehol) update() {
|
||||
|
||||
func (f *Firehol) updateFromFile(mutex sync.Locker,
|
||||
ranger cidranger.Ranger,
|
||||
scanner *bufio.Scanner) error {
|
||||
scanner *bufio.Scanner,
|
||||
) error {
|
||||
for scanner.Scan() {
|
||||
text := scanner.Text()
|
||||
text = fireholRegexpComment.ReplaceAllLiteralString(text, "")
|
||||
@@ -216,7 +217,8 @@ func NewFirehol(logger mtglib.Logger, network mtglib.Network,
|
||||
downloadConcurrency uint,
|
||||
urls []string,
|
||||
localFiles []string,
|
||||
updateCallback FireholUpdateCallback) (*Firehol, error) {
|
||||
updateCallback FireholUpdateCallback,
|
||||
) (*Firehol, error) {
|
||||
blocklists := []files.File{}
|
||||
|
||||
for _, v := range localFiles {
|
||||
@@ -245,7 +247,8 @@ func NewFirehol(logger mtglib.Logger, network mtglib.Network,
|
||||
func NewFireholFromFiles(logger mtglib.Logger,
|
||||
downloadConcurrency uint,
|
||||
blocklists []files.File,
|
||||
updateCallback FireholUpdateCallback) (*Firehol, error) {
|
||||
updateCallback FireholUpdateCallback,
|
||||
) (*Firehol, error) {
|
||||
if downloadConcurrency == 0 {
|
||||
downloadConcurrency = DefaultFireholDownloadConcurrency
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/v2/internal/cli"
|
||||
@@ -26,6 +29,32 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if buildInfo, ok := debug.ReadBuildInfo(); ok {
|
||||
vcsCommit := "<no-commit>"
|
||||
vcsDate := time.Now()
|
||||
vcsDirty := ""
|
||||
|
||||
for _, setting := range buildInfo.Settings {
|
||||
switch setting.Key {
|
||||
case "vcs.time":
|
||||
vcsDate, _ = time.Parse(time.RFC3339, setting.Value)
|
||||
case "vcs.revision":
|
||||
vcsCommit = setting.Value
|
||||
case "vcs.modified":
|
||||
if isDirty, _ := strconv.ParseBool(setting.Value); isDirty {
|
||||
vcsDirty = " [dirty]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
version = fmt.Sprintf("%s (%s: %s on %s%s)",
|
||||
version,
|
||||
buildInfo.GoVersion,
|
||||
vcsDate.Format(time.RFC3339),
|
||||
vcsCommit,
|
||||
vcsDirty)
|
||||
}
|
||||
|
||||
cli := &cli.CLI{}
|
||||
ctx := kong.Parse(cli, kong.Vars{
|
||||
"version": version,
|
||||
|
||||
+16
-2
@@ -83,7 +83,8 @@ type EventConcurrencyLimited struct {
|
||||
type EventIPBlocklisted struct {
|
||||
eventBase
|
||||
|
||||
RemoteIP net.IP
|
||||
RemoteIP net.IP
|
||||
IsBlockList bool
|
||||
}
|
||||
|
||||
// EventReplayAttack is emitted when mtg detects a replay attack on a
|
||||
@@ -172,7 +173,20 @@ func NewEventIPBlocklisted(remoteIP net.IP) EventIPBlocklisted {
|
||||
eventBase: eventBase{
|
||||
timestamp: time.Now(),
|
||||
},
|
||||
RemoteIP: remoteIP,
|
||||
RemoteIP: remoteIP,
|
||||
IsBlockList: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewEventIPAllowlisted creates a NewEventIPBlocklisted event with a mark that
|
||||
// it is supposed to be for allow list.
|
||||
func NewEventIPAllowlisted(remoteIP net.IP) EventIPBlocklisted {
|
||||
return EventIPBlocklisted{
|
||||
eventBase: eventBase{
|
||||
timestamp: time.Now(),
|
||||
},
|
||||
RemoteIP: remoteIP,
|
||||
IsBlockList: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,15 @@ func (suite *EventsTestSuite) TestEventIPBlocklisted() {
|
||||
|
||||
suite.Empty(evt.StreamID())
|
||||
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
|
||||
suite.True(evt.IsBlockList)
|
||||
}
|
||||
|
||||
func (suite *EventsTestSuite) TestEventIPAllowlisted() {
|
||||
evt := mtglib.NewEventIPAllowlisted(net.ParseIP("10.0.0.10"))
|
||||
|
||||
suite.Empty(evt.StreamID())
|
||||
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
|
||||
suite.False(evt.IsBlockList)
|
||||
}
|
||||
|
||||
func (suite *EventsTestSuite) TestEventReplayAttack() {
|
||||
|
||||
@@ -48,6 +48,10 @@ var (
|
||||
// create a proxy but ip blocklist instance is not defined.
|
||||
ErrIPBlocklistIsNotDefined = errors.New("ip blocklist is not defined")
|
||||
|
||||
// ErrIPAllowlistIsNotDefined is returned if you are trying to
|
||||
// create a proxy but ip allowlist instance is not defined.
|
||||
ErrIPAllowlistIsNotDefined = errors.New("ip allowlist is not defined")
|
||||
|
||||
// ErrEventStreamIsNotDefined is returned if you are trying to create a
|
||||
// proxy but event stream instance is not defined.
|
||||
ErrEventStreamIsNotDefined = errors.New("event stream is not defined")
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package faketls_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/faketls"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var FuzzClientHelloSecret = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
|
||||
func FuzzClientHello(f *testing.F) {
|
||||
f.Add([]byte{1, 2, 3})
|
||||
|
||||
f.Fuzz(func(t *testing.T, frame []byte) {
|
||||
_, err := faketls.ParseClientHello(FuzzClientHelloSecret, frame)
|
||||
|
||||
// a probability of having != err is almost negligible
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -19,7 +19,7 @@ const (
|
||||
|
||||
// ClientHelloMinLen is a minimal possible length of
|
||||
// ClientHello record.
|
||||
ClientHelloMinLen = 4
|
||||
ClientHelloMinLen = 6
|
||||
|
||||
// WelcomePacketRandomOffset is an offset of random in ServerHello
|
||||
// packet (including record envelope).
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package obfuscated2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var FuzzClientHandshakeSecret = []byte{1, 2, 3}
|
||||
|
||||
func FuzzClientHandshake(f *testing.F) {
|
||||
f.Add([]byte{1, 2, 3})
|
||||
|
||||
f.Fuzz(func(t *testing.T, frame []byte) {
|
||||
data := bytes.NewReader(frame)
|
||||
|
||||
if _, _, _, err := ClientHandshake(FuzzClientHandshakeSecret, data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
handshake := clientHandhakeFrame{}
|
||||
require.Len(t, frame, handshakeFrameLen)
|
||||
|
||||
copy(handshake.data[:], frame)
|
||||
|
||||
decryptor := handshake.decryptor(FuzzClientHandshakeSecret)
|
||||
decryptor.XORKeyStream(handshake.data[:], handshake.data[:])
|
||||
|
||||
require.Equal(t, handshakeConnectionType, handshake.connectionType())
|
||||
})
|
||||
}
|
||||
@@ -1,12 +1,20 @@
|
||||
package obfuscated2_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/9seconds/mtg/v2/internal/testlib"
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type snapshotBytes struct {
|
||||
@@ -50,6 +58,14 @@ type SnapshotTestSuite struct {
|
||||
snapshots map[string]*Obfuscated2Snapshot
|
||||
}
|
||||
|
||||
type ServerHandshakeTestData struct {
|
||||
connMock *testlib.EssentialsConnMock
|
||||
|
||||
proxyConn obfuscated2.Conn
|
||||
encryptor cipher.Stream
|
||||
decryptor cipher.Stream
|
||||
}
|
||||
|
||||
func (suite *SnapshotTestSuite) IngestSnapshots(dirname, namePrefix string) error {
|
||||
suite.snapshots = map[string]*Obfuscated2Snapshot{}
|
||||
|
||||
@@ -81,3 +97,41 @@ func (suite *SnapshotTestSuite) IngestSnapshots(dirname, namePrefix string) erro
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewServerHandshakeTestData(t *testing.T) ServerHandshakeTestData {
|
||||
buf := &bytes.Buffer{}
|
||||
connMock := &testlib.EssentialsConnMock{}
|
||||
|
||||
handshakeEnc, handshakeDec, err := obfuscated2.ServerHandshake(buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
serverEncrypted := buf.Bytes()
|
||||
decBlock, _ := aes.NewCipher(serverEncrypted[8 : 8+32])
|
||||
decryptor := cipher.NewCTR(decBlock, serverEncrypted[8+32:8+32+16])
|
||||
|
||||
serverDecrypted := make([]byte, len(serverEncrypted))
|
||||
decryptor.XORKeyStream(serverDecrypted, serverEncrypted)
|
||||
|
||||
require.Equal(t, "3d3d3Q",
|
||||
base64.RawStdEncoding.EncodeToString(serverDecrypted[8+32+16:8+32+16+4]))
|
||||
|
||||
serverEncryptedReverted := make([]byte, len(serverEncrypted))
|
||||
|
||||
for i := 0; i < 32+16; i++ {
|
||||
serverEncryptedReverted[8+i] = serverEncrypted[8+32+16-1-i]
|
||||
}
|
||||
|
||||
encBlock, _ := aes.NewCipher(serverEncryptedReverted[8 : 8+32])
|
||||
encryptor := cipher.NewCTR(encBlock, serverEncryptedReverted[8+32:8+32+16])
|
||||
|
||||
return ServerHandshakeTestData{
|
||||
connMock: connMock,
|
||||
proxyConn: obfuscated2.Conn{
|
||||
Conn: connMock,
|
||||
Encryptor: handshakeEnc,
|
||||
Decryptor: handshakeDec,
|
||||
},
|
||||
encryptor: encryptor,
|
||||
decryptor: decryptor,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package obfuscated2
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func FuzzServerGenerateHandshakeFrame(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, arg int) {
|
||||
frame := generateServerHanshakeFrame()
|
||||
|
||||
assert.NotEqualValues(t, 0xef, frame.data[0])
|
||||
|
||||
firstBytes := binary.LittleEndian.Uint32(frame.data[:4])
|
||||
assert.NotEqualValues(t, 0x44414548, firstBytes)
|
||||
assert.NotEqualValues(t, 0x54534f50, firstBytes)
|
||||
assert.NotEqualValues(t, 0x20544547, firstBytes)
|
||||
assert.NotEqualValues(t, 0x4954504f, firstBytes)
|
||||
assert.NotEqualValues(t, 0xeeeeeeee, firstBytes)
|
||||
|
||||
assert.NotEqualValues(
|
||||
t,
|
||||
0,
|
||||
frame.data[4]|frame.data[5]|frame.data[6]|frame.data[7])
|
||||
|
||||
assert.Equal(t, handshakeConnectionType, frame.connectionType())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package obfuscated2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func FuzzServerSend(f *testing.F) {
|
||||
f.Add([]byte{1, 2, 3, 4, 5})
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
handshakeData := NewServerHandshakeTestData(t)
|
||||
|
||||
handshakeData.connMock.
|
||||
On("Write", mock.Anything).
|
||||
Return(len(data), nil).
|
||||
Once().
|
||||
Run(func(args mock.Arguments) {
|
||||
message := make([]byte, len(data))
|
||||
handshakeData.decryptor.XORKeyStream(message, args.Get(0).([]byte)) // nolint: forcetypeassert
|
||||
assert.Equal(t, message, data)
|
||||
})
|
||||
|
||||
n, err := handshakeData.proxyConn.Write(data)
|
||||
|
||||
assert.EqualValues(t, len(data), n)
|
||||
assert.NoError(t, err)
|
||||
handshakeData.connMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzServerReceive(f *testing.F) {
|
||||
f.Add([]byte{1, 2, 3, 4, 5})
|
||||
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
handshakeData := NewServerHandshakeTestData(t)
|
||||
buffer := make([]byte, len(data))
|
||||
|
||||
handshakeData.connMock.
|
||||
On("Read", mock.Anything).
|
||||
Return(len(data), nil).
|
||||
Once().
|
||||
Run(func(args mock.Arguments) {
|
||||
message := make([]byte, len(data))
|
||||
handshakeData.encryptor.XORKeyStream(message, data)
|
||||
copy(args.Get(0).([]byte), message) // nolint: forcetypeassert
|
||||
})
|
||||
|
||||
n, err := handshakeData.proxyConn.Read(buffer)
|
||||
|
||||
assert.EqualValues(t, len(data), n)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, data, buffer)
|
||||
handshakeData.connMock.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
@@ -1,14 +1,8 @@
|
||||
package obfuscated2_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/9seconds/mtg/v2/internal/testlib"
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/obfuscated2"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
@@ -16,64 +10,31 @@ import (
|
||||
type ServerHandshakeTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
connMock *testlib.EssentialsConnMock
|
||||
proxyConn obfuscated2.Conn
|
||||
encryptor cipher.Stream
|
||||
decryptor cipher.Stream
|
||||
data ServerHandshakeTestData
|
||||
}
|
||||
|
||||
func (suite *ServerHandshakeTestSuite) SetupTest() {
|
||||
buf := &bytes.Buffer{}
|
||||
suite.connMock = &testlib.EssentialsConnMock{}
|
||||
|
||||
encryptor, decryptor, err := obfuscated2.ServerHandshake(buf)
|
||||
suite.NoError(err)
|
||||
|
||||
suite.proxyConn = obfuscated2.Conn{
|
||||
Conn: suite.connMock,
|
||||
Encryptor: encryptor,
|
||||
Decryptor: decryptor,
|
||||
}
|
||||
|
||||
serverEncrypted := buf.Bytes()
|
||||
|
||||
decBlock, _ := aes.NewCipher(serverEncrypted[8 : 8+32])
|
||||
suite.decryptor = cipher.NewCTR(decBlock, serverEncrypted[8+32:8+32+16])
|
||||
|
||||
serverDecrypted := make([]byte, len(serverEncrypted))
|
||||
suite.decryptor.XORKeyStream(serverDecrypted, serverEncrypted)
|
||||
|
||||
suite.Equal("3d3d3Q",
|
||||
base64.RawStdEncoding.EncodeToString(serverDecrypted[8+32+16:8+32+16+4]))
|
||||
|
||||
serverEncryptedReverted := make([]byte, len(serverEncrypted))
|
||||
|
||||
for i := 0; i < 32+16; i++ {
|
||||
serverEncryptedReverted[8+i] = serverEncrypted[8+32+16-1-i]
|
||||
}
|
||||
|
||||
encBlock, _ := aes.NewCipher(serverEncryptedReverted[8 : 8+32])
|
||||
suite.encryptor = cipher.NewCTR(encBlock, serverEncryptedReverted[8+32:8+32+16])
|
||||
suite.data = NewServerHandshakeTestData(suite.T())
|
||||
}
|
||||
|
||||
func (suite *ServerHandshakeTestSuite) TearDownTest() {
|
||||
suite.connMock.AssertExpectations(suite.T())
|
||||
suite.data.connMock.AssertExpectations(suite.T())
|
||||
}
|
||||
|
||||
func (suite *ServerHandshakeTestSuite) TestSendToTelegram() {
|
||||
messageToTelegram := []byte{10, 11, 12, 13, 14, 'a'}
|
||||
|
||||
suite.connMock.
|
||||
suite.data.connMock.
|
||||
On("Write", mock.Anything).
|
||||
Return(len(messageToTelegram), nil).
|
||||
Once().
|
||||
Run(func(args mock.Arguments) {
|
||||
message := make([]byte, len(messageToTelegram))
|
||||
suite.decryptor.XORKeyStream(message, args.Get(0).([]byte)) // nolint: forcetypeassert
|
||||
suite.data.decryptor.XORKeyStream(message, args.Get(0).([]byte)) // nolint: forcetypeassert
|
||||
suite.Equal(messageToTelegram, message)
|
||||
})
|
||||
|
||||
n, err := suite.proxyConn.Write(messageToTelegram)
|
||||
n, err := suite.data.proxyConn.Write(messageToTelegram)
|
||||
suite.EqualValues(len(messageToTelegram), n)
|
||||
suite.NoError(err)
|
||||
}
|
||||
@@ -82,17 +43,17 @@ func (suite *ServerHandshakeTestSuite) TestRecieveFromTelegram() {
|
||||
messageFromTelegram := []byte{10, 11, 12, 13, 14, 'a'}
|
||||
buffer := make([]byte, len(messageFromTelegram))
|
||||
|
||||
suite.connMock.
|
||||
suite.data.connMock.
|
||||
On("Read", mock.Anything).
|
||||
Return(len(messageFromTelegram), nil).
|
||||
Once().
|
||||
Run(func(args mock.Arguments) {
|
||||
message := make([]byte, len(messageFromTelegram))
|
||||
suite.encryptor.XORKeyStream(message, messageFromTelegram)
|
||||
suite.data.encryptor.XORKeyStream(message, messageFromTelegram)
|
||||
copy(args.Get(0).([]byte), message) // nolint: forcetypeassert
|
||||
})
|
||||
|
||||
n, err := suite.proxyConn.Read(buffer)
|
||||
n, err := suite.data.proxyConn.Read(buffer)
|
||||
suite.EqualValues(len(messageFromTelegram), n)
|
||||
suite.NoError(err)
|
||||
suite.Equal(messageFromTelegram, buffer)
|
||||
|
||||
+7
-10
@@ -34,7 +34,7 @@ type Proxy struct {
|
||||
network Network
|
||||
antiReplayCache AntiReplayCache
|
||||
blocklist IPBlocklist
|
||||
whitelist IPBlocklist
|
||||
allowlist IPBlocklist
|
||||
eventStream EventStream
|
||||
logger Logger
|
||||
}
|
||||
@@ -91,7 +91,7 @@ func (p *Proxy) ServeConn(conn essentials.Conn) {
|
||||
}
|
||||
|
||||
// Serve starts a proxy on a given listener.
|
||||
func (p *Proxy) Serve(listener net.Listener) error { // nolint: cyclop
|
||||
func (p *Proxy) Serve(listener net.Listener) error {
|
||||
p.streamWaitGroup.Add(1)
|
||||
defer p.streamWaitGroup.Done()
|
||||
|
||||
@@ -109,10 +109,10 @@ func (p *Proxy) Serve(listener net.Listener) error { // nolint: cyclop
|
||||
ipAddr := conn.RemoteAddr().(*net.TCPAddr).IP // nolint: forcetypeassert
|
||||
logger := p.logger.BindStr("ip", ipAddr.String())
|
||||
|
||||
if p.whitelist != nil && !p.whitelist.Contains(ipAddr) {
|
||||
if !p.allowlist.Contains(ipAddr) {
|
||||
conn.Close()
|
||||
logger.Info("ip was rejected by whitelist")
|
||||
p.eventStream.Send(p.ctx, NewEventIPBlocklisted(ipAddr))
|
||||
logger.Info("ip was rejected by allowlist")
|
||||
p.eventStream.Send(p.ctx, NewEventIPAllowlisted(ipAddr))
|
||||
|
||||
continue
|
||||
}
|
||||
@@ -145,10 +145,7 @@ func (p *Proxy) Shutdown() {
|
||||
p.streamWaitGroup.Wait()
|
||||
p.workerPool.Release()
|
||||
|
||||
if p.whitelist != nil {
|
||||
p.whitelist.Shutdown()
|
||||
}
|
||||
|
||||
p.allowlist.Shutdown()
|
||||
p.blocklist.Shutdown()
|
||||
}
|
||||
|
||||
@@ -308,7 +305,7 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) {
|
||||
network: opts.Network,
|
||||
antiReplayCache: opts.AntiReplayCache,
|
||||
blocklist: opts.IPBlocklist,
|
||||
whitelist: opts.IPWhitelist,
|
||||
allowlist: opts.IPAllowlist,
|
||||
eventStream: opts.EventStream,
|
||||
logger: opts.getLogger("proxy"),
|
||||
domainFrontingPort: opts.getDomainFrontingPort(),
|
||||
|
||||
@@ -28,10 +28,10 @@ type ProxyOpts struct {
|
||||
// This is a mandatory setting.
|
||||
IPBlocklist IPBlocklist
|
||||
|
||||
// IPWhitelist defines a whitelist of IPs to allow to use proxy.
|
||||
// IPAllowlist defines a whitelist of IPs to allow to use proxy.
|
||||
//
|
||||
// This is an optional setting, ignored by default (no restrictions).
|
||||
IPWhitelist IPBlocklist
|
||||
IPAllowlist IPBlocklist
|
||||
|
||||
// EventStream defines an instance of event stream.
|
||||
//
|
||||
@@ -125,6 +125,8 @@ func (p ProxyOpts) valid() error {
|
||||
return ErrAntiReplayCacheIsNotDefined
|
||||
case p.IPBlocklist == nil:
|
||||
return ErrIPBlocklistIsNotDefined
|
||||
case p.IPAllowlist == nil:
|
||||
return ErrIPAllowlistIsNotDefined
|
||||
case p.EventStream == nil:
|
||||
return ErrEventStreamIsNotDefined
|
||||
case p.Logger == nil:
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/9seconds/mtg/v2/antireplay"
|
||||
"github.com/9seconds/mtg/v2/events"
|
||||
"github.com/9seconds/mtg/v2/ipblocklist"
|
||||
"github.com/9seconds/mtg/v2/ipblocklist/files"
|
||||
"github.com/9seconds/mtg/v2/logger"
|
||||
"github.com/9seconds/mtg/v2/mtglib"
|
||||
"github.com/9seconds/mtg/v2/network"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
"github.com/gotd/td/telegram/dcs"
|
||||
"github.com/gotd/td/tg"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/yl2chen/cidranger"
|
||||
)
|
||||
|
||||
type ProxyTestSuite struct {
|
||||
@@ -49,11 +51,26 @@ func (suite *ProxyTestSuite) SetupSuite() {
|
||||
ntw, err := network.NewNetwork(dialer, "mtgtest", "1.1.1.1", 0)
|
||||
suite.NoError(err)
|
||||
|
||||
allowlist, _ := ipblocklist.NewFireholFromFiles(
|
||||
logger.NewNoopLogger(),
|
||||
1,
|
||||
[]files.File{
|
||||
files.NewMem([]*net.IPNet{
|
||||
cidranger.AllIPv4,
|
||||
cidranger.AllIPv6,
|
||||
}),
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
go allowlist.Run(time.Second)
|
||||
|
||||
suite.opts = &mtglib.ProxyOpts{
|
||||
Secret: mtglib.GenerateSecret("httpbin.org"),
|
||||
Network: ntw,
|
||||
AntiReplayCache: antireplay.NewNoop(),
|
||||
IPBlocklist: ipblocklist.NewNoop(),
|
||||
IPAllowlist: allowlist,
|
||||
EventStream: events.NewNoopStream(),
|
||||
Logger: logger.NewNoopLogger(),
|
||||
UseTestDCs: true,
|
||||
@@ -114,6 +131,14 @@ func (suite *ProxyTestSuite) TestCannotInitNoIPBlocklist() {
|
||||
suite.Error(err)
|
||||
}
|
||||
|
||||
func (suite *ProxyTestSuite) TestCannotInitNoIPAllowlist() {
|
||||
opts := *suite.opts
|
||||
opts.IPAllowlist = nil
|
||||
|
||||
_, err := mtglib.NewProxy(opts)
|
||||
suite.Error(err)
|
||||
}
|
||||
|
||||
func (suite *ProxyTestSuite) TestCannotInitNoEventStream() {
|
||||
opts := *suite.opts
|
||||
opts.EventStream = nil
|
||||
|
||||
@@ -36,7 +36,8 @@ func (c *circuitBreakerDialer) Dial(network, address string) (essentials.Conn, e
|
||||
}
|
||||
|
||||
func (c *circuitBreakerDialer) DialContext(ctx context.Context,
|
||||
network, address string) (essentials.Conn, error) {
|
||||
network, address string,
|
||||
) (essentials.Conn, error) {
|
||||
switch atomic.LoadUint32(&c.state) {
|
||||
case circuitBreakerStateClosed:
|
||||
return c.doClosed(ctx, network, address)
|
||||
@@ -48,7 +49,8 @@ func (c *circuitBreakerDialer) DialContext(ctx context.Context,
|
||||
}
|
||||
|
||||
func (c *circuitBreakerDialer) doClosed(ctx context.Context,
|
||||
network, address string) (essentials.Conn, error) {
|
||||
network, address string,
|
||||
) (essentials.Conn, error) {
|
||||
conn, err := c.Dialer.DialContext(ctx, network, address)
|
||||
|
||||
select {
|
||||
@@ -80,7 +82,8 @@ func (c *circuitBreakerDialer) doClosed(ctx context.Context,
|
||||
}
|
||||
|
||||
func (c *circuitBreakerDialer) doHalfOpened(ctx context.Context,
|
||||
network, address string) (essentials.Conn, error) {
|
||||
network, address string,
|
||||
) (essentials.Conn, error) {
|
||||
if !atomic.CompareAndSwapUint32(&c.halfOpenAttempts, 0, 1) {
|
||||
return nil, ErrCircuitBreakerOpened
|
||||
}
|
||||
@@ -174,14 +177,16 @@ func (c *circuitBreakerDialer) stopTimer(timerRef **time.Timer) {
|
||||
}
|
||||
|
||||
func (c *circuitBreakerDialer) ensureTimer(timerRef **time.Timer,
|
||||
timeout time.Duration, callback func()) {
|
||||
timeout time.Duration, callback func(),
|
||||
) {
|
||||
if *timerRef == nil {
|
||||
*timerRef = time.AfterFunc(timeout, callback)
|
||||
}
|
||||
}
|
||||
|
||||
func newCircuitBreakerDialer(baseDialer Dialer,
|
||||
openThreshold uint32, halfOpenTimeout, resetFailuresTimeout time.Duration) Dialer {
|
||||
openThreshold uint32, halfOpenTimeout, resetFailuresTimeout time.Duration,
|
||||
) Dialer {
|
||||
cb := &circuitBreakerDialer{
|
||||
Dialer: baseDialer,
|
||||
stateMutexChan: make(chan bool, 1),
|
||||
|
||||
+6
-3
@@ -61,7 +61,8 @@ func (n *network) DialContext(ctx context.Context, protocol, address string) (es
|
||||
}
|
||||
|
||||
func (n *network) MakeHTTPClient(dialFunc func(ctx context.Context,
|
||||
network, address string) (essentials.Conn, error)) *http.Client {
|
||||
network, address string) (essentials.Conn, error),
|
||||
) *http.Client {
|
||||
if dialFunc == nil {
|
||||
dialFunc = n.DialContext
|
||||
}
|
||||
@@ -123,7 +124,8 @@ func (n *network) dnsResolve(protocol, address string) ([]string, error) {
|
||||
// It brings simple DNS cache and DNS-Over-HTTPS when necessary.
|
||||
func NewNetwork(dialer Dialer,
|
||||
userAgent, dohHostname string,
|
||||
httpTimeout time.Duration) (mtglib.Network, error) {
|
||||
httpTimeout time.Duration,
|
||||
) (mtglib.Network, error) {
|
||||
switch {
|
||||
case httpTimeout < 0:
|
||||
return nil, fmt.Errorf("timeout should be positive number %s", httpTimeout)
|
||||
@@ -146,7 +148,8 @@ func NewNetwork(dialer Dialer,
|
||||
|
||||
func makeHTTPClient(userAgent string,
|
||||
timeout time.Duration,
|
||||
dialFunc func(ctx context.Context, network, address string) (essentials.Conn, error)) *http.Client {
|
||||
dialFunc func(ctx context.Context, network, address string) (essentials.Conn, error),
|
||||
) *http.Client {
|
||||
return &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: networkHTTPTransport{
|
||||
|
||||
+14
-9
@@ -110,8 +110,13 @@ func (p prometheusProcessor) EventConcurrencyLimited(_ mtglib.EventConcurrencyLi
|
||||
p.factory.metricConcurrencyLimited.Inc()
|
||||
}
|
||||
|
||||
func (p prometheusProcessor) EventIPBlocklisted(_ mtglib.EventIPBlocklisted) {
|
||||
p.factory.metricIPBlocklisted.Inc()
|
||||
func (p prometheusProcessor) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
|
||||
tag := TagIPListBlock
|
||||
if !evt.IsBlockList {
|
||||
tag = TagIPListAllow
|
||||
}
|
||||
|
||||
p.factory.metricIPBlocklisted.WithLabelValues(tag).Inc()
|
||||
}
|
||||
|
||||
func (p prometheusProcessor) EventReplayAttack(_ mtglib.EventReplayAttack) {
|
||||
@@ -150,10 +155,10 @@ type PrometheusFactory struct {
|
||||
|
||||
metricTelegramTraffic *prometheus.CounterVec
|
||||
metricDomainFrontingTraffic *prometheus.CounterVec
|
||||
metricIPBlocklisted *prometheus.CounterVec
|
||||
|
||||
metricDomainFronting prometheus.Counter
|
||||
metricConcurrencyLimited prometheus.Counter
|
||||
metricIPBlocklisted prometheus.Counter
|
||||
metricReplayAttacks prometheus.Counter
|
||||
}
|
||||
|
||||
@@ -223,6 +228,11 @@ func NewPrometheus(metricPrefix, httpPath string) *PrometheusFactory { // nolint
|
||||
Name: MetricDomainFrontingTraffic,
|
||||
Help: "Traffic which is generated talking with front domain.",
|
||||
}, []string{TagDirection}),
|
||||
metricIPBlocklisted: prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: metricPrefix,
|
||||
Name: MetricIPBlocklisted,
|
||||
Help: "A number of rejected sessions due to ip blocklisting.",
|
||||
}, []string{TagIPList}),
|
||||
|
||||
metricDomainFronting: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: metricPrefix,
|
||||
@@ -234,11 +244,6 @@ func NewPrometheus(metricPrefix, httpPath string) *PrometheusFactory { // nolint
|
||||
Name: MetricConcurrencyLimited,
|
||||
Help: "A number of sessions that were rejected by concurrency limiter.",
|
||||
}),
|
||||
metricIPBlocklisted: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: metricPrefix,
|
||||
Name: MetricIPBlocklisted,
|
||||
Help: "A number of rejected sessions due to ip blocklisting.",
|
||||
}),
|
||||
metricReplayAttacks: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: metricPrefix,
|
||||
Name: MetricReplayAttacks,
|
||||
@@ -253,10 +258,10 @@ func NewPrometheus(metricPrefix, httpPath string) *PrometheusFactory { // nolint
|
||||
|
||||
registry.MustRegister(factory.metricTelegramTraffic)
|
||||
registry.MustRegister(factory.metricDomainFrontingTraffic)
|
||||
registry.MustRegister(factory.metricIPBlocklisted)
|
||||
|
||||
registry.MustRegister(factory.metricDomainFronting)
|
||||
registry.MustRegister(factory.metricConcurrencyLimited)
|
||||
registry.MustRegister(factory.metricIPBlocklisted)
|
||||
registry.MustRegister(factory.metricReplayAttacks)
|
||||
|
||||
return factory
|
||||
|
||||
@@ -156,7 +156,18 @@ func (suite *PrometheusTestSuite) TestEventIPBlocklisted() {
|
||||
|
||||
data, err := suite.Get()
|
||||
suite.NoError(err)
|
||||
suite.Contains(data, `mtg_ip_blocklisted 1`)
|
||||
suite.Contains(data, `mtg_ip_blocklisted{ip_list="blocklist"} 1`)
|
||||
}
|
||||
|
||||
func (suite *PrometheusTestSuite) TestEventIPAllowlisted() {
|
||||
suite.prometheus.EventIPBlocklisted(
|
||||
mtglib.NewEventIPAllowlisted(net.ParseIP("2001:db8::68")))
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
data, err := suite.Get()
|
||||
suite.NoError(err)
|
||||
suite.Contains(data, `mtg_ip_blocklisted{ip_list="allowlist"} 1`)
|
||||
}
|
||||
|
||||
func (suite *PrometheusTestSuite) TestEventReplayAttack() {
|
||||
|
||||
+9
-3
@@ -113,8 +113,13 @@ func (s statsdProcessor) EventConcurrencyLimited(_ mtglib.EventConcurrencyLimite
|
||||
s.client.Incr(MetricConcurrencyLimited, 1)
|
||||
}
|
||||
|
||||
func (s statsdProcessor) EventIPBlocklisted(_ mtglib.EventIPBlocklisted) {
|
||||
s.client.Incr(MetricIPBlocklisted, 1)
|
||||
func (s statsdProcessor) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
|
||||
tag := TagIPListBlock
|
||||
if !evt.IsBlockList {
|
||||
tag = TagIPListAllow
|
||||
}
|
||||
|
||||
s.client.Incr(MetricIPBlocklisted, 1, statsd.StringTag(TagIPList, tag))
|
||||
}
|
||||
|
||||
func (s statsdProcessor) EventReplayAttack(_ mtglib.EventReplayAttack) {
|
||||
@@ -171,7 +176,8 @@ func (s StatsdFactory) Make() events.Observer {
|
||||
//
|
||||
// Valid tagFormats are 'datadog', 'influxdb' and 'graphite'.
|
||||
func NewStatsd(address string, log logger.StdLikeLogger,
|
||||
metricPrefix, tagFormat string) (StatsdFactory, error) {
|
||||
metricPrefix, tagFormat string,
|
||||
) (StatsdFactory, error) {
|
||||
options := []statsd.Option{
|
||||
statsd.MetricPrefix(metricPrefix),
|
||||
statsd.Logger(log),
|
||||
|
||||
@@ -186,7 +186,15 @@ func (suite *StatsdTestSuite) TestEventIPBlocklisted() {
|
||||
mtglib.NewEventIPBlocklisted(net.ParseIP("10.0.0.10")))
|
||||
|
||||
time.Sleep(statsdSleepTime)
|
||||
suite.Equal("mtg.ip_blocklisted:1|c", suite.statsdServer.String())
|
||||
suite.Equal("mtg.ip_blocklisted:1|c|#ip_list:blocklist", suite.statsdServer.String())
|
||||
}
|
||||
|
||||
func (suite *StatsdTestSuite) TestEventIPAllowlisted() {
|
||||
suite.statsd.EventIPBlocklisted(
|
||||
mtglib.NewEventIPAllowlisted(net.ParseIP("10.0.0.10")))
|
||||
|
||||
time.Sleep(statsdSleepTime)
|
||||
suite.Equal("mtg.ip_blocklisted:1|c|#ip_list:allowlist", suite.statsdServer.String())
|
||||
}
|
||||
|
||||
func (suite *StatsdTestSuite) TestEventReplayAttack() {
|
||||
|
||||
Reference in New Issue
Block a user