mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-09-01 02:34:02 +03:00
REPOSITORY / ScuroNeko/mtg
Compare commits
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0882d45d6a | ||
|
|
86c6541e0d | ||
|
|
cc1f60c4a6 | ||
|
|
700f71bb31 | ||
|
|
097b52c8ef | ||
|
|
c3ffd7a307 | ||
|
|
fe3b0f3ca5 | ||
|
|
2273a0e4af | ||
|
|
115510985a | ||
|
|
2b5b325722 | ||
|
|
c536cbecd5 | ||
|
|
5e9d0649a2 | ||
|
|
585ebfeb50 | ||
|
|
81bca75c25 | ||
|
|
4f048a3b82 | ||
|
|
43f5d2131e | ||
|
|
e2073f0585 | ||
|
|
0652f322fa | ||
|
|
d3aa0f2de5 | ||
|
|
40c6cf294a | ||
|
|
4c38ea2b11 | ||
|
|
e6d444546f | ||
|
|
611583ba88 | ||
|
|
463af648ce | ||
|
|
b748603096 | ||
|
|
54a7c6a2a5 | ||
|
|
a3bae795c4 | ||
|
|
372c703ad6 | ||
|
|
0dd890a09d | ||
|
|
0274b3436a | ||
|
|
04b88cc864 | ||
|
|
b3112c5c67 | ||
|
|
3661fe108d | ||
|
|
5d5b77d73b | ||
|
|
7bffdcdf4a | ||
|
|
ea7033d569 | ||
|
|
24fdec6694 | ||
|
|
2fe04c7c9b | ||
|
|
7b5cb8e14b | ||
|
|
8566cede74 | ||
|
|
2dc30c5837 | ||
|
|
2cdff017e2 | ||
|
|
766800700a | ||
|
|
7e7823118e | ||
|
|
784a5818c8 | ||
|
|
75b95995a9 | ||
|
|
5941f0674a | ||
|
|
155a97c25d | ||
|
|
1fd0ebea9b | ||
|
|
988f61718c | ||
|
|
3d9a63a1f0 | ||
|
|
6c92ec314d | ||
|
|
132a01c27e | ||
|
|
b91f28d7d9 | ||
|
|
339840c14a | ||
|
|
a368975959 | ||
|
|
102e7610be | ||
|
|
e78c4d3bde | ||
|
|
1925efc2ec | ||
|
|
9562a86038 | ||
|
|
21d4f3d101 | ||
|
|
38ff62fddc | ||
|
|
26070d5b3e | ||
|
|
03d98ee256 | ||
|
|
dc28677086 | ||
|
|
54f4b397b6 | ||
|
|
96bcb6cd8e | ||
|
|
491b674e70 | ||
|
|
fdbee86d5e | ||
|
|
5b0d54e74d | ||
|
|
64754d1039 | ||
|
|
23c42def4f | ||
|
|
87a720dd58 | ||
|
|
10b78322a3 | ||
|
|
841a4d2227 | ||
|
|
3cacd74e12 | ||
|
|
71d4c6c42f | ||
|
|
75b05b9b14 | ||
|
|
daa8b9c798 | ||
|
|
bef14bd009 | ||
|
|
36d695118e | ||
|
|
4c3f42e264 | ||
|
|
3992054560 | ||
|
|
db8614999a | ||
|
|
bc2bd4510a | ||
|
|
5eca6ecb05 | ||
|
|
ce30e128e5 | ||
|
|
dedd67780b | ||
|
|
d8d954bbe8 | ||
|
|
bddf180575 | ||
|
|
9b78f766e8 | ||
|
|
e2e464d32c | ||
|
|
40b483357f | ||
|
|
f0efa4697e | ||
|
|
6d92d5fe21 | ||
|
|
c3e8e8b1fe | ||
|
|
a444a10bd0 | ||
|
|
d3551aa9cc | ||
|
|
4a2d1df384 | ||
|
|
a3c64c1d1e | ||
|
|
bbe8f34e72 | ||
|
|
02aa969d6c | ||
|
|
4da2a4f10f | ||
|
|
336e825e3a | ||
|
|
b42e51e300 | ||
|
|
6b1bfe7b17 | ||
|
|
6219f4bd90 | ||
|
|
3076c52adb | ||
|
|
933855a233 | ||
|
|
ea698259d5 | ||
|
|
aefa003355 | ||
|
|
e3b7472239 | ||
|
|
a3362c7ea4 | ||
|
|
66c45dc83b | ||
|
|
f7c33ee333 | ||
|
|
42160a08fe | ||
|
|
925a02dac3 | ||
|
|
7108fe99ec | ||
|
|
69203f3e23 | ||
|
|
4d2d21e101 | ||
|
|
8de727932d | ||
|
|
188fa6a04a | ||
|
|
0330a0e5cd | ||
|
|
0ad2d61742 | ||
|
|
f155b9f37e | ||
|
|
3a44bcb854 | ||
|
|
58335b3e59 | ||
|
|
657a74a5c2 | ||
|
|
f3112d4ba6 | ||
|
|
172b596cf1 | ||
|
|
b258581f47 | ||
|
|
60d5b3cadd | ||
|
|
f21ee40baf | ||
|
|
adf4ab1a35 | ||
|
|
83eeedc008 | ||
|
|
57cb1b5aa0 | ||
|
|
5e31b95bb5 | ||
|
|
80a1de60e6 | ||
|
|
2408f1530f | ||
|
|
23519913f2 | ||
|
|
7955ac6a46 | ||
|
|
dbbfdaf918 | ||
|
|
8e7207d975 | ||
|
|
c04f9e392a | ||
|
|
46bd617581 | ||
|
|
7617aeadda | ||
|
|
e009d05a90 | ||
|
|
f0063ba089 | ||
|
|
b08d945d7c | ||
|
|
abff0cf211 | ||
|
|
f52b3391d1 | ||
|
|
cae33a22e6 | ||
|
|
3dc263d6d7 | ||
|
|
8eb5fed92f | ||
|
|
ab1b3b4863 | ||
|
|
ab585fd699 | ||
|
|
91a1ee956c | ||
|
|
f8ad90c845 | ||
|
|
37a78bd1c3 | ||
|
|
18e3ca77de | ||
|
|
185baf6bc9 | ||
|
|
546a5849f3 | ||
|
|
2cc81219b1 | ||
|
|
1f862027af | ||
|
|
02643fb6bb | ||
|
|
e167b85d3b | ||
|
|
c0b6124d94 | ||
|
|
113b5ecbe0 | ||
|
|
4daf927a72 | ||
|
|
1218f18af9 | ||
|
|
7ca143d352 | ||
|
|
428010880e | ||
|
|
dc81740bda | ||
|
|
f7dc54b652 | ||
|
|
c0ab254acf | ||
|
|
5c4536c591 | ||
|
|
d48e82be7c | ||
|
|
bc4b14c83d | ||
|
|
6b9b437a5a | ||
|
|
d6566e5dfb | ||
|
|
6b28488fbd | ||
|
|
1a02511afe | ||
|
|
757ea5b63c | ||
|
|
24add0dce4 | ||
|
|
299c6478c2 | ||
|
|
ddd3d608fa | ||
|
|
d5b0a47d52 | ||
|
|
ca0a550f12 | ||
|
|
94084674c0 | ||
|
|
9aa56191f9 | ||
|
|
a34949f15c | ||
|
|
cbcc113e41 | ||
|
|
6f0b0e0c32 | ||
|
|
098a9c411a | ||
|
|
d1dd56550f | ||
|
|
59f328b804 | ||
|
|
783c49db37 | ||
|
|
015f02c077 | ||
|
|
e0833e86d9 | ||
|
|
f89f5f8469 | ||
|
|
0395ce8d1a | ||
|
|
ef87d031ed | ||
|
|
ba875da9b5 | ||
|
|
8f7e79862b | ||
|
|
9124ffabf1 | ||
|
|
c8914f90db | ||
|
|
7c43a4b0b7 | ||
|
|
e386ae0daf | ||
|
|
b5346668f8 | ||
|
|
7002e4cd09 | ||
|
|
ecfd550a95 | ||
|
|
5b46152945 | ||
|
|
88e076a283 | ||
|
|
0ed7e10379 | ||
|
|
21ee1e2c6c | ||
|
|
09f8233bc6 | ||
|
|
d5147f1935 | ||
|
|
4689479745 | ||
|
|
2be900745f | ||
|
|
baee322cd7 | ||
|
|
7718f62477 | ||
|
|
df0994dad3 | ||
|
|
dca43853fb | ||
|
|
ea8e31346c | ||
|
|
ed495b3800 | ||
|
|
4025f223c4 | ||
|
|
90e0ce4c48 | ||
|
|
ac0b442b43 | ||
|
|
af7021fac8 | ||
|
|
5468625212 | ||
|
|
345c1bf715 | ||
|
|
52d47a9786 | ||
|
|
2ba79a8508 | ||
|
|
ce7769472d | ||
|
|
eb5eea2625 | ||
|
|
d965dc3c07 | ||
|
|
040b3c371c | ||
|
|
c7ba8e54be | ||
|
|
33b1a5426c | ||
|
|
91197c0334 | ||
|
|
e075169dd4 | ||
|
|
225444b1b3 | ||
|
|
586ac9dcb9 | ||
|
|
c3be5311e8 | ||
|
|
bfeded67ee | ||
|
|
dde0d25b77 | ||
|
|
296a34cd6e | ||
|
|
44cea68e83 | ||
|
|
5d17e264e6 | ||
|
|
7ef1e99f66 | ||
|
|
3acbacbdb3 | ||
|
|
5fd7062972 | ||
|
|
7a6695f6c4 | ||
|
|
8ac786581b | ||
|
|
86a8f69273 | ||
|
|
95b16cd805 | ||
|
|
3996b9a6b8 | ||
|
|
e286dea650 | ||
|
|
5e27e48129 | ||
|
|
95abb54dc8 | ||
|
|
dbd3d3c266 | ||
|
|
ad7acee64d | ||
|
|
9ce6674070 | ||
|
|
25edb60f2c | ||
|
|
b5a8d8b804 | ||
|
|
35f0fca3c5 | ||
|
|
cd46a734ea | ||
|
|
cd92a343aa | ||
|
|
289e8f5945 | ||
|
|
850bed0dfc | ||
|
|
8977d77a13 | ||
|
|
91726d461f | ||
|
|
be1a47287a | ||
|
|
426483d27b | ||
|
|
bc72717582 | ||
|
|
391d901402 | ||
|
|
837d96dc43 | ||
|
|
9125a29e79 | ||
|
|
6449109b2b | ||
|
|
829669eefd | ||
|
|
2d9f2345dc | ||
|
|
6a9069ca72 | ||
|
|
d72ed03a43 | ||
|
|
f0d2cd494a | ||
|
|
b5810c955d | ||
|
|
4ebae693e0 | ||
|
|
1a619e3090 | ||
|
|
8e29ff78ba | ||
|
|
efdac2c0d8 | ||
|
|
e559388802 | ||
|
|
cb691453f0 | ||
|
|
020065f389 | ||
|
|
cf5197cee0 | ||
|
|
720158f15c | ||
|
|
07f00a363e | ||
|
|
d32ab34e60 | ||
|
|
5a52418394 | ||
|
|
cf43ae780b | ||
|
|
2407748ab0 | ||
|
|
a47edf08d0 | ||
|
|
566955b8b7 | ||
|
|
5cc07a54a2 | ||
|
|
ba2143a6ae | ||
|
|
acb02baa2e | ||
|
|
422ed5b071 | ||
|
|
19efcc93fc | ||
|
|
b4c47ff4e1 | ||
|
|
8a783d6b4e | ||
|
|
c5029c706c | ||
|
|
d7e565e0f5 | ||
|
|
93686377d5 | ||
|
|
87de28636a | ||
|
|
7788b169b6 | ||
|
|
6b9601fc2c | ||
|
|
a65cb05d20 | ||
|
|
31866d5f64 | ||
|
|
0db83a0cc8 | ||
|
|
40bb59faf5 | ||
|
|
307910fe4a | ||
|
|
4c2c468289 | ||
|
|
f2f7c2a4de | ||
|
|
8fdd4292b1 | ||
|
|
44d339d777 | ||
|
|
c6223e256d | ||
|
|
7928c2b323 | ||
|
|
b2e7e1a225 | ||
|
|
843fc4b70f | ||
|
|
ed9689d813 | ||
|
|
59ddffa911 | ||
|
|
3af9ede1c1 | ||
|
|
47364e7ba8 | ||
|
|
b6251608d9 | ||
|
|
6dce3944e4 | ||
|
|
112d3a916c | ||
|
|
5d3824704c | ||
|
|
0ff5ca6808 | ||
|
|
8e0436e89a | ||
|
|
646294b6ba | ||
|
|
196ef3d449 | ||
|
|
4f8755879d | ||
|
|
3451832d13 | ||
|
|
dfa53af567 | ||
|
|
27c5646680 | ||
|
|
596fafe30d | ||
|
|
e465817011 | ||
|
|
ef60877cb0 | ||
|
|
8b802819ee | ||
|
|
3c75fcca80 | ||
|
|
1258f07197 | ||
|
|
24137b6ea2 | ||
|
|
7d1d9007af | ||
|
|
ae479adeaa | ||
|
|
6732e80d06 | ||
|
|
2bf291bab5 | ||
|
|
273aa18367 | ||
|
|
b491e03504 | ||
|
|
a7a4da8ee5 | ||
|
|
34ad883559 | ||
|
|
22905b2a25 | ||
|
|
0ff9a58780 | ||
|
|
ed180ecb3d | ||
|
|
f005592872 | ||
|
|
d4cd779c42 | ||
|
|
9e3683ce08 | ||
|
|
d8216f776d | ||
|
|
038b2b200d | ||
|
|
fd8506c82a | ||
|
|
559a1a666b | ||
|
|
4c5f1a5636 | ||
|
|
fbbf9b1634 | ||
|
|
1e428e13fc | ||
|
|
f531d90fc4 | ||
|
|
5f168d1ee4 | ||
|
|
5009859a1e | ||
|
|
432e2970a0 | ||
|
|
6e154b3f44 | ||
|
|
56cf90b13d | ||
|
|
d10729490d | ||
|
|
d459efcf9c | ||
|
|
2eba78b0db | ||
|
|
61450ea3db | ||
|
|
c62afbdfd8 | ||
|
|
828627b0d8 | ||
|
|
c68f2cd50f | ||
|
|
e81c5970d4 | ||
|
|
1a7eee444e | ||
|
|
d9bd07b027 | ||
|
|
3ae69183aa | ||
|
|
6c7edfb7db | ||
|
|
d44474012a | ||
|
|
413cafeeb6 | ||
|
|
c9743b5675 | ||
|
|
072bce2922 | ||
|
|
386522ba22 | ||
|
|
029245cd16 | ||
|
|
b280f47731 | ||
|
|
1617866c24 | ||
|
|
691954bd16 | ||
|
|
9534cb19d0 | ||
|
|
7827d1255a | ||
|
|
d431feb0ba | ||
|
|
3816dbf5b1 | ||
|
|
2918ed11e5 | ||
|
|
701f62ed4c | ||
|
|
299a34252e | ||
|
|
2492a47d0a | ||
|
|
c2b8e88961 | ||
|
|
c9d3020ea6 | ||
|
|
018e5b96f4 | ||
|
|
07985cf418 | ||
|
|
389f6fd4bf | ||
|
|
09c7ce45d2 | ||
|
|
199eed0f18 | ||
|
|
82ec3871a4 | ||
|
|
2748fbd1e5 | ||
|
|
f14f104bea | ||
|
|
b1074193a8 | ||
|
|
78dea9ae3f | ||
|
|
da8dba1585 | ||
|
|
33852ca481 | ||
|
|
eff53694a0 | ||
|
|
74a7c505eb | ||
|
|
9983f7fcba | ||
|
|
a90072260e | ||
|
|
b153240bc3 | ||
|
|
6dfbd26524 | ||
|
|
39e7663e0c | ||
|
|
71f7bf6cad | ||
|
|
fba0e235c1 | ||
|
|
70be70de3c | ||
|
|
b565d83d40 | ||
|
|
ebc459440c | ||
|
|
6e6049a73a | ||
|
|
916714a909 | ||
|
|
f68b195306 | ||
|
|
5f1d1ce883 | ||
|
|
16558a7c55 | ||
|
|
e7958aaf33 | ||
|
|
7d6b661d97 | ||
|
|
28b3cbe91a | ||
|
|
6818364231 | ||
|
|
ea97bf51c8 | ||
|
|
1678ddbd53 | ||
|
|
f81f29cbb4 | ||
|
|
2d9259db48 | ||
|
|
c721c636d9 | ||
|
|
60472072aa | ||
|
|
6721e6fd9f | ||
|
|
47fb5c23cb | ||
|
|
fce6118c71 | ||
|
|
d277a2975a | ||
|
|
c3f21a7b0d | ||
|
|
61a1024264 | ||
|
|
4695a0c433 | ||
|
|
a9d0ca1e90 | ||
|
|
9346c11f37 | ||
|
|
5f00363da5 | ||
|
|
3aec9657d7 | ||
|
|
358d42ec61 | ||
|
|
ac2625209b | ||
|
|
c74e0ee359 | ||
|
|
7c463851b2 | ||
|
|
c9f6b922c3 | ||
|
|
7128e70e50 | ||
|
|
cf9bf56d8e | ||
|
|
76cfbb009a | ||
|
|
cb78169d6a | ||
|
|
d1703873c1 | ||
|
|
ac33abbbb1 | ||
|
|
a6893c8df7 | ||
|
|
7182c7bf65 | ||
|
|
84f4a37d11 | ||
|
|
3cf7f1396b | ||
|
|
835ab94470 | ||
|
|
278753d91e | ||
|
|
b81505ec21 | ||
|
|
0828bd85ad | ||
|
|
96e80fc0d6 | ||
|
|
591500de41 | ||
|
|
07ddfa12f7 | ||
|
|
618d91b15d | ||
|
|
a83d20dd46 | ||
|
|
1e9bbfae55 | ||
|
|
8268620615 | ||
|
|
753639beb8 | ||
|
|
1ee34fd502 | ||
|
|
243a89a68c | ||
|
|
9f20e8749a | ||
|
|
b0d86abc74 | ||
|
|
6c0030d20e | ||
|
|
8733f6d191 | ||
|
|
8906ae0db0 | ||
|
|
2e8c9623de | ||
|
|
0ce325f908 | ||
|
|
b86ad07ba9 | ||
|
|
6253c21615 | ||
|
|
a00408562f | ||
|
|
0d0bdd9fde | ||
|
|
c8da08ecce | ||
|
|
c5f99d17b7 | ||
|
|
c1b862c1e9 | ||
|
|
c1e5c33076 | ||
|
|
0f732684de | ||
|
|
eb1b2ac6f1 | ||
|
|
c814e9405d | ||
|
|
0344330230 | ||
|
|
5ddf3d77d1 | ||
|
|
c64f97082c | ||
|
|
18e46241f8 | ||
|
|
c8bfa4f49f | ||
|
|
247ee5b20b | ||
|
|
71da117d94 | ||
|
|
d0ae470c73 | ||
|
|
daa2d5b901 | ||
|
|
253c705ed6 | ||
|
|
e8a3d570d1 | ||
|
|
607f4f42c4 | ||
|
|
94c2f3c215 | ||
|
|
1f74aabbe4 | ||
|
|
6c0954004e | ||
|
|
c71c90420e | ||
|
|
31f71b0e12 | ||
|
|
0b555c4deb | ||
|
|
3d8ffdcc56 | ||
|
|
25d02cf120 | ||
|
|
7a99a4ec32 |
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
fixes:
|
||||||
|
- "github.com/9seconds/mtg/v2/::"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
vendor/
|
||||||
|
tags
|
||||||
|
.github/
|
||||||
|
.bin/
|
||||||
|
*.md
|
||||||
|
mtg
|
||||||
|
.golangci.toml
|
||||||
|
.gitignore
|
||||||
|
run.sh
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- stable
|
||||||
|
- v1
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- published
|
||||||
|
- released
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- edited
|
||||||
|
- reopened
|
||||||
|
- synchronize
|
||||||
|
- ready_for_review
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go_version:
|
||||||
|
- ^1.16
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go_version }}
|
||||||
|
|
||||||
|
- 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 tests
|
||||||
|
run: go test -coverprofile=./coverage.txt -covermode=atomic -v ./...
|
||||||
|
|
||||||
|
- name: Collect coverage
|
||||||
|
uses: codecov/codecov-action@v1
|
||||||
|
with:
|
||||||
|
file: ./coverage.txt
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Run linter
|
||||||
|
uses: golangci/golangci-lint-action@v2
|
||||||
|
with:
|
||||||
|
version: v1.39.0
|
||||||
|
|
||||||
|
docker:
|
||||||
|
name: Docker
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Get Docker meta
|
||||||
|
id: docker_meta
|
||||||
|
uses: crazy-max/ghaction-docker-meta@v1
|
||||||
|
with:
|
||||||
|
images: nineseconds/mtg,ghcr.io/9seconds/mtg
|
||||||
|
tag-semver: "{{version}},{{major}},{{major}}.{{minor}}"
|
||||||
|
|
||||||
|
- name: Setup QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
|
- name: Setup BuildX
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: Setup cache
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: /tmp/buildx-cache
|
||||||
|
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-buildx-
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Login to GHCR.io
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GH_PAT }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
pull: true
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7,linux/arm/v6
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||||
|
cache-from: type=local,src=/tmp/buildx-cache
|
||||||
|
cache-to: type=local,dest=/tmp/buildx-cache
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- stable
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ master ]
|
||||||
|
schedule:
|
||||||
|
- cron: '24 20 * * 5'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||||
|
# Learn more:
|
||||||
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
||||||
+3
-1
@@ -7,4 +7,6 @@
|
|||||||
*.out
|
*.out
|
||||||
mtg
|
mtg
|
||||||
vendor/
|
vendor/
|
||||||
version.go
|
ccbuilds/
|
||||||
|
.bin/
|
||||||
|
coverage.txt
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[run]
|
||||||
|
concurrency = 4
|
||||||
|
deadline = "2m"
|
||||||
|
tests = true
|
||||||
|
skip-dirs = ["vendor"]
|
||||||
|
|
||||||
|
[output]
|
||||||
|
format = "colored-line-number"
|
||||||
|
|
||||||
|
[linters]
|
||||||
|
enable-all = true
|
||||||
|
disable = ["gochecknoglobals", "gas", "goerr113", "exhaustivestruct"]
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"aggregate": true,
|
|
||||||
"vendor": true
|
|
||||||
}
|
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
|
|
||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
dist: trusty
|
|
||||||
|
|
||||||
go:
|
|
||||||
- "1.9.x"
|
|
||||||
- "1.10.x"
|
|
||||||
- master
|
|
||||||
|
|
||||||
script:
|
|
||||||
- make lint
|
|
||||||
- make test
|
|
||||||
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- vendor
|
|
||||||
+10
-17
@@ -1,7 +1,7 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
# BUILD STAGE
|
# BUILD STAGE
|
||||||
|
|
||||||
FROM golang:alpine
|
FROM golang:1.16-alpine AS build
|
||||||
|
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& apk --no-cache --update add \
|
&& apk --no-cache --update add \
|
||||||
@@ -9,17 +9,13 @@ RUN set -x \
|
|||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
git \
|
git \
|
||||||
make \
|
make
|
||||||
upx \
|
|
||||||
&& update-ca-certificates
|
|
||||||
|
|
||||||
ADD . /go/src/github.com/9seconds/mtg
|
COPY . /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& cd /go/src/github.com/9seconds/mtg \
|
&& make -j 4 static
|
||||||
&& make clean \
|
|
||||||
&& make -j 4 static \
|
|
||||||
&& upx --ultra-brute -qq ./mtg
|
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -27,12 +23,9 @@ RUN set -x \
|
|||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/mtg"]
|
ENTRYPOINT ["/mtg"]
|
||||||
ENV MTG_IP=0.0.0.0 \
|
CMD ["run", "/config.toml"]
|
||||||
MTG_PORT=3128 \
|
|
||||||
MTG_STATS_IP=0.0.0.0 \
|
|
||||||
MTG_STATS_PORT=3129
|
|
||||||
EXPOSE 3128 3129
|
|
||||||
|
|
||||||
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
COPY --from=0 /go/src/github.com/9seconds/mtg/mtg /usr/local/bin/mtg
|
COPY --from=build /app/mtg /mtg
|
||||||
|
COPY --from=build /app/example.config.toml /config.toml
|
||||||
|
|||||||
Generated
-91
@@ -1,91 +0,0 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/alecthomas/template"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"parse"
|
|
||||||
]
|
|
||||||
revision = "a0175ee3bccc567396460bf5acd36800cb10c49c"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/alecthomas/units"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/davecgh/go-spew"
|
|
||||||
packages = ["spew"]
|
|
||||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
|
||||||
version = "v1.1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/dustin/go-humanize"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "02af3965c54e8cacf948b97fef38925c4120652c"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/juju/errors"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "c7d06af17c68cd34c835053720b21f6549d9b0ee"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/pmezard/go-difflib"
|
|
||||||
packages = ["difflib"]
|
|
||||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/satori/go.uuid"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
|
|
||||||
version = "v1.2.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/stretchr/testify"
|
|
||||||
packages = ["assert"]
|
|
||||||
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
|
|
||||||
version = "v1.2.2"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "go.uber.org/atomic"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "1ea20fb1cbb1cc08cbd0d913a96dead89aa18289"
|
|
||||||
version = "v1.3.2"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "go.uber.org/multierr"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a"
|
|
||||||
version = "v1.1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "go.uber.org/zap"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"buffer",
|
|
||||||
"internal/bufferpool",
|
|
||||||
"internal/color",
|
|
||||||
"internal/exit",
|
|
||||||
"zapcore"
|
|
||||||
]
|
|
||||||
revision = "eeedf312bc6c57391d84767a4cd413f02a917974"
|
|
||||||
version = "v1.8.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "gopkg.in/alecthomas/kingpin.v2"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "947dcec5ba9c011838740e680966fd7087a71d0d"
|
|
||||||
version = "v2.2.6"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
inputs-digest = "c4fdd3664f683342ad0c2509f4a8bcfe5b267a6e8cdaf36f70d39536bbf89834"
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
#
|
|
||||||
# [prune]
|
|
||||||
# non-go = false
|
|
||||||
# go-tests = true
|
|
||||||
# unused-packages = true
|
|
||||||
|
|
||||||
|
|
||||||
[prune]
|
|
||||||
go-tests = true
|
|
||||||
unused-packages = true
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "gopkg.in/alecthomas/kingpin.v2"
|
|
||||||
version = "2.2.6"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/juju/errors"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/stretchr/testify"
|
|
||||||
version = "1.2.1"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/satori/go.uuid"
|
|
||||||
version = "1.2.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/dustin/go-humanize"
|
|
||||||
@@ -1,80 +1,107 @@
|
|||||||
ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||||
IMAGE_NAME := mtg
|
IMAGE_NAME := mtg
|
||||||
APP_NAME := $(IMAGE_NAME)
|
APP_NAME := $(IMAGE_NAME)
|
||||||
GOMETALINTER := gometalinter
|
|
||||||
|
|
||||||
VENDOR_FILES := $(shell find "$(ROOT_DIR)/vendor" 2>/dev/null || echo -n "vendor")
|
CC_BINARIES := $(shell bash -c "echo -n $(APP_NAME)-{linux,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}")
|
||||||
CC_BINARIES := $(shell bash -c "echo -n $(APP_NAME)-{linux,windows,darwin,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}")
|
|
||||||
APP_DEPS := version.go $(VENDOR_FILES)
|
|
||||||
|
|
||||||
COMMON_BUILD_FLAGS := -ldflags="-s -w"
|
GOLANGCI_LINT_VERSION := v1.39.0
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
VERSION_GO := $(shell go version)
|
||||||
|
VERSION_DATE := $(shell date -Ru)
|
||||||
|
VERSION_TAG := $(shell git describe --tags --always)
|
||||||
|
COMMON_BUILD_FLAGS := -trimpath -mod=readonly -ldflags="-extldflags '-static' -s -w -X 'main.version=$(VERSION_TAG) ($(VERSION_GO)) [$(VERSION_DATE)]'"
|
||||||
|
|
||||||
$(APP_NAME): $(APP_DEPS)
|
GOBIN := $(ROOT_DIR)/.bin
|
||||||
@go build $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)"
|
GOTOOL := env "GOBIN=$(GOBIN)" "PATH=$(ROOT_DIR)/.bin:$(PATH)"
|
||||||
|
|
||||||
static-$(APP_NAME): $(APP_DEPS)
|
|
||||||
@env CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)"
|
|
||||||
|
|
||||||
$(APP_NAME)-%: GOOS=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f1 -d-)
|
|
||||||
$(APP_NAME)-%: GOARCH=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f2 -d-)
|
|
||||||
$(APP_NAME)-%: $(APP_DEPS) ccbuilds
|
|
||||||
@env "GOOS=$(GOOS)" "GOARCH=$(GOARCH)" \
|
|
||||||
go build \
|
|
||||||
$(COMMON_BUILD_FLAGS) \
|
|
||||||
-o "./ccbuilds/$(APP_NAME)-$(GOOS)-$(GOARCH)"
|
|
||||||
|
|
||||||
ccbuilds:
|
|
||||||
@rm -rf ./ccbuilds && mkdir -p ./ccbuilds
|
|
||||||
|
|
||||||
version.go:
|
|
||||||
@go generate main.go
|
|
||||||
|
|
||||||
vendor: Gopkg.lock Gopkg.toml install-cli
|
|
||||||
@dep ensure
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: $(APP_NAME)
|
all: build
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
@go build $(COMMON_BUILD_FLAGS) -o "$(APP_NAME)"
|
||||||
|
|
||||||
|
$(APP_NAME): build
|
||||||
|
|
||||||
.PHONY: static
|
.PHONY: static
|
||||||
static: static-$(APP_NAME)
|
static:
|
||||||
|
@env CGO_ENABLED=0 GOOS=linux go build \
|
||||||
|
$(COMMON_BUILD_FLAGS) \
|
||||||
|
-tags netgo \
|
||||||
|
-a \
|
||||||
|
-o "$(APP_NAME)"
|
||||||
|
|
||||||
|
$(APP_NAME)-%: GOOS=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f1 -d-)
|
||||||
|
$(APP_NAME)-%: GOARCH=$(shell echo -n "$@" | sed 's?$(APP_NAME)-??' | cut -f2 -d-)
|
||||||
|
$(APP_NAME)-%: ccbuilds
|
||||||
|
@env "GOOS=$(GOOS)" "GOARCH=$(GOARCH)" \
|
||||||
|
go build \
|
||||||
|
$(COMMON_BUILD_FLAGS) \
|
||||||
|
-tags netgo \
|
||||||
|
-a \
|
||||||
|
-o "./ccbuilds/$(APP_NAME)-$(GOOS)-$(GOARCH)"
|
||||||
|
|
||||||
|
.PHONY: ccbuilds
|
||||||
|
ccbuilds:
|
||||||
|
@rm -rf ./ccbuilds && mkdir -p ./ccbuilds
|
||||||
|
|
||||||
|
vendor: go.mod go.sum
|
||||||
|
@$(MOD_ON) go mod vendor
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
fmt:
|
||||||
|
@$(GOTOOL) gofumpt -w -s -extra "$(ROOT_DIR)"
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
@go test -v ./...
|
||||||
|
|
||||||
|
.PHONY: citest
|
||||||
|
citest:
|
||||||
|
@go test -coverprofile=coverage.txt -covermode=atomic -parallel 2 -race -v ./...
|
||||||
|
|
||||||
.PHONY: crosscompile
|
.PHONY: crosscompile
|
||||||
crosscompile: $(CC_BINARIES)
|
crosscompile: $(CC_BINARIES)
|
||||||
|
|
||||||
.PHONY: crosscompile-dir
|
|
||||||
crosscompile-dir:
|
|
||||||
@rm -rf "$(CC_DIR)" && mkdir -p "$(CC_DIR)"
|
|
||||||
|
|
||||||
.PHONY: test
|
|
||||||
test: vendor install-cli version.go
|
|
||||||
@go test -v ./...
|
|
||||||
|
|
||||||
.PHONY: lint
|
|
||||||
lint: vendor install-cli version.go
|
|
||||||
@$(GOMETALINTER) --deadline=2m ./...
|
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
@git clean -xfd && \
|
@git clean -xfd && \
|
||||||
git reset --hard >/dev/null && \
|
git reset --hard >/dev/null && \
|
||||||
git submodule foreach --recursive sh -c 'git clean -xfd && git reset --hard' >/dev/null
|
git submodule foreach --recursive sh -c 'git clean -xfd && git reset --hard' >/dev/null
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint:
|
||||||
|
@$(GOTOOL) golangci-lint run
|
||||||
|
|
||||||
.PHONY: docker
|
.PHONY: docker
|
||||||
docker:
|
docker:
|
||||||
@docker build --pull -t "$(IMAGE_NAME)" "$(ROOT_DIR)"
|
@docker build --pull -t "$(IMAGE_NAME)" "$(ROOT_DIR)"
|
||||||
|
|
||||||
.PHONY: install-cli
|
.PHONY: doc
|
||||||
install-cli: install-dep install-lint
|
doc:
|
||||||
|
@$(GOTOOL) godoc -http 0.0.0.0:10000
|
||||||
|
|
||||||
.PHONY: install-dep
|
.PHONY: install-tools
|
||||||
install-dep:
|
install-tools: install-tools-lint install-tools-godoc install-tools-gofumpt
|
||||||
@go get github.com/golang/dep/cmd/dep
|
|
||||||
|
|
||||||
.PHONY: install-lint
|
.PHONY: install-tools-lint
|
||||||
install-lint:
|
install-tools-lint:
|
||||||
@go get github.com/alecthomas/gometalinter && \
|
@mkdir -p "$(GOBIN)" || true && \
|
||||||
$(GOMETALINTER) --install >/dev/null
|
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh \
|
||||||
|
| bash -s -- -b "$(GOBIN)" "$(GOLANGCI_LINT_VERSION)"
|
||||||
|
|
||||||
|
.PHONY: install-tools-godoc
|
||||||
|
install-tools-godoc:
|
||||||
|
@mkdir -p "$(GOBIN)" || true && \
|
||||||
|
$(GOTOOL) go get -u golang.org/x/tools/cmd/godoc
|
||||||
|
|
||||||
|
.PHONY: install-tools-gofumpt
|
||||||
|
install-tools-gofumpt:
|
||||||
|
@mkdir -p "$(GOBIN)" || true && \
|
||||||
|
$(GOTOOL) go get -u mvdan.cc/gofumpt
|
||||||
|
|
||||||
|
.PHONY: update-deps
|
||||||
|
update-deps:
|
||||||
|
@go get -u && go mod tidy
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
# mtg
|
# mtg
|
||||||
|
|
||||||
Bullshit-free MTPROTO proxy for Telegram
|
Highly-opionated (ex-bullshit-free) MTPROTO proxy for
|
||||||
|
[Telegram](https://telegram.org/).
|
||||||
|
|
||||||
[](https://travis-ci.org/9seconds/mtg)
|
[](https://github.com/9seconds/mtg/actions/workflows/ci.yaml)
|
||||||
[](https://hub.docker.com/r/nineseconds/mtg/)
|
[](https://codecov.io/gh/9seconds/mtg)
|
||||||
|
[](https://pkg.go.dev/github.com/9seconds/mtg/v2)
|
||||||
|
|
||||||
|
**If you use v1.0 or upgrade broke you proxy, please read the chapter
|
||||||
|
[Version 2](#version-2)**
|
||||||
|
|
||||||
# Rationale
|
## Rationale
|
||||||
|
|
||||||
There are several available proxies for Telegram MTPROTO available. Here
|
There are several available proxies for Telegram MTPROTO available. Here
|
||||||
are the most notable:
|
are the most notable:
|
||||||
@@ -14,99 +18,323 @@ are the most notable:
|
|||||||
* [Official](https://github.com/TelegramMessenger/MTProxy)
|
* [Official](https://github.com/TelegramMessenger/MTProxy)
|
||||||
* [Python](https://github.com/alexbers/mtprotoproxy)
|
* [Python](https://github.com/alexbers/mtprotoproxy)
|
||||||
* [Erlang](https://github.com/seriyps/mtproto_proxy)
|
* [Erlang](https://github.com/seriyps/mtproto_proxy)
|
||||||
* [JS](https://github.com/FreedomPrevails/JSMTProxy)
|
|
||||||
|
|
||||||
Almost all of them follow the way how official proxy was build. This
|
You can use any of these. They work great and all implementations have
|
||||||
includes support of multiple secrets, support of promoted channels etc.
|
feature parity now. This includes support of adtag, replay attack
|
||||||
|
protection, domain fronting, faketls, and so on. mtg has a similar
|
||||||
|
goal: to give a possibility to connect to Telegram in a restricted,
|
||||||
|
censored environment. But it does it slightly differently in details
|
||||||
|
that probably matter.
|
||||||
|
|
||||||
mtg is an implementation in golang which is intended to be:
|
* **Resource-efficient**
|
||||||
|
|
||||||
|
It has to be resource-efficient. It does not mean that you will see
|
||||||
|
the smallest memory usage. It means that it will try to use allocated
|
||||||
|
resources in zero-waste mode, reusing as much memory as possible and
|
||||||
|
so on.
|
||||||
|
|
||||||
* **Lightweight**
|
|
||||||
It has to consume as less resources as possible but not by losing
|
|
||||||
maintainability.
|
|
||||||
* **Easily deployable**
|
* **Easily deployable**
|
||||||
|
|
||||||
I strongly believe that Telegram proxies should follow the way of
|
I strongly believe that Telegram proxies should follow the way of
|
||||||
ShadowSocks: promoted channels is a strange way of doing business
|
[ShadowSocks](https://shadowsocks.org): promoted channels is a strange
|
||||||
I suppose. I think the only viable way is to have a proxy with
|
way of doing business I suppose. I think the only viable way is to
|
||||||
minimum configuration which should work everywhere.
|
have a proxy that can be restored anywhere easily.
|
||||||
* **Single secret**
|
|
||||||
I think that multiple secrets solves no problems and just complexify
|
* **A single secret**
|
||||||
software. I also believe that in case of throwout proxies, this feature
|
|
||||||
is useless luxury.
|
I think that multiple secrets solve no problems and just complex
|
||||||
* **Minimum docker image size**
|
software. I also believe that in the case of throwout proxies, this
|
||||||
Official image is less than 2 megabytes. Literally.
|
the feature is a useless luxury.
|
||||||
|
|
||||||
|
* **No adtag support**
|
||||||
|
|
||||||
|
Please read [Version 2](#version-2) chapter.
|
||||||
|
|
||||||
* **No management WebUI**
|
* **No management WebUI**
|
||||||
This is an implementation of simple lightweight proxy. I won't do that.
|
|
||||||
|
|
||||||
This proxy supports 2 modes of work: direct connection to Telegram and
|
This is an implementation of a simple lightweight proxy. I won't do that.
|
||||||
promoted channel mode. If you do not need promoted channels, I would
|
|
||||||
recommend you to go with direct mode: this is way more robust.
|
|
||||||
|
|
||||||
To run proxy in direct mode, all you need to do is just provide a
|
* **Proxy chaining**
|
||||||
secret. If you do not provide ADTag as a second parameter, promoted
|
|
||||||
channels mode won't be activated.
|
|
||||||
|
|
||||||
To get promoted channel, please contact
|
mtg has the support of [SOCKS5](https://en.wikipedia.org/wiki/SOCKS)
|
||||||
[@MTProxybot](https://t.me/MTProxybot) and provide generated adtag as a
|
proxies. So, in theory, you can run this proxy as a frontend
|
||||||
second parameter.
|
and route traffic via [v2ray](https://www.v2ray.com/),
|
||||||
|
[Gost](https://docs.ginuerzh.xyz/gost/),
|
||||||
|
[Trojan](https://trojan-gfw.github.io/trojan/), or any other project
|
||||||
|
you like.
|
||||||
|
|
||||||
|
* **Native blocklist support**
|
||||||
|
|
||||||
# How to build
|
Previously, this was delegated to the [FireHOL](https://firehol.org/)
|
||||||
|
project or similar ones which track attacks and publish a list of
|
||||||
|
potentially dangerous IPs. mtg has native support of such blocklists.
|
||||||
|
|
||||||
|
* **Can be used as a library**
|
||||||
|
|
||||||
|
mtg v2 was redesigned in a way so it can be embedded into your
|
||||||
|
software (written in Golang) with a minimum effort + you can replace
|
||||||
|
some parts with those you want.
|
||||||
|
|
||||||
|
### Version 2
|
||||||
|
|
||||||
|
If you use version 1.x before, you are probably noticed some major
|
||||||
|
backward non-compatible details:
|
||||||
|
|
||||||
|
1. Configuration file
|
||||||
|
2. Removed support of adtag
|
||||||
|
|
||||||
|
For the configuration file, please check out the full example in this
|
||||||
|
repository. It has a lot of comments and most of the options are
|
||||||
|
optional. We do have only `secret` and `bind-to` sections mandatory.
|
||||||
|
Other sections in the example configuration file are filled with default
|
||||||
|
values.
|
||||||
|
|
||||||
|
Adtag support was removed completely. This was done to debloat mtg and
|
||||||
|
keep it simple and obvious. Hopefully, this goal is achieved and the
|
||||||
|
source code is clean and straightforward enough.
|
||||||
|
|
||||||
|
I always was quite skeptical about adtag. In my POV, a proxy as a fat
|
||||||
|
big connectivity point for hundreds of clients is an illusion. If you
|
||||||
|
work in a censored environment, the first thing that authority does is
|
||||||
|
IP blocking. For us, it means, those big proxies that can benefit from
|
||||||
|
having a pinned channel are going to be blocked in a minute.
|
||||||
|
|
||||||
|
Proxy has to be intimate. It has to be shared within a small group as
|
||||||
|
a family or maybe your college friends. It has to have a small number
|
||||||
|
of connections and never publicly announced its presence. It has to fly
|
||||||
|
under the radar. If the proxy is detected, you need to be able to give
|
||||||
|
a rebirth on a new IP address as soon as possible. I do no think that
|
||||||
|
having some special channel for such a use case makes any sense.
|
||||||
|
|
||||||
|
But other details like replay attack protection, domain fronting,
|
||||||
|
accurate FakeTLS implementation, IP blacklisting, and proxy
|
||||||
|
chaining matter here. If you work in censored perimeter like
|
||||||
|
[GFW](https://en.wikipedia.org/wiki/Great_Firewall)-protected
|
||||||
|
country, you probably want to have an MTPROTO proxy as
|
||||||
|
a frontend that transports traffic via cloaked tunnels
|
||||||
|
made by [Trojan](https://trojan-gfw.github.io/trojan/),
|
||||||
|
[Shadowsocks](https://shadowsocks.org), [v2ray](https://www.v2ray.com/),
|
||||||
|
or [Gost](https://docs.ginuerzh.xyz/gost/). That's why you have to have
|
||||||
|
the support of chaining as a first-class citizen.
|
||||||
|
|
||||||
|
Yes, this is possible and doable with optional adtag support. But the
|
||||||
|
truth is that the MTPROTO proxy for Telegram is just a thing that either
|
||||||
|
work as a normal client (direct mode) or doing some RPC calls in [TL
|
||||||
|
language](https://core.telegram.org/mtproto/TL) (adtag support). I
|
||||||
|
understand the intention of the developers and I understand that they
|
||||||
|
were under high pressure fighting with [RKN](https://rkn.gov.ru/) and
|
||||||
|
doing TON after that. Nothing is ideal. But for the proxy, it means that
|
||||||
|
source code is full of complex non-trivial code which is required only
|
||||||
|
to support a feature that we barely need.
|
||||||
|
|
||||||
|
So, to have a reasonable MTPROTO proxy, adtag support was removed. This
|
||||||
|
is a rare chance in my career where software v2 debloats a previous
|
||||||
|
version. It feels so good :)
|
||||||
|
|
||||||
|
### Version 1 and 2
|
||||||
|
|
||||||
|
I do continue to support both versions 1 and 2. But in a different mode.
|
||||||
|
|
||||||
|
Version 1 is now officially in maintenance mode. It means that I won't
|
||||||
|
make any new features or improvements there. You can consider a feature
|
||||||
|
freeze there. No bugs are going to be fixed there except for critical
|
||||||
|
ones. PRs are welcome though. The goal is to keep it working. It will
|
||||||
|
get some periodical updates like updates to the new Golang version of
|
||||||
|
dependencies version bump, but that's mostly it.
|
||||||
|
|
||||||
|
**If you want to have mtg with _adtag support_, please use version 1**.
|
||||||
|
|
||||||
|
Version 2 is going to have all my love, active support, bug fixing, etc.
|
||||||
|
It is under active development and maintenance.
|
||||||
|
|
||||||
|
This project has several main branches
|
||||||
|
|
||||||
|
1. [`master`](https://github.com/9seconds/mtg/tree/master) branch
|
||||||
|
contains a bleeding edge. It may potentially have some features
|
||||||
|
which will break your source code.
|
||||||
|
2. [`stable`](https://github.com/9seconds/mtg/tree/stable) branch contains
|
||||||
|
dumps of a master branch when we consider it 'stable'. This is a
|
||||||
|
branch you probably want to pick.
|
||||||
|
3. [`v2`](https://github.com/9seconds/mtg/tree/v2) has a development
|
||||||
|
of the v2.x version. In theory, it is the same as `master` but this
|
||||||
|
will change when we have v3.x.
|
||||||
|
4. [`v1`](https://github.com/9seconds/mtg/tree/v1) has a version 1.x.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
### Download a tool
|
||||||
|
|
||||||
|
#### Download binaries
|
||||||
|
|
||||||
|
Binaries can be downloaded from the release page. Also, you can download
|
||||||
|
docker image.
|
||||||
|
|
||||||
|
For the current version, please download like
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ make
|
docker pull nineseconds/mtg:2
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to build for another platform:
|
For version 1:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ make crosscompile
|
docker pull nineseconds/mtg:1
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to build Docker image (called `mtg`):
|
You may also check both [Docker
|
||||||
|
Hub](https://hub.docker.com/r/nineseconds/mtg/tags) and [Github
|
||||||
|
Registry](https://github.com/users/9seconds/packages/container/package/mtg).
|
||||||
|
Please do not choose `latest` or `stable` if you want to avoid
|
||||||
|
surprises. Always choose some version tag.
|
||||||
|
|
||||||
|
Also, if you have `go` installed, you can always download this tool with `go get`:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ make docker
|
go get github.com/9seconds/mtg/v2
|
||||||
```
|
```
|
||||||
|
|
||||||
# Docker image
|
#### Build from sources
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker pull nineseconds/mtg
|
git clone https://github.com:9seconds/mtg.git
|
||||||
|
cd mtg
|
||||||
|
make static
|
||||||
```
|
```
|
||||||
|
|
||||||
# Configuration
|
or for the docker image:
|
||||||
|
|
||||||
Basically, to run this tool you need to configure as less as possible.
|
|
||||||
|
|
||||||
First, you need to generate a secret:
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ openssl rand -hex 16
|
make docker
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generate secret
|
||||||
|
|
||||||
|
If you already have a secret in Base64 format or that, which starts with `ee`,
|
||||||
|
you can skip this chapter. Otherwise:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ mtg generate-secret google.com
|
||||||
|
7ibaERuTSGPH1RdztfYnN4tnb29nbGUuY29t
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ head -c 512 /dev/urandom | md5sum | cut -f 1 -d ' '
|
$ mtg generate-secret --hex google.com
|
||||||
|
ee473ce5d4958eb5f968c87680a23854a0676f6f676c652e636f6d
|
||||||
```
|
```
|
||||||
|
|
||||||
Now run the tool:
|
This secret is a keystone for a proxy and your password for a client.
|
||||||
|
You need to keep it secured.
|
||||||
|
|
||||||
|
We recommend choosing a hostname wisely. Here we have a _google.com_
|
||||||
|
but in reality, all providers can easily detect that this is not a
|
||||||
|
Google. Google has a list of networks it officially uses and your IP
|
||||||
|
address won't probably belong to it. It is a great idea to hide behind
|
||||||
|
some domain that has some relation to this IP address.
|
||||||
|
|
||||||
|
For example, you've bought a VPS from [Digital
|
||||||
|
Ocean](https://www.digitalocean.com/). Then it might be a good idea to
|
||||||
|
generate a secret for _digitalocean.com_ then.
|
||||||
|
|
||||||
|
### Prepare a configuration file
|
||||||
|
|
||||||
|
Please checkout an example configuration file. All options except of
|
||||||
|
`secret` and `bind-to` are optional. You can safely have this minimal
|
||||||
|
configuration file:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
secret = "ee473ce5d4958eb5f968c87680a23854a0676f6f676c652e636f6d"
|
||||||
|
bind-to = "0.0.0.0:443"
|
||||||
|
```
|
||||||
|
|
||||||
|
This is enough to run the whole application. All other
|
||||||
|
options already have sensible defaults for the app at almost any scale.
|
||||||
|
|
||||||
|
Oh, the configuration is done in [TOML format](https://toml.io/en/).
|
||||||
|
|
||||||
|
### Run a proxy
|
||||||
|
|
||||||
|
Put a binary and a config into your webserver. Just for example,
|
||||||
|
a binary goes to `/usr/local/bin/mtg` and configuration to `/etc/mtg.toml`.
|
||||||
|
|
||||||
|
Now you can create a systemd unit:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ mtg <secret>
|
$ cat /etc/systemd/system/mtg.service
|
||||||
|
[Unit]
|
||||||
|
Description=mtg
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/local/bin/mtg run /etc/mtg.toml
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
$ sudo systemctl daemon-reload
|
||||||
|
$ sudo systemctl enable mtg
|
||||||
|
$ sudo systemctl start mtg
|
||||||
```
|
```
|
||||||
|
|
||||||
This tool will listen on port 3128 by default with the given secret.
|
or you can run a docker image
|
||||||
|
|
||||||
# One-line runner
|
```console
|
||||||
|
docker run -d -v /etc/mtg.toml:/config.toml -p 443:3128 --restart=unless-stopped nineseconds/mtg:2
|
||||||
```
|
|
||||||
$ docker run --name mtg --restart=unless-stopped -p 3128:3128 -p 3129:3129 -d nineseconds/mtg $(openssl rand -hex 16)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You will have this tool up and running on port 3128. Now curl
|
where _443_ is a host port (a port you want to connect to from a
|
||||||
`localhost:3129` to get `tg://` links or do `docker logs mtg`. Also,
|
client), and _3128_ is the one you have in your config in the `bind-to`
|
||||||
port 3129 will show you some statistics if you are interested in.
|
section.
|
||||||
|
|
||||||
Also, you can use [run-mtg.sh](https://github.com/9seconds/mtg/blob/master/run-mtg.sh) script
|
### Access a proxy
|
||||||
|
|
||||||
|
Now you can generate some useful links:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ mtg access /etc/mtg.toml
|
||||||
|
{
|
||||||
|
"ipv4": {
|
||||||
|
"ip": "x.y.z.a",
|
||||||
|
"port": 3128,
|
||||||
|
"tg_url": "tg://proxy?...",
|
||||||
|
"tg_qrcode": "https://api.qrserver.com/v1/create-qr-code?data...",
|
||||||
|
"tme_url": "https://t.me/proxy?...",
|
||||||
|
"tme_qrcode": "https://api.qrserver.com/v1/create-qr-code?data..."
|
||||||
|
},
|
||||||
|
"secret": {
|
||||||
|
"hex": "...",
|
||||||
|
"base64": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Metrics
|
||||||
|
|
||||||
|
Out of the box, mtg works with
|
||||||
|
[statsd](https://github.com/statsd/statsd) and
|
||||||
|
[Prometheus](https://prometheus.io/). Please check configuration file
|
||||||
|
example to get how to set this integration up.
|
||||||
|
|
||||||
|
Here goes a list of metrics with their types but without a prefix.
|
||||||
|
|
||||||
|
| Name | Type | Tags | Description |
|
||||||
|
|-----------------------------|---------|----------------------------------|--------------------------------------------------------------------------------------------|
|
||||||
|
| client_connections | gauge | `ip_family` | Count of processing client connections. |
|
||||||
|
| telegram_connections | gauge | `telegram_ip`, `dc` | Count of connections to Telegram servers. |
|
||||||
|
| domain_fronting_connections | gauge | `ip_family` | Count of connections to fronting domain. |
|
||||||
|
| telegram_traffic | counter | `telegram_ip`, `dc`, `direction` | Count of bytes, transmitted to/from Telegram. |
|
||||||
|
| domain_fronting_traffic | counter | `direction` | Count of bytes, transmitted to/from fronting domain. |
|
||||||
|
| domain_fronting | counter | – | Count of domain fronting events. |
|
||||||
|
| concurrency_limited | counter | – | Count of events, when client connection was rejected due to concurrency limit. |
|
||||||
|
| ip_blocklisted | counter | – | Count of events when client connection was rejected because IP was found in the blacklist. |
|
||||||
|
| replay_attacks | counter | – | Count of detected replay attacks. |
|
||||||
|
|
||||||
|
Tag meaning:
|
||||||
|
|
||||||
|
| Name | Values | Description |
|
||||||
|
|-------------|----------------------------|-----------------------------------------------|
|
||||||
|
| ip_family | `ipv4`, `ipv6` | A version of the IP protocol. |
|
||||||
|
| dc | | A number of the Telegram DC for a connection. |
|
||||||
|
| telegram_ip | | IP address of the Telegram server. |
|
||||||
|
| direction | `to_client`, `from_client` | A direction of the traffic flow. |
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
We support 2 tracks: 2.x and 1.x
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| 2.x | :white_check_mark: |
|
||||||
|
| 1.x | :interrobang: |
|
||||||
|
| < 1.0 | :x: |
|
||||||
|
|
||||||
|
1.x has adtag support. We do not plan any active development there but we guarantee:
|
||||||
|
|
||||||
|
1. Regular dependency updates
|
||||||
|
2. Updates for Golang versions
|
||||||
|
3. Security vulnerability fixes
|
||||||
|
4. Merging small PRs
|
||||||
|
|
||||||
|
2.x is in active development and have a full support.
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you have found a vulnerability, please report about it to nineseconds@yandex.ru, telegram @9seconds or here in issues
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// Antireplay package has cache implementations that are effective
|
||||||
|
// against replay attacks.
|
||||||
|
//
|
||||||
|
// To understand more about replay attacks, please read documentation
|
||||||
|
// for mtglib.AntiReplayCache interface. This package has a list of some
|
||||||
|
// implementations of this interface.
|
||||||
|
package antireplay
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultStableBloomFilterMaxSize is a recommended byte size for a
|
||||||
|
// stable bloom filter.
|
||||||
|
DefaultStableBloomFilterMaxSize = 1024 * 1024 // 1MiB
|
||||||
|
|
||||||
|
// DefaultStableBloomFilterErrorRate is a recommended default error
|
||||||
|
// rate for a stable bloom filter.
|
||||||
|
DefaultStableBloomFilterErrorRate = 0.001
|
||||||
|
)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package antireplay
|
||||||
|
|
||||||
|
import "github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
|
||||||
|
type noop struct{}
|
||||||
|
|
||||||
|
func (n noop) SeenBefore(_ []byte) bool { return false }
|
||||||
|
|
||||||
|
// NewNoop returns an implementation that does nothing. A corresponding
|
||||||
|
// method always returns false, so this cache accepts everything you
|
||||||
|
// pass to it.
|
||||||
|
func NewNoop() mtglib.AntiReplayCache {
|
||||||
|
return noop{}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package antireplay_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/antireplay"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NoopTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *NoopTestSuite) TestOp() {
|
||||||
|
filter := antireplay.NewNoop()
|
||||||
|
|
||||||
|
suite.False(filter.SeenBefore([]byte{1, 2, 3}))
|
||||||
|
suite.False(filter.SeenBefore([]byte{4, 5, 6}))
|
||||||
|
suite.False(filter.SeenBefore([]byte{1, 2, 3}))
|
||||||
|
suite.False(filter.SeenBefore([]byte{4, 5, 6}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoop(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &NoopTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package antireplay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/OneOfOne/xxhash"
|
||||||
|
boom "github.com/tylertreat/BoomFilters"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stableBloomFilter struct {
|
||||||
|
filter boom.StableBloomFilter
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stableBloomFilter) SeenBefore(digest []byte) bool {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
return s.filter.TestAndAdd(digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStableBloomFilter returns an implementation of AntiReplayCache
|
||||||
|
// based on stable bloom filter.
|
||||||
|
//
|
||||||
|
// http://webdocs.cs.ualberta.ca/~drafiei/papers/DupDet06Sigmod.pdf
|
||||||
|
//
|
||||||
|
// The basic idea of a stable bloom filter is quite simple: each time
|
||||||
|
// when you set a new element, you randomly reset P elements. There is a
|
||||||
|
// hardcore math which proves that if you choose this P correctly, you
|
||||||
|
// can maintain the same error rate for a stream of elements.
|
||||||
|
//
|
||||||
|
// byteSize is the number of bytes you want to give to a bloom filter.
|
||||||
|
// errorRate is desired false-positive error rate. If you want to use
|
||||||
|
// default values, please pass 0 for byteSize and <0 for errorRate.
|
||||||
|
func NewStableBloomFilter(byteSize uint, errorRate float64) mtglib.AntiReplayCache {
|
||||||
|
if byteSize == 0 {
|
||||||
|
byteSize = DefaultStableBloomFilterMaxSize
|
||||||
|
}
|
||||||
|
|
||||||
|
if errorRate < 0 {
|
||||||
|
errorRate = DefaultStableBloomFilterErrorRate
|
||||||
|
}
|
||||||
|
|
||||||
|
sf := boom.NewDefaultStableBloomFilter(byteSize*8, errorRate) // nolint: gomnd
|
||||||
|
sf.SetHash(xxhash.New64())
|
||||||
|
|
||||||
|
return &stableBloomFilter{
|
||||||
|
filter: *sf,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package antireplay_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/antireplay"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StableBloomFilterTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *StableBloomFilterTestSuite) TestOp() {
|
||||||
|
filter := antireplay.NewStableBloomFilter(500, 0.001)
|
||||||
|
|
||||||
|
suite.False(filter.SeenBefore([]byte{1, 2, 3}))
|
||||||
|
suite.False(filter.SeenBefore([]byte{4, 5, 6}))
|
||||||
|
suite.True(filter.SeenBefore([]byte{1, 2, 3}))
|
||||||
|
suite.True(filter.SeenBefore([]byte{4, 5, 6}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStableBloomFilter(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &StableBloomFilterTestSuite{})
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/9seconds/mtg/config"
|
|
||||||
"github.com/9seconds/mtg/mtproto"
|
|
||||||
"github.com/9seconds/mtg/wrappers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Init defines common method for initializing client connections.
|
|
||||||
type Init func(net.Conn, string, *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error)
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/juju/errors"
|
|
||||||
|
|
||||||
"github.com/9seconds/mtg/config"
|
|
||||||
"github.com/9seconds/mtg/mtproto"
|
|
||||||
"github.com/9seconds/mtg/obfuscated2"
|
|
||||||
"github.com/9seconds/mtg/wrappers"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
handshakeTimeout = 10 * time.Second
|
|
||||||
readBufferSize = 64 * 1024
|
|
||||||
writeBufferSize = 64 * 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
// DirectInit initializes client connection for proxy which connects to
|
|
||||||
// Telegram directly.
|
|
||||||
func DirectInit(socket net.Conn, connID string, conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) {
|
|
||||||
tcpSocket := socket.(*net.TCPConn)
|
|
||||||
if err := tcpSocket.SetNoDelay(false); err != nil {
|
|
||||||
return nil, nil, errors.Annotate(err, "Cannot disable NO_DELAY to client socket")
|
|
||||||
}
|
|
||||||
if err := tcpSocket.SetReadBuffer(readBufferSize); err != nil {
|
|
||||||
return nil, nil, errors.Annotate(err, "Cannot set read buffer size of client socket")
|
|
||||||
}
|
|
||||||
if err := tcpSocket.SetWriteBuffer(writeBufferSize); err != nil {
|
|
||||||
return nil, nil, errors.Annotate(err, "Cannot set write buffer size of client socket")
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.SetReadDeadline(time.Now().Add(handshakeTimeout)) // nolint: errcheck
|
|
||||||
frame, err := obfuscated2.ExtractFrame(socket)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Annotate(err, "Cannot extract frame")
|
|
||||||
}
|
|
||||||
socket.SetReadDeadline(time.Time{}) // nolint: errcheck
|
|
||||||
|
|
||||||
conn := wrappers.NewConn(socket, connID, wrappers.ConnPurposeClient, conf.PublicIPv4, conf.PublicIPv6)
|
|
||||||
obfs2, connOpts, err := obfuscated2.ParseObfuscated2ClientFrame(conf.Secret, frame)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Annotate(err, "Cannot parse obfuscated frame")
|
|
||||||
}
|
|
||||||
connOpts.ConnectionProto = mtproto.ConnectionProtocolAny
|
|
||||||
connOpts.ClientAddr = conn.RemoteAddr()
|
|
||||||
|
|
||||||
conn = wrappers.NewStreamCipher(conn, obfs2.Encryptor, obfs2.Decryptor)
|
|
||||||
|
|
||||||
conn.Logger().Infow("Client connection initialized")
|
|
||||||
|
|
||||||
return conn, connOpts, nil
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/9seconds/mtg/config"
|
|
||||||
"github.com/9seconds/mtg/mtproto"
|
|
||||||
"github.com/9seconds/mtg/wrappers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MiddleInit initializes client connection for proxy which has to
|
|
||||||
// support promoted channels, connect to Telegram middle proxies etc.
|
|
||||||
func MiddleInit(socket net.Conn, connID string, conf *config.Config) (wrappers.Wrap, *mtproto.ConnectionOpts, error) {
|
|
||||||
conn, opts, err := DirectInit(socket, connID, conf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
connStream := conn.(wrappers.StreamReadWriteCloser)
|
|
||||||
|
|
||||||
newConn := wrappers.NewMTProtoAbridged(connStream, opts)
|
|
||||||
if opts.ConnectionType != mtproto.ConnectionTypeAbridged {
|
|
||||||
newConn = wrappers.NewMTProtoIntermediate(connStream, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.ConnectionProto = mtproto.ConnectionProtocolIPv4
|
|
||||||
if socket.LocalAddr().(*net.TCPAddr).IP.To4() == nil {
|
|
||||||
opts.ConnectionProto = mtproto.ConnectionProtocolIPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
return newConn, opts, err
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/juju/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Buffer sizes define internal socket buffer sizes.
|
|
||||||
const (
|
|
||||||
BufferWriteSize = 32 * 1024
|
|
||||||
BufferReadSize = 32 * 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config represents common configuration of mtg.
|
|
||||||
type Config struct {
|
|
||||||
Debug bool
|
|
||||||
Verbose bool
|
|
||||||
|
|
||||||
BindPort uint16
|
|
||||||
PublicIPv4Port uint16
|
|
||||||
PublicIPv6Port uint16
|
|
||||||
StatsPort uint16
|
|
||||||
|
|
||||||
BindIP net.IP
|
|
||||||
PublicIPv4 net.IP
|
|
||||||
PublicIPv6 net.IP
|
|
||||||
StatsIP net.IP
|
|
||||||
|
|
||||||
Secret []byte
|
|
||||||
AdTag []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// URLs contains links to the proxy (tg://, t.me) and their QR codes.
|
|
||||||
type URLs struct {
|
|
||||||
TG string `json:"tg_url"`
|
|
||||||
TMe string `json:"tme_url"`
|
|
||||||
TGQRCode string `json:"tg_qrcode"`
|
|
||||||
TMeQRCode string `json:"tme_qrcode"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPURLs contains links to both ipv4 and ipv6 of the proxy.
|
|
||||||
type IPURLs struct {
|
|
||||||
IPv4 URLs `json:"ipv4"`
|
|
||||||
IPv6 URLs `json:"ipv6"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BindAddr returns connection for this server to bind to.
|
|
||||||
func (c *Config) BindAddr() string {
|
|
||||||
return getAddr(c.BindIP, c.BindPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StatAddr returns connection string to the stats API.
|
|
||||||
func (c *Config) StatAddr() string {
|
|
||||||
return getAddr(c.StatsIP, c.StatsPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UseMiddleProxy defines if this proxy has to connect middle proxies
|
|
||||||
// which supports promoted channels or directly access Telegram.
|
|
||||||
func (c *Config) UseMiddleProxy() bool {
|
|
||||||
return len(c.AdTag) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetURLs returns configured IPURLs instance with links to this server.
|
|
||||||
func (c *Config) GetURLs() IPURLs {
|
|
||||||
urls := IPURLs{}
|
|
||||||
if c.PublicIPv4 != nil {
|
|
||||||
urls.IPv4 = getURLs(c.PublicIPv4, c.PublicIPv4Port, c.Secret)
|
|
||||||
}
|
|
||||||
if c.PublicIPv6 != nil {
|
|
||||||
urls.IPv6 = getURLs(c.PublicIPv6, c.PublicIPv6Port, c.Secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
return urls
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAddr(host fmt.Stringer, port uint16) string {
|
|
||||||
return net.JoinHostPort(host.String(), strconv.Itoa(int(port)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConfig returns new configuration. If required, it manages and
|
|
||||||
// fetches data from external sources. Parameters passed to this
|
|
||||||
// function, should come from command line arguments.
|
|
||||||
func NewConfig(debug, verbose bool, // nolint: gocyclo
|
|
||||||
bindIP net.IP, bindPort uint16,
|
|
||||||
publicIPv4 net.IP, PublicIPv4Port uint16,
|
|
||||||
publicIPv6 net.IP, publicIPv6Port uint16,
|
|
||||||
statsIP net.IP, statsPort uint16,
|
|
||||||
secret, adtag string) (*Config, error) {
|
|
||||||
if len(secret) != 32 {
|
|
||||||
return nil, errors.New("Telegram demands secret of length 32")
|
|
||||||
}
|
|
||||||
secretBytes, err := hex.DecodeString(secret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Annotate(err, "Cannot create config")
|
|
||||||
}
|
|
||||||
|
|
||||||
var adTagBytes []byte
|
|
||||||
if len(adtag) != 0 {
|
|
||||||
adTagBytes, err = hex.DecodeString(adtag)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Annotate(err, "Cannot create config")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if publicIPv4 == nil {
|
|
||||||
publicIPv4, err = getGlobalIPv4()
|
|
||||||
if err != nil {
|
|
||||||
publicIPv4 = nil
|
|
||||||
} else if publicIPv4.To4() == nil {
|
|
||||||
return nil, errors.Errorf("IP %s is not IPv4", publicIPv4.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if PublicIPv4Port == 0 {
|
|
||||||
PublicIPv4Port = bindPort
|
|
||||||
}
|
|
||||||
|
|
||||||
if publicIPv6 == nil {
|
|
||||||
publicIPv6, err = getGlobalIPv6()
|
|
||||||
if err != nil {
|
|
||||||
publicIPv6 = nil
|
|
||||||
} else if publicIPv6.To4() != nil {
|
|
||||||
return nil, errors.Errorf("IP %s is not IPv6", publicIPv6.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if publicIPv6Port == 0 {
|
|
||||||
publicIPv6Port = bindPort
|
|
||||||
}
|
|
||||||
|
|
||||||
if statsIP == nil {
|
|
||||||
statsIP = publicIPv4
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := &Config{
|
|
||||||
Debug: debug,
|
|
||||||
Verbose: verbose,
|
|
||||||
BindIP: bindIP,
|
|
||||||
BindPort: bindPort,
|
|
||||||
PublicIPv4: publicIPv4,
|
|
||||||
PublicIPv4Port: PublicIPv4Port,
|
|
||||||
PublicIPv6: publicIPv6,
|
|
||||||
PublicIPv6Port: publicIPv6Port,
|
|
||||||
StatsIP: statsIP,
|
|
||||||
StatsPort: statsPort,
|
|
||||||
Secret: secretBytes,
|
|
||||||
AdTag: adTagBytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
return conf, nil
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/juju/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getGlobalIPv4() (net.IP, error) {
|
|
||||||
return fetchIP("https://v4.ifconfig.co/ip")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getGlobalIPv6() (net.IP, error) {
|
|
||||||
return fetchIP("https://v6.ifconfig.co/ip")
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchIP(url string) (net.IP, error) {
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close() // nolint: errcheck
|
|
||||||
|
|
||||||
respDataBytes, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
respData := strings.TrimSpace(string(respDataBytes))
|
|
||||||
|
|
||||||
ip := net.ParseIP(respData)
|
|
||||||
if ip == nil {
|
|
||||||
return nil, errors.Errorf("ifconfig.co returns incorrect IP %s", respData)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ip, nil
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getURLs(addr net.IP, port uint16, secret []byte) (urls URLs) {
|
|
||||||
values := url.Values{}
|
|
||||||
values.Set("server", addr.String())
|
|
||||||
values.Set("port", strconv.Itoa(int(port)))
|
|
||||||
values.Set("secret", hex.EncodeToString(secret))
|
|
||||||
|
|
||||||
urls.TG = makeTGURL(values)
|
|
||||||
urls.TMe = makeTMeURL(values)
|
|
||||||
urls.TGQRCode = makeQRCodeURL(urls.TG)
|
|
||||||
urls.TMeQRCode = makeQRCodeURL(urls.TG)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTGURL(values url.Values) string {
|
|
||||||
tgURL := url.URL{
|
|
||||||
Scheme: "tg",
|
|
||||||
Host: "proxy",
|
|
||||||
RawQuery: values.Encode(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return tgURL.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTMeURL(values url.Values) string {
|
|
||||||
tMeURL := url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
Host: "t.me",
|
|
||||||
Path: "proxy",
|
|
||||||
RawQuery: values.Encode(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return tMeURL.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeQRCodeURL(data string) string {
|
|
||||||
QRURL := url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
Host: "api.qrserver.com",
|
|
||||||
Path: "v1/create-qr-code",
|
|
||||||
}
|
|
||||||
|
|
||||||
values := url.Values{}
|
|
||||||
values.Set("qzone", "4")
|
|
||||||
values.Set("format", "svg")
|
|
||||||
values.Set("data", data)
|
|
||||||
QRURL.RawQuery = values.Encode()
|
|
||||||
|
|
||||||
return QRURL.String()
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package events
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/OneOfOne/xxhash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EventStream is a default implementation of the mtglib.EventStream
|
||||||
|
// interface.
|
||||||
|
//
|
||||||
|
// EventStream manages a set of goroutines, observers. Main
|
||||||
|
// responsibility of the event stream is to route an event to relevant
|
||||||
|
// observer based on some hash so each observer will have all events
|
||||||
|
// which belong to some stream id.
|
||||||
|
//
|
||||||
|
// Thus, EventStream can spawn many observers.
|
||||||
|
type EventStream struct {
|
||||||
|
ctx context.Context
|
||||||
|
ctxCancel context.CancelFunc
|
||||||
|
chans []chan mtglib.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send starts delivering of the message to observer with respect to a
|
||||||
|
// given context If context is closed, message could be not delivered.
|
||||||
|
func (e EventStream) Send(ctx context.Context, evt mtglib.Event) {
|
||||||
|
var chanNo uint32
|
||||||
|
|
||||||
|
if streamID := evt.StreamID(); streamID != "" {
|
||||||
|
chanNo = xxhash.ChecksumString32(streamID)
|
||||||
|
} else {
|
||||||
|
chanNo = rand.Uint32()
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case <-e.ctx.Done():
|
||||||
|
case e.chans[int(chanNo)%len(e.chans)] <- evt:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown stops an event stream pipeline.
|
||||||
|
func (e EventStream) Shutdown() {
|
||||||
|
e.ctxCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventStream builds a new default event stream.
|
||||||
|
//
|
||||||
|
// If you give an empty array of observers, then NoopObserver is going
|
||||||
|
// to be used. If you give many observers, then they will process a
|
||||||
|
// message concurrently.
|
||||||
|
func NewEventStream(observerFactories []ObserverFactory) EventStream {
|
||||||
|
if len(observerFactories) == 0 {
|
||||||
|
observerFactories = append(observerFactories, NewNoopObserver)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
rv := EventStream{
|
||||||
|
ctx: ctx,
|
||||||
|
ctxCancel: cancel,
|
||||||
|
chans: make([]chan mtglib.Event, runtime.NumCPU()),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < runtime.NumCPU(); i++ {
|
||||||
|
rv.chans[i] = make(chan mtglib.Event, 1)
|
||||||
|
|
||||||
|
if len(observerFactories) == 1 {
|
||||||
|
go eventStreamProcessor(ctx, rv.chans[i], observerFactories[0]())
|
||||||
|
} else {
|
||||||
|
go eventStreamProcessor(ctx, rv.chans[i], newMultiObserver(observerFactories))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventStreamProcessor(ctx context.Context, eventChan <-chan mtglib.Event, observer Observer) { // nolint: cyclop
|
||||||
|
defer observer.Shutdown()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case evt := <-eventChan:
|
||||||
|
switch typedEvt := evt.(type) {
|
||||||
|
case mtglib.EventTraffic:
|
||||||
|
observer.EventTraffic(typedEvt)
|
||||||
|
case mtglib.EventStart:
|
||||||
|
observer.EventStart(typedEvt)
|
||||||
|
case mtglib.EventFinish:
|
||||||
|
observer.EventFinish(typedEvt)
|
||||||
|
case mtglib.EventConnectedToDC:
|
||||||
|
observer.EventConnectedToDC(typedEvt)
|
||||||
|
case mtglib.EventDomainFronting:
|
||||||
|
observer.EventDomainFronting(typedEvt)
|
||||||
|
case mtglib.EventIPBlocklisted:
|
||||||
|
observer.EventIPBlocklisted(typedEvt)
|
||||||
|
case mtglib.EventConcurrencyLimited:
|
||||||
|
observer.EventConcurrencyLimited(typedEvt)
|
||||||
|
case mtglib.EventReplayAttack:
|
||||||
|
observer.EventReplayAttack(typedEvt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
package events_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/events"
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventStreamTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
ctxCancel context.CancelFunc
|
||||||
|
observerMock1 *ObserverMock
|
||||||
|
observerMock2 *ObserverMock
|
||||||
|
stream events.EventStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *EventStreamTestSuite) SetupTest() {
|
||||||
|
suite.ctx, suite.ctxCancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
suite.observerMock1 = &ObserverMock{}
|
||||||
|
suite.observerMock2 = &ObserverMock{}
|
||||||
|
|
||||||
|
suite.observerMock1.On("Shutdown")
|
||||||
|
suite.observerMock2.On("Shutdown")
|
||||||
|
|
||||||
|
factories := make([]events.ObserverFactory, 2)
|
||||||
|
factories[0] = func() events.Observer { return suite.observerMock1 }
|
||||||
|
factories[1] = func() events.Observer { return suite.observerMock2 }
|
||||||
|
|
||||||
|
suite.stream = events.NewEventStream(factories)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *EventStreamTestSuite) TestEventStart() {
|
||||||
|
evt := mtglib.NewEventStart("connID", net.ParseIP("10.0.0.1"))
|
||||||
|
|
||||||
|
for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
|
||||||
|
v.
|
||||||
|
On("EventStart", mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
caught, ok := args.Get(0).(mtglib.EventStart)
|
||||||
|
|
||||||
|
suite.True(ok)
|
||||||
|
suite.Equal(evt.RemoteIP.String(), caught.RemoteIP.String())
|
||||||
|
suite.Equal(evt.StreamID(), caught.StreamID())
|
||||||
|
suite.Equal(evt.Timestamp(), caught.Timestamp())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.stream.Send(suite.ctx, evt)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *EventStreamTestSuite) TestEventConnectedToDC() {
|
||||||
|
evt := mtglib.NewEventConnectedToDC("connID", net.ParseIP("10.0.0.1"), 3)
|
||||||
|
|
||||||
|
for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
|
||||||
|
v.
|
||||||
|
On("EventConnectedToDC", mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
caught, ok := args.Get(0).(mtglib.EventConnectedToDC)
|
||||||
|
|
||||||
|
suite.True(ok)
|
||||||
|
suite.Equal(evt.RemoteIP.String(), caught.RemoteIP.String())
|
||||||
|
suite.Equal(evt.StreamID(), caught.StreamID())
|
||||||
|
suite.Equal(evt.DC, caught.DC)
|
||||||
|
suite.Equal(evt.Timestamp(), caught.Timestamp())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.stream.Send(suite.ctx, evt)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *EventStreamTestSuite) TestEventDomainFronting() {
|
||||||
|
evt := mtglib.NewEventDomainFronting("connID")
|
||||||
|
|
||||||
|
for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
|
||||||
|
v.
|
||||||
|
On("EventDomainFronting", mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
caught, ok := args.Get(0).(mtglib.EventDomainFronting)
|
||||||
|
|
||||||
|
suite.True(ok)
|
||||||
|
suite.Equal(evt.StreamID(), caught.StreamID())
|
||||||
|
suite.Equal(evt.Timestamp(), caught.Timestamp())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.stream.Send(suite.ctx, evt)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *EventStreamTestSuite) TestEventTraffic() {
|
||||||
|
evt := mtglib.NewEventTraffic("connID", 1024, true)
|
||||||
|
|
||||||
|
for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
|
||||||
|
v.
|
||||||
|
On("EventTraffic", mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
caught, ok := args.Get(0).(mtglib.EventTraffic)
|
||||||
|
|
||||||
|
suite.True(ok)
|
||||||
|
suite.Equal(evt.StreamID(), caught.StreamID())
|
||||||
|
suite.Equal(evt.Timestamp(), caught.Timestamp())
|
||||||
|
suite.Equal(evt.Traffic, caught.Traffic)
|
||||||
|
suite.Equal(evt.IsRead, caught.IsRead)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.stream.Send(suite.ctx, evt)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *EventStreamTestSuite) TestEventFinish() {
|
||||||
|
evt := mtglib.NewEventFinish("connID")
|
||||||
|
|
||||||
|
for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
|
||||||
|
v.
|
||||||
|
On("EventFinish", mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
caught, ok := args.Get(0).(mtglib.EventFinish)
|
||||||
|
|
||||||
|
suite.True(ok)
|
||||||
|
suite.Equal(evt.StreamID(), caught.StreamID())
|
||||||
|
suite.Equal(evt.Timestamp(), caught.Timestamp())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.stream.Send(suite.ctx, evt)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *EventStreamTestSuite) TestEventConcurrencyLimited() {
|
||||||
|
evt := mtglib.NewEventConcurrencyLimited()
|
||||||
|
|
||||||
|
for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
|
||||||
|
v.
|
||||||
|
On("EventConcurrencyLimited", mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
caught, ok := args.Get(0).(mtglib.EventConcurrencyLimited)
|
||||||
|
|
||||||
|
suite.True(ok)
|
||||||
|
suite.Equal(evt.Timestamp(), caught.Timestamp())
|
||||||
|
suite.Empty(evt.StreamID())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.stream.Send(suite.ctx, evt)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *EventStreamTestSuite) TestEventIPBlocklisted() {
|
||||||
|
evt := mtglib.NewEventIPBlocklisted(net.ParseIP("10.0.0.10"))
|
||||||
|
|
||||||
|
for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
|
||||||
|
v.
|
||||||
|
On("EventIPBlocklisted", mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
caught, ok := args.Get(0).(mtglib.EventIPBlocklisted)
|
||||||
|
|
||||||
|
suite.True(ok)
|
||||||
|
suite.Equal(evt.StreamID(), caught.StreamID())
|
||||||
|
suite.Equal(evt.Timestamp(), caught.Timestamp())
|
||||||
|
suite.Equal(evt.RemoteIP.String(), caught.RemoteIP.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.stream.Send(suite.ctx, evt)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *EventStreamTestSuite) TestEventReplayAttack() {
|
||||||
|
evt := mtglib.NewEventReplayAttack("CONNID")
|
||||||
|
|
||||||
|
for _, v := range []*ObserverMock{suite.observerMock1, suite.observerMock2} {
|
||||||
|
v.
|
||||||
|
On("EventReplayAttack", mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
caught, ok := args.Get(0).(mtglib.EventReplayAttack)
|
||||||
|
|
||||||
|
suite.True(ok)
|
||||||
|
suite.Equal(evt.StreamID(), caught.StreamID())
|
||||||
|
suite.Equal(evt.Timestamp(), caught.Timestamp())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.stream.Send(suite.ctx, evt)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *EventStreamTestSuite) TearDownTest() {
|
||||||
|
suite.stream.Shutdown()
|
||||||
|
suite.ctxCancel()
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
suite.observerMock1.AssertExpectations(suite.T())
|
||||||
|
suite.observerMock2.AssertExpectations(suite.T())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventStream(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &EventStreamTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Events has a default implementations of EventStream for mtglib.
|
||||||
|
//
|
||||||
|
// Please see documentation for mtglib.EventStream interface to get an
|
||||||
|
// idea of such an abstraction. This package has implementations for the
|
||||||
|
// default event stream.
|
||||||
|
//
|
||||||
|
// Default event stream has a list of its own concepts. First, all it
|
||||||
|
// does is a routing of messages to known observers. It takes an event,
|
||||||
|
// defines its type and pass this message to a method of the observer.
|
||||||
|
//
|
||||||
|
// There might be many observers, but default event stream has a
|
||||||
|
// guarantee though. It uses StreamID as a sharding key and guarantees
|
||||||
|
// that a message with the same StreamID will be devlivered to the same
|
||||||
|
// observer instance. So, each producer is guarateed to get all relevant
|
||||||
|
// messages related to the same session. It is not possible that it will
|
||||||
|
// get EventFinish if it has not seen EventStart for that session yet.
|
||||||
|
package events
|
||||||
|
|
||||||
|
import "github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
|
||||||
|
// Observer is an instance that listens for the incoming events.
|
||||||
|
//
|
||||||
|
// As it is said in the package description, the default event stream
|
||||||
|
// guarantees that all events with the same StreamID are going to be
|
||||||
|
// routed to the same instance of the observer. So, there is no need
|
||||||
|
// to synchronize information about streams between many observers
|
||||||
|
// instances, they can have their local storage.
|
||||||
|
type Observer interface {
|
||||||
|
// EventStart reacts on incoming mtglib.EventStart event.
|
||||||
|
EventStart(mtglib.EventStart)
|
||||||
|
|
||||||
|
// EventFinish reacts on incoming mtglib.EventFinish event.
|
||||||
|
EventFinish(mtglib.EventFinish)
|
||||||
|
|
||||||
|
// EventConnectedToDC reacts on incoming mtglib.EventConnectedToDC
|
||||||
|
// event.
|
||||||
|
EventConnectedToDC(mtglib.EventConnectedToDC)
|
||||||
|
|
||||||
|
// EventDomainFronting reacts on incoming mtglib.EventDomainFronting
|
||||||
|
// event.
|
||||||
|
EventDomainFronting(mtglib.EventDomainFronting)
|
||||||
|
|
||||||
|
// EventTraffic reacts on incoming mtglib.EventTraffic event.
|
||||||
|
EventTraffic(mtglib.EventTraffic)
|
||||||
|
|
||||||
|
// EventConcurrencyLimited reacts on incoming
|
||||||
|
// mtglib.EventConcurrencyLimited event.
|
||||||
|
EventConcurrencyLimited(mtglib.EventConcurrencyLimited)
|
||||||
|
|
||||||
|
// EventIPBlocklisted reacts on incoming mtglib.EventIPBlocklisted event.
|
||||||
|
EventIPBlocklisted(mtglib.EventIPBlocklisted)
|
||||||
|
|
||||||
|
// EventReplayAttack reacts on incoming mtglib.EventReplayAttack event.
|
||||||
|
EventReplayAttack(mtglib.EventReplayAttack)
|
||||||
|
|
||||||
|
// Shutdown stop observer. Default event stream guarantees:
|
||||||
|
// 1. If shutdown is executed, it is executed only once
|
||||||
|
// 2. Observer won't receieve any new message after this
|
||||||
|
// function call.
|
||||||
|
Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObserverFactory creates a new instance of the observer.
|
||||||
|
//
|
||||||
|
// Default event stream creates a small set of goroutines to manage
|
||||||
|
// incoming messages. Each message is routed to an appropriate observer
|
||||||
|
// based on a sharding key, stream id. So, it is possible that an
|
||||||
|
// instance of mtg will have many observer instances, not a single one.
|
||||||
|
type ObserverFactory func() Observer
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package events_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ObserverMock struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObserverMock) EventStart(evt mtglib.EventStart) {
|
||||||
|
o.Called(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObserverMock) EventConnectedToDC(evt mtglib.EventConnectedToDC) {
|
||||||
|
o.Called(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObserverMock) EventDomainFronting(evt mtglib.EventDomainFronting) {
|
||||||
|
o.Called(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObserverMock) EventTraffic(evt mtglib.EventTraffic) {
|
||||||
|
o.Called(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObserverMock) EventFinish(evt mtglib.EventFinish) {
|
||||||
|
o.Called(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObserverMock) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimited) {
|
||||||
|
o.Called(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObserverMock) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
|
||||||
|
o.Called(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObserverMock) EventReplayAttack(evt mtglib.EventReplayAttack) {
|
||||||
|
o.Called(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ObserverMock) Shutdown() {
|
||||||
|
o.Called()
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package events
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
)
|
||||||
|
|
||||||
|
type multiObserver struct {
|
||||||
|
observers []Observer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m multiObserver) EventStart(evt mtglib.EventStart) {
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(len(m.observers))
|
||||||
|
|
||||||
|
for _, v := range m.observers {
|
||||||
|
go func(obs Observer) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
obs.EventStart(evt)
|
||||||
|
}(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m multiObserver) EventConnectedToDC(evt mtglib.EventConnectedToDC) {
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(len(m.observers))
|
||||||
|
|
||||||
|
for _, v := range m.observers {
|
||||||
|
go func(obs Observer) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
obs.EventConnectedToDC(evt)
|
||||||
|
}(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m multiObserver) EventDomainFronting(evt mtglib.EventDomainFronting) {
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(len(m.observers))
|
||||||
|
|
||||||
|
for _, v := range m.observers {
|
||||||
|
go func(obs Observer) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
obs.EventDomainFronting(evt)
|
||||||
|
}(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m multiObserver) EventTraffic(evt mtglib.EventTraffic) {
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(len(m.observers))
|
||||||
|
|
||||||
|
for _, v := range m.observers {
|
||||||
|
go func(obs Observer) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
obs.EventTraffic(evt)
|
||||||
|
}(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m multiObserver) EventFinish(evt mtglib.EventFinish) {
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(len(m.observers))
|
||||||
|
|
||||||
|
for _, v := range m.observers {
|
||||||
|
go func(obs Observer) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
obs.EventFinish(evt)
|
||||||
|
}(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m multiObserver) EventConcurrencyLimited(evt mtglib.EventConcurrencyLimited) {
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(len(m.observers))
|
||||||
|
|
||||||
|
for _, v := range m.observers {
|
||||||
|
go func(obs Observer) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
obs.EventConcurrencyLimited(evt)
|
||||||
|
}(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m multiObserver) EventIPBlocklisted(evt mtglib.EventIPBlocklisted) {
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(len(m.observers))
|
||||||
|
|
||||||
|
for _, v := range m.observers {
|
||||||
|
go func(obs Observer) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
obs.EventIPBlocklisted(evt)
|
||||||
|
}(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m multiObserver) EventReplayAttack(evt mtglib.EventReplayAttack) {
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(len(m.observers))
|
||||||
|
|
||||||
|
for _, v := range m.observers {
|
||||||
|
go func(obs Observer) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
obs.EventReplayAttack(evt)
|
||||||
|
}(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m multiObserver) Shutdown() {
|
||||||
|
for _, v := range m.observers {
|
||||||
|
v.Shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMultiObserver(factories []ObserverFactory) Observer {
|
||||||
|
observers := make([]Observer, len(factories))
|
||||||
|
|
||||||
|
for i, v := range factories {
|
||||||
|
observers[i] = v()
|
||||||
|
}
|
||||||
|
|
||||||
|
return multiObserver{
|
||||||
|
observers: observers,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package events
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
)
|
||||||
|
|
||||||
|
type noop struct{}
|
||||||
|
|
||||||
|
func (n noop) Send(ctx context.Context, evt mtglib.Event) {}
|
||||||
|
|
||||||
|
// NewNoopStream creates a stream which discards each message.
|
||||||
|
func NewNoopStream() mtglib.EventStream {
|
||||||
|
return noop{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type noopObserver struct{}
|
||||||
|
|
||||||
|
func (n noopObserver) EventStart(_ mtglib.EventStart) {}
|
||||||
|
func (n noopObserver) EventConnectedToDC(_ mtglib.EventConnectedToDC) {}
|
||||||
|
func (n noopObserver) EventDomainFronting(_ mtglib.EventDomainFronting) {}
|
||||||
|
func (n noopObserver) EventTraffic(_ mtglib.EventTraffic) {}
|
||||||
|
func (n noopObserver) EventFinish(_ mtglib.EventFinish) {}
|
||||||
|
func (n noopObserver) EventConcurrencyLimited(_ mtglib.EventConcurrencyLimited) {}
|
||||||
|
func (n noopObserver) EventIPBlocklisted(_ mtglib.EventIPBlocklisted) {}
|
||||||
|
func (n noopObserver) EventReplayAttack(_ mtglib.EventReplayAttack) {}
|
||||||
|
func (n noopObserver) Shutdown() {}
|
||||||
|
|
||||||
|
// NewNoopObserver creates an observer which discards each message.
|
||||||
|
func NewNoopObserver() Observer {
|
||||||
|
return noopObserver{}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package events_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/events"
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NoopTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
testData map[string]mtglib.Event
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *NoopTestSuite) SetupSuite() {
|
||||||
|
suite.testData = map[string]mtglib.Event{
|
||||||
|
"start": mtglib.NewEventStart("connID", net.ParseIP("127.0.0.1")),
|
||||||
|
"connected-to-dc": mtglib.NewEventConnectedToDC("connID", net.ParseIP("127.1.0.1"), 2),
|
||||||
|
"domain-fronting": mtglib.NewEventDomainFronting("connID"),
|
||||||
|
"traffic": mtglib.NewEventTraffic("connID", 1000, true),
|
||||||
|
"finish": mtglib.NewEventFinish("connID"),
|
||||||
|
"concurrency-limited": mtglib.NewEventConcurrencyLimited(),
|
||||||
|
"ip-blacklisted": mtglib.NewEventIPBlocklisted(net.ParseIP("10.0.0.10")),
|
||||||
|
"replay-attack": mtglib.NewEventReplayAttack("connID"),
|
||||||
|
}
|
||||||
|
suite.ctx = context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *NoopTestSuite) TestStream() {
|
||||||
|
stream := events.NewNoopStream()
|
||||||
|
|
||||||
|
for name, v := range suite.testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
suite.T().Run(name, func(t *testing.T) {
|
||||||
|
stream.Send(suite.ctx, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *NoopTestSuite) TestObserver() {
|
||||||
|
observer := events.NewNoopObserver()
|
||||||
|
|
||||||
|
for name, v := range suite.testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
suite.T().Run(name, func(t *testing.T) {
|
||||||
|
switch typedEvt := value.(type) {
|
||||||
|
case mtglib.EventStart:
|
||||||
|
observer.EventStart(typedEvt)
|
||||||
|
case mtglib.EventConnectedToDC:
|
||||||
|
observer.EventConnectedToDC(typedEvt)
|
||||||
|
case mtglib.EventDomainFronting:
|
||||||
|
observer.EventDomainFronting(typedEvt)
|
||||||
|
case mtglib.EventFinish:
|
||||||
|
observer.EventFinish(typedEvt)
|
||||||
|
case mtglib.EventConcurrencyLimited:
|
||||||
|
observer.EventConcurrencyLimited(typedEvt)
|
||||||
|
case mtglib.EventIPBlocklisted:
|
||||||
|
observer.EventIPBlocklisted(typedEvt)
|
||||||
|
case mtglib.EventReplayAttack:
|
||||||
|
observer.EventReplayAttack(typedEvt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
observer.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoop(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &NoopTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
# This is an example of the configuration file for mtg. You actually can
|
||||||
|
# run mtg with it. It starts a proxy on all interfaces with a secret
|
||||||
|
# ee367a189aee18fa31c190054efd4a8e9573746f726167652e676f6f676c65617069732e636f6d
|
||||||
|
#
|
||||||
|
# It has all possible options with default values. So, a real world
|
||||||
|
# configuration file should contain only those options you are going to
|
||||||
|
# use. You do not need to enumerate all of them. In other words, each
|
||||||
|
# option here has a default value. If you comment a key-value pair, it
|
||||||
|
# should not make any effect.
|
||||||
|
#
|
||||||
|
# stats is the only exception.
|
||||||
|
|
||||||
|
# Debug starts application in debug mode. It starts to be quite verbose
|
||||||
|
# in output. Actually, the idea is that you run it in debug mode only if
|
||||||
|
# you have any issue.
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
# A secret. Please remember that mtg supports only FakeTLS mode, legacy
|
||||||
|
# simple and secured mode are prohibited. For you it means that secret
|
||||||
|
# should either be base64-encoded or starts with ee.
|
||||||
|
secret = "ee367a189aee18fa31c190054efd4a8e9573746f726167652e676f6f676c65617069732e636f6d"
|
||||||
|
|
||||||
|
# Host:port pair to run proxy on.
|
||||||
|
bind-to = "0.0.0.0:3128"
|
||||||
|
|
||||||
|
# Defines how many concurrent connections are allowed to this proxy.
|
||||||
|
# All other incoming connections are going to be dropped.
|
||||||
|
concurrency = 8192
|
||||||
|
|
||||||
|
# A size of user-space buffer for TCP to use. Since we do 2 connections,
|
||||||
|
# then we have tcp-buffer * (4 + 2) per each connection: read/write for
|
||||||
|
# each connection + 2 copy buffers to pump the data between sockets.
|
||||||
|
tcp-buffer = "4kb"
|
||||||
|
|
||||||
|
# Sometimes you want to enforce mtg to use some types of
|
||||||
|
# IP connectivity to Telegram. We have 4 modes:
|
||||||
|
# - prefer-ipv6:
|
||||||
|
# We can use both ipv4 and ipv6 but ipv6 has a preference
|
||||||
|
# - prefer-ipv4:
|
||||||
|
# We can use both ipv4 and ipv6 but ipv4 has a preference
|
||||||
|
# - only-ipv6:
|
||||||
|
# Only ipv6 connectivity is used
|
||||||
|
# - only-ipv4:
|
||||||
|
# Only ipv4 connectivity is used
|
||||||
|
prefer-ip = "prefer-ipv6"
|
||||||
|
|
||||||
|
# FakeTLS uses domain fronting protection. So it needs to know a port to
|
||||||
|
# access.
|
||||||
|
domain-fronting-port = 443
|
||||||
|
|
||||||
|
# FakeTLS can compare timestamps to prevent probes. Each message has
|
||||||
|
# encrypted timestamp. So, mtg can compare this timestamp and decide if
|
||||||
|
# we need to proceed with connection or not.
|
||||||
|
#
|
||||||
|
# Sometimes time can be skewed so we accept all messages within a
|
||||||
|
# time range of this parameter.
|
||||||
|
tolerate-time-skewness = "5s"
|
||||||
|
|
||||||
|
# network defines different network-related settings
|
||||||
|
[network]
|
||||||
|
# please be aware that mtg needs to do some external requests. For
|
||||||
|
# example, if you do not pass public ips, it will request your public ip
|
||||||
|
# address from some external service.
|
||||||
|
#
|
||||||
|
# As for 2.0, if you set a public-ip on your own, mtg won't issue any
|
||||||
|
# network requests except of those required for Telegram.
|
||||||
|
#
|
||||||
|
# so, in order of doing them, it needs to do DNS lookup. mtg ignores DNS
|
||||||
|
# resolver of the operating system and uses DOH instead. This is a host
|
||||||
|
# it has to access.
|
||||||
|
#
|
||||||
|
# By default we use Quad9.
|
||||||
|
doh-ip = "9.9.9.9"
|
||||||
|
|
||||||
|
# mtg can work via proxies (for now, we support only socks5). Proxy
|
||||||
|
# configuration is done via list. So, you can specify many proxies
|
||||||
|
# there.
|
||||||
|
#
|
||||||
|
# Actually, if you supply an empty list, then no proxies are going to be
|
||||||
|
# used. If you supply a single proxy, then mtg will use it exclusively.
|
||||||
|
# If you supply >= 2, then mtg will load balance between them.
|
||||||
|
#
|
||||||
|
# If you add an empty string here, this is an equivalent of 'plain network',
|
||||||
|
# with no proxy usage.
|
||||||
|
#
|
||||||
|
# Proxy configuration is done via ordinary URI schema:
|
||||||
|
#
|
||||||
|
# socks5://user:password@host:port?open_threshold=5&half_open_timeout=1m&reset_failures_timeout=10s
|
||||||
|
#
|
||||||
|
# Only socks5 proxy is used. user/password is optional. As you can
|
||||||
|
# see, you can specify some parameters in GET query. These parameters
|
||||||
|
# configure circuit breaker.
|
||||||
|
#
|
||||||
|
# open_threshold means a number of errors which should happen so we stop
|
||||||
|
# use a proxy.
|
||||||
|
#
|
||||||
|
# half_open_timeout means a time period (in Golang duration notation)
|
||||||
|
# after which we can retry with this proxy
|
||||||
|
#
|
||||||
|
# reset_failures_timeout means a time period when we flush out errors
|
||||||
|
# when circuit breaker in closed state.
|
||||||
|
#
|
||||||
|
# Please see https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker
|
||||||
|
# on details about circuit breakers.
|
||||||
|
proxies = [
|
||||||
|
# "socks5://user:password@host:port?open_threshold=5&half_open_timeout=1m&reset_failures_timeout=10s"
|
||||||
|
]
|
||||||
|
|
||||||
|
# network timeouts define different settings for timeouts. tcp timeout
|
||||||
|
# define a global timeout on establishing of network connections. idle
|
||||||
|
# means a timeout on pumping data between sockset when nothing is
|
||||||
|
# happening.
|
||||||
|
#
|
||||||
|
# please be noticed that handshakes have no timeouts intentionally. You can
|
||||||
|
# find a reasoning here:
|
||||||
|
# https://www.ndss-symposium.org/wp-content/uploads/2020/02/23087-paper.pdf
|
||||||
|
[network.timeout]
|
||||||
|
tcp = "5s"
|
||||||
|
http = "10s"
|
||||||
|
idle = "1m"
|
||||||
|
|
||||||
|
# Some countries do active probing on Telegram connections. This technique
|
||||||
|
# allows to protect from such effort.
|
||||||
|
#
|
||||||
|
# mtg has a cache of some connection fingerprints. Actually, first bytes
|
||||||
|
# of each connection. So, it stores them in some in-memory LRU+TTL cache.
|
||||||
|
# You can configure this cache here.
|
||||||
|
[defense.anti-replay]
|
||||||
|
# You can enable/disable this feature.
|
||||||
|
enabled = true
|
||||||
|
# max size of such a cache. Please be aware that this number is
|
||||||
|
# approximate we try hard to store data quite dense but it is possible
|
||||||
|
# that we can go over this limit for 10-20% under some conditions and
|
||||||
|
# architectures.
|
||||||
|
max-size = "1mib"
|
||||||
|
# we use stable bloom filters for anti-replay cache. This helps
|
||||||
|
# to maintain a desired error ratio.
|
||||||
|
error-rate = 0.001
|
||||||
|
|
||||||
|
# You can protect proxies by using different blocklists. If client has
|
||||||
|
# ip from the given range, we do not try to do a proper handshake. We
|
||||||
|
# actually route it to fronting domain. So, this client will never ever
|
||||||
|
# have a chance to use mtg to access Telegram.
|
||||||
|
#
|
||||||
|
# Please remember that blocklists are initialized in async way. So,
|
||||||
|
# when you start a proxy, blocklists are empty, they are populated and
|
||||||
|
# processed in backgrounds. An error in any URL is ignored.
|
||||||
|
[defense.blocklist]
|
||||||
|
# You can enable/disable this feature.
|
||||||
|
enabled = true
|
||||||
|
# This is a limiter for concurrency. In order to protect website
|
||||||
|
# from overloading, we download files in this number of threads.
|
||||||
|
download-concurrency = 2
|
||||||
|
# A list of URLs in FireHOL format (https://iplists.firehol.org/)
|
||||||
|
# You can provider links here (starts with https:// or http://) or
|
||||||
|
# path to a local file, but in this case it should be absolute.
|
||||||
|
urls = [
|
||||||
|
# "https://iplists.firehol.org/files/firehol_level1.netset",
|
||||||
|
# "/local.file"
|
||||||
|
]
|
||||||
|
# How often do we need to update a blocklist set.
|
||||||
|
update-each = "24h"
|
||||||
|
|
||||||
|
# statsd statistics integration.
|
||||||
|
[stats.statsd]
|
||||||
|
# enabled/disabled
|
||||||
|
enabled = false
|
||||||
|
# host:port for UDP endpoint of statsd
|
||||||
|
address = "127.0.0.1:8888"
|
||||||
|
# prefix of metric for statsd
|
||||||
|
metric-prefix = "mtg"
|
||||||
|
# tag format to use
|
||||||
|
# supported values are 'datadog', 'influxdb' and 'graphite'
|
||||||
|
# default format is graphite.
|
||||||
|
tag-format = "datadog"
|
||||||
|
|
||||||
|
# prometheus metrics integration.
|
||||||
|
[stats.prometheus]
|
||||||
|
# enabled/disabled
|
||||||
|
enabled = true
|
||||||
|
# host:port where to start http server for endpoint
|
||||||
|
bind-to = "127.0.0.1:3129"
|
||||||
|
# prefix of http path
|
||||||
|
http-path = "/"
|
||||||
|
# prefix for metrics for prometheus
|
||||||
|
metric-prefix = "mtg"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
module github.com/9seconds/mtg/v2
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/OneOfOne/xxhash v1.2.8
|
||||||
|
github.com/alecthomas/kong v0.2.16
|
||||||
|
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||||
|
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6
|
||||||
|
github.com/d4l3k/messagediff v1.2.1 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/gotd/td v0.34.0
|
||||||
|
github.com/jarcoal/httpmock v1.0.8
|
||||||
|
github.com/kentik/patricia v0.0.0-20201202224819-f9447a6e25f1
|
||||||
|
github.com/libp2p/go-reuseport v0.0.2
|
||||||
|
github.com/mccutchen/go-httpbin v1.1.1
|
||||||
|
github.com/panjf2000/ants/v2 v2.4.4
|
||||||
|
github.com/pelletier/go-toml v1.9.0
|
||||||
|
github.com/prometheus/client_golang v1.10.0
|
||||||
|
github.com/prometheus/common v0.23.0 // indirect
|
||||||
|
github.com/rs/zerolog v1.21.0
|
||||||
|
github.com/smira/go-statsd v1.3.2
|
||||||
|
github.com/stretchr/objx v0.3.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.7.0
|
||||||
|
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0
|
||||||
|
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e
|
||||||
|
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125
|
||||||
|
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6
|
||||||
|
)
|
||||||
@@ -0,0 +1,536 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
|
||||||
|
github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
|
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||||
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
|
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||||
|
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||||
|
github.com/alecthomas/kong v0.2.16 h1:F232CiYSn54Tnl1sJGTeHmx4vJDNLVP2b9yCVMOQwHQ=
|
||||||
|
github.com/alecthomas/kong v0.2.16/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
|
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4=
|
||||||
|
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||||
|
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
|
||||||
|
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
|
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
|
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||||
|
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||||
|
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||||
|
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
|
||||||
|
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
|
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||||
|
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||||
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||||
|
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||||
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
|
github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
|
||||||
|
github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
|
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||||
|
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
|
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
github.com/gotd/getdoc v0.6.2/go.mod h1:6Ul8cyaq+Aw0gVyuU+TKZPCmNjdlylzSIWVxjzI5t/U=
|
||||||
|
github.com/gotd/ige v0.1.5 h1:qITql4hZpqPM/DSeO5IVlxzXJxrs4ZZiKRwPif2K76U=
|
||||||
|
github.com/gotd/ige v0.1.5/go.mod h1:LbvqjUuGELVuHcKjfJZ2NPNzegICQU4eqrPAkXy0nTs=
|
||||||
|
github.com/gotd/neo v0.1.2 h1:hayFfKNSH5RP/L+KKKnsKMdDNCdhSfzVuo5TF4wvpa8=
|
||||||
|
github.com/gotd/neo v0.1.2/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ=
|
||||||
|
github.com/gotd/td v0.34.0 h1:BLIdpv2sxbCuWI2fyXCECRQuLJdnYk3VMZDRKNog/E8=
|
||||||
|
github.com/gotd/td v0.34.0/go.mod h1:4s/7cuEscdvZIecM/pVi5L3nTK+djSoVLAhgfk40OE4=
|
||||||
|
github.com/gotd/tl v0.4.0/go.mod h1:CMIcjPWFS4qxxJ+1Ce7U/ilbtPrkoVo/t8uhN5Y/D7c=
|
||||||
|
github.com/gotd/xor v0.1.0/go.mod h1:ZTmdgqf6SOHder8/MFp9CNkXIadzID5lIiaZxRZICH0=
|
||||||
|
github.com/gotd/xor v0.1.1 h1:LSPEeuf7noTo4fi4PrEsAaWXOSwjsY2e+IINPiR+c7s=
|
||||||
|
github.com/gotd/xor v0.1.1/go.mod h1:ZTmdgqf6SOHder8/MFp9CNkXIadzID5lIiaZxRZICH0=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
||||||
|
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||||
|
github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k=
|
||||||
|
github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
|
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||||
|
github.com/k0kubun/pp v2.4.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
|
||||||
|
github.com/kentik/patricia v0.0.0-20201202224819-f9447a6e25f1 h1:D7qhJP3R49ZjUzpzKQ6B2H3lgejPs6DTO5gRomhhOpE=
|
||||||
|
github.com/kentik/patricia v0.0.0-20201202224819-f9447a6e25f1/go.mod h1:2OfLA+0esiUJpwMjrH39pEk79cb8MvGTBS9YlZpejJ4=
|
||||||
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/libp2p/go-reuseport v0.0.2 h1:XSG94b1FJfGA01BUrT82imejHQyTxO4jEWqheyCXYvU=
|
||||||
|
github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ=
|
||||||
|
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||||
|
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||||
|
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||||
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/mccutchen/go-httpbin v1.1.1 h1:aEws49HEJEyXHLDnshQVswfUlCVoS8g6h9YaDyaW7RE=
|
||||||
|
github.com/mccutchen/go-httpbin v1.1.1/go.mod h1:fhpOYavp5g2K74XDl/ao2y4KvhqVtKlkg1e+0UaQv7I=
|
||||||
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||||
|
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||||
|
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||||
|
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||||
|
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
|
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
|
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||||
|
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
|
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||||
|
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||||
|
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
|
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||||
|
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||||
|
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||||
|
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||||
|
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||||
|
github.com/panjf2000/ants/v2 v2.4.4 h1:kebk2KSiXHGeiYS6b+w2RqNN5+IKoqlBNd7cuC7MvQI=
|
||||||
|
github.com/panjf2000/ants/v2 v2.4.4/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
|
||||||
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||||
|
github.com/pelletier/go-toml v1.9.0 h1:NOd0BRdOKpPf0SxkL3HxSQOG7rNh+4kl6PHcBPFs7Q0=
|
||||||
|
github.com/pelletier/go-toml v1.9.0/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||||
|
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||||
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||||
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
|
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||||
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
|
github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg=
|
||||||
|
github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||||
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||||
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
|
github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
|
||||||
|
github.com/prometheus/common v0.23.0 h1:GXWvPYuTUenIa+BhOq/x+L/QZzCqASkVRny5KTlPDGM=
|
||||||
|
github.com/prometheus/common v0.23.0/go.mod h1:H6QK/N6XVT42whUeIdI3dp36w49c+/iMDk7UAI2qm7Q=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||||
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
|
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||||
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
|
github.com/quasilyte/go-ruleguard/dsl v0.3.2/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
|
||||||
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
|
github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM=
|
||||||
|
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
|
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||||
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
|
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
|
||||||
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/smira/go-statsd v1.3.2 h1:1EeuzxNZ/TD9apbTOFSM9nulqfcsQFmT4u1A2DREabI=
|
||||||
|
github.com/smira/go-statsd v1.3.2/go.mod h1:1srXJ9/pbnN04G8f4F1jUzsGOnwkPKXciyqpewGlkC4=
|
||||||
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
|
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||||
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
|
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||||
|
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||||
|
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
|
||||||
|
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
github.com/stretchr/testify v1.1.5-0.20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
|
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43 h1:QEePdg0ty2r0t1+qwfZmQ4OOl/MB2UXIeJSpIZv56lg=
|
||||||
|
github.com/tylertreat/BoomFilters v0.0.0-20210315201527-1a82519a3e43/go.mod h1:OYRfF6eb5wY9VRFkXJH8FFBi3plw2v+giaIu7P054pM=
|
||||||
|
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||||
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||||
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
|
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||||
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
|
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
|
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
|
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||||
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||||
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
|
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||||
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
|
go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
|
||||||
|
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||||
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||||
|
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
|
||||||
|
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI=
|
||||||
|
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 h1:Ugb8sMTWuWRC3+sz5WeN/4kejDx9BvIwnPUiJBjJE+8=
|
||||||
|
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
|
||||||
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6 h1:cdsMqa2nXzqlgs183pHxtvoVwU7CyzaCTAUOg94af4c=
|
||||||
|
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||||
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||||
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
|
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type accessResponse struct {
|
||||||
|
IPv4 *accessResponseURLs `json:"ipv4,omitempty"`
|
||||||
|
IPv6 *accessResponseURLs `json:"ipv6,omitempty"`
|
||||||
|
Secret struct {
|
||||||
|
Hex string `json:"hex"`
|
||||||
|
Base64 string `json:"base64"`
|
||||||
|
} `json:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type accessResponseURLs struct {
|
||||||
|
IP net.IP `json:"ip"`
|
||||||
|
Port uint `json:"port"`
|
||||||
|
TgURL string `json:"tg_url"`
|
||||||
|
TgQrCode string `json:"tg_qrcode"`
|
||||||
|
TmeURL string `json:"tme_url"`
|
||||||
|
TmeQrCode string `json:"tme_qrcode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Access struct {
|
||||||
|
base
|
||||||
|
|
||||||
|
PublicIPv4 net.IP `kong:"help='Public IPv4 address for proxy. By default it is resolved via remote website',name='ipv4',short='i'"` // nolint: lll
|
||||||
|
PublicIPv6 net.IP `kong:"help='Public IPv6 address for proxy. By default it is resolved via remote website',name='ipv6',short='I'"` // nolint: lll
|
||||||
|
Port uint `kong:"help='Port number. Default port is taken from configuration file, bind-to parameter',type:'uint',short='p'"` // nolint: lll
|
||||||
|
Hex bool `kong:"help='Print secret in hex encoding.',short='x'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Access) Run(cli *CLI, version string) error {
|
||||||
|
if err := c.ReadConfig(version); err != nil {
|
||||||
|
return fmt.Errorf("cannot init config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Execute(cli)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Access) Execute(cli *CLI) error {
|
||||||
|
resp := &accessResponse{}
|
||||||
|
resp.Secret.Base64 = c.Config.Secret.Base64()
|
||||||
|
resp.Secret.Hex = c.Config.Secret.Hex()
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(2) // nolint: gomnd
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
ip := cli.Access.PublicIPv4
|
||||||
|
if ip == nil {
|
||||||
|
ip = c.getIP("tcp4")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip != nil {
|
||||||
|
ip = ip.To4()
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.IPv4 = c.makeURLs(ip, cli)
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
ip := cli.Access.PublicIPv6
|
||||||
|
if ip == nil {
|
||||||
|
ip = c.getIP("tcp6")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip != nil {
|
||||||
|
ip = ip.To16()
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.IPv6 = c.makeURLs(ip, cli)
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(os.Stdout)
|
||||||
|
encoder.SetEscapeHTML(false)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
|
||||||
|
if err := encoder.Encode(resp); err != nil {
|
||||||
|
return fmt.Errorf("cannot dump access json: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Access) getIP(protocol string) net.IP {
|
||||||
|
client := c.Network.MakeHTTPClient(func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
return c.Network.DialContext(ctx, protocol, address) // nolint: wrapcheck
|
||||||
|
})
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "https://ifconfig.co", nil) // nolint: noctx
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Accept", "text/plain")
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
io.Copy(io.Discard, resp.Body) // nolint: errcheck
|
||||||
|
resp.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.ParseIP(strings.TrimSpace(string(data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Access) makeURLs(ip net.IP, cli *CLI) *accessResponseURLs {
|
||||||
|
if ip == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
portNo := cli.Access.Port
|
||||||
|
if portNo == 0 {
|
||||||
|
portNo = c.Config.BindTo.PortValue(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
values := url.Values{}
|
||||||
|
values.Set("server", ip.String())
|
||||||
|
values.Set("port", strconv.Itoa(int(portNo)))
|
||||||
|
|
||||||
|
if cli.Access.Hex {
|
||||||
|
values.Set("secret", c.Config.Secret.Hex())
|
||||||
|
} else {
|
||||||
|
values.Set("secret", c.Config.Secret.Base64())
|
||||||
|
}
|
||||||
|
|
||||||
|
urlQuery := values.Encode()
|
||||||
|
|
||||||
|
rv := &accessResponseURLs{
|
||||||
|
IP: ip,
|
||||||
|
Port: portNo,
|
||||||
|
TgURL: (&url.URL{
|
||||||
|
Scheme: "tg",
|
||||||
|
Host: "proxy",
|
||||||
|
RawQuery: urlQuery,
|
||||||
|
}).String(),
|
||||||
|
TmeURL: (&url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "t.me",
|
||||||
|
Path: "proxy",
|
||||||
|
RawQuery: urlQuery,
|
||||||
|
}).String(),
|
||||||
|
}
|
||||||
|
rv.TgQrCode = c.makeQRCode(rv.TgURL)
|
||||||
|
rv.TmeQrCode = c.makeQRCode(rv.TmeURL)
|
||||||
|
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Access) makeQRCode(data string) string {
|
||||||
|
values := url.Values{}
|
||||||
|
values.Set("qzone", "4")
|
||||||
|
values.Set("format", "svg")
|
||||||
|
values.Set("data", data)
|
||||||
|
|
||||||
|
return (&url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "api.qrserver.com",
|
||||||
|
Path: "v1/create-qr-code",
|
||||||
|
RawQuery: values.Encode(),
|
||||||
|
}).String()
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/9seconds/mtg/v2/internal/testlib"
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/jarcoal/httpmock"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"github.com/xeipuuv/gojsonschema"
|
||||||
|
)
|
||||||
|
|
||||||
|
var accressResponseJSONSchema = func() *gojsonschema.Schema {
|
||||||
|
schema, err := gojsonschema.NewSchema(gojsonschema.NewStringLoader(`
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["secret"],
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"secret": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"hex",
|
||||||
|
"base64"
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"hex": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 34
|
||||||
|
},
|
||||||
|
"base64": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ipv4": {
|
||||||
|
"$ref": "#/definitions/ip"
|
||||||
|
},
|
||||||
|
"ipv6": {
|
||||||
|
"$ref": "#/definitions/ip"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"ip": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"ip",
|
||||||
|
"port",
|
||||||
|
"tg_url",
|
||||||
|
"tg_qrcode",
|
||||||
|
"tme_url",
|
||||||
|
"tme_qrcode"
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"ip": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"format": "ipv4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "ipv6"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"type": "integer",
|
||||||
|
"multipleOf": 1.0,
|
||||||
|
"exclusiveMinimum": 0,
|
||||||
|
"exclusiveMaximum": 65536
|
||||||
|
},
|
||||||
|
"tg_url": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
|
"tg_qrcode": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
|
"tme_url": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
|
"tme_qrcode": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"format": "uri"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}()
|
||||||
|
|
||||||
|
type AccessTestSuite struct {
|
||||||
|
CommonTestSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccessTestSuite) SetupTest() {
|
||||||
|
suite.CommonTestSuite.SetupTest()
|
||||||
|
|
||||||
|
suite.cli.Access.Config = &config.Config{}
|
||||||
|
suite.cli.Access.Config.Secret = mtglib.GenerateSecret("google.com")
|
||||||
|
suite.cli.Access.Network = suite.networkMock
|
||||||
|
|
||||||
|
suite.NoError(
|
||||||
|
suite.cli.Access.Config.BindTo.UnmarshalText([]byte("0.0.0.0:80")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccessTestSuite) TestGenerateNoCalls() {
|
||||||
|
suite.cli.Access.PublicIPv4 = net.ParseIP("10.0.0.10")
|
||||||
|
suite.cli.Access.PublicIPv6 = net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
|
||||||
|
|
||||||
|
output := testlib.CaptureStdout(func() {
|
||||||
|
suite.NoError(suite.cli.Access.Execute(suite.cli))
|
||||||
|
})
|
||||||
|
|
||||||
|
validated, err := accressResponseJSONSchema.Validate(
|
||||||
|
gojsonschema.NewStringLoader(output))
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Empty(validated.Errors())
|
||||||
|
suite.True(validated.Valid())
|
||||||
|
|
||||||
|
suite.Contains(output, "10.0.0.10")
|
||||||
|
suite.Contains(output, "2001:db8:85a3::8a2e:370:7334")
|
||||||
|
suite.Contains(output, "ipv4")
|
||||||
|
suite.Contains(output, "ipv6")
|
||||||
|
suite.Contains(output, suite.cli.Access.Config.Secret.Base64())
|
||||||
|
suite.Contains(output, suite.cli.Access.Config.Secret.Hex())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccessTestSuite) TestGenerateIPv4Call() {
|
||||||
|
suite.cli.Access.PublicIPv6 = net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
|
||||||
|
|
||||||
|
httpmock.RegisterResponder(http.MethodGet, "https://ifconfig.co",
|
||||||
|
httpmock.NewStringResponder(http.StatusOK, "10.11.12.13"))
|
||||||
|
|
||||||
|
output := testlib.CaptureStdout(func() {
|
||||||
|
suite.NoError(suite.cli.Access.Execute(suite.cli))
|
||||||
|
})
|
||||||
|
|
||||||
|
validated, err := accressResponseJSONSchema.Validate(
|
||||||
|
gojsonschema.NewStringLoader(output))
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Empty(validated.Errors())
|
||||||
|
suite.True(validated.Valid())
|
||||||
|
|
||||||
|
suite.Contains(output, "10.11.12.13")
|
||||||
|
suite.Contains(output, "2001:db8:85a3::8a2e:370:7334")
|
||||||
|
suite.Contains(output, "ipv4")
|
||||||
|
suite.Contains(output, "ipv6")
|
||||||
|
suite.Contains(output, suite.cli.Access.Config.Secret.Base64())
|
||||||
|
suite.Contains(output, suite.cli.Access.Config.Secret.Hex())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AccessTestSuite) TestIPv4CallFail() {
|
||||||
|
suite.cli.Access.PublicIPv6 = net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
|
||||||
|
|
||||||
|
httpmock.RegisterResponder(http.MethodGet, "https://ifconfig.co",
|
||||||
|
httpmock.NewStringResponder(http.StatusForbidden, ""))
|
||||||
|
|
||||||
|
output := testlib.CaptureStdout(func() {
|
||||||
|
suite.NoError(suite.cli.Access.Execute(suite.cli))
|
||||||
|
})
|
||||||
|
|
||||||
|
validated, err := accressResponseJSONSchema.Validate(
|
||||||
|
gojsonschema.NewStringLoader(output))
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Empty(validated.Errors())
|
||||||
|
suite.True(validated.Valid())
|
||||||
|
|
||||||
|
suite.Contains(output, "2001:db8:85a3::8a2e:370:7334")
|
||||||
|
suite.NotContains(output, "ipv4")
|
||||||
|
suite.Contains(output, "ipv6")
|
||||||
|
suite.Contains(output, suite.cli.Access.Config.Secret.Base64())
|
||||||
|
suite.Contains(output, suite.cli.Access.Config.Secret.Hex())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccess(t *testing.T) { // nolint: paralleltest
|
||||||
|
suite.Run(t, &AccessTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/9seconds/mtg/v2/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type base struct {
|
||||||
|
ConfigPath string `kong:"arg,required,type='existingfile',help='Path to the configuration file.',name='config-path'"` // nolint: lll
|
||||||
|
|
||||||
|
Network mtglib.Network `kong:"-"`
|
||||||
|
Config *config.Config `kong:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *base) ReadConfig(version string) error {
|
||||||
|
content, err := os.ReadFile(b.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot read config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conf, err := config.Parse(content)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot parse config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ntw, err := b.makeNetwork(conf, version)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot build a network: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Config = conf
|
||||||
|
b.Network = ntw
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *base) makeNetwork(conf *config.Config, version string) (mtglib.Network, error) {
|
||||||
|
tcpTimeout := conf.Network.Timeout.TCP.Value(network.DefaultTimeout)
|
||||||
|
httpTimeout := conf.Network.Timeout.HTTP.Value(network.DefaultHTTPTimeout)
|
||||||
|
dohIP := conf.Network.DOHIP.Value(net.ParseIP(network.DefaultDOHHostname)).String()
|
||||||
|
bufferSize := conf.TCPBuffer.Value(network.DefaultBufferSize)
|
||||||
|
userAgent := "mtg/" + version
|
||||||
|
|
||||||
|
baseDialer, err := network.NewDefaultDialer(tcpTimeout, int(bufferSize))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot build a default dialer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyURLs := make([]*url.URL, 0, len(conf.Network.Proxies))
|
||||||
|
|
||||||
|
for _, v := range conf.Network.Proxies {
|
||||||
|
if value := v.Value(nil); value != nil {
|
||||||
|
proxyURLs = append(proxyURLs, v.Value(nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(proxyURLs) {
|
||||||
|
case 0:
|
||||||
|
return network.NewNetwork(baseDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
|
||||||
|
case 1:
|
||||||
|
socksDialer, err := network.NewSocks5Dialer(baseDialer, proxyURLs[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
socksDialer, err := network.NewLoadBalancedSocks5Dialer(baseDialer, proxyURLs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return network.NewNetwork(socksDialer, userAgent, dohIP, httpTimeout) // nolint: wrapcheck
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaseTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
b base
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BaseTestSuite) SetupTest() {
|
||||||
|
suite.b = base{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BaseTestSuite) TestReadConfigNok() {
|
||||||
|
suite.b.ConfigPath = filepath.Join("testdata", "unknown")
|
||||||
|
suite.Error(suite.b.ReadConfig("dev"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BaseTestSuite) TestReadConfig() {
|
||||||
|
suite.b.ConfigPath = filepath.Join("testdata", "minimal.toml")
|
||||||
|
suite.NoError(suite.b.ReadConfig("dev"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBase(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &BaseTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import "github.com/alecthomas/kong"
|
||||||
|
|
||||||
|
type CLI struct {
|
||||||
|
GenerateSecret GenerateSecret `kong:"cmd,help='Generate new proxy secret'"`
|
||||||
|
Access Access `kong:"cmd,help='Print access information.'"`
|
||||||
|
Run Proxy `kong:"cmd,help='Run proxy.'"`
|
||||||
|
Version kong.VersionFlag `kong:"help='Print version.',short='v'"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GenerateSecret struct {
|
||||||
|
HostName string `kong:"arg,required,help='Hostname to use for domain fronting.',name='hostname'"`
|
||||||
|
Hex bool `kong:"help='Print secret in hex encoding.',short='x'"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GenerateSecret) Run(cli *CLI, _ string) error {
|
||||||
|
secret := mtglib.GenerateSecret(cli.GenerateSecret.HostName)
|
||||||
|
|
||||||
|
if cli.GenerateSecret.Hex {
|
||||||
|
fmt.Println(secret.Hex()) // nolint: forbidigo
|
||||||
|
} else {
|
||||||
|
fmt.Println(secret.Base64()) // nolint: forbidigo
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/testlib"
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GenerateSecretTestSuite struct {
|
||||||
|
CommonTestSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *GenerateSecretTestSuite) SetupTest() {
|
||||||
|
suite.CommonTestSuite.SetupTest()
|
||||||
|
|
||||||
|
suite.cli.GenerateSecret.HostName = "google.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *GenerateSecretTestSuite) TestDefault() {
|
||||||
|
output := testlib.CaptureStdout(func() {
|
||||||
|
suite.NoError(suite.cli.GenerateSecret.Run(suite.cli, "dev"))
|
||||||
|
})
|
||||||
|
suite.True(strings.HasPrefix(output, "7"))
|
||||||
|
|
||||||
|
secret, err := mtglib.ParseSecret(output)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.True(secret.Valid())
|
||||||
|
suite.Equal("google.com", secret.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *GenerateSecretTestSuite) TestHex() {
|
||||||
|
suite.cli.GenerateSecret.Hex = true
|
||||||
|
|
||||||
|
output := testlib.CaptureStdout(func() {
|
||||||
|
suite.NoError(suite.cli.GenerateSecret.Run(suite.cli, "dev"))
|
||||||
|
})
|
||||||
|
suite.True(strings.HasPrefix(output, "ee"))
|
||||||
|
|
||||||
|
secret, err := mtglib.ParseSecret(output)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.True(secret.Valid())
|
||||||
|
suite.Equal("google.com", secret.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateSecret(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &GenerateSecretTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/cli"
|
||||||
|
"github.com/9seconds/mtg/v2/internal/testlib"
|
||||||
|
"github.com/jarcoal/httpmock"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommonTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
cli *cli.CLI
|
||||||
|
networkMock *testlib.MtglibNetworkMock
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CommonTestSuite) SetupTest() {
|
||||||
|
suite.networkMock = &testlib.MtglibNetworkMock{}
|
||||||
|
suite.httpClient = &http.Client{}
|
||||||
|
suite.cli = &cli.CLI{}
|
||||||
|
|
||||||
|
httpmock.ActivateNonDefault(suite.httpClient)
|
||||||
|
|
||||||
|
suite.networkMock.
|
||||||
|
On("MakeHTTPClient", mock.Anything).
|
||||||
|
Maybe().
|
||||||
|
Return(suite.httpClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CommonTestSuite) TearDownTest() {
|
||||||
|
suite.networkMock.AssertExpectations(suite.T())
|
||||||
|
httpmock.DeactivateAndReset()
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/antireplay"
|
||||||
|
"github.com/9seconds/mtg/v2/events"
|
||||||
|
"github.com/9seconds/mtg/v2/internal/utils"
|
||||||
|
"github.com/9seconds/mtg/v2/ipblocklist"
|
||||||
|
"github.com/9seconds/mtg/v2/logger"
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/9seconds/mtg/v2/stats"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Proxy struct {
|
||||||
|
base
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Proxy) Run(cli *CLI, version string) error {
|
||||||
|
if err := c.ReadConfig(version); err != nil {
|
||||||
|
return fmt.Errorf("cannot init config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Proxy) Execute() error {
|
||||||
|
zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
|
||||||
|
zerolog.TimestampFieldName = "timestamp"
|
||||||
|
zerolog.LevelFieldName = "level"
|
||||||
|
|
||||||
|
if c.Config.Debug {
|
||||||
|
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||||
|
} else {
|
||||||
|
zerolog.SetGlobalLevel(zerolog.WarnLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := utils.RootContext()
|
||||||
|
opts := mtglib.ProxyOpts{
|
||||||
|
Logger: logger.NewZeroLogger(zerolog.New(os.Stdout).With().Timestamp().Logger()),
|
||||||
|
Network: c.Network,
|
||||||
|
AntiReplayCache: antireplay.NewNoop(),
|
||||||
|
IPBlocklist: ipblocklist.NewNoop(),
|
||||||
|
EventStream: events.NewNoopStream(),
|
||||||
|
|
||||||
|
Secret: c.Config.Secret,
|
||||||
|
BufferSize: c.Config.TCPBuffer.Value(mtglib.DefaultBufferSize),
|
||||||
|
DomainFrontingPort: c.Config.DomainFrontingPort.Value(mtglib.DefaultDomainFrontingPort),
|
||||||
|
IdleTimeout: c.Config.Network.Timeout.Idle.Value(mtglib.DefaultIdleTimeout),
|
||||||
|
PreferIP: c.Config.PreferIP.Value(mtglib.DefaultPreferIP),
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.Logger.BindStr("configuration", c.Config.String()).Debug("configuration")
|
||||||
|
|
||||||
|
c.setupAntiReplayCache(&opts)
|
||||||
|
|
||||||
|
if err := c.setupIPBlocklist(&opts); err != nil {
|
||||||
|
return fmt.Errorf("cannot setup ipblocklist: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.setupEventStream(&opts); err != nil {
|
||||||
|
return fmt.Errorf("cannot setup event stream: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy, err := mtglib.NewProxy(opts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot create a proxy: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", c.Config.BindTo.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot start proxy: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go proxy.Serve(listener) // nolint: errcheck
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
listener.Close()
|
||||||
|
proxy.Shutdown()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Proxy) setupAntiReplayCache(opts *mtglib.ProxyOpts) {
|
||||||
|
if !c.Config.Defense.AntiReplay.Enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.AntiReplayCache = antireplay.NewStableBloomFilter(
|
||||||
|
c.Config.Defense.AntiReplay.MaxSize.Value(antireplay.DefaultStableBloomFilterMaxSize),
|
||||||
|
c.Config.Defense.AntiReplay.ErrorRate.Value(antireplay.DefaultStableBloomFilterErrorRate),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Proxy) setupIPBlocklist(opts *mtglib.ProxyOpts) error {
|
||||||
|
if !c.Config.Defense.Blocklist.Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteURLs := []string{}
|
||||||
|
localFiles := []string{}
|
||||||
|
|
||||||
|
for _, v := range c.Config.Defense.Blocklist.URLs {
|
||||||
|
if v.IsRemote() {
|
||||||
|
remoteURLs = append(remoteURLs, v.String())
|
||||||
|
} else {
|
||||||
|
localFiles = append(localFiles, v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
firehol, err := ipblocklist.NewFirehol(opts.Logger.Named("ipblockist"),
|
||||||
|
c.Network,
|
||||||
|
c.Config.Defense.Blocklist.DownloadConcurrency,
|
||||||
|
remoteURLs,
|
||||||
|
localFiles)
|
||||||
|
if err != nil {
|
||||||
|
return err // nolint: wrapcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
go firehol.Run(c.Config.Defense.Blocklist.UpdateEach.Value(ipblocklist.DefaultFireholUpdateEach))
|
||||||
|
|
||||||
|
opts.IPBlocklist = firehol
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Proxy) setupEventStream(opts *mtglib.ProxyOpts) error {
|
||||||
|
factories := make([]events.ObserverFactory, 0, 2)
|
||||||
|
|
||||||
|
if c.Config.Stats.StatsD.Enabled {
|
||||||
|
statsdFactory, err := stats.NewStatsd(
|
||||||
|
c.Config.Stats.StatsD.Address.String(),
|
||||||
|
opts.Logger.Named("statsd"),
|
||||||
|
c.Config.Stats.StatsD.MetricPrefix.Value(stats.DefaultStatsdMetricPrefix),
|
||||||
|
c.Config.Stats.StatsD.TagFormat.Value(stats.DefaultStatsdTagFormat))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot build statsd observer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
factories = append(factories, statsdFactory.Make)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Config.Stats.Prometheus.Enabled {
|
||||||
|
prometheus := stats.NewPrometheus(
|
||||||
|
c.Config.Stats.Prometheus.MetricPrefix.Value(stats.DefaultMetricPrefix),
|
||||||
|
c.Config.Stats.Prometheus.HTTPPath.Value("/"),
|
||||||
|
)
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", c.Config.Stats.Prometheus.BindTo.String())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot start a listener for prometheus: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go prometheus.Serve(listener) // nolint: errcheck
|
||||||
|
|
||||||
|
factories = append(factories, prometheus.Make)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(factories) > 0 {
|
||||||
|
opts.EventStream = events.NewEventStream(factories)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
secret = "7mqFMMq3P2Tvvt_rPx5qhmFnb29nbGUuY29t"
|
||||||
|
bind-to = "0.0.0.0:80"
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/pelletier/go-toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Debug bool `json:"debug"`
|
||||||
|
Secret mtglib.Secret `json:"secret"`
|
||||||
|
BindTo TypeHostPort `json:"bind-to"`
|
||||||
|
TCPBuffer TypeBytes `json:"tcp-buffer"`
|
||||||
|
PreferIP TypePreferIP `json:"prefer-ip"`
|
||||||
|
DomainFrontingPort TypePort `json:"domain-fronting-port"`
|
||||||
|
TolerateTimeSkewness TypeDuration `json:"tolerate-time-skewness"`
|
||||||
|
Concurrency uint `json:"concurrency"`
|
||||||
|
Defense struct {
|
||||||
|
AntiReplay struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
MaxSize TypeBytes `json:"max-size"`
|
||||||
|
ErrorRate TypeErrorRate `json:"error-rate"`
|
||||||
|
} `json:"anti-replay"`
|
||||||
|
Blocklist struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
DownloadConcurrency uint `json:"download-concurrency"`
|
||||||
|
URLs []TypeBlocklistURI `json:"urls"`
|
||||||
|
UpdateEach TypeDuration `json:"update-each"`
|
||||||
|
} `json:"blocklist"`
|
||||||
|
} `json:"defense"`
|
||||||
|
Network struct {
|
||||||
|
Timeout struct {
|
||||||
|
TCP TypeDuration `json:"tcp"`
|
||||||
|
HTTP TypeDuration `json:"http"`
|
||||||
|
Idle TypeDuration `json:"idle"`
|
||||||
|
} `json:"timeout"`
|
||||||
|
DOHIP TypeIP `json:"doh-ip"`
|
||||||
|
Proxies []TypeURL `json:"proxies"`
|
||||||
|
} `json:"network"`
|
||||||
|
Stats struct {
|
||||||
|
StatsD struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Address TypeHostPort `json:"address"`
|
||||||
|
MetricPrefix TypeMetricPrefix `json:"metric-prefix"`
|
||||||
|
TagFormat TypeStatsdTagFormat `json:"tag-format"`
|
||||||
|
} `json:"statsd"`
|
||||||
|
Prometheus struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
BindTo TypeHostPort `json:"bind-to"`
|
||||||
|
HTTPPath TypeHTTPPath `json:"http-path"`
|
||||||
|
MetricPrefix TypeMetricPrefix `json:"metric-prefix"`
|
||||||
|
} `json:"prometheus"`
|
||||||
|
} `json:"stats"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Validate() error {
|
||||||
|
if !c.Secret.Valid() {
|
||||||
|
return fmt.Errorf("invalid secret %s", c.Secret.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.BindTo.HostValue(nil)) == 0 || c.BindTo.PortValue(0) == 0 {
|
||||||
|
return fmt.Errorf("incorrect bind-to parameter %s", c.BindTo.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) String() string {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
encoder := json.NewEncoder(buf)
|
||||||
|
|
||||||
|
encoder.SetEscapeHTML(false)
|
||||||
|
|
||||||
|
if err := encoder.Encode(c); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type configRaw struct {
|
||||||
|
Debug bool `toml:"debug" json:"debug,omitempty"`
|
||||||
|
Secret string `toml:"secret" json:"secret"`
|
||||||
|
BindTo string `toml:"bind-to" json:"bind-to"`
|
||||||
|
TCPBuffer string `toml:"tcp-buffer" json:"tcp-buffer,omitempty"`
|
||||||
|
PreferIP string `toml:"prefer-ip" json:"prefer-ip,omitempty"`
|
||||||
|
DomainFrontingPort uint `toml:"domain-fronting-port" json:"domain-fronting-port,omitempty"`
|
||||||
|
TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerate-time-skewness,omitempty"`
|
||||||
|
Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"`
|
||||||
|
Defense struct {
|
||||||
|
AntiReplay struct {
|
||||||
|
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
|
||||||
|
MaxSize string `toml:"max-size" json:"max-size,omitempty"`
|
||||||
|
ErrorRate float64 `toml:"error-rate" json:"error-rate,omitempty"`
|
||||||
|
} `toml:"anti-replay" json:"anti-replay,omitempty"`
|
||||||
|
Blocklist struct {
|
||||||
|
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
|
||||||
|
DownloadConcurrency uint `toml:"download-concurrency" json:"download-concurrency,omitempty"`
|
||||||
|
URLs []string `toml:"urls" json:"urls,omitempty"`
|
||||||
|
UpdateEach string `toml:"update-each" json:"update-each,omitempty"`
|
||||||
|
} `toml:"blocklist" json:"blocklist,omitempty"`
|
||||||
|
} `toml:"defense" json:"defense,omitempty"`
|
||||||
|
Network struct {
|
||||||
|
Timeout struct {
|
||||||
|
TCP string `toml:"tcp" json:"tcp,omitempty"`
|
||||||
|
HTTP string `toml:"http" json:"http,omitempty"`
|
||||||
|
Idle string `toml:"idle" json:"idle,omitempty"`
|
||||||
|
} `toml:"timeout" json:"timeout,omitempty"`
|
||||||
|
DOHIP string `toml:"doh-ip" json:"doh-ip,omitempty"`
|
||||||
|
Proxies []string `toml:"proxies" json:"proxies,omitempty"`
|
||||||
|
} `toml:"network" json:"network,omitempty"`
|
||||||
|
Stats struct {
|
||||||
|
StatsD struct {
|
||||||
|
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
|
||||||
|
Address string `toml:"address" json:"address,omitempty"`
|
||||||
|
MetricPrefix string `toml:"metric-prefix" json:"metric-prefix,omitempty"`
|
||||||
|
TagFormat string `toml:"tag-format" json:"tag-format,omitempty"`
|
||||||
|
} `toml:"statsd" json:"statsd,omitempty"`
|
||||||
|
Prometheus struct {
|
||||||
|
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
|
||||||
|
BindTo string `toml:"bind-to" json:"bind-to,omitempty"`
|
||||||
|
HTTPPath string `toml:"http-path" json:"http-path,omitempty"`
|
||||||
|
MetricPrefix string `toml:"metric-prefix" json:"metric-prefix,omitempty"`
|
||||||
|
} `toml:"prometheus" json:"prometheus,omitempty"`
|
||||||
|
} `toml:"stats" json:"stats,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(rawData []byte) (*Config, error) {
|
||||||
|
rawConf := &configRaw{}
|
||||||
|
jsonBuf := &bytes.Buffer{}
|
||||||
|
conf := &Config{}
|
||||||
|
|
||||||
|
jsonEncoder := json.NewEncoder(jsonBuf)
|
||||||
|
jsonEncoder.SetEscapeHTML(false)
|
||||||
|
jsonEncoder.SetIndent("", "")
|
||||||
|
|
||||||
|
if err := toml.Unmarshal(rawData, rawConf); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse toml config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := jsonEncoder.Encode(rawConf); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(jsonBuf).Decode(conf); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse a config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := conf.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot validate config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConfigTestSuite) ReadConfig(filename string) []byte {
|
||||||
|
data, err := os.ReadFile(filepath.Join("testdata", filename))
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConfigTestSuite) TestParseEmpty() {
|
||||||
|
_, err := config.Parse([]byte{})
|
||||||
|
suite.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConfigTestSuite) TestParseBrokenToml() {
|
||||||
|
_, err := config.Parse(suite.ReadConfig("broken.toml"))
|
||||||
|
suite.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConfigTestSuite) TestParseOnlySecret() {
|
||||||
|
_, err := config.Parse(suite.ReadConfig("only_secret.toml"))
|
||||||
|
suite.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConfigTestSuite) TestParseMinimalConfig() {
|
||||||
|
conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Equal("7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t", conf.Secret.Base64())
|
||||||
|
suite.Equal("0.0.0.0:3128", conf.BindTo.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConfigTestSuite) TestString() {
|
||||||
|
conf, err := config.Parse(suite.ReadConfig("minimal.toml"))
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.NotEmpty(conf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &ConfigTestSuite{})
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
s = sdfsdfds
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
|
||||||
|
bind-to = "0.0.0.0:3128"
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
secret = "7oe1GqLy6TBc38CV3jx7q09nb29nbGUuY29t"
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TypeBlocklistURI struct {
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeBlocklistURI) UnmarshalText(data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
text := string(data)
|
||||||
|
if filepath.IsAbs(text) {
|
||||||
|
if _, err := os.Stat(text); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("filepath %s does not exist", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.value = text
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedURL, err := url.Parse(text)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("incorrect url: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch parsedURL.Scheme {
|
||||||
|
case "http", "https": // nolint: goconst
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown schema %s", parsedURL.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedURL.Host == "" {
|
||||||
|
return fmt.Errorf("incorrect url %s", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.value = parsedURL.String()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeBlocklistURI) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(c.value), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeBlocklistURI) String() string {
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeBlocklistURI) IsRemote() bool {
|
||||||
|
return !filepath.IsAbs(c.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeBlocklistURI) Value(defaultValue string) string {
|
||||||
|
if c.value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type typeBlocklistURITestStruct struct {
|
||||||
|
Value config.TypeBlocklistURI `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeBlocklistURITestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBlocklistURITestSuite) TestUnmarshalNil() {
|
||||||
|
typ := &config.TypeBlocklistURI{}
|
||||||
|
suite.NoError(typ.UnmarshalText(nil))
|
||||||
|
suite.Empty(typ.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBlocklistURITestSuite) TestUnknownSchema() {
|
||||||
|
typ := &config.TypeBlocklistURI{}
|
||||||
|
suite.Error(typ.UnmarshalText([]byte("gopher://lalala")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBlocklistURITestSuite) TestEmptyHost() {
|
||||||
|
typ := &config.TypeBlocklistURI{}
|
||||||
|
suite.Error(typ.UnmarshalText([]byte("https:///path")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBlocklistURITestSuite) TestIncorrectURL() {
|
||||||
|
typ := &config.TypeBlocklistURI{}
|
||||||
|
suite.Error(typ.UnmarshalText([]byte("h:/--")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBlocklistURITestSuite) TestUnmarshalFail() {
|
||||||
|
rnd := make([]byte, 48)
|
||||||
|
|
||||||
|
rand.Read(rnd) // nolint: errcheck
|
||||||
|
|
||||||
|
unknownPath := base64.StdEncoding.EncodeToString(rnd)
|
||||||
|
|
||||||
|
testData := []string{
|
||||||
|
"1",
|
||||||
|
unknownPath,
|
||||||
|
"/" + unknownPath,
|
||||||
|
"http:/",
|
||||||
|
"gopher://lalalal",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
assert.Error(t, json.Unmarshal(data, &typeBlocklistURITestStruct{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBlocklistURITestSuite) TestUnmarshalOk() {
|
||||||
|
dir, _ := os.Getwd()
|
||||||
|
dir, _ = filepath.Abs(dir)
|
||||||
|
|
||||||
|
testData := []string{
|
||||||
|
"http://lalala",
|
||||||
|
filepath.Join(dir, "config.go"),
|
||||||
|
"https://lalala",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
testStruct := &typeBlocklistURITestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.EqualValues(t, value, testStruct.Value.Value(""))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBlocklistURITestSuite) TestMarshalOk() {
|
||||||
|
dir, _ := os.Getwd()
|
||||||
|
dir, _ = filepath.Abs(dir)
|
||||||
|
|
||||||
|
testData := []string{
|
||||||
|
"http://lalalal",
|
||||||
|
filepath.Join(dir, "config.go"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
name := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": name,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(name, func(t *testing.T) {
|
||||||
|
testStruct := &typeBlocklistURITestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, name, testStruct.Value.String())
|
||||||
|
|
||||||
|
marshalled, err := testStruct.Value.MarshalText()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, name, string(marshalled))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBlocklistURITestSuite) TestValue() {
|
||||||
|
testStruct := &typeBlocklistURITestStruct{}
|
||||||
|
|
||||||
|
suite.Equal("http://lalala", testStruct.Value.Value("http://lalala"))
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": "http://blablabla",
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.NoError(json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
suite.Equal("http://blablabla", testStruct.Value.Value(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBlocklistURITestSuite) TestIsRemote() {
|
||||||
|
dir, _ := os.Getwd()
|
||||||
|
dir, _ = filepath.Abs(dir)
|
||||||
|
|
||||||
|
testData := map[bool]string{
|
||||||
|
true: "http://lalalal",
|
||||||
|
false: filepath.Join(dir, "config.go"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range testData {
|
||||||
|
ok := k
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(strconv.FormatBool(ok), func(t *testing.T) {
|
||||||
|
testStruct := &typeBlocklistURITestStruct{}
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
assert.True(t, testStruct.Value.IsRemote())
|
||||||
|
} else {
|
||||||
|
assert.False(t, testStruct.Value.IsRemote())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeBlocklistURI(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &TypeBlocklistURITestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alecthomas/units"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TypeBytes struct {
|
||||||
|
value units.Base2Bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeBytes) UnmarshalText(data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizedData := strings.ToUpper(string(data))
|
||||||
|
normalizedData = strings.ReplaceAll(normalizedData, "IB", "iB")
|
||||||
|
|
||||||
|
value, err := units.ParseBase2Bytes(normalizedData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("incorrect bytes value: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value < 0 {
|
||||||
|
return fmt.Errorf("%d should be positive number", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.value = value
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeBytes) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(c.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeBytes) String() string {
|
||||||
|
if c.value == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.ToLower(c.value.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeBytes) Value(defaultValue uint) uint {
|
||||||
|
if c.value == 0 {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint(c.value)
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type typeBytesTestStruct struct {
|
||||||
|
Value config.TypeBytes `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeBytesTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBytesTestSuite) TestUnmarshalNil() {
|
||||||
|
typ := &config.TypeBytes{}
|
||||||
|
suite.NoError(typ.UnmarshalText(nil))
|
||||||
|
suite.Empty(typ.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBytesTestSuite) TestUnmarshalFail() {
|
||||||
|
testData := []string{
|
||||||
|
"1m",
|
||||||
|
"1",
|
||||||
|
"-1kb",
|
||||||
|
"-1kib",
|
||||||
|
"-1QB",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
assert.Error(t, json.Unmarshal(data, &typeBytesTestStruct{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBytesTestSuite) TestUnmarshalOk() {
|
||||||
|
testData := map[string]uint{
|
||||||
|
"1b": 1,
|
||||||
|
"1kb": 1024,
|
||||||
|
"1kib": 1024,
|
||||||
|
"2mb": 2 * 1024 * 1024,
|
||||||
|
"2mib": 2 * 1024 * 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": k,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(k, func(t *testing.T) {
|
||||||
|
testStruct := &typeBytesTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.EqualValues(t, value, testStruct.Value.Value(0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBytesTestSuite) TestMarshalOk() {
|
||||||
|
testData := []string{
|
||||||
|
"1b",
|
||||||
|
"1kib",
|
||||||
|
"2mib",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
name := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": name,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(name, func(t *testing.T) {
|
||||||
|
testStruct := &typeBytesTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, name, testStruct.Value.String())
|
||||||
|
|
||||||
|
marshalled, err := testStruct.Value.MarshalText()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, name, string(marshalled))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeBytesTestSuite) TestValue() {
|
||||||
|
testStruct := &typeBytesTestStruct{}
|
||||||
|
|
||||||
|
suite.EqualValues(0, testStruct.Value.Value(0))
|
||||||
|
suite.EqualValues(1, testStruct.Value.Value(1))
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": "1kb",
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.NoError(json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
suite.EqualValues(1024, testStruct.Value.Value(0))
|
||||||
|
suite.EqualValues(1024, testStruct.Value.Value(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeBytes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &TypeBytesTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TypeDuration struct {
|
||||||
|
value time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeDuration) UnmarshalText(data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dur, err := time.ParseDuration(strings.ToLower(string(data)))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("incorrect duration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dur < 0 {
|
||||||
|
return fmt.Errorf("%s should be positive duration", dur)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.value = dur
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeDuration) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(c.value.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeDuration) String() string {
|
||||||
|
return c.value.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeDuration) Value(defaultValue time.Duration) time.Duration {
|
||||||
|
if c.value == 0 {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type typeDurationTestStruct struct {
|
||||||
|
Value config.TypeDuration `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeDurationTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeDurationTestSuite) TestUnmarshalNil() {
|
||||||
|
typ := &config.TypeDuration{}
|
||||||
|
suite.NoError(typ.UnmarshalText(nil))
|
||||||
|
suite.EqualValues(0, typ.Value(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeDurationTestSuite) TestUnmarshalFail() {
|
||||||
|
testData := []string{
|
||||||
|
"1t",
|
||||||
|
"1",
|
||||||
|
"-1s",
|
||||||
|
"-1h",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
assert.Error(t, json.Unmarshal(data, &typeDurationTestStruct{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeDurationTestSuite) TestUnmarshalOk() {
|
||||||
|
testData := map[string]time.Duration{
|
||||||
|
"1s": time.Second,
|
||||||
|
"1m": time.Minute,
|
||||||
|
"2h1s": 2*time.Hour + time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": k,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(k, func(t *testing.T) {
|
||||||
|
testStruct := &typeDurationTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, value, testStruct.Value.Value(0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeDurationTestSuite) TestMarshalOk() {
|
||||||
|
testData := []string{
|
||||||
|
"1s",
|
||||||
|
"1m0s",
|
||||||
|
"2h0m1s",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
name := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": name,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(name, func(t *testing.T) {
|
||||||
|
testStruct := &typeDurationTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, name, testStruct.Value.String())
|
||||||
|
|
||||||
|
marshalled, err := testStruct.Value.MarshalText()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, name, string(marshalled))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeDurationTestSuite) TestValue() {
|
||||||
|
testStruct := &typeDurationTestStruct{}
|
||||||
|
|
||||||
|
suite.EqualValues(0, testStruct.Value.Value(0))
|
||||||
|
suite.Equal(time.Second, testStruct.Value.Value(time.Second))
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": "1s",
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.NoError(json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
suite.Equal(time.Second, testStruct.Value.Value(0))
|
||||||
|
suite.Equal(time.Second, testStruct.Value.Value(time.Minute))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeDuration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &TypeDurationTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const typeErrorRateIgnoreLess = 1e-8
|
||||||
|
|
||||||
|
type TypeErrorRate struct {
|
||||||
|
value float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeErrorRate) UnmarshalJSON(data []byte) error {
|
||||||
|
value, err := strconv.ParseFloat(string(data), 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("incorrect float value: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value <= 0 || value >= 100 {
|
||||||
|
return fmt.Errorf("%f should be 0 < x < 100", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.value = value
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeErrorRate) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(c.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeErrorRate) String() string {
|
||||||
|
return strconv.FormatFloat(c.value, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeErrorRate) Value(defaultValue float64) float64 {
|
||||||
|
if c.value < typeErrorRateIgnoreLess {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type typeErrorRateTestStruct struct {
|
||||||
|
Value config.TypeErrorRate `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeErrorRateTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeErrorRateTestSuite) TestUnmarshalFail() {
|
||||||
|
testData := []float64{
|
||||||
|
1000,
|
||||||
|
-100,
|
||||||
|
-0.0001,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
data, err := json.Marshal(map[string]float64{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(strconv.FormatFloat(v, 'f', -1, 64), func(t *testing.T) {
|
||||||
|
assert.Error(t, json.Unmarshal(data, &typeErrorRateTestStruct{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": "hello",
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Error(json.Unmarshal(data, &typeErrorRateTestStruct{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeErrorRateTestSuite) TestUnmarshalOk() {
|
||||||
|
testData := []float64{
|
||||||
|
1,
|
||||||
|
55.5,
|
||||||
|
0.0001,
|
||||||
|
1e-6,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]float64{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(strconv.FormatFloat(v, 'f', -1, 64), func(t *testing.T) {
|
||||||
|
testStruct := &typeErrorRateTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.InEpsilon(t, value, testStruct.Value.Value(0), 1e-10)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeErrorRateTestSuite) TestMarshalOk() {
|
||||||
|
testData := []float64{
|
||||||
|
1,
|
||||||
|
55.5,
|
||||||
|
0.0001,
|
||||||
|
1e-6,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]float64{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(strconv.FormatFloat(v, 'f', -1, 64), func(t *testing.T) {
|
||||||
|
testStruct := &typeErrorRateTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
parsed, err := strconv.ParseFloat(testStruct.Value.String(), 64)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.InEpsilon(t, value, parsed, 1e-10)
|
||||||
|
|
||||||
|
marshalled, err := testStruct.Value.MarshalText()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
parsed, err = strconv.ParseFloat(string(marshalled), 64)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.InEpsilon(t, value, parsed, 1e-10)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeErrorRateTestSuite) TestValue() {
|
||||||
|
testStruct := &typeErrorRateTestStruct{}
|
||||||
|
|
||||||
|
suite.InEpsilon(1, testStruct.Value.Value(1), 1e-10)
|
||||||
|
suite.InEpsilon(2, testStruct.Value.Value(2), 1e-10)
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]float64{
|
||||||
|
"value": 1,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.NoError(json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
suite.InEpsilon(1, testStruct.Value.Value(2), 1e-10)
|
||||||
|
suite.InEpsilon(1, testStruct.Value.Value(3), 1e-10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeErrorRate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &TypeErrorRateTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TypeHostPort struct {
|
||||||
|
host TypeIP
|
||||||
|
port TypePort
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeHostPort) UnmarshalText(data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
text := string(data)
|
||||||
|
|
||||||
|
host, port, err := net.SplitHostPort(text)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("incorrect host:port syntax: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if port == "" {
|
||||||
|
return fmt.Errorf("port in %s host:port pair cannot be empty", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.port.UnmarshalJSON([]byte(port)); err != nil {
|
||||||
|
return fmt.Errorf("incorrect port in host:port: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.host.UnmarshalText([]byte(host)); err != nil {
|
||||||
|
return fmt.Errorf("incorrect host: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeHostPort) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(c.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeHostPort) String() string {
|
||||||
|
return c.Value(net.IP{}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeHostPort) HostValue(defaultValue net.IP) net.IP {
|
||||||
|
return c.host.Value(defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeHostPort) PortValue(defaultValue uint) uint {
|
||||||
|
return c.port.Value(defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeHostPort) Value(defaultHostValue net.IP, defaultPortValue uint) string {
|
||||||
|
host := c.HostValue(defaultHostValue)
|
||||||
|
port := c.PortValue(defaultPortValue)
|
||||||
|
|
||||||
|
hostStr := ""
|
||||||
|
if len(host) > 0 {
|
||||||
|
hostStr = host.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.JoinHostPort(hostStr, strconv.Itoa(int(port)))
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type typeHostPortTestStruct struct {
|
||||||
|
Value config.TypeHostPort `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeHostPortTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeHostPortTestSuite) TestUnmarshalFail() {
|
||||||
|
testData := []string{
|
||||||
|
"10.0.0.10:aaa",
|
||||||
|
"10.0.0.10:",
|
||||||
|
":",
|
||||||
|
"xxx",
|
||||||
|
"xxx:80",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
assert.Error(t, json.Unmarshal(data, &typeHostPortTestStruct{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeHostPortTestSuite) TestUnmarshalOk() {
|
||||||
|
testData := []string{
|
||||||
|
"10.0.0.10:80",
|
||||||
|
"0.0.0.0:80",
|
||||||
|
":8000",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
testStruct := &typeHostPortTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.EqualValues(t, value, testStruct.Value.Value(nil, 0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeHostPortTestSuite) TestMarshalOk() {
|
||||||
|
testData := []string{
|
||||||
|
"10.0.0.10:80",
|
||||||
|
"0.0.0.0:80",
|
||||||
|
":8000",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
testStruct := &typeHostPortTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, value, testStruct.Value.String())
|
||||||
|
|
||||||
|
marshalled, err := testStruct.Value.MarshalText()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, value, string(marshalled))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeHostPortTestSuite) TestValue() {
|
||||||
|
testStruct := &typeHostPortTestStruct{}
|
||||||
|
|
||||||
|
suite.EqualValues("127.0.0.1:80",
|
||||||
|
testStruct.Value.Value(net.ParseIP("127.0.0.1"), 80))
|
||||||
|
suite.EqualValues("127.1.0.1:80",
|
||||||
|
testStruct.Value.Value(net.ParseIP("127.1.0.1"), 80))
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": "127.0.0.1:80",
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.NoError(json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
suite.EqualValues("127.0.0.1:80", testStruct.Value.Value(nil, 0))
|
||||||
|
suite.EqualValues("127.0.0.1:80", testStruct.Value.Value(net.ParseIP("10.0.0.10"), 3000))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeHostPort(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &TypeHostPortTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type TypeHTTPPath struct {
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeHTTPPath) UnmarshalText(data []byte) error {
|
||||||
|
if len(data) > 0 {
|
||||||
|
c.value = "/" + strings.Trim(string(data), "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeHTTPPath) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(c.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeHTTPPath) String() string {
|
||||||
|
if c.value == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeHTTPPath) Value(defaultValue string) string {
|
||||||
|
if c.value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type typeHTTPPathTestStruct struct {
|
||||||
|
Value config.TypeHTTPPath `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeHTTPPathTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeHTTPPathTestSuite) TestUnmarshal() {
|
||||||
|
testData := []string{
|
||||||
|
"/hello",
|
||||||
|
"hello",
|
||||||
|
"hello/",
|
||||||
|
"/hello/",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
testStruct := &typeHTTPPathTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, "/hello", testStruct.Value.Value(""))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeHTTPPathTestSuite) TestMarshalOk() {
|
||||||
|
testData := map[string]string{
|
||||||
|
"": "/",
|
||||||
|
"/hello": "/hello",
|
||||||
|
"/hello/": "/hello",
|
||||||
|
"hello/": "/hello",
|
||||||
|
"hello": "/hello",
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range testData {
|
||||||
|
toPass := k
|
||||||
|
compareWith := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": toPass,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(toPass, func(t *testing.T) {
|
||||||
|
testStruct := &typeHTTPPathTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, compareWith, testStruct.Value.String())
|
||||||
|
|
||||||
|
marshalled, err := testStruct.Value.MarshalText()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, compareWith, string(marshalled))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeHTTPPathTestSuite) TestValue() {
|
||||||
|
testStruct := &typeHTTPPathTestStruct{}
|
||||||
|
|
||||||
|
suite.Equal("/hello", testStruct.Value.Value("/hello"))
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": "/map",
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.NoError(json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
suite.Equal("/map", testStruct.Value.Value("/hello"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeHTTPPath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &TypeHTTPPathTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TypeIP struct {
|
||||||
|
value net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeIP) UnmarshalText(data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(string(data))
|
||||||
|
if ip == nil {
|
||||||
|
return fmt.Errorf("incorrect ip address: %s", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.value = ip
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeIP) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(c.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeIP) String() string {
|
||||||
|
if len(c.value) > 0 {
|
||||||
|
return c.value.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeIP) Value(defaultValue net.IP) net.IP {
|
||||||
|
if c.value == nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type typeIPTestStruct struct {
|
||||||
|
Value config.TypeIP `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeIPTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeIPTestSuite) TestUnmarshalFail() {
|
||||||
|
testData := []string{
|
||||||
|
"0.0.10",
|
||||||
|
"10.0.0.10:",
|
||||||
|
"xxx:80",
|
||||||
|
"2001:0db8:85a3:0000:0000:8a2e:4",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
assert.Error(t, json.Unmarshal(data, &typeIPTestStruct{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeIPTestSuite) TestUnmarshalOk() {
|
||||||
|
testData := []string{
|
||||||
|
"0.0.0.0",
|
||||||
|
"10.0.0.10",
|
||||||
|
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
testStruct := &typeIPTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t,
|
||||||
|
net.ParseIP(value).String(),
|
||||||
|
testStruct.Value.Value(nil).String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeIPTestSuite) TestMarshalOk() {
|
||||||
|
testData := []string{
|
||||||
|
"0.0.0.0",
|
||||||
|
"10.0.0.10",
|
||||||
|
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := net.ParseIP(v).String()
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
testStruct := &typeIPTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, value, testStruct.Value.String())
|
||||||
|
|
||||||
|
marshalled, err := testStruct.Value.MarshalText()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, value, string(marshalled))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeIPTestSuite) TestValue() {
|
||||||
|
testStruct := &typeIPTestStruct{}
|
||||||
|
suite.Empty(testStruct.Value.String())
|
||||||
|
|
||||||
|
suite.Nil(testStruct.Value.Value(nil))
|
||||||
|
suite.Equal("127.1.0.1", testStruct.Value.Value(net.ParseIP("127.1.0.1")).String())
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": "127.0.0.1",
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.NoError(json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
suite.Equal("127.0.0.1", testStruct.Value.Value(nil).String())
|
||||||
|
suite.Equal("127.0.0.1", testStruct.Value.Value(net.ParseIP("10.0.0.10")).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeIP(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &TypeIPTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TypeMetricPrefix struct {
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeMetricPrefix) UnmarshalText(data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := string(data)
|
||||||
|
if ok, err := regexp.MatchString("^[a-z0-9]+$", prefix); !ok || err != nil {
|
||||||
|
return fmt.Errorf("incorrect metric prefix: %s", prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.value = prefix
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeMetricPrefix) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(c.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeMetricPrefix) String() string {
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeMetricPrefix) Value(defaultValue string) string {
|
||||||
|
if c.value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type typeMetricPrefixTestStruct struct {
|
||||||
|
Value config.TypeMetricPrefix `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeMetricPrefixTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeMetricPrefixTestSuite) TestUnmarshalNil() {
|
||||||
|
typ := &config.TypeMetricPrefix{}
|
||||||
|
suite.NoError(typ.UnmarshalText(nil))
|
||||||
|
suite.Empty(typ.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeMetricPrefixTestSuite) TestUnmarshalFail() {
|
||||||
|
testData := []string{
|
||||||
|
"aaa.aaa",
|
||||||
|
"aaa-bbb",
|
||||||
|
"aaa:ccc",
|
||||||
|
"metric prefix",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
assert.Error(t, json.Unmarshal(data, &typeMetricPrefixTestStruct{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeMetricPrefixTestSuite) TestUnmarshalOk() {
|
||||||
|
testData := []string{
|
||||||
|
"mtg",
|
||||||
|
"mtg111",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
testStruct := &typeMetricPrefixTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, value, testStruct.Value.Value(""))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeMetricPrefixTestSuite) TestMarshalOk() {
|
||||||
|
testData := []string{
|
||||||
|
"mtg",
|
||||||
|
"mtg111",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
testStruct := &typeMetricPrefixTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, value, testStruct.Value.String())
|
||||||
|
|
||||||
|
marshalled, err := testStruct.Value.MarshalText()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, value, string(marshalled))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeMetricPrefixTestSuite) TestValue() {
|
||||||
|
testStruct := &typeMetricPrefixTestStruct{}
|
||||||
|
|
||||||
|
suite.Equal("mtg", testStruct.Value.Value("mtg"))
|
||||||
|
suite.Equal("vvv", testStruct.Value.Value("vvv"))
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": "aaa",
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.NoError(json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
suite.Equal("aaa", testStruct.Value.Value("mtg"))
|
||||||
|
suite.Equal("aaa", testStruct.Value.Value("vvv"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeMetricPrefix(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &TypeMetricPrefixTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TypePort struct {
|
||||||
|
value uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypePort) UnmarshalJSON(data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
intValue, err := strconv.ParseUint(string(data), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("port number is not a number: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if intValue == 0 || intValue >= 65536 {
|
||||||
|
return fmt.Errorf("port number should be 0 < portNo < 65536: %d", intValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.value = uint(intValue)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypePort) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(c.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypePort) String() string {
|
||||||
|
return strconv.Itoa(int(c.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypePort) Value(defaultValue uint) uint {
|
||||||
|
if c.value == 0 {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type typePortTestStruct struct {
|
||||||
|
Value config.TypePort `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypePortTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypePortTestSuite) TestUnmarshalNil() {
|
||||||
|
typ := &config.TypePort{}
|
||||||
|
suite.NoError(typ.UnmarshalJSON(nil))
|
||||||
|
suite.Equal("0", typ.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypePortTestSuite) TestUnmarshalFail() {
|
||||||
|
testData := []int{
|
||||||
|
-1,
|
||||||
|
1_000_000,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
data, err := json.Marshal(map[string]int{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(strconv.Itoa(v), func(t *testing.T) {
|
||||||
|
assert.Error(t, json.Unmarshal(data, &typePortTestStruct{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypePortTestSuite) TestUnmarshalOk() {
|
||||||
|
testData := []int{
|
||||||
|
1,
|
||||||
|
1_000,
|
||||||
|
65535,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]int{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(strconv.Itoa(v), func(t *testing.T) {
|
||||||
|
testStruct := &typePortTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.EqualValues(t, value, testStruct.Value.Value(0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypePortTestSuite) TestMarshalOk() {
|
||||||
|
testData := map[string]int{
|
||||||
|
"1": 1,
|
||||||
|
"1000": 1000,
|
||||||
|
"65535": 65535,
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range testData {
|
||||||
|
name := k
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]int{
|
||||||
|
"value": value,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(name, func(t *testing.T) {
|
||||||
|
testStruct := &typePortTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, name, testStruct.Value.String())
|
||||||
|
|
||||||
|
marshalled, err := testStruct.Value.MarshalJSON()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, name, string(marshalled))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypePortTestSuite) TestValue() {
|
||||||
|
testStruct := &typePortTestStruct{}
|
||||||
|
|
||||||
|
suite.EqualValues(0, testStruct.Value.Value(0))
|
||||||
|
suite.EqualValues(1, testStruct.Value.Value(1))
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]int{
|
||||||
|
"value": 5,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.NoError(json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
suite.EqualValues(5, testStruct.Value.Value(0))
|
||||||
|
suite.EqualValues(5, testStruct.Value.Value(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypePort(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &TypePortTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypePreferIPPreferIPv4 = "prefer-ipv4"
|
||||||
|
TypePreferIPPreferIPv6 = "prefer-ipv6"
|
||||||
|
TypePreferOnlyIPv4 = "only-ipv4"
|
||||||
|
TypePreferOnlyIPv6 = "only-ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TypePreferIP struct {
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypePreferIP) UnmarshalText(data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
text := strings.ToLower(string(data))
|
||||||
|
|
||||||
|
switch text {
|
||||||
|
case TypePreferIPPreferIPv4, TypePreferIPPreferIPv6, TypePreferOnlyIPv4, TypePreferOnlyIPv6:
|
||||||
|
c.value = text
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("incorrect prefer-ip value: %s", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypePreferIP) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(c.value), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypePreferIP) String() string {
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypePreferIP) Value(defaultValue string) string {
|
||||||
|
if c.value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type typePreferIPTestStruct struct {
|
||||||
|
Value config.TypePreferIP `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypePreferIPTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypePreferIPTestSuite) TestUnmarshalNil() {
|
||||||
|
typ := &config.TypePreferIP{}
|
||||||
|
suite.NoError(typ.UnmarshalText(nil))
|
||||||
|
suite.Empty(typ.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypePreferIPTestSuite) TestUnmarshalFail() {
|
||||||
|
testData := []string{
|
||||||
|
"p",
|
||||||
|
"ipv4",
|
||||||
|
"onlyipv4",
|
||||||
|
"ipv6prefer",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
assert.Error(t, json.Unmarshal(data, &typePreferIPTestStruct{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypePreferIPTestSuite) TestUnmarshalOk() {
|
||||||
|
testData := []string{
|
||||||
|
config.TypePreferIPPreferIPv4,
|
||||||
|
config.TypePreferIPPreferIPv6,
|
||||||
|
config.TypePreferOnlyIPv4,
|
||||||
|
config.TypePreferOnlyIPv6,
|
||||||
|
strings.ToUpper(config.TypePreferIPPreferIPv4),
|
||||||
|
strings.ToUpper(config.TypePreferIPPreferIPv6),
|
||||||
|
strings.ToUpper(config.TypePreferOnlyIPv4),
|
||||||
|
strings.ToUpper(config.TypePreferOnlyIPv6),
|
||||||
|
strings.ToLower(config.TypePreferIPPreferIPv4),
|
||||||
|
strings.ToLower(config.TypePreferIPPreferIPv6),
|
||||||
|
strings.ToLower(config.TypePreferOnlyIPv4),
|
||||||
|
strings.ToLower(config.TypePreferOnlyIPv6),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
testStruct := &typePreferIPTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.EqualValues(t,
|
||||||
|
strings.ToLower(value),
|
||||||
|
testStruct.Value.Value(config.TypePreferIPPreferIPv4))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypePreferIPTestSuite) TestMarshalOk() {
|
||||||
|
testData := []string{
|
||||||
|
config.TypePreferIPPreferIPv4,
|
||||||
|
config.TypePreferIPPreferIPv6,
|
||||||
|
config.TypePreferOnlyIPv4,
|
||||||
|
config.TypePreferOnlyIPv6,
|
||||||
|
strings.ToUpper(config.TypePreferIPPreferIPv4),
|
||||||
|
strings.ToUpper(config.TypePreferIPPreferIPv6),
|
||||||
|
strings.ToUpper(config.TypePreferOnlyIPv4),
|
||||||
|
strings.ToUpper(config.TypePreferOnlyIPv6),
|
||||||
|
strings.ToLower(config.TypePreferIPPreferIPv4),
|
||||||
|
strings.ToLower(config.TypePreferIPPreferIPv6),
|
||||||
|
strings.ToLower(config.TypePreferOnlyIPv4),
|
||||||
|
strings.ToLower(config.TypePreferOnlyIPv6),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
testStruct := &typePreferIPTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, strings.ToLower(value), testStruct.Value.String())
|
||||||
|
|
||||||
|
marshalled, err := testStruct.Value.MarshalText()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, strings.ToLower(value), string(marshalled))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypePreferIPTestSuite) TestValue() {
|
||||||
|
testStruct := &typePreferIPTestStruct{}
|
||||||
|
|
||||||
|
suite.EqualValues(config.TypePreferIPPreferIPv4,
|
||||||
|
testStruct.Value.Value(config.TypePreferIPPreferIPv4))
|
||||||
|
suite.EqualValues(config.TypePreferIPPreferIPv6,
|
||||||
|
testStruct.Value.Value(config.TypePreferIPPreferIPv6))
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": config.TypePreferOnlyIPv4,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.NoError(json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
suite.EqualValues(config.TypePreferOnlyIPv4,
|
||||||
|
testStruct.Value.Value(config.TypePreferOnlyIPv6))
|
||||||
|
suite.EqualValues(config.TypePreferOnlyIPv4,
|
||||||
|
testStruct.Value.Value(config.TypePreferIPPreferIPv6))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypePreferIP(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &TypePreferIPTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeStatsdTagFormatInfluxdb = "influxdb"
|
||||||
|
TypeStatsdTagFormatDatadog = "datadog"
|
||||||
|
TypeStatsdTagFormatGraphite = "graphite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TypeStatsdTagFormat struct {
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeStatsdTagFormat) UnmarshalText(data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
text := strings.ToLower(string(data))
|
||||||
|
|
||||||
|
switch text {
|
||||||
|
case TypeStatsdTagFormatInfluxdb, TypeStatsdTagFormatDatadog, TypeStatsdTagFormatGraphite:
|
||||||
|
c.value = text
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("incorrect tag format value: %s", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeStatsdTagFormat) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(c.value), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeStatsdTagFormat) String() string {
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeStatsdTagFormat) Value(defaultValue string) string {
|
||||||
|
if c.value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type typeStatsdTagFormatTestStruct struct {
|
||||||
|
Value config.TypeStatsdTagFormat `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeStatsdTagFormatTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalNil() {
|
||||||
|
typ := &config.TypeStatsdTagFormat{}
|
||||||
|
suite.NoError(typ.UnmarshalText(nil))
|
||||||
|
suite.Equal("lalala", typ.Value("lalala"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalFail() {
|
||||||
|
testData := []string{
|
||||||
|
"p",
|
||||||
|
"ipv4",
|
||||||
|
"onlyipv4",
|
||||||
|
"ipv6prefer",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
assert.Error(t, json.Unmarshal(data, &typeStatsdTagFormatTestStruct{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeStatsdTagFormatTestSuite) TestUnmarshalOk() {
|
||||||
|
testData := []string{
|
||||||
|
config.TypeStatsdTagFormatDatadog,
|
||||||
|
config.TypeStatsdTagFormatInfluxdb,
|
||||||
|
config.TypeStatsdTagFormatGraphite,
|
||||||
|
strings.ToUpper(config.TypeStatsdTagFormatDatadog),
|
||||||
|
strings.ToUpper(config.TypeStatsdTagFormatInfluxdb),
|
||||||
|
strings.ToUpper(config.TypeStatsdTagFormatGraphite),
|
||||||
|
strings.ToLower(config.TypeStatsdTagFormatDatadog),
|
||||||
|
strings.ToLower(config.TypeStatsdTagFormatInfluxdb),
|
||||||
|
strings.ToLower(config.TypeStatsdTagFormatGraphite),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
testStruct := &typeStatsdTagFormatTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.EqualValues(t,
|
||||||
|
strings.ToLower(value),
|
||||||
|
testStruct.Value.Value(config.TypeStatsdTagFormatDatadog))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeStatsdTagFormatTestSuite) TestMarshalOk() {
|
||||||
|
testData := []string{
|
||||||
|
config.TypeStatsdTagFormatDatadog,
|
||||||
|
config.TypeStatsdTagFormatInfluxdb,
|
||||||
|
config.TypeStatsdTagFormatGraphite,
|
||||||
|
strings.ToUpper(config.TypeStatsdTagFormatDatadog),
|
||||||
|
strings.ToUpper(config.TypeStatsdTagFormatInfluxdb),
|
||||||
|
strings.ToUpper(config.TypeStatsdTagFormatGraphite),
|
||||||
|
strings.ToLower(config.TypeStatsdTagFormatDatadog),
|
||||||
|
strings.ToLower(config.TypeStatsdTagFormatInfluxdb),
|
||||||
|
strings.ToLower(config.TypeStatsdTagFormatGraphite),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
testStruct := &typeStatsdTagFormatTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, strings.ToLower(value), testStruct.Value.String())
|
||||||
|
|
||||||
|
marshalled, err := testStruct.Value.MarshalText()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, strings.ToLower(value), string(marshalled))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeStatsdTagFormatTestSuite) TestValue() {
|
||||||
|
testStruct := &typePreferIPTestStruct{}
|
||||||
|
|
||||||
|
suite.EqualValues(config.TypePreferIPPreferIPv4,
|
||||||
|
testStruct.Value.Value(config.TypePreferIPPreferIPv4))
|
||||||
|
suite.EqualValues(config.TypePreferIPPreferIPv6,
|
||||||
|
testStruct.Value.Value(config.TypePreferIPPreferIPv6))
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": config.TypePreferOnlyIPv4,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.NoError(json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
suite.EqualValues(config.TypePreferOnlyIPv4,
|
||||||
|
testStruct.Value.Value(config.TypePreferOnlyIPv6))
|
||||||
|
suite.EqualValues(config.TypePreferOnlyIPv4,
|
||||||
|
testStruct.Value.Value(config.TypePreferIPPreferIPv6))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeStatsdTagFormat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &TypeStatsdTagFormatTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TypeURL struct {
|
||||||
|
value *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeURL) UnmarshalText(data []byte) error { // nolint: cyclop
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := url.Parse(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("incorrect URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch value.Scheme {
|
||||||
|
case "http", "https", "socks5":
|
||||||
|
case "":
|
||||||
|
return fmt.Errorf("url %s has to have a schema", value)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported schema %s", value.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.Host == "" {
|
||||||
|
return fmt.Errorf("url %s has to have a host", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, _, err := net.SplitHostPort(value.Host); err != nil {
|
||||||
|
switch value.Scheme {
|
||||||
|
case "http":
|
||||||
|
value.Host = net.JoinHostPort(value.Host, "80")
|
||||||
|
case "https":
|
||||||
|
value.Host = net.JoinHostPort(value.Host, "443")
|
||||||
|
case "socks5":
|
||||||
|
value.Host = net.JoinHostPort(value.Host, "1080")
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("cannot set a default port for %s", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.value = value
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypeURL) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(c.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeURL) String() string {
|
||||||
|
if c.value == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.value.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TypeURL) Value(defaultValue *url.URL) *url.URL {
|
||||||
|
if c.value == nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.value
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type typeURLTestStruct struct {
|
||||||
|
Value config.TypeURL `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeURLTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeURLTestSuite) TestUnmarshalNil() {
|
||||||
|
u, _ := url.Parse("https://google.com")
|
||||||
|
|
||||||
|
typ := &config.TypeURL{}
|
||||||
|
suite.NoError(typ.UnmarshalText(nil))
|
||||||
|
suite.Empty(typ.String())
|
||||||
|
suite.Equal("https://google.com", typ.Value(u).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeURLTestSuite) TestUnmarshalFail() {
|
||||||
|
testData := []string{
|
||||||
|
"http:/aaa.com",
|
||||||
|
"ipv4",
|
||||||
|
"111",
|
||||||
|
"://111",
|
||||||
|
"http://aaa.com:xxx",
|
||||||
|
"gopher://aaa.com:888",
|
||||||
|
"gopher://aaa.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range testData {
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": v,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(v, func(t *testing.T) {
|
||||||
|
assert.Error(t, json.Unmarshal(data, &typeURLTestStruct{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeURLTestSuite) TestUnmarshalOk() {
|
||||||
|
testData := map[string]string{
|
||||||
|
"https://10.0.0.10:80": "https://10.0.0.10:80",
|
||||||
|
"https://10.0.0.10:443": "https://10.0.0.10",
|
||||||
|
"http://10.0.0.10:8": "http://10.0.0.10:8",
|
||||||
|
"http://10.0.0.10:80": "http://10.0.0.10",
|
||||||
|
"socks5://10.0.0.10:1080": "socks5://10.0.0.10",
|
||||||
|
"socks5://10.0.0.10:888": "socks5://10.0.0.10:888",
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range testData {
|
||||||
|
expected := k
|
||||||
|
actual := v
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": actual,
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.T().Run(actual, func(t *testing.T) {
|
||||||
|
testStruct := &typeURLTestStruct{}
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, testStruct))
|
||||||
|
assert.Equal(t, expected, testStruct.Value.Value(nil).String())
|
||||||
|
|
||||||
|
marshalled, err := testStruct.Value.MarshalText()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expected, string(marshalled))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *TypeURLTestSuite) TestValue() {
|
||||||
|
testStruct := &typeURLTestStruct{}
|
||||||
|
|
||||||
|
u1, _ := url.Parse("https://10.0.0.10:80")
|
||||||
|
u2, _ := url.Parse("https://10.1.0.10:80")
|
||||||
|
|
||||||
|
suite.Equal("https://10.0.0.10:80", testStruct.Value.Value(u1).String())
|
||||||
|
suite.Equal("https://10.1.0.10:80", testStruct.Value.Value(u2).String())
|
||||||
|
|
||||||
|
data, err := json.Marshal(map[string]string{
|
||||||
|
"value": "http://127.0.0.1:80",
|
||||||
|
})
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.NoError(json.Unmarshal(data, testStruct))
|
||||||
|
|
||||||
|
suite.Equal("http://127.0.0.1:80", testStruct.Value.Value(u1).String())
|
||||||
|
suite.Equal("http://127.0.0.1:80", testStruct.Value.Value(u2).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeURL(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &TypeURLTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package testlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CaptureStdout(callback func()) string {
|
||||||
|
return captureOutput(&os.Stdout, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CaptureStderr(callback func()) string {
|
||||||
|
return captureOutput(&os.Stderr, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
func captureOutput(filefp **os.File, callback func()) string {
|
||||||
|
oldFp := *filefp
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
*filefp = oldFp
|
||||||
|
}()
|
||||||
|
|
||||||
|
reader, writer, _ := os.Pipe()
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
closeChan := make(chan bool)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
io.Copy(buf, reader) // nolint: errcheck
|
||||||
|
close(closeChan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
*filefp = writer
|
||||||
|
|
||||||
|
callback()
|
||||||
|
|
||||||
|
writer.Close()
|
||||||
|
<-closeChan
|
||||||
|
|
||||||
|
return strings.TrimSpace(buf.String())
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package testlib
|
||||||
|
|
||||||
|
import "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
|
type EventsObserverMock struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package testlib
|
||||||
|
|
||||||
|
import "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
|
type MtglibAntiReplayCacheMock struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MtglibAntiReplayCacheMock) SeenBefore(data []byte) bool {
|
||||||
|
return m.Called(data).Bool(0)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package testlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MtglibNetworkMock struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MtglibNetworkMock) Dial(network, address string) (net.Conn, error) {
|
||||||
|
args := m.Called(network, address)
|
||||||
|
|
||||||
|
return args.Get(0).(net.Conn), args.Error(1) // nolint: wrapcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MtglibNetworkMock) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
args := m.Called(ctx, network, address)
|
||||||
|
|
||||||
|
return args.Get(0).(net.Conn), args.Error(1) // nolint: wrapcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MtglibNetworkMock) MakeHTTPClient(dialFunc func(ctx context.Context,
|
||||||
|
network, address string) (net.Conn, error)) *http.Client {
|
||||||
|
return m.Called(dialFunc).Get(0).(*http.Client)
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package testlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetConnMock struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetConnMock) Read(b []byte) (int, error) {
|
||||||
|
args := n.Called(b)
|
||||||
|
|
||||||
|
return args.Int(0), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetConnMock) Write(b []byte) (int, error) {
|
||||||
|
args := n.Called(b)
|
||||||
|
|
||||||
|
return args.Int(0), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetConnMock) Close() error {
|
||||||
|
return n.Called().Error(0) // nolint: wrapcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetConnMock) LocalAddr() net.Addr {
|
||||||
|
return n.Called().Get(0).(net.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetConnMock) RemoteAddr() net.Addr {
|
||||||
|
return n.Called().Get(0).(net.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetConnMock) SetDeadline(t time.Time) error {
|
||||||
|
return n.Called(t).Error(0) // nolint: wrapcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetConnMock) SetReadDeadline(t time.Time) error {
|
||||||
|
return n.Called(t).Error(0) // nolint: wrapcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetConnMock) SetWriteDeadline(t time.Time) error {
|
||||||
|
return n.Called(t).Error(0) // nolint: wrapcheck
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetLimits() error {
|
||||||
|
rLimit := unix.Rlimit{}
|
||||||
|
if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil {
|
||||||
|
return fmt.Errorf("cannot get rlimit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rLimit.Cur = rLimit.Max
|
||||||
|
|
||||||
|
if err := unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil {
|
||||||
|
return fmt.Errorf("cannot set rlimit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
func SetLimits() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RootContext() context.Context {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
|
||||||
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for range sigChan {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RootContext() context.Context {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
|
||||||
|
signal.Notify(sigChan, os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
for range sigChan {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
@@ -0,0 +1,371 @@
|
|||||||
|
package ipblocklist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/kentik/patricia"
|
||||||
|
"github.com/kentik/patricia/bool_tree"
|
||||||
|
"github.com/panjf2000/ants/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fireholIPv4DefaultCIDR = 32
|
||||||
|
fireholIPv6DefaultCIDR = 128
|
||||||
|
)
|
||||||
|
|
||||||
|
var fireholRegexpComment = regexp.MustCompile(`\s*#.*?$`)
|
||||||
|
|
||||||
|
// Firehol is IPBlocklist which uses lists from FireHOL:
|
||||||
|
// https://iplists.firehol.org/
|
||||||
|
//
|
||||||
|
// It can use both local files and remote URLs. This is not necessary
|
||||||
|
// that blocklists should be taken from this website, we expect only
|
||||||
|
// compatible formats here.
|
||||||
|
//
|
||||||
|
// Example of the format:
|
||||||
|
//
|
||||||
|
// # this is a comment
|
||||||
|
// # to ignore
|
||||||
|
// 127.0.0.1 # you can specify an IP
|
||||||
|
// 10.0.0.0/8 # or cidr
|
||||||
|
type Firehol struct {
|
||||||
|
ctx context.Context
|
||||||
|
ctxCancel context.CancelFunc
|
||||||
|
logger mtglib.Logger
|
||||||
|
|
||||||
|
rwMutex sync.RWMutex
|
||||||
|
|
||||||
|
remoteURLs []string
|
||||||
|
localFiles []string
|
||||||
|
|
||||||
|
httpClient *http.Client
|
||||||
|
workerPool *ants.Pool
|
||||||
|
|
||||||
|
treeV4 *bool_tree.TreeV4
|
||||||
|
treeV6 *bool_tree.TreeV6
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown stop a background update process.
|
||||||
|
func (f *Firehol) Shutdown() {
|
||||||
|
f.ctxCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains is given IP list can be found in FireHOL blocklists.
|
||||||
|
func (f *Firehol) Contains(ip net.IP) bool {
|
||||||
|
if ip == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
f.rwMutex.RLock()
|
||||||
|
defer f.rwMutex.RUnlock()
|
||||||
|
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
return f.containsIPv4(ip4)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.containsIPv6(ip.To16())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run starts a background update process.
|
||||||
|
//
|
||||||
|
// This is a blocking method so you probably want to run it in a
|
||||||
|
// goroutine.
|
||||||
|
func (f *Firehol) Run(updateEach time.Duration) {
|
||||||
|
if updateEach == 0 {
|
||||||
|
updateEach = DefaultFireholUpdateEach
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(updateEach)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
ticker.Stop()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := f.update(); err != nil {
|
||||||
|
f.logger.WarningError("cannot update blocklist", err)
|
||||||
|
} else {
|
||||||
|
f.logger.Info("blocklist was updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-f.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := f.update(); err != nil {
|
||||||
|
f.logger.WarningError("cannot update blocklist", err)
|
||||||
|
} else {
|
||||||
|
f.logger.Info("blocklist was updated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firehol) containsIPv4(addr net.IP) bool {
|
||||||
|
ip := patricia.NewIPv4AddressFromBytes(addr, 32)
|
||||||
|
|
||||||
|
if ok, _, err := f.treeV4.FindDeepestTag(ip); ok && err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firehol) containsIPv6(addr net.IP) bool {
|
||||||
|
ip := patricia.NewIPv6Address(addr, 128)
|
||||||
|
|
||||||
|
if ok, _, err := f.treeV6.FindDeepestTag(ip); ok && err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firehol) update() error { // nolint: funlen, cyclop
|
||||||
|
ctx, cancel := context.WithCancel(f.ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(len(f.remoteURLs) + len(f.localFiles))
|
||||||
|
|
||||||
|
treeMutex := &sync.Mutex{}
|
||||||
|
v4tree := bool_tree.NewTreeV4()
|
||||||
|
v6tree := bool_tree.NewTreeV6()
|
||||||
|
|
||||||
|
errorChan := make(chan error, 1)
|
||||||
|
defer close(errorChan)
|
||||||
|
|
||||||
|
for _, v := range f.localFiles {
|
||||||
|
go func(filename string) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if err := f.updateLocalFile(ctx, filename, treeMutex, v4tree, v6tree); err != nil {
|
||||||
|
cancel()
|
||||||
|
f.logger.BindStr("filename", filename).WarningError("cannot update", err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case errorChan <- err:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range f.remoteURLs {
|
||||||
|
value := v
|
||||||
|
|
||||||
|
f.workerPool.Submit(func() { // nolint: errcheck
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if err := f.updateRemoteURL(ctx, value, treeMutex, v4tree, v6tree); err != nil {
|
||||||
|
cancel()
|
||||||
|
f.logger.BindStr("url", value).WarningError("cannot update", err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case errorChan <- err:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errorChan:
|
||||||
|
return fmt.Errorf("cannot update trees: %w", err)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
f.rwMutex.Lock()
|
||||||
|
defer f.rwMutex.Unlock()
|
||||||
|
|
||||||
|
f.treeV4 = v4tree
|
||||||
|
f.treeV6 = v6tree
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firehol) updateLocalFile(ctx context.Context, filename string,
|
||||||
|
mutex sync.Locker,
|
||||||
|
v4tree *bool_tree.TreeV4, v6tree *bool_tree.TreeV6) error {
|
||||||
|
filefp, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot open file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(ctx context.Context, closer io.Closer) {
|
||||||
|
<-ctx.Done()
|
||||||
|
closer.Close()
|
||||||
|
}(ctx, filefp)
|
||||||
|
|
||||||
|
defer filefp.Close()
|
||||||
|
|
||||||
|
return f.updateTrees(mutex, filefp, v4tree, v6tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firehol) updateRemoteURL(ctx context.Context, url string,
|
||||||
|
mutex sync.Locker,
|
||||||
|
v4tree *bool_tree.TreeV4, v6tree *bool_tree.TreeV6) error {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot build a request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := f.httpClient.Do(req) // nolint: bodyclose
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot request a remote URL %s: %w", url, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(ctx context.Context, closer io.Closer) {
|
||||||
|
<-ctx.Done()
|
||||||
|
closer.Close()
|
||||||
|
}(ctx, resp.Body)
|
||||||
|
|
||||||
|
defer func(rc io.ReadCloser) {
|
||||||
|
io.Copy(io.Discard, rc) // nolint: errcheck
|
||||||
|
rc.Close()
|
||||||
|
}(resp.Body)
|
||||||
|
|
||||||
|
return f.updateTrees(mutex, resp.Body, v4tree, v6tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firehol) updateTrees(mutex sync.Locker,
|
||||||
|
reader io.Reader,
|
||||||
|
v4tree *bool_tree.TreeV4,
|
||||||
|
v6tree *bool_tree.TreeV6) error {
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
text := scanner.Text()
|
||||||
|
text = fireholRegexpComment.ReplaceAllLiteralString(text, "")
|
||||||
|
text = strings.TrimSpace(text)
|
||||||
|
|
||||||
|
if text == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, cidr, err := f.updateParseLine(text)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot parse a line: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.updateAddToTrees(ip, cidr, mutex, v4tree, v6tree); err != nil {
|
||||||
|
return fmt.Errorf("cannot add a node to the tree: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanner.Err() != nil {
|
||||||
|
return fmt.Errorf("cannot parse a response: %w", scanner.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firehol) updateParseLine(text string) (net.IP, uint, error) {
|
||||||
|
_, ipnet, err := net.ParseCIDR(text)
|
||||||
|
if err != nil {
|
||||||
|
ipaddr := net.ParseIP(text)
|
||||||
|
if ipaddr == nil {
|
||||||
|
return nil, 0, fmt.Errorf("incorrect ip address %s", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip4 := ipaddr.To4()
|
||||||
|
if ip4 != nil {
|
||||||
|
return ip4, fireholIPv4DefaultCIDR, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipaddr.To16(), fireholIPv6DefaultCIDR, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ones, _ := ipnet.Mask.Size()
|
||||||
|
|
||||||
|
return ipnet.IP, uint(ones), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firehol) updateAddToTrees(ip net.IP, cidr uint,
|
||||||
|
mutex sync.Locker,
|
||||||
|
v4tree *bool_tree.TreeV4, v6tree *bool_tree.TreeV6) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
if ip.To4() != nil {
|
||||||
|
addr := patricia.NewIPv4AddressFromBytes(ip, cidr)
|
||||||
|
|
||||||
|
if _, _, err := v4tree.Set(addr, true); err != nil {
|
||||||
|
return err // nolint: wrapcheck
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addr := patricia.NewIPv6Address(ip, cidr)
|
||||||
|
|
||||||
|
if _, _, err := v6tree.Set(addr, true); err != nil {
|
||||||
|
return err // nolint: wrapcheck
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFirehol creates a new instance of FireHOL IP blocklist.
|
||||||
|
//
|
||||||
|
// This method does not start an update process so please execute Run
|
||||||
|
// when it is necessary.
|
||||||
|
func NewFirehol(logger mtglib.Logger, network mtglib.Network,
|
||||||
|
downloadConcurrency uint,
|
||||||
|
remoteURLs []string,
|
||||||
|
localFiles []string) (*Firehol, error) {
|
||||||
|
for _, v := range remoteURLs {
|
||||||
|
parsed, err := url.Parse(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("incorrect url %s: %w", v, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch parsed.Scheme {
|
||||||
|
case "http", "https":
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported url %s", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range localFiles {
|
||||||
|
if stat, err := os.Stat(v); os.IsNotExist(err) || stat.IsDir() || stat.Mode().Perm()&0o400 == 0 {
|
||||||
|
return nil, fmt.Errorf("%s is not a readable file", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if downloadConcurrency == 0 {
|
||||||
|
downloadConcurrency = DefaultFireholDownloadConcurrency
|
||||||
|
}
|
||||||
|
|
||||||
|
workerPool, _ := ants.NewPool(int(downloadConcurrency))
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
return &Firehol{
|
||||||
|
ctx: ctx,
|
||||||
|
ctxCancel: cancel,
|
||||||
|
logger: logger.Named("firehol"),
|
||||||
|
httpClient: network.MakeHTTPClient(nil),
|
||||||
|
treeV4: bool_tree.NewTreeV4(),
|
||||||
|
treeV6: bool_tree.NewTreeV6(),
|
||||||
|
workerPool: workerPool,
|
||||||
|
remoteURLs: remoteURLs,
|
||||||
|
localFiles: localFiles,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package ipblocklist_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/testlib"
|
||||||
|
"github.com/9seconds/mtg/v2/ipblocklist"
|
||||||
|
"github.com/9seconds/mtg/v2/logger"
|
||||||
|
"github.com/9seconds/mtg/v2/network"
|
||||||
|
"github.com/jarcoal/httpmock"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FireholTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
networkMock *testlib.MtglibNetworkMock
|
||||||
|
httpServer *httptest.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FireholTestSuite) SetupSuite() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
filefp, err := os.Open(filepath.Join("testdata", "remote_ipset.ipset"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer filefp.Close()
|
||||||
|
|
||||||
|
io.Copy(w, filefp) // nolint: errcheck
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.httpServer = httptest.NewServer(mux)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FireholTestSuite) SetupTest() {
|
||||||
|
httpClient := &http.Client{}
|
||||||
|
suite.networkMock = &testlib.MtglibNetworkMock{}
|
||||||
|
|
||||||
|
httpmock.ActivateNonDefault(httpClient)
|
||||||
|
|
||||||
|
suite.networkMock.
|
||||||
|
On("MakeHTTPClient", mock.Anything).
|
||||||
|
Maybe().
|
||||||
|
Return(httpClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FireholTestSuite) TearDownTest() {
|
||||||
|
suite.networkMock.AssertExpectations(suite.T())
|
||||||
|
httpmock.DeactivateAndReset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FireholTestSuite) TearDownSuite() {
|
||||||
|
suite.httpServer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FireholTestSuite) TestLocalFail() {
|
||||||
|
blocklist, err := ipblocklist.NewFirehol(logger.NewNoopLogger(),
|
||||||
|
suite.networkMock, 2,
|
||||||
|
nil, []string{filepath.Join("testdata", "broken_ipset.ipset")})
|
||||||
|
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
go blocklist.Run(time.Hour)
|
||||||
|
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
suite.False(blocklist.Contains(net.ParseIP("10.0.0.10")))
|
||||||
|
suite.False(blocklist.Contains(net.ParseIP("127.0.0.1")))
|
||||||
|
|
||||||
|
blocklist.Shutdown()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FireholTestSuite) TestLocalOk() {
|
||||||
|
blocklist, err := ipblocklist.NewFirehol(logger.NewNoopLogger(),
|
||||||
|
suite.networkMock, 2,
|
||||||
|
nil, []string{filepath.Join("testdata", "good_ipset.ipset")})
|
||||||
|
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
go blocklist.Run(time.Hour)
|
||||||
|
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
suite.True(blocklist.Contains(net.ParseIP("10.0.0.10")))
|
||||||
|
suite.False(blocklist.Contains(net.ParseIP("127.0.0.1")))
|
||||||
|
|
||||||
|
blocklist.Shutdown()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FireholTestSuite) TestRemoteFail() {
|
||||||
|
blocklist, err := ipblocklist.NewFirehol(logger.NewNoopLogger(),
|
||||||
|
suite.networkMock, 2,
|
||||||
|
[]string{"https://google.com"}, nil)
|
||||||
|
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
go blocklist.Run(time.Hour)
|
||||||
|
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
suite.False(blocklist.Contains(net.ParseIP("10.2.2.2")))
|
||||||
|
|
||||||
|
blocklist.Shutdown()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *FireholTestSuite) TestMixed() {
|
||||||
|
dialer, _ := network.NewDefaultDialer(0, 0)
|
||||||
|
ntw, _ := network.NewNetwork(dialer, "mtg", "1.1.1.1", 0)
|
||||||
|
|
||||||
|
blocklist, err := ipblocklist.NewFirehol(logger.NewNoopLogger(),
|
||||||
|
ntw, 2,
|
||||||
|
[]string{
|
||||||
|
suite.httpServer.URL,
|
||||||
|
}, []string{
|
||||||
|
filepath.Join("testdata", "good_ipset.ipset"),
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
go blocklist.Run(time.Hour)
|
||||||
|
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
suite.True(blocklist.Contains(net.ParseIP("10.2.2.2")))
|
||||||
|
suite.True(blocklist.Contains(net.ParseIP("10.1.0.100")))
|
||||||
|
|
||||||
|
blocklist.Shutdown()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFirehol(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &FireholTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// Package ipblocklist contains default implementation of the
|
||||||
|
// IPBlocklist for mtg.
|
||||||
|
//
|
||||||
|
// Please check documentation for mtglib.IPBlocklist interface to get an
|
||||||
|
// idea of this abstraction.
|
||||||
|
package ipblocklist
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultFireholDownloadConcurrency defines a default max number of
|
||||||
|
// concurrent downloads of ip blocklists for Firehol.
|
||||||
|
DefaultFireholDownloadConcurrency = 1
|
||||||
|
|
||||||
|
// DefaultFireholUpdateEach defines a default time period when
|
||||||
|
// Firehol requests updates of the blocklists.
|
||||||
|
DefaultFireholUpdateEach = 6 * time.Hour
|
||||||
|
)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package ipblocklist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
)
|
||||||
|
|
||||||
|
type noop struct{}
|
||||||
|
|
||||||
|
func (n noop) Contains(ip net.IP) bool { return false }
|
||||||
|
|
||||||
|
// NewNoop returns a dummy ipblocklist which allows all incoming
|
||||||
|
// connections.
|
||||||
|
func NewNoop() mtglib.IPBlocklist {
|
||||||
|
return noop{}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package ipblocklist_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/ipblocklist"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NoopTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *NoopTestSuite) TestOp() {
|
||||||
|
suite.False(ipblocklist.NewNoop().Contains(net.ParseIP("10.0.0.10")))
|
||||||
|
suite.False(ipblocklist.NewNoop().Contains(net.ParseIP("10.0.0.10")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoop(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &NoopTestSuite{})
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#
|
||||||
|
# This is an intentionally broken ipset.
|
||||||
|
#
|
||||||
|
|
||||||
|
ajsdkfbd
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
# This is very good ipset
|
||||||
|
#
|
||||||
|
|
||||||
|
10.0.0.10 # just an example
|
||||||
|
10.1.0.0/24
|
||||||
|
2001:0db8:85a3:0000:0000:8a2e:0370:7334
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
10.2.2.2
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// Package logger has implementation of loggers for mtglib.Logger
|
||||||
|
// interface.
|
||||||
|
//
|
||||||
|
// Please see a description of that interface to get some agreements
|
||||||
|
// which are used by mtglib.
|
||||||
|
package logger
|
||||||
|
|
||||||
|
// StdLikeLogger is an interface which is close to log.Logger. This is
|
||||||
|
// commonly used by many 3pp tools. While mtglib itself does not need
|
||||||
|
// it, it is always a good idea to support it and have a transient end
|
||||||
|
// to end logging.
|
||||||
|
type StdLikeLogger interface {
|
||||||
|
Printf(format string, args ...interface{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import "github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
|
||||||
|
type noopLogger struct{}
|
||||||
|
|
||||||
|
func (n noopLogger) Named(_ string) mtglib.Logger { return n }
|
||||||
|
func (n noopLogger) BindInt(_ string, _ int) mtglib.Logger { return n }
|
||||||
|
func (n noopLogger) BindStr(_, _ string) mtglib.Logger { return n }
|
||||||
|
func (n noopLogger) Printf(_ string, _ ...interface{}) {}
|
||||||
|
func (n noopLogger) Info(_ string) {}
|
||||||
|
func (n noopLogger) Warning(_ string) {}
|
||||||
|
func (n noopLogger) Debug(_ string) {}
|
||||||
|
func (n noopLogger) InfoError(_ string, _ error) {}
|
||||||
|
func (n noopLogger) WarningError(_ string, _ error) {}
|
||||||
|
func (n noopLogger) DebugError(_ string, _ error) {}
|
||||||
|
|
||||||
|
// NewNoopLogger returns a logger which discards all events.
|
||||||
|
func NewNoopLogger() mtglib.Logger {
|
||||||
|
return noopLogger{}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package logger_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/testlib"
|
||||||
|
"github.com/9seconds/mtg/v2/logger"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NoopLoggerTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *NoopLoggerTestSuite) TestLog() {
|
||||||
|
suite.Empty(testlib.CaptureStdout(func() {
|
||||||
|
suite.Empty(testlib.CaptureStderr(func() {
|
||||||
|
log := logger.NewNoopLogger().Named("name")
|
||||||
|
|
||||||
|
log.BindInt("int", 1).BindStr("str", "1").Printf("info", 1, 2)
|
||||||
|
log.BindInt("int", 1).BindStr("str", "1").Info("info")
|
||||||
|
log.BindInt("int", 1).BindStr("str", "1").Warning("info")
|
||||||
|
log.BindInt("int", 1).BindStr("str", "1").Debug("info")
|
||||||
|
log.BindInt("int", 1).BindStr("str", "1").InfoError("info", io.EOF)
|
||||||
|
log.BindInt("int", 1).BindStr("str", "1").WarningError("info", io.EOF)
|
||||||
|
log.BindInt("int", 1).BindStr("str", "1").DebugError("info", io.EOF)
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoopLogger(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &NoopLoggerTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const loggerFieldName = "logger"
|
||||||
|
|
||||||
|
type zeroLogContextVarType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
zeroLogContextVarTypeUnknown zeroLogContextVarType = iota
|
||||||
|
zeroLogContextVarTypeStr
|
||||||
|
zeroLogContextVarTypeInt
|
||||||
|
)
|
||||||
|
|
||||||
|
type zeroLogContext struct {
|
||||||
|
name string
|
||||||
|
log *zerolog.Logger
|
||||||
|
|
||||||
|
ctxVarType zeroLogContextVarType
|
||||||
|
ctxVarName string
|
||||||
|
ctxVarStr string
|
||||||
|
ctxVarInt int
|
||||||
|
|
||||||
|
parent *zeroLogContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zeroLogContext) Named(name string) mtglib.Logger {
|
||||||
|
loggerName := z.name
|
||||||
|
if loggerName == "" {
|
||||||
|
loggerName = name
|
||||||
|
} else {
|
||||||
|
loggerName += "." + name
|
||||||
|
}
|
||||||
|
|
||||||
|
return &zeroLogContext{
|
||||||
|
name: loggerName,
|
||||||
|
log: z.log,
|
||||||
|
parent: z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zeroLogContext) BindInt(name string, value int) mtglib.Logger {
|
||||||
|
return &zeroLogContext{
|
||||||
|
name: z.name,
|
||||||
|
log: z.log,
|
||||||
|
ctxVarType: zeroLogContextVarTypeInt,
|
||||||
|
ctxVarInt: value,
|
||||||
|
ctxVarName: name,
|
||||||
|
parent: z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zeroLogContext) BindStr(name, value string) mtglib.Logger {
|
||||||
|
return &zeroLogContext{
|
||||||
|
name: z.name,
|
||||||
|
log: z.log,
|
||||||
|
ctxVarType: zeroLogContextVarTypeStr,
|
||||||
|
ctxVarStr: value,
|
||||||
|
ctxVarName: name,
|
||||||
|
parent: z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zeroLogContext) Printf(format string, args ...interface{}) {
|
||||||
|
z.Debug(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zeroLogContext) Info(msg string) {
|
||||||
|
z.InfoError(msg, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zeroLogContext) Warning(msg string) {
|
||||||
|
z.WarningError(msg, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zeroLogContext) Debug(msg string) {
|
||||||
|
z.DebugError(msg, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zeroLogContext) InfoError(msg string, err error) {
|
||||||
|
z.emitLog(z.log.Info(), msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zeroLogContext) WarningError(msg string, err error) {
|
||||||
|
z.emitLog(z.log.Warn(), msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zeroLogContext) DebugError(msg string, err error) {
|
||||||
|
z.emitLog(z.log.Debug(), msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zeroLogContext) emitLog(evt *zerolog.Event, msg string, err error) {
|
||||||
|
z.attachCtx(evt)
|
||||||
|
|
||||||
|
for current := z.parent; current != nil; current = current.parent {
|
||||||
|
current.attachCtx(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
evt.Str(loggerFieldName, z.name).Err(err).Msg(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *zeroLogContext) attachCtx(evt *zerolog.Event) {
|
||||||
|
switch z.ctxVarType {
|
||||||
|
case zeroLogContextVarTypeStr:
|
||||||
|
evt.Str(z.ctxVarName, z.ctxVarStr)
|
||||||
|
case zeroLogContextVarTypeInt:
|
||||||
|
evt.Int(z.ctxVarName, z.ctxVarInt)
|
||||||
|
case zeroLogContextVarTypeUnknown:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewZeroLogger returns a logger which is using rs/zerolog library.
|
||||||
|
func NewZeroLogger(log zerolog.Logger) mtglib.Logger {
|
||||||
|
return &zeroLogContext{
|
||||||
|
log: &log,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package logger_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/logger"
|
||||||
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type zeroLoggerLogMessage struct {
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Level string `json:"level"`
|
||||||
|
StrParam string `json:"strparam"`
|
||||||
|
IntParam int `json:"intparam"`
|
||||||
|
Logger string `json:"logger"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ZeroLoggerTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ZeroLoggerTestSuite) SetupSuite() {
|
||||||
|
zerolog.SetGlobalLevel(zerolog.TraceLevel)
|
||||||
|
|
||||||
|
zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
|
||||||
|
zerolog.TimestampFieldName = "timestamp"
|
||||||
|
zerolog.LevelFieldName = "level"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ZeroLoggerTestSuite) TestLog() {
|
||||||
|
testData := map[string]func(mtglib.Logger){
|
||||||
|
"info": func(l mtglib.Logger) { l.Info("hello") },
|
||||||
|
"warn": func(l mtglib.Logger) { l.Warning("hello") },
|
||||||
|
"printf": func(l mtglib.Logger) { l.Printf("hello") },
|
||||||
|
"debug": func(l mtglib.Logger) { l.Debug("hello") },
|
||||||
|
"info-error": func(l mtglib.Logger) { l.InfoError("hello", io.EOF) },
|
||||||
|
"warn-error": func(l mtglib.Logger) { l.WarningError("hello", io.EOF) },
|
||||||
|
"debug-error": func(l mtglib.Logger) { l.DebugError("hello", io.EOF) },
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range testData {
|
||||||
|
name := k
|
||||||
|
callback := v
|
||||||
|
level := strings.TrimSuffix(name, "-error")
|
||||||
|
|
||||||
|
suite.T().Run(name, func(t *testing.T) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
log := logger.NewZeroLogger(zerolog.New(buf).With().Timestamp().Logger())
|
||||||
|
|
||||||
|
callback(log.Named("name").BindInt("intparam", 1).BindStr("strparam", name))
|
||||||
|
|
||||||
|
msg := &zeroLoggerLogMessage{}
|
||||||
|
assert.NoError(t, json.Unmarshal(buf.Bytes(), msg))
|
||||||
|
|
||||||
|
timestamp := time.Unix(msg.Timestamp/1000, (msg.Timestamp%1000)*1_000_000)
|
||||||
|
assert.WithinDuration(t, time.Now(), timestamp, 100*time.Millisecond)
|
||||||
|
|
||||||
|
if level == "printf" {
|
||||||
|
level = "debug"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, level, msg.Level)
|
||||||
|
assert.Equal(t, name, msg.StrParam)
|
||||||
|
assert.EqualValues(t, 1, msg.IntParam)
|
||||||
|
assert.Equal(t, "name", msg.Logger)
|
||||||
|
assert.Equal(t, "hello", msg.Message)
|
||||||
|
|
||||||
|
if level != name && name != "printf" {
|
||||||
|
assert.Equal(t, io.EOF.Error(), msg.Error)
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, msg.Error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ZeroLoggerTestSuite) TestIndependence() {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
log := logger.NewZeroLogger(zerolog.New(buf).With().Timestamp().Logger())
|
||||||
|
|
||||||
|
log1 := log.Named("1")
|
||||||
|
log2 := log.Named("2")
|
||||||
|
log12 := log1.Named("2")
|
||||||
|
|
||||||
|
log1.BindInt("param", 1).Info("hello")
|
||||||
|
|
||||||
|
log1Output := buf.String()
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
log2.BindInt("lalala", 2).Info("hello")
|
||||||
|
|
||||||
|
log2Output := buf.String()
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
log12.BindStr("tttt", "qqq").Info("hello")
|
||||||
|
|
||||||
|
log12Output := buf.String()
|
||||||
|
|
||||||
|
suite.NotContains("lalala", log1Output)
|
||||||
|
suite.NotContains("tttt", log1Output)
|
||||||
|
suite.NotContains("param", log2Output)
|
||||||
|
suite.NotContains("tttt", log1Output)
|
||||||
|
suite.NotContains("param", log12Output)
|
||||||
|
suite.NotContains("lalala", log12Output)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestZeroLogger(t *testing.T) { // nolint: paralleltest
|
||||||
|
suite.Run(t, &ZeroLoggerTestSuite{})
|
||||||
|
}
|
||||||
@@ -1,166 +1,35 @@
|
|||||||
|
// mtg is just a command-line application that starts a proxy.
|
||||||
|
//
|
||||||
|
// Application logic is how to read a config and configure mtglib.Proxy.
|
||||||
|
// So, probably you need to read the documentation for mtglib package
|
||||||
|
// first.
|
||||||
|
//
|
||||||
|
// mtglib is a core of the application. The rest of the packages provide
|
||||||
|
// some default implementations for the interfaces, defined in mtglib.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate scripts/generate_version.sh
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"github.com/9seconds/mtg/v2/internal/cli"
|
||||||
"go.uber.org/zap/zapcore"
|
"github.com/9seconds/mtg/v2/internal/utils"
|
||||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
"github.com/alecthomas/kong"
|
||||||
|
|
||||||
"github.com/9seconds/mtg/config"
|
|
||||||
"github.com/9seconds/mtg/proxy"
|
|
||||||
"github.com/9seconds/mtg/stats"
|
|
||||||
"github.com/juju/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var version = "dev" // has to be set by ldflags
|
||||||
app = kingpin.New("mtg", "Simple MTPROTO proxy.")
|
|
||||||
|
|
||||||
debug = app.Flag("debug", "Run in debug mode.").
|
|
||||||
Short('d').
|
|
||||||
Envar("MTG_DEBUG").
|
|
||||||
Bool()
|
|
||||||
verbose = app.Flag("verbose", "Run in verbose mode.").
|
|
||||||
Short('v').
|
|
||||||
Envar("MTG_VERBOSE").
|
|
||||||
Bool()
|
|
||||||
|
|
||||||
bindIP = app.Flag("bind-ip", "Which IP to bind to.").
|
|
||||||
Short('b').
|
|
||||||
Envar("MTG_IP").
|
|
||||||
Default("127.0.0.1").
|
|
||||||
IP()
|
|
||||||
bindPort = app.Flag("bind-port", "Which port to bind to.").
|
|
||||||
Short('p').
|
|
||||||
Envar("MTG_PORT").
|
|
||||||
Default("3128").
|
|
||||||
Uint16()
|
|
||||||
|
|
||||||
publicIPv4 = app.Flag("public-ipv4", "Which IPv4 address is public.").
|
|
||||||
Short('4').
|
|
||||||
Envar("MTG_IPV4").
|
|
||||||
IP()
|
|
||||||
publicIPv4Port = app.Flag("public-ipv4-port", "Which IPv4 port is public. Default is 'bind-port' value.").
|
|
||||||
Envar("MTG_IPV4_PORT").
|
|
||||||
Uint16()
|
|
||||||
|
|
||||||
publicIPv6 = app.Flag("public-ipv6", "Which IPv6 address is public.").
|
|
||||||
Short('6').
|
|
||||||
Envar("MTG_IPV6").
|
|
||||||
IP()
|
|
||||||
publicIPv6Port = app.Flag("public-ipv6-port", "Which IPv6 port is public. Default is 'bind-port' value.").
|
|
||||||
Envar("MTG_IPV6_PORT").
|
|
||||||
Uint16()
|
|
||||||
|
|
||||||
statsIP = app.Flag("stats-ip", "Which IP bind stats server to").
|
|
||||||
Short('t').
|
|
||||||
Envar("MTG_STATS_IP").
|
|
||||||
Default("127.0.0.1").
|
|
||||||
IP()
|
|
||||||
statsPort = app.Flag("stats-port", "Which port bind stats to.").
|
|
||||||
Short('q').
|
|
||||||
Envar("MTG_STATS_PORT").
|
|
||||||
Default("3129").
|
|
||||||
Uint16()
|
|
||||||
|
|
||||||
secret = app.Arg("secret", "Secret of this proxy.").Required().String()
|
|
||||||
adtag = app.Arg("adtag", "ADTag of the proxy.").String()
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UTC().UnixNano())
|
|
||||||
app.Version(version)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
kingpin.MustParse(app.Parse(os.Args[1:]))
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
|
|
||||||
err := setRLimit()
|
if err := utils.SetLimits(); err != nil {
|
||||||
if err != nil {
|
|
||||||
usage(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
conf, err := config.NewConfig(*debug, *verbose,
|
|
||||||
*bindIP, *bindPort,
|
|
||||||
*publicIPv4, *publicIPv4Port,
|
|
||||||
*publicIPv6, *publicIPv6Port,
|
|
||||||
*statsIP, *statsPort,
|
|
||||||
*secret, *adtag,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
usage(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
atom := zap.NewAtomicLevel()
|
|
||||||
if conf.Debug {
|
|
||||||
atom.SetLevel(zapcore.DebugLevel)
|
|
||||||
} else if conf.Verbose {
|
|
||||||
atom.SetLevel(zapcore.InfoLevel)
|
|
||||||
} else {
|
|
||||||
atom.SetLevel(zapcore.ErrorLevel)
|
|
||||||
}
|
|
||||||
encoderCfg := zap.NewProductionEncoderConfig()
|
|
||||||
logger := zap.New(zapcore.NewCore(
|
|
||||||
zapcore.NewJSONEncoder(encoderCfg),
|
|
||||||
zapcore.Lock(os.Stderr),
|
|
||||||
atom,
|
|
||||||
))
|
|
||||||
zap.ReplaceGlobals(logger)
|
|
||||||
defer logger.Sync() // nolint: errcheck
|
|
||||||
|
|
||||||
printURLs(conf.GetURLs())
|
|
||||||
|
|
||||||
if conf.UseMiddleProxy() {
|
|
||||||
zap.S().Infow("Use middle proxy connection to Telegram")
|
|
||||||
} else {
|
|
||||||
zap.S().Infow("Use direct connection to Telegram")
|
|
||||||
}
|
|
||||||
|
|
||||||
go stats.Start(conf)
|
|
||||||
|
|
||||||
server := proxy.NewProxy(conf)
|
|
||||||
if err := server.Serve(); err != nil {
|
|
||||||
zap.S().Fatalw("Server stopped", "error", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setRLimit() (err error) {
|
|
||||||
rLimit := syscall.Rlimit{}
|
|
||||||
err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
|
|
||||||
if err != nil {
|
|
||||||
err = errors.Annotate(err, "Cannot get rlimit")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rLimit.Cur = rLimit.Max
|
|
||||||
|
|
||||||
err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
|
|
||||||
if err != nil {
|
|
||||||
err = errors.Annotate(err, "Cannot set rlimit")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func printURLs(data interface{}) {
|
|
||||||
encoder := json.NewEncoder(os.Stdout)
|
|
||||||
encoder.SetEscapeHTML(false)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
|
|
||||||
err := encoder.Encode(data)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func usage(msg string) {
|
cli := &cli.CLI{}
|
||||||
io.WriteString(os.Stderr, msg+"\n") // nolint: errcheck
|
ctx := kong.Parse(cli, kong.Vars{
|
||||||
os.Exit(1)
|
"version": version,
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.FatalIfErrorf(ctx.Run(cli, version))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package mtglib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type connTraffic struct {
|
||||||
|
net.Conn
|
||||||
|
|
||||||
|
streamID string
|
||||||
|
stream EventStream
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c connTraffic) Read(b []byte) (int, error) {
|
||||||
|
n, err := c.Conn.Read(b)
|
||||||
|
|
||||||
|
if n > 0 {
|
||||||
|
c.stream.Send(c.ctx, NewEventTraffic(c.streamID, uint(n), true))
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, err // nolint: wrapcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c connTraffic) Write(b []byte) (int, error) {
|
||||||
|
n, err := c.Conn.Write(b)
|
||||||
|
|
||||||
|
if n > 0 {
|
||||||
|
c.stream.Send(c.ctx, NewEventTraffic(c.streamID, uint(n), false))
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, err // nolint: wrapcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
type connRewind struct {
|
||||||
|
net.Conn
|
||||||
|
|
||||||
|
active io.Reader
|
||||||
|
buf bytes.Buffer
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connRewind) Read(p []byte) (int, error) {
|
||||||
|
c.mutex.RLock()
|
||||||
|
defer c.mutex.RUnlock()
|
||||||
|
|
||||||
|
return c.active.Read(p) // nolint: wrapcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connRewind) Rewind() {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
c.active = io.MultiReader(&c.buf, c.Conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConnRewind(conn net.Conn) *connRewind {
|
||||||
|
rv := &connRewind{
|
||||||
|
Conn: conn,
|
||||||
|
}
|
||||||
|
rv.active = io.TeeReader(conn, &rv.buf)
|
||||||
|
|
||||||
|
return rv
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
package mtglib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/internal/testlib"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConnRewindBaseConn struct {
|
||||||
|
testlib.NetConnMock
|
||||||
|
|
||||||
|
readBuffer bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnRewindBaseConn) Read(p []byte) (int, error) {
|
||||||
|
c.Called(p)
|
||||||
|
|
||||||
|
return c.readBuffer.Read(p) // nolint: wrapcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnTrafficTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
eventStreamMock *EventStreamMock
|
||||||
|
connMock *testlib.NetConnMock
|
||||||
|
conn io.ReadWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConnTrafficTestSuite) SetupTest() {
|
||||||
|
suite.eventStreamMock = &EventStreamMock{}
|
||||||
|
suite.connMock = &testlib.NetConnMock{}
|
||||||
|
suite.conn = connTraffic{
|
||||||
|
Conn: suite.connMock,
|
||||||
|
streamID: "CONNID",
|
||||||
|
ctx: context.Background(),
|
||||||
|
stream: suite.eventStreamMock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConnTrafficTestSuite) TearDownTest() {
|
||||||
|
suite.eventStreamMock.AssertExpectations(suite.T())
|
||||||
|
suite.connMock.AssertExpectations(suite.T())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConnTrafficTestSuite) TestReadOk() {
|
||||||
|
suite.eventStreamMock.
|
||||||
|
On("Send", mock.Anything, mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
evt, ok := args.Get(1).(EventTraffic)
|
||||||
|
|
||||||
|
suite.True(ok)
|
||||||
|
suite.Equal("CONNID", evt.StreamID())
|
||||||
|
suite.WithinDuration(time.Now(), evt.Timestamp(), time.Second)
|
||||||
|
suite.EqualValues(10, evt.Traffic)
|
||||||
|
suite.True(evt.IsRead)
|
||||||
|
})
|
||||||
|
suite.connMock.On("Read", mock.Anything).Once().Return(10, nil)
|
||||||
|
|
||||||
|
n, err := suite.conn.Read(make([]byte, 10))
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Equal(10, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConnTrafficTestSuite) TestReadErr() { // nolint: dupl
|
||||||
|
suite.eventStreamMock.
|
||||||
|
On("Send", mock.Anything, mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
evt, ok := args.Get(1).(EventTraffic)
|
||||||
|
|
||||||
|
suite.True(ok)
|
||||||
|
suite.Equal("CONNID", evt.StreamID())
|
||||||
|
suite.WithinDuration(time.Now(), evt.Timestamp(), time.Second)
|
||||||
|
suite.EqualValues(10, evt.Traffic)
|
||||||
|
suite.True(evt.IsRead)
|
||||||
|
})
|
||||||
|
suite.connMock.On("Read", mock.Anything).Once().Return(10, io.EOF)
|
||||||
|
|
||||||
|
n, err := suite.conn.Read(make([]byte, 10))
|
||||||
|
suite.True(errors.Is(err, io.EOF))
|
||||||
|
suite.Equal(10, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConnTrafficTestSuite) TestReadNothingOk() {
|
||||||
|
suite.connMock.On("Read", mock.Anything).Once().Return(0, nil)
|
||||||
|
|
||||||
|
n, err := suite.conn.Read(make([]byte, 10))
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Equal(0, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConnTrafficTestSuite) TestReadNothingErr() {
|
||||||
|
suite.connMock.On("Read", mock.Anything).Once().Return(0, io.EOF)
|
||||||
|
|
||||||
|
n, err := suite.conn.Read(make([]byte, 10))
|
||||||
|
suite.True(errors.Is(err, io.EOF))
|
||||||
|
suite.Equal(0, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConnTrafficTestSuite) TestWriteOk() {
|
||||||
|
suite.eventStreamMock.
|
||||||
|
On("Send", mock.Anything, mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
evt, ok := args.Get(1).(EventTraffic)
|
||||||
|
|
||||||
|
suite.True(ok)
|
||||||
|
suite.Equal("CONNID", evt.StreamID())
|
||||||
|
suite.WithinDuration(time.Now(), evt.Timestamp(), time.Second)
|
||||||
|
suite.EqualValues(10, evt.Traffic)
|
||||||
|
suite.False(evt.IsRead)
|
||||||
|
})
|
||||||
|
suite.connMock.On("Write", mock.Anything).Once().Return(10, nil)
|
||||||
|
|
||||||
|
n, err := suite.conn.Write(make([]byte, 10))
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Equal(10, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConnTrafficTestSuite) TestWriteErr() { // nolint: dupl
|
||||||
|
suite.eventStreamMock.
|
||||||
|
On("Send", mock.Anything, mock.Anything).
|
||||||
|
Once().
|
||||||
|
Run(func(args mock.Arguments) {
|
||||||
|
evt, ok := args.Get(1).(EventTraffic)
|
||||||
|
|
||||||
|
suite.True(ok)
|
||||||
|
suite.Equal("CONNID", evt.StreamID())
|
||||||
|
suite.WithinDuration(time.Now(), evt.Timestamp(), time.Second)
|
||||||
|
suite.EqualValues(10, evt.Traffic)
|
||||||
|
suite.False(evt.IsRead)
|
||||||
|
})
|
||||||
|
suite.connMock.On("Write", mock.Anything).Once().Return(10, io.EOF)
|
||||||
|
|
||||||
|
n, err := suite.conn.Write(make([]byte, 10))
|
||||||
|
suite.True(errors.Is(err, io.EOF))
|
||||||
|
suite.Equal(10, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConnTrafficTestSuite) TestWriteNothingOk() {
|
||||||
|
suite.connMock.On("Write", mock.Anything).Once().Return(0, nil)
|
||||||
|
|
||||||
|
n, err := suite.conn.Write(make([]byte, 10))
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Equal(0, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConnTrafficTestSuite) TestWriteNothingErr() {
|
||||||
|
suite.connMock.On("Write", mock.Anything).Once().Return(0, io.EOF)
|
||||||
|
|
||||||
|
n, err := suite.conn.Write(make([]byte, 10))
|
||||||
|
suite.True(errors.Is(err, io.EOF))
|
||||||
|
suite.Equal(0, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnRewindTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
connMock *ConnRewindBaseConn
|
||||||
|
conn *connRewind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConnRewindTestSuite) SetupTest() {
|
||||||
|
suite.connMock = &ConnRewindBaseConn{}
|
||||||
|
suite.conn = newConnRewind(suite.connMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConnRewindTestSuite) TearDownTest() {
|
||||||
|
suite.connMock.AssertExpectations(suite.T())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConnRewindTestSuite) TestRead() {
|
||||||
|
suite.connMock.On("Read", mock.Anything)
|
||||||
|
suite.connMock.readBuffer.Write([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
|
||||||
|
|
||||||
|
buf := make([]byte, 2)
|
||||||
|
|
||||||
|
n, err := suite.conn.Read(buf)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Equal(2, n)
|
||||||
|
suite.Equal([]byte{1, 2}, buf)
|
||||||
|
|
||||||
|
n, err = suite.conn.Read(buf)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Equal(2, n)
|
||||||
|
suite.Equal([]byte{3, 4}, buf)
|
||||||
|
|
||||||
|
suite.conn.Rewind()
|
||||||
|
|
||||||
|
data, err := io.ReadAll(suite.conn)
|
||||||
|
suite.NoError(err)
|
||||||
|
suite.Equal([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnTraffic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &ConnTrafficTestSuite{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnRewind(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
suite.Run(t, &ConnRewindTestSuite{})
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package mtglib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type eventBase struct {
|
||||||
|
streamID string
|
||||||
|
timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamID returns a ID of the stream this event belongs to.
|
||||||
|
func (e eventBase) StreamID() string {
|
||||||
|
return e.streamID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timestamp return a time when this event was generated.
|
||||||
|
func (e eventBase) Timestamp() time.Time {
|
||||||
|
return e.timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventStart is emitted when mtg proxy starts to process a new
|
||||||
|
// connection.
|
||||||
|
type EventStart struct {
|
||||||
|
eventBase
|
||||||
|
|
||||||
|
// RemoteIP is an IP address of the client.
|
||||||
|
RemoteIP net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventConnectedToDC is emitted when mtg proxy has connected to a
|
||||||
|
// Telegram server.
|
||||||
|
type EventConnectedToDC struct {
|
||||||
|
eventBase
|
||||||
|
|
||||||
|
// RemoteIP is an IP address of the Telegram server proxy has been
|
||||||
|
// connected to.
|
||||||
|
RemoteIP net.IP
|
||||||
|
|
||||||
|
// DC is an index of the datacenter proxy has been connected to.
|
||||||
|
DC int
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventTraffic is emitted when we read/write some bytes on a connection.
|
||||||
|
type EventTraffic struct {
|
||||||
|
eventBase
|
||||||
|
|
||||||
|
// Traffic is a count of bytes which were transmitted.
|
||||||
|
Traffic uint
|
||||||
|
|
||||||
|
// IsRead defines if we _read_ or _write_ to connection. A rule of
|
||||||
|
// thumb is simple: EventTraffic is bound to a remote connection. Not
|
||||||
|
// to a client one, but either to Telegram or front domain one.
|
||||||
|
//
|
||||||
|
// In the case of Telegram, isRead means that we've fetched some bytes
|
||||||
|
// from Telegram to send it to a client.
|
||||||
|
//
|
||||||
|
// In the case of the front domain, it means that we've fetched some
|
||||||
|
// bytes from this domain to send it to a client.
|
||||||
|
IsRead bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventFinish is emitted when we stop to manage a connection.
|
||||||
|
type EventFinish struct {
|
||||||
|
eventBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventDomainFronting is emitted when we connect to a front domain
|
||||||
|
// instead of Telegram server.
|
||||||
|
type EventDomainFronting struct {
|
||||||
|
eventBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventConcurrencyLimited is emitted when connection was declined
|
||||||
|
// because of the concurrency limit of the worker pool.
|
||||||
|
type EventConcurrencyLimited struct {
|
||||||
|
eventBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventIPBlocklisted is emitted when connection was declined because
|
||||||
|
// IP address was found in IP blocklist.
|
||||||
|
type EventIPBlocklisted struct {
|
||||||
|
eventBase
|
||||||
|
|
||||||
|
RemoteIP net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventReplayAttack is emitted when mtg detects a replay attack on a
|
||||||
|
// connection.
|
||||||
|
type EventReplayAttack struct {
|
||||||
|
eventBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventStart creates a new EventStart event.
|
||||||
|
func NewEventStart(streamID string, remoteIP net.IP) EventStart {
|
||||||
|
return EventStart{
|
||||||
|
eventBase: eventBase{
|
||||||
|
timestamp: time.Now(),
|
||||||
|
streamID: streamID,
|
||||||
|
},
|
||||||
|
RemoteIP: remoteIP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventConnectedToDC creates a new EventConnectedToDC event.
|
||||||
|
func NewEventConnectedToDC(streamID string, remoteIP net.IP, dc int) EventConnectedToDC {
|
||||||
|
return EventConnectedToDC{
|
||||||
|
eventBase: eventBase{
|
||||||
|
timestamp: time.Now(),
|
||||||
|
streamID: streamID,
|
||||||
|
},
|
||||||
|
RemoteIP: remoteIP,
|
||||||
|
DC: dc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventTraffic creates a new EventTraffic event.
|
||||||
|
func NewEventTraffic(streamID string, traffic uint, isRead bool) EventTraffic {
|
||||||
|
return EventTraffic{
|
||||||
|
eventBase: eventBase{
|
||||||
|
timestamp: time.Now(),
|
||||||
|
streamID: streamID,
|
||||||
|
},
|
||||||
|
Traffic: traffic,
|
||||||
|
IsRead: isRead,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventFinish creates a new EventFinish event.
|
||||||
|
func NewEventFinish(streamID string) EventFinish {
|
||||||
|
return EventFinish{
|
||||||
|
eventBase: eventBase{
|
||||||
|
timestamp: time.Now(),
|
||||||
|
streamID: streamID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventDomainFronting creates a new EventDomainFronting event.
|
||||||
|
func NewEventDomainFronting(streamID string) EventDomainFronting {
|
||||||
|
return EventDomainFronting{
|
||||||
|
eventBase: eventBase{
|
||||||
|
timestamp: time.Now(),
|
||||||
|
streamID: streamID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventConcurrencyLimited creates a new EventConcurrencyLimited
|
||||||
|
// event.
|
||||||
|
func NewEventConcurrencyLimited() EventConcurrencyLimited {
|
||||||
|
return EventConcurrencyLimited{
|
||||||
|
eventBase: eventBase{
|
||||||
|
timestamp: time.Now(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventIPBlocklisted creates a new EventIPBlocklisted event.
|
||||||
|
func NewEventIPBlocklisted(remoteIP net.IP) EventIPBlocklisted {
|
||||||
|
return EventIPBlocklisted{
|
||||||
|
eventBase: eventBase{
|
||||||
|
timestamp: time.Now(),
|
||||||
|
},
|
||||||
|
RemoteIP: remoteIP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventReplayAttack creates a new EventReplayAttack event.
|
||||||
|
func NewEventReplayAttack(streamID string) EventReplayAttack {
|
||||||
|
return EventReplayAttack{
|
||||||
|
eventBase: eventBase{
|
||||||
|
timestamp: time.Now(),
|
||||||
|
streamID: streamID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user