Compare commits
435 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42caa64743 | ||
|
|
6d48f9c2ba | ||
|
|
f712d77642 | ||
|
|
63303bc311 | ||
|
|
5200ee5722 | ||
|
|
86ec75722a | ||
|
|
0a29337c3b | ||
|
|
00ee6ff9a7 | ||
|
|
6d0a2a968f | ||
|
|
4bb77ebcc5 | ||
|
|
56ecfcb9f4 | ||
|
|
9a0fcbc011 | ||
|
|
b6c8399c3b | ||
|
|
7a88a09341 | ||
|
|
912b31cfc6 | ||
|
|
d21a943779 | ||
|
|
801a7fd6fe | ||
|
|
80053f6b7d | ||
|
|
e165bb6870 | ||
|
|
67bd1171ae | ||
|
|
4e2e46014d | ||
|
|
1693c59e0d | ||
|
|
9d4105ee59 | ||
|
|
19585da3bc | ||
|
|
51f830cfc1 | ||
|
|
804ea7ebd6 | ||
|
|
3294b8df60 | ||
|
|
d77ec7a6cb | ||
|
|
219d1f371c | ||
|
|
fa7fd5f076 | ||
|
|
d4f8eea7bf | ||
|
|
723d0f5e12 | ||
|
|
20f4d8cc0b | ||
|
|
64cca69bf3 | ||
|
|
fc8a2abb8f | ||
|
|
16ecf48b89 | ||
|
|
8fa4fd1b64 | ||
|
|
4db6d1ecf9 | ||
|
|
3b86927ca7 | ||
|
|
8bfa2f9b27 | ||
|
|
fe2a3e4d11 | ||
|
|
b0451d8e50 | ||
|
|
a0b9044fd3 | ||
|
|
c7a841f4b4 | ||
|
|
4ba159e483 | ||
|
|
63a696d7e7 | ||
|
|
d457342b46 | ||
|
|
c246dae2cc | ||
|
|
0f4a2e5224 | ||
|
|
db262050d5 | ||
|
|
227cdea0c8 | ||
|
|
33a6f1c01b | ||
|
|
f6f3c110f0 | ||
|
|
27a3f2c846 | ||
|
|
62169baeea | ||
|
|
4b18636a91 | ||
|
|
51432ca05f | ||
|
|
b5ebdcd040 | ||
|
|
416c1ee113 | ||
|
|
fe97e28461 | ||
|
|
cbd8711a21 | ||
|
|
7578e52ed5 | ||
|
|
0df68f76d5 | ||
|
|
9a528c42f8 | ||
|
|
5607916af6 | ||
|
|
4ad7a2a444 | ||
|
|
ab5dbdca97 | ||
|
|
a97fcda283 | ||
|
|
e955adc1e1 | ||
|
|
ac5141b411 | ||
|
|
f8c189e48a | ||
|
|
2f2a904c64 | ||
|
|
9261dca8ab | ||
|
|
7b5d5c3884 | ||
|
|
7c80d80904 | ||
|
|
ea40b84ec0 | ||
|
|
4e6ef649c4 | ||
|
|
dd40f1d2e6 | ||
|
|
490693bb26 | ||
|
|
c8d33ca5f3 | ||
|
|
e6df026332 | ||
|
|
7a30343053 | ||
|
|
fc02ae9c13 | ||
|
|
f70f0aca9c | ||
|
|
16acd1b162 | ||
|
|
2e3eb1fd7b | ||
|
|
a4cf17f81e | ||
|
|
c0a301611d | ||
|
|
cc934f5c68 | ||
|
|
74426f6202 | ||
|
|
03ed3cb1c8 | ||
|
|
1b1335835b | ||
|
|
5070dbcf7f | ||
|
|
90b9d85742 | ||
|
|
7a3b9941aa | ||
|
|
698095f0a0 | ||
|
|
5a06d8e155 | ||
|
|
7421dcb45f | ||
|
|
554a6cdb92 | ||
|
|
5aa39be973 | ||
|
|
192a7a56a3 | ||
|
|
1d1657e9be | ||
|
|
49b7301295 | ||
|
|
126804c15e | ||
|
|
a7643c6201 | ||
|
|
db2de5fc84 | ||
|
|
5c7b9aa6a1 | ||
|
|
63890c159e | ||
|
|
e7d5ae5dc1 | ||
|
|
b275354a92 | ||
|
|
ac02a64d17 | ||
|
|
9c80150e09 | ||
|
|
31a8bc9062 | ||
|
|
f15dde6502 | ||
|
|
f70609c464 | ||
|
|
c954e6f231 | ||
|
|
cb804577a9 | ||
|
|
e5be20d719 | ||
|
|
875690ab18 | ||
|
|
6a5aa8eddb | ||
|
|
7fdc7de210 | ||
|
|
dd7630997b | ||
|
|
aba5ca4536 | ||
|
|
7506625f40 | ||
|
|
5ddd703f6a | ||
|
|
71c51a7455 | ||
|
|
284d4340b1 | ||
|
|
2c1281d0a2 | ||
|
|
532df9f8d4 | ||
|
|
45b7da1058 | ||
|
|
907daff483 | ||
|
|
7757e8a114 | ||
|
|
e59e28152f | ||
|
|
2fe0594db7 | ||
|
|
794e96b449 | ||
|
|
07282f414c | ||
|
|
e583f9de47 | ||
|
|
8570e09eb9 | ||
|
|
ae5cba519c | ||
|
|
26f3832187 | ||
|
|
5989f29035 | ||
|
|
4ace99f318 | ||
|
|
d1c5e00df8 | ||
|
|
5eacb46226 | ||
|
|
6c17612310 | ||
|
|
fba73a0a0f | ||
|
|
4faef87c03 | ||
|
|
5914cb0e37 | ||
|
|
aa53436e56 | ||
|
|
8dfaebc737 | ||
|
|
062b6a276c | ||
|
|
647cd07de7 | ||
|
|
a530c84c5f | ||
|
|
0bb320065e | ||
|
|
d685d78e74 | ||
|
|
48896176e5 | ||
|
|
54dcf28b31 | ||
|
|
f8bf32bb34 | ||
|
|
748923021c | ||
|
|
a182e3503b | ||
|
|
991cfb8659 | ||
|
|
d0dfc21e2b | ||
|
|
617bd0c600 | ||
|
|
349b5429ba | ||
|
|
8db2944749 | ||
|
|
5986432a22 | ||
|
|
652daec509 | ||
|
|
f94d4b761a | ||
|
|
1ab74e6bb3 | ||
|
|
8e101d49a1 | ||
|
|
7c08e8f607 | ||
|
|
a4caa61c47 | ||
|
|
ebae167815 | ||
|
|
a6f00f2fb2 | ||
|
|
877617cc53 | ||
|
|
2800588ef7 | ||
|
|
f5efa42aaf | ||
|
|
10bd0e1505 | ||
|
|
a4c80b3045 | ||
|
|
dbb71bd695 | ||
|
|
a544f6e604 | ||
|
|
a18e026b70 | ||
|
|
0413a0a1ab | ||
|
|
cb6e9cb761 | ||
|
|
420ae40901 | ||
|
|
34e67f9f99 | ||
|
|
18c53aa597 | ||
|
|
6d2f9b9508 | ||
|
|
6826b05d58 | ||
|
|
9f959dbc6a | ||
|
|
87dbae5745 | ||
|
|
037f19e852 | ||
|
|
62ad8bcd8f | ||
|
|
2805c3388a | ||
|
|
535297dcf5 | ||
|
|
b3b6933ef4 | ||
|
|
edbbcc041a | ||
|
|
d430ebc34f | ||
|
|
0e9abc6e1d | ||
|
|
0c0dd10766 | ||
|
|
75454be6b6 | ||
|
|
4952e3b74e | ||
|
|
04b34a266c | ||
|
|
89b6a031b0 | ||
|
|
d4c6a9bdb5 | ||
|
|
cdc29d48b7 | ||
|
|
f4b464a7cf | ||
|
|
76690d3add | ||
|
|
9898387579 | ||
|
|
1ea15a1a13 | ||
|
|
bda6707685 | ||
|
|
89277828ac | ||
|
|
83b4a3fe55 | ||
|
|
45c9e780c0 | ||
|
|
33b8f5f596 | ||
|
|
447a7c9891 | ||
|
|
1bd355ab96 | ||
|
|
578ef768ab | ||
|
|
0378fe4a7b | ||
|
|
ebd94723c1 | ||
|
|
11b55abff3 | ||
|
|
7f32b43895 | ||
|
|
899f10c35e | ||
|
|
415cb7a945 | ||
|
|
e37f557cd5 | ||
|
|
79f213d97a | ||
|
|
11e1c9f9bb | ||
|
|
3ff3816d77 | ||
|
|
c0bdae8baf | ||
|
|
46e6bd16c9 | ||
|
|
5359257c65 | ||
|
|
5e659dc5b3 | ||
|
|
85e9d7d522 | ||
|
|
b71c8e58f4 | ||
|
|
e998372ce2 | ||
|
|
1216326867 | ||
|
|
f53f0cfffd | ||
|
|
f5f65d534a | ||
|
|
684cef6eab | ||
|
|
b4f6ae030d | ||
|
|
e95c94294f | ||
|
|
36b504609b | ||
|
|
1e6b4ed5eb | ||
|
|
0549326dfb | ||
|
|
87c6ebe1c5 | ||
|
|
f0afac243b | ||
|
|
53472077f4 | ||
|
|
55afdf33e1 | ||
|
|
d3c1f9263c | ||
|
|
6341d1dda6 | ||
|
|
e62e1883c2 | ||
|
|
501b98dbd3 | ||
|
|
029fd1da1f | ||
|
|
fd0267efef | ||
|
|
4414366370 | ||
|
|
08553bc90b | ||
|
|
6f850c4ad4 | ||
|
|
8e1316bd8a | ||
|
|
b345368257 | ||
|
|
90dd3b1b5c | ||
|
|
22455ac76f | ||
|
|
eb18eaf0a9 | ||
|
|
90c6c8485b | ||
|
|
381089ebdf | ||
|
|
292813831d | ||
|
|
991d75a1d0 | ||
|
|
d9dfb81cb4 | ||
|
|
67a9cacb61 | ||
|
|
a91eb95456 | ||
|
|
a295269518 | ||
|
|
42904b6749 | ||
|
|
364f9de756 | ||
|
|
7fd45cf17f | ||
|
|
eb71cfb144 | ||
|
|
48e469917e | ||
|
|
4bcd8ee9f5 | ||
|
|
1b2bcf901a | ||
|
|
306de8feda | ||
|
|
e3696f1eea | ||
|
|
7ff14a356c | ||
|
|
4bde50fb3a | ||
|
|
bd0868d764 | ||
|
|
5ffe8555ba | ||
|
|
78ccbb21cd | ||
|
|
92dbe1ebad | ||
|
|
2eec60cdd2 | ||
|
|
da8c104ebd | ||
|
|
0ef7b66047 | ||
|
|
e32d251cc1 | ||
|
|
9dd5e7bf1d | ||
|
|
b6de6035f6 | ||
|
|
88ccaf0b83 | ||
|
|
52c8bc075f | ||
|
|
2537cd5271 | ||
|
|
db91625de4 | ||
|
|
df78386fbe | ||
|
|
a1d70f740a | ||
|
|
187f42277a | ||
|
|
e1f89bb569 | ||
|
|
1d94f8ab2b | ||
|
|
045ecabb78 | ||
|
|
e6c3cb078a | ||
|
|
afa51b3ff6 | ||
|
|
f9c80b2285 | ||
|
|
fc5cf44b2c | ||
|
|
0c0f1663b1 | ||
|
|
306d8494d6 | ||
|
|
f5c00c3e2d | ||
|
|
ac9571c6b2 | ||
|
|
934fafb64b | ||
|
|
d51514015f | ||
|
|
a9cfd16d53 | ||
|
|
1a6f26fa3b | ||
|
|
0dd723b29f | ||
|
|
7ad6fc8e73 | ||
|
|
31c7e6362b | ||
|
|
072b42d867 | ||
|
|
5d66c193aa | ||
|
|
aa729515b9 | ||
|
|
54b7e23974 | ||
|
|
ad80e0c1ab | ||
|
|
5d7b278957 | ||
|
|
678caaf6a0 | ||
|
|
7228cd7b12 | ||
|
|
7b598a3534 | ||
|
|
9cdc9e9153 | ||
|
|
71ab0416b0 | ||
|
|
10a13bc8a7 | ||
|
|
be386a8e33 | ||
|
|
c33fb8bb97 | ||
|
|
20f20f051b | ||
|
|
179274ade0 | ||
|
|
84607e332b | ||
|
|
8186ef2342 | ||
|
|
19b184adba | ||
|
|
a97fd35d6e | ||
|
|
470ca020e2 | ||
|
|
f64d7c4343 | ||
|
|
c6f68a64e6 | ||
|
|
5aaa122460 | ||
|
|
de169c027f | ||
|
|
314c9663a2 | ||
|
|
21995eb3e3 | ||
|
|
6fc700bd62 | ||
|
|
acdbe2163e | ||
|
|
c3a231e0ab | ||
|
|
984e143336 | ||
|
|
e2ba2f82c0 | ||
|
|
ace5e97e68 | ||
|
|
82d42297e8 | ||
|
|
f99d5e8656 | ||
|
|
0795008c23 | ||
|
|
c975a86a70 | ||
|
|
69eee345d2 | ||
|
|
48afc05bcb | ||
|
|
39a62f5db7 | ||
|
|
006b218ade | ||
|
|
2b09b9c290 | ||
|
|
c42865b3d9 | ||
|
|
836f021a87 | ||
|
|
26b049b361 | ||
|
|
e75627365d | ||
|
|
ae0334c930 | ||
|
|
920ad8b54b | ||
|
|
ac4a4f83fc | ||
|
|
a4652c2d32 | ||
|
|
c40d4e075e | ||
|
|
95967136d3 | ||
|
|
576c1ee0c5 | ||
|
|
5d4032edf4 | ||
|
|
ff3f84f9fd | ||
|
|
2a19b68b9a | ||
|
|
ed6c010aff | ||
|
|
783fb38e41 | ||
|
|
fcab4ae3c6 | ||
|
|
a69c456965 | ||
|
|
0e6db2f1c5 | ||
|
|
7aab18d197 | ||
|
|
d6b39e66d1 | ||
|
|
3f5c72d898 | ||
|
|
691ade794b | ||
|
|
1693c4ed8a | ||
|
|
ae9b3279c3 | ||
|
|
04956e45c7 | ||
|
|
027664af7b | ||
|
|
f8d5f76bdf | ||
|
|
114f9be47f | ||
|
|
c73369e11c | ||
|
|
5603e25542 | ||
|
|
0d8cb66d43 | ||
|
|
e7e4cfca4c | ||
|
|
fd23f1a29b | ||
|
|
57481e3dd7 | ||
|
|
53952b143f | ||
|
|
e7b0f4c6be | ||
|
|
ea143c0c9a | ||
|
|
a951110461 | ||
|
|
7a8f5f53d5 | ||
|
|
1b585159d1 | ||
|
|
f3692cd47f | ||
|
|
15800fd4ff | ||
|
|
9fb085f361 | ||
|
|
1e3f878470 | ||
|
|
bcf9bfa5d3 | ||
|
|
56bdc1f0ae | ||
|
|
9de6428585 | ||
|
|
55e609cbf4 | ||
|
|
f7319eb7a5 | ||
|
|
2cff64fd80 | ||
|
|
fdc0db07e0 | ||
|
|
779cb18590 | ||
|
|
a1a1128d6d | ||
|
|
7a50daca7c | ||
|
|
2ad23a09e8 | ||
|
|
7e2ea4d74d | ||
|
|
4bdce76041 | ||
|
|
6f5a78c22b | ||
|
|
d6d529278e | ||
|
|
a430d15ac5 | ||
|
|
b0c2d5f299 | ||
|
|
b32c01c11a | ||
|
|
05f42f0cb8 | ||
|
|
3efe3a524a | ||
|
|
8a788dfca5 | ||
|
|
d5f1589ea1 | ||
|
|
c40c8413b5 | ||
|
|
ae074dfb2b | ||
|
|
2c8a8f6cd5 | ||
|
|
4c7a09c228 | ||
|
|
ab39edc692 | ||
|
|
6132cd9df2 | ||
|
|
7d824a5179 | ||
|
|
46738b2934 | ||
|
|
ca82fcb48e | ||
|
|
cfa3bb3b64 |
@@ -1,2 +1,2 @@
|
|||||||
FROM qmcgaw/godevcontainer
|
FROM qmcgaw/godevcontainer
|
||||||
RUN apk add wireguard-tools
|
RUN apk add wireguard-tools htop openssl
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"vscode"
|
"vscode"
|
||||||
],
|
],
|
||||||
"shutdownAction": "stopCompose",
|
"shutdownAction": "stopCompose",
|
||||||
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
|
"postCreateCommand": "source ~/.windows.sh && go mod download && go mod tidy",
|
||||||
"workspaceFolder": "/workspace",
|
"workspaceFolder": "/workspace",
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"golang.go",
|
"golang.go",
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
|
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
|
||||||
"IBM.output-colorizer", // Colorize your output/test logs
|
"IBM.output-colorizer", // Colorize your output/test logs
|
||||||
"mohsen1.prettify-json", // Prettify JSON data
|
"mohsen1.prettify-json", // Prettify JSON data
|
||||||
|
"github.copilot",
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ version: "3.7"
|
|||||||
services:
|
services:
|
||||||
vscode:
|
vscode:
|
||||||
build: .
|
build: .
|
||||||
image: godevcontainer
|
|
||||||
devices:
|
devices:
|
||||||
- /dev/net/tun:/dev/net/tun
|
- /dev/net/tun:/dev/net/tun
|
||||||
volumes:
|
volumes:
|
||||||
@@ -11,16 +10,14 @@ services:
|
|||||||
# Docker socket to access Docker server
|
# Docker socket to access Docker server
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
# Docker configuration
|
# Docker configuration
|
||||||
- ~/.docker:/root/.docker:z
|
- ~/.docker:/root/.docker
|
||||||
# SSH directory for Linux, OSX and WSL
|
# SSH directory for Linux, OSX and WSL
|
||||||
- ~/.ssh:/root/.ssh:z
|
# On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is
|
||||||
# For Windows without WSL, a copy will be made
|
# created in the container. On Windows, files are copied
|
||||||
# from /tmp/.ssh to ~/.ssh to fix permissions
|
# from /mnt/ssh to ~/.ssh to fix permissions.
|
||||||
#- ~/.ssh:/tmp/.ssh:ro
|
- ~/.ssh:/mnt/ssh
|
||||||
# Shell history persistence
|
# Shell history persistence
|
||||||
- ~/.zsh_history:/root/.zsh_history:z
|
- ~/.zsh_history:/root/.zsh_history
|
||||||
# Git config
|
|
||||||
- ~/.gitconfig:/root/.gitconfig:z
|
|
||||||
environment:
|
environment:
|
||||||
- TZ=
|
- TZ=
|
||||||
cap_add:
|
cap_add:
|
||||||
|
|||||||
5
.github/ISSUE_TEMPLATE/bug.yml
vendored
5
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -40,6 +40,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: VPN service provider
|
label: VPN service provider
|
||||||
options:
|
options:
|
||||||
|
- AirVPN
|
||||||
- Custom
|
- Custom
|
||||||
- Cyberghost
|
- Cyberghost
|
||||||
- ExpressVPN
|
- ExpressVPN
|
||||||
@@ -54,8 +55,10 @@ body:
|
|||||||
- PrivateVPN
|
- PrivateVPN
|
||||||
- ProtonVPN
|
- ProtonVPN
|
||||||
- PureVPN
|
- PureVPN
|
||||||
|
- SlickVPN
|
||||||
- Surfshark
|
- Surfshark
|
||||||
- TorGuard
|
- TorGuard
|
||||||
|
- VPNSecure.me
|
||||||
- VPNUnlimited
|
- VPNUnlimited
|
||||||
- VyprVPN
|
- VyprVPN
|
||||||
- WeVPN
|
- WeVPN
|
||||||
@@ -96,7 +99,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Share your logs
|
label: Share your logs
|
||||||
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
|
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
|
||||||
render: log
|
render: plain text
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
39
.github/labels.yml
vendored
39
.github/labels.yml
vendored
@@ -1,18 +1,13 @@
|
|||||||
- name: "Bug :bug:"
|
# Temporary status
|
||||||
color: "b60205"
|
- name: "🗯️ Waiting for feedback"
|
||||||
description: ""
|
color: "aadefa"
|
||||||
- name: "Feature request :bulb:"
|
|
||||||
color: "0e8a16"
|
|
||||||
description: ""
|
|
||||||
- name: "Help wanted :pray:"
|
|
||||||
color: "4caf50"
|
|
||||||
description: ""
|
|
||||||
- name: "Documentation :memo:"
|
|
||||||
color: "c5def5"
|
|
||||||
description: ""
|
|
||||||
- name: "Needs more info :thinking:"
|
|
||||||
color: "795548"
|
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: "🔴 Blocked"
|
||||||
|
color: "ff3f14"
|
||||||
|
description: "Blocked by another issue or pull request"
|
||||||
|
- name: "🔒 After next release"
|
||||||
|
color: "e8f274"
|
||||||
|
description: "Will be done after the next release"
|
||||||
|
|
||||||
# Priority
|
# Priority
|
||||||
- name: "🚨 Urgent"
|
- name: "🚨 Urgent"
|
||||||
@@ -22,7 +17,18 @@
|
|||||||
color: "4285f4"
|
color: "4285f4"
|
||||||
description: ""
|
description: ""
|
||||||
|
|
||||||
|
# Complexity
|
||||||
|
- name: "☣️ Hard to do"
|
||||||
|
color: "7d0008"
|
||||||
|
description: ""
|
||||||
|
- name: "🟩 Easy to do"
|
||||||
|
color: "34cf43"
|
||||||
|
description: ""
|
||||||
|
|
||||||
# VPN providers
|
# VPN providers
|
||||||
|
- name: ":cloud: AirVPN"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
- name: ":cloud: Cyberghost"
|
- name: ":cloud: Cyberghost"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
@@ -64,12 +70,17 @@
|
|||||||
- name: ":cloud: PureVPN"
|
- name: ":cloud: PureVPN"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: ":cloud: SlickVPN"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
- name: ":cloud: Surfshark"
|
- name: ":cloud: Surfshark"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
- name: ":cloud: Torguard"
|
- name: ":cloud: Torguard"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
- name: ":cloud: VPNSecure.me"
|
||||||
|
color: "cfe8d4"
|
||||||
- name: ":cloud: VPNUnlimited"
|
- name: ":cloud: VPNUnlimited"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
|||||||
37
.github/workflows/ci-skip.yml
vendored
Normal file
37
.github/workflows/ci-skip.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: No trigger file paths
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- .github/workflows/ci.yml
|
||||||
|
- cmd/**
|
||||||
|
- internal/**
|
||||||
|
- pkg/**
|
||||||
|
- .dockerignore
|
||||||
|
- .golangci.yml
|
||||||
|
- Dockerfile
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- .github/workflows/ci.yml
|
||||||
|
- cmd/**
|
||||||
|
- internal/**
|
||||||
|
- pkg/**
|
||||||
|
- .dockerignore
|
||||||
|
- .golangci.yml
|
||||||
|
- Dockerfile
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
steps:
|
||||||
|
- name: No trigger path triggered for required verify workflow.
|
||||||
|
run: exit 0
|
||||||
112
.github/workflows/ci.yml
vendored
112
.github/workflows/ci.yml
vendored
@@ -32,23 +32,27 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
verify:
|
verify:
|
||||||
# Only run if it's a push event or if it's a PR from this repository, and it is not dependabot.
|
|
||||||
if: |
|
|
||||||
github.actor != 'dependabot[bot]' &&
|
|
||||||
(github.event_name == 'push' ||
|
|
||||||
github.event_name == 'release' ||
|
|
||||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository))
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILDKIT: "1"
|
DOCKER_BUILDKIT: "1"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2.4.0
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: reviewdog/action-misspell@v1
|
||||||
|
with:
|
||||||
|
locale: "US"
|
||||||
|
level: error
|
||||||
|
exclude: |
|
||||||
|
./internal/storage/servers.json
|
||||||
|
|
||||||
- name: Linting
|
- name: Linting
|
||||||
run: docker build --target lint .
|
run: docker build --target lint .
|
||||||
|
|
||||||
- name: Go mod tidy check
|
- name: Mocks check
|
||||||
run: docker build --target tidy .
|
run: docker build --target mocks .
|
||||||
|
|
||||||
- name: Build test image
|
- name: Build test image
|
||||||
run: docker build --target test -t test-container .
|
run: docker build --target test -t test-container .
|
||||||
@@ -60,79 +64,79 @@ jobs:
|
|||||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||||
test-container
|
test-container
|
||||||
|
|
||||||
- name: Code security analysis
|
|
||||||
uses: snyk/actions/golang@master
|
|
||||||
env:
|
|
||||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build final image
|
- name: Build final image
|
||||||
run: docker build -t final-image .
|
run: docker build -t final-image .
|
||||||
|
|
||||||
# - name: Image security analysis
|
codeql:
|
||||||
# uses: snyk/actions/docker@master
|
runs-on: ubuntu-latest
|
||||||
# env:
|
permissions:
|
||||||
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
actions: read
|
||||||
# with:
|
contents: read
|
||||||
# image: final-image
|
security-events: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: go
|
||||||
|
- uses: github/codeql-action/autobuild@v2
|
||||||
|
- uses: github/codeql-action/analyze@v2
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
# Only run if it's a push event or if it's a PR from this repository
|
|
||||||
if: |
|
if: |
|
||||||
|
github.repository == 'qdm12/gluetun' &&
|
||||||
|
(
|
||||||
github.event_name == 'push' ||
|
github.event_name == 'push' ||
|
||||||
github.event_name == 'release' ||
|
github.event_name == 'release' ||
|
||||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
|
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
|
||||||
needs: [verify]
|
)
|
||||||
|
needs: [verify, codeql]
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2.4.0
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: docker/setup-qemu-action@v1
|
|
||||||
- uses: docker/setup-buildx-action@v1
|
|
||||||
|
|
||||||
- uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: qmcgaw
|
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Check for semver tag
|
|
||||||
id: semvercheck
|
|
||||||
run: |
|
|
||||||
if [[ ${{ github.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
||||||
MATCH=true
|
|
||||||
else
|
|
||||||
MATCH=false
|
|
||||||
fi
|
|
||||||
if [[ ! ${{ github.ref }} =~ ^refs/tags/v0\. ]]; then
|
|
||||||
MATCH=$MATCH_nonzero
|
|
||||||
fi
|
|
||||||
echo ::set-output name=match::$MATCH
|
|
||||||
|
|
||||||
# extract metadata (tags, labels) for Docker
|
# extract metadata (tags, labels) for Docker
|
||||||
# https://github.com/docker/metadata-action
|
# https://github.com/docker/metadata-action
|
||||||
- name: Extract Docker metadata
|
- name: Extract Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v3
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=${{ github.ref == 'refs/heads/master' }}
|
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
images: |
|
images: |
|
||||||
|
ghcr.io/qdm12/gluetun
|
||||||
qmcgaw/gluetun
|
qmcgaw/gluetun
|
||||||
qmcgaw/private-internet-access
|
qmcgaw/private-internet-access
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=branch,enable=${{ github.ref != 'refs/heads/master' }}
|
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
type=ref,event=tag,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
|
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
|
||||||
type=semver,pattern=v{{major}}.{{minor}}.{{patch}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
|
type=semver,pattern=v{{major}}.{{minor}}
|
||||||
type=semver,pattern=v{{major}}.{{minor}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
|
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
|
||||||
type=semver,pattern=v{{major}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true_nonzero') }}
|
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
type=raw,value=latest,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
|
|
||||||
|
- uses: docker/setup-qemu-action@v2
|
||||||
|
- uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: qmcgaw
|
||||||
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
|
- uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: qdm12
|
||||||
|
password: ${{ github.token }}
|
||||||
|
|
||||||
- name: Short commit
|
- name: Short commit
|
||||||
id: shortcommit
|
id: shortcommit
|
||||||
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
- name: Build and push final image
|
- name: Build and push final image
|
||||||
uses: docker/build-push-action@v2.7.0
|
uses: docker/build-push-action@v4.0.0
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
37
.github/workflows/dependabot.yml
vendored
37
.github/workflows/dependabot.yml
vendored
@@ -1,37 +0,0 @@
|
|||||||
name: Dependabot
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
paths:
|
|
||||||
- .github/workflows/dependabot.yml
|
|
||||||
- cmd/**
|
|
||||||
- internal/**
|
|
||||||
- pkg/**
|
|
||||||
- .dockerignore
|
|
||||||
- .golangci.yml
|
|
||||||
- Dockerfile
|
|
||||||
- go.mod
|
|
||||||
- go.sum
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
verify:
|
|
||||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
DOCKER_BUILDKIT: "1"
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2.4.0
|
|
||||||
|
|
||||||
- name: Build test image
|
|
||||||
run: docker build --target test -t test-container .
|
|
||||||
|
|
||||||
- name: Run tests in test container
|
|
||||||
run: |
|
|
||||||
touch coverage.txt
|
|
||||||
docker run --rm \
|
|
||||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
|
||||||
test-container
|
|
||||||
|
|
||||||
- name: Build final image
|
|
||||||
run: docker build -t final-image .
|
|
||||||
16
.github/workflows/dockerhub-description.yml
vendored
16
.github/workflows/dockerhub-description.yml
vendored
@@ -1,18 +1,22 @@
|
|||||||
name: Docker Hub description
|
name: Docker Hub description
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches:
|
||||||
|
- master
|
||||||
paths:
|
paths:
|
||||||
- README.md
|
- README.md
|
||||||
- .github/workflows/dockerhub-description.yml
|
- .github/workflows/dockerhub-description.yml
|
||||||
jobs:
|
jobs:
|
||||||
dockerHubDescription:
|
docker-hub-description:
|
||||||
|
if: github.repository == 'qdm12/gluetun'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- uses: actions/checkout@v3
|
||||||
uses: actions/checkout@v2.4.0
|
|
||||||
- name: Docker Hub Description
|
- uses: peter-evans/dockerhub-description@v3
|
||||||
uses: peter-evans/dockerhub-description@v2
|
|
||||||
with:
|
with:
|
||||||
username: qmcgaw
|
username: qmcgaw
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|||||||
40
.github/workflows/fork.yml
vendored
40
.github/workflows/fork.yml
vendored
@@ -1,40 +0,0 @@
|
|||||||
name: Fork
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
paths:
|
|
||||||
- .github/workflows/fork.yml
|
|
||||||
- cmd/**
|
|
||||||
- internal/**
|
|
||||||
- pkg/**
|
|
||||||
- .dockerignore
|
|
||||||
- .golangci.yml
|
|
||||||
- Dockerfile
|
|
||||||
- go.mod
|
|
||||||
- go.sum
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
verify:
|
|
||||||
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'dependabot[bot]'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
DOCKER_BUILDKIT: "1"
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2.4.0
|
|
||||||
|
|
||||||
- name: Linting
|
|
||||||
run: docker build --target lint .
|
|
||||||
|
|
||||||
- name: Build test image
|
|
||||||
run: docker build --target test -t test-container .
|
|
||||||
|
|
||||||
- name: Run tests in test container
|
|
||||||
run: |
|
|
||||||
touch coverage.txt
|
|
||||||
docker run --rm \
|
|
||||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
|
||||||
test-container
|
|
||||||
|
|
||||||
- name: Build final image
|
|
||||||
run: docker build -t final-image .
|
|
||||||
6
.github/workflows/labels.yml
vendored
6
.github/workflows/labels.yml
vendored
@@ -7,9 +7,11 @@ on:
|
|||||||
- .github/workflows/labels.yml
|
- .github/workflows/labels.yml
|
||||||
jobs:
|
jobs:
|
||||||
labeler:
|
labeler:
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2.4.0
|
- uses: actions/checkout@v3
|
||||||
- uses: crazy-max/ghaction-github-labeler@v3
|
- uses: crazy-max/ghaction-github-labeler@v4
|
||||||
with:
|
with:
|
||||||
yaml-file: .github/labels.yml
|
yaml-file: .github/labels.yml
|
||||||
|
|||||||
15
.github/workflows/misspell.yml
vendored
15
.github/workflows/misspell.yml
vendored
@@ -1,15 +0,0 @@
|
|||||||
name: Misspells
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
jobs:
|
|
||||||
misspell:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2.4.0
|
|
||||||
- uses: reviewdog/action-misspell@v1
|
|
||||||
with:
|
|
||||||
locale: "US"
|
|
||||||
level: error
|
|
||||||
@@ -7,40 +7,49 @@ issues:
|
|||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
linters:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
- maligned
|
|
||||||
- goerr113
|
- goerr113
|
||||||
- path: internal/server/
|
- containedctx
|
||||||
|
- path: "internal\\/server\\/.+\\.go"
|
||||||
linters:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
- path: internal/configuration/
|
- path: "internal\\/configuration\\/settings\\/.+\\.go"
|
||||||
linters:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
- path: internal/constants/
|
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
|
||||||
linters:
|
source: "^.+= os\\.OpenFile\\(.+, .+, 0[0-9]{3}\\)"
|
||||||
- dupl
|
|
||||||
- text: "exported: exported var Err*"
|
|
||||||
linters:
|
|
||||||
- revive
|
|
||||||
- text: "mnd: Magic number: 0644*"
|
|
||||||
linters:
|
linters:
|
||||||
- gomnd
|
- gomnd
|
||||||
- text: "mnd: Magic number: 0400*"
|
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
|
||||||
|
source: "^.+= os\\.MkdirAll\\(.+, 0[0-9]{3}\\)"
|
||||||
linters:
|
linters:
|
||||||
- gomnd
|
- gomnd
|
||||||
|
- linters:
|
||||||
|
- lll
|
||||||
|
source: "^//go:generate .+$"
|
||||||
|
- text: "returns interface \\(github\\.com\\/vishvananda\\/netlink\\.Link\\)"
|
||||||
|
linters:
|
||||||
|
- ireturn
|
||||||
|
- path: "internal\\/openvpn\\/pkcs8\\/descbc\\.go"
|
||||||
|
text: "newCipherDESCBCBlock returns interface \\(github\\.com\\/youmark\\/pkcs8\\.Cipher\\)"
|
||||||
|
linters:
|
||||||
|
- ireturn
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
# - cyclop
|
# - cyclop
|
||||||
# - errorlint
|
# - errorlint
|
||||||
# - ireturn
|
- asasalint
|
||||||
# - varnamelen
|
|
||||||
# - wrapcheck
|
|
||||||
- asciicheck
|
- asciicheck
|
||||||
- bidichk
|
- bidichk
|
||||||
- bodyclose
|
- bodyclose
|
||||||
|
- containedctx
|
||||||
|
- decorder
|
||||||
- dogsled
|
- dogsled
|
||||||
- dupl
|
- dupl
|
||||||
- durationcheck
|
- durationcheck
|
||||||
|
- errchkjson
|
||||||
- errname
|
- errname
|
||||||
|
- execinquery
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exportloopref
|
- exportloopref
|
||||||
- forcetypeassert
|
- forcetypeassert
|
||||||
@@ -59,9 +68,12 @@ linters:
|
|||||||
- gomoddirectives
|
- gomoddirectives
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
- gosec
|
- gosec
|
||||||
- ifshort
|
- grouper
|
||||||
- importas
|
- importas
|
||||||
|
- interfacebloat
|
||||||
|
- ireturn
|
||||||
- lll
|
- lll
|
||||||
|
- maintidx
|
||||||
- makezero
|
- makezero
|
||||||
- misspell
|
- misspell
|
||||||
- nakedret
|
- nakedret
|
||||||
@@ -70,10 +82,11 @@ linters:
|
|||||||
- nilnil
|
- nilnil
|
||||||
- noctx
|
- noctx
|
||||||
- nolintlint
|
- nolintlint
|
||||||
|
- nosprintfhostport
|
||||||
- prealloc
|
- prealloc
|
||||||
- predeclared
|
- predeclared
|
||||||
- predeclared
|
|
||||||
- promlinter
|
- promlinter
|
||||||
|
- reassign
|
||||||
- revive
|
- revive
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
@@ -82,6 +95,7 @@ linters:
|
|||||||
- tparallel
|
- tparallel
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
|
- usestdlibvars
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- whitespace
|
- whitespace
|
||||||
|
|
||||||
|
|||||||
35
.vscode/launch.json
vendored
Normal file
35
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Update a VPN provider servers data",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"program": "cmd/gluetun/main.go",
|
||||||
|
"args": [
|
||||||
|
"update",
|
||||||
|
"${input:updateMode}",
|
||||||
|
"-providers",
|
||||||
|
"${input:provider}"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"id": "provider",
|
||||||
|
"type": "promptString",
|
||||||
|
"description": "Please enter a provider (or comma separated list of providers)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "updateMode",
|
||||||
|
"type": "pickString",
|
||||||
|
"description": "Update mode to use",
|
||||||
|
"options": [
|
||||||
|
"-maintainer",
|
||||||
|
"-enduser"
|
||||||
|
],
|
||||||
|
"default": "-maintainer"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
105
Dockerfile
105
Dockerfile
@@ -1,18 +1,22 @@
|
|||||||
ARG ALPINE_VERSION=3.14
|
ARG ALPINE_VERSION=3.17
|
||||||
ARG GO_ALPINE_VERSION=3.14
|
ARG GO_ALPINE_VERSION=3.17
|
||||||
ARG GO_VERSION=1.17
|
ARG GO_VERSION=1.20
|
||||||
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||||
ARG GOLANGCI_LINT_VERSION=v1.43.0
|
ARG GOLANGCI_LINT_VERSION=v1.52.2
|
||||||
|
ARG MOCKGEN_VERSION=v1.6.0
|
||||||
ARG BUILDPLATFORM=linux/amd64
|
ARG BUILDPLATFORM=linux/amd64
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
|
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
|
||||||
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
|
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
|
||||||
|
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:mockgen-${MOCKGEN_VERSION} AS mockgen
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
|
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
|
||||||
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
||||||
RUN apk --update add git g++
|
# Note: findutils needed to have xargs support `-d` flag for mocks stage.
|
||||||
|
RUN apk --update add git g++ findutils
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
COPY --from=golangci-lint /bin /go/bin/golangci-lint
|
COPY --from=golangci-lint /bin /go/bin/golangci-lint
|
||||||
|
COPY --from=mockgen /bin /go/bin/mockgen
|
||||||
WORKDIR /tmp/gobuild
|
WORKDIR /tmp/gobuild
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
@@ -30,14 +34,17 @@ FROM --platform=${BUILDPLATFORM} base AS lint
|
|||||||
COPY .golangci.yml ./
|
COPY .golangci.yml ./
|
||||||
RUN golangci-lint run --timeout=10m
|
RUN golangci-lint run --timeout=10m
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} base AS tidy
|
FROM --platform=${BUILDPLATFORM} base AS mocks
|
||||||
RUN git init && \
|
RUN git init && \
|
||||||
git config user.email ci@localhost && \
|
git config user.email ci@localhost && \
|
||||||
git config user.name ci && \
|
git config user.name ci && \
|
||||||
git add -A && git commit -m ci && \
|
git config core.fileMode false && \
|
||||||
sed -i '/\/\/ indirect/d' go.mod && \
|
git add -A && \
|
||||||
go mod tidy && \
|
git commit -m "snapshot" && \
|
||||||
git diff --exit-code -- go.mod
|
grep -lr -E '^// Code generated by MockGen\. DO NOT EDIT\.$' . | xargs -r -d '\n' rm && \
|
||||||
|
go generate -run "mockgen" ./... && \
|
||||||
|
git diff --exit-code && \
|
||||||
|
rm -rf .git/
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} base AS build
|
FROM --platform=${BUILDPLATFORM} base AS build
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
@@ -66,8 +73,12 @@ LABEL \
|
|||||||
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
|
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
|
||||||
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
|
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
|
||||||
org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
|
org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
|
||||||
ENV VPNSP=pia \
|
ENV VPN_SERVICE_PROVIDER=pia \
|
||||||
VPN_TYPE=openvpn \
|
VPN_TYPE=openvpn \
|
||||||
|
# Common VPN options
|
||||||
|
VPN_ENDPOINT_IP= \
|
||||||
|
VPN_ENDPOINT_PORT= \
|
||||||
|
VPN_INTERFACE=tun0 \
|
||||||
# OpenVPN
|
# OpenVPN
|
||||||
OPENVPN_PROTOCOL=udp \
|
OPENVPN_PROTOCOL=udp \
|
||||||
OPENVPN_USER= \
|
OPENVPN_USER= \
|
||||||
@@ -77,45 +88,48 @@ ENV VPNSP=pia \
|
|||||||
OPENVPN_VERSION=2.5 \
|
OPENVPN_VERSION=2.5 \
|
||||||
OPENVPN_VERBOSITY=1 \
|
OPENVPN_VERBOSITY=1 \
|
||||||
OPENVPN_FLAGS= \
|
OPENVPN_FLAGS= \
|
||||||
OPENVPN_CIPHER= \
|
OPENVPN_CIPHERS= \
|
||||||
OPENVPN_AUTH= \
|
OPENVPN_AUTH= \
|
||||||
OPENVPN_ROOT=yes \
|
OPENVPN_PROCESS_USER= \
|
||||||
OPENVPN_TARGET_IP= \
|
|
||||||
OPENVPN_IPV6=off \
|
|
||||||
OPENVPN_CUSTOM_CONFIG= \
|
OPENVPN_CUSTOM_CONFIG= \
|
||||||
OPENVPN_INTERFACE=tun0 \
|
|
||||||
OPENVPN_PORT= \
|
|
||||||
# Wireguard
|
# Wireguard
|
||||||
WIREGUARD_PRIVATE_KEY= \
|
WIREGUARD_PRIVATE_KEY= \
|
||||||
WIREGUARD_PRESHARED_KEY= \
|
WIREGUARD_PRESHARED_KEY= \
|
||||||
WIREGUARD_PUBLIC_KEY= \
|
WIREGUARD_PUBLIC_KEY= \
|
||||||
WIREGUARD_ADDRESS= \
|
WIREGUARD_ADDRESSES= \
|
||||||
WIREGUARD_ENDPOINT_IP= \
|
WIREGUARD_IMPLEMENTATION=auto \
|
||||||
WIREGUARD_ENDPOINT_PORT= \
|
|
||||||
WIREGUARD_INTERFACE=wg0 \
|
|
||||||
# VPN server filtering
|
# VPN server filtering
|
||||||
REGION= \
|
SERVER_REGIONS= \
|
||||||
COUNTRY= \
|
SERVER_COUNTRIES= \
|
||||||
CITY= \
|
SERVER_CITIES= \
|
||||||
SERVER_HOSTNAME= \
|
SERVER_HOSTNAMES= \
|
||||||
# # Mullvad only:
|
# # Mullvad only:
|
||||||
ISP= \
|
ISP= \
|
||||||
OWNED=no \
|
OWNED_ONLY=no \
|
||||||
# # Private Internet Access only:
|
# # Private Internet Access only:
|
||||||
PIA_ENCRYPTION=strong \
|
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
|
||||||
PORT_FORWARDING=off \
|
VPN_PORT_FORWARDING=off \
|
||||||
PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||||
# # Cyberghost only:
|
# # Cyberghost only:
|
||||||
|
OPENVPN_CERT= \
|
||||||
|
OPENVPN_KEY= \
|
||||||
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
||||||
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
||||||
|
# # VPNSecure only:
|
||||||
|
OPENVPN_ENCRYPTED_KEY= \
|
||||||
|
OPENVPN_ENCRYPTED_KEY_SECRETFILE=/run/secrets/openvpn_encrypted_key \
|
||||||
|
OPENVPN_KEY_PASSPHRASE= \
|
||||||
|
OPENVPN_KEY_PASSPHRASE_SECRETFILE=/run/secrets/openvpn_key_passphrase \
|
||||||
# # Nordvpn only:
|
# # Nordvpn only:
|
||||||
SERVER_NUMBER= \
|
SERVER_NUMBER= \
|
||||||
# # PIA and ProtonVPN only:
|
# # PIA only:
|
||||||
SERVER_NAME= \
|
SERVER_NAMES= \
|
||||||
# # ProtonVPN only:
|
# # ProtonVPN only:
|
||||||
FREE_ONLY= \
|
FREE_ONLY= \
|
||||||
# # Surfshark only:
|
# # Surfshark only:
|
||||||
MULTIHOP_ONLY= \
|
MULTIHOP_ONLY= \
|
||||||
|
# # VPN Secure only:
|
||||||
|
PREMIUM_ONLY= \
|
||||||
# Firewall
|
# Firewall
|
||||||
FIREWALL=on \
|
FIREWALL=on \
|
||||||
FIREWALL_VPN_INPUT_PORTS= \
|
FIREWALL_VPN_INPUT_PORTS= \
|
||||||
@@ -126,7 +140,8 @@ ENV VPNSP=pia \
|
|||||||
LOG_LEVEL=info \
|
LOG_LEVEL=info \
|
||||||
# Health
|
# Health
|
||||||
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
||||||
HEALTH_ADDRESS_TO_PING=github.com \
|
HEALTH_TARGET_ADDRESS=cloudflare.com:443 \
|
||||||
|
HEALTH_SUCCESS_WAIT_DURATION=5s \
|
||||||
HEALTH_VPN_DURATION_INITIAL=6s \
|
HEALTH_VPN_DURATION_INITIAL=6s \
|
||||||
HEALTH_VPN_DURATION_ADDITION=5s \
|
HEALTH_VPN_DURATION_ADDITION=5s \
|
||||||
# DNS over TLS
|
# DNS over TLS
|
||||||
@@ -143,12 +158,12 @@ ENV VPNSP=pia \
|
|||||||
BLOCK_ADS=off \
|
BLOCK_ADS=off \
|
||||||
UNBLOCK= \
|
UNBLOCK= \
|
||||||
DNS_UPDATE_PERIOD=24h \
|
DNS_UPDATE_PERIOD=24h \
|
||||||
DNS_PLAINTEXT_ADDRESS=1.1.1.1 \
|
DNS_ADDRESS=127.0.0.1 \
|
||||||
DNS_KEEP_NAMESERVER=off \
|
DNS_KEEP_NAMESERVER=off \
|
||||||
# HTTP proxy
|
# HTTP proxy
|
||||||
HTTPPROXY= \
|
HTTPPROXY= \
|
||||||
HTTPPROXY_LOG=off \
|
HTTPPROXY_LOG=off \
|
||||||
HTTPPROXY_PORT=8888 \
|
HTTPPROXY_LISTENING_ADDRESS=":8888" \
|
||||||
HTTPPROXY_USER= \
|
HTTPPROXY_USER= \
|
||||||
HTTPPROXY_PASSWORD= \
|
HTTPPROXY_PASSWORD= \
|
||||||
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
|
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
|
||||||
@@ -156,26 +171,36 @@ ENV VPNSP=pia \
|
|||||||
# Shadowsocks
|
# Shadowsocks
|
||||||
SHADOWSOCKS=off \
|
SHADOWSOCKS=off \
|
||||||
SHADOWSOCKS_LOG=off \
|
SHADOWSOCKS_LOG=off \
|
||||||
SHADOWSOCKS_ADDRESS=":8388" \
|
SHADOWSOCKS_LISTENING_ADDRESS=":8388" \
|
||||||
SHADOWSOCKS_PASSWORD= \
|
SHADOWSOCKS_PASSWORD= \
|
||||||
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||||
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
||||||
|
# Control server
|
||||||
|
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
|
||||||
# Server data updater
|
# Server data updater
|
||||||
UPDATER_PERIOD=0 \
|
UPDATER_PERIOD=0 \
|
||||||
|
UPDATER_MIN_RATIO=0.8 \
|
||||||
|
UPDATER_VPN_SERVICE_PROVIDERS= \
|
||||||
# Public IP
|
# Public IP
|
||||||
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
||||||
PUBLICIP_PERIOD=12h \
|
PUBLICIP_PERIOD=12h \
|
||||||
|
# Pprof
|
||||||
|
PPROF_ENABLED=no \
|
||||||
|
PPROF_BLOCK_PROFILE_RATE=0 \
|
||||||
|
PPROF_MUTEX_PROFILE_RATE=0 \
|
||||||
|
PPROF_HTTP_SERVER_ADDRESS=":6060" \
|
||||||
# Extras
|
# Extras
|
||||||
VERSION_INFORMATION=on \
|
VERSION_INFORMATION=on \
|
||||||
TZ= \
|
TZ= \
|
||||||
PUID= \
|
PUID= \
|
||||||
PGID=
|
PGID=
|
||||||
ENTRYPOINT ["/entrypoint"]
|
ENTRYPOINT ["/gluetun-entrypoint"]
|
||||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
||||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /entrypoint healthcheck
|
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
RUN apk add --no-cache --update -l apk-tools && \
|
RUN apk add --no-cache --update -l wget && \
|
||||||
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.11-r0 && \
|
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \
|
||||||
|
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.16/main" openssl\~1.1 && \
|
||||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
|
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
|
||||||
apk del openvpn && \
|
apk del openvpn && \
|
||||||
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
||||||
@@ -186,4 +211,4 @@ RUN apk add --no-cache --update -l apk-tools && \
|
|||||||
deluser openvpn && \
|
deluser openvpn && \
|
||||||
deluser unbound && \
|
deluser unbound && \
|
||||||
mkdir /gluetun
|
mkdir /gluetun
|
||||||
COPY --from=build /tmp/gobuild/entrypoint /entrypoint
|
COPY --from=build /tmp/gobuild/entrypoint /gluetun-entrypoint
|
||||||
|
|||||||
31
README.md
31
README.md
@@ -1,11 +1,6 @@
|
|||||||
# Gluetun VPN client
|
# Gluetun VPN client
|
||||||
|
|
||||||
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, FastestVPN,
|
Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
||||||
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Privado, Private Internet Access, PrivateVPN,
|
|
||||||
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers
|
|
||||||
using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
|
||||||
|
|
||||||
**ANNOUNCEMENT**: Wireguard is now supported for all providers supporting it!
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -53,6 +48,7 @@ using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP
|
|||||||
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
||||||
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
||||||
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
|
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
|
||||||
|
- **Want to add a VPN provider?** check [Development](https://github.com/qdm12/gluetun/wiki/Development) and [Add a provider](https://github.com/qdm12/gluetun/wiki/Add-a-provider)
|
||||||
- Video:
|
- Video:
|
||||||
|
|
||||||
[](https://youtu.be/0F6I03LQcI4)
|
[](https://youtu.be/0F6I03LQcI4)
|
||||||
@@ -61,12 +57,12 @@ using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.14 for a small Docker image of 33MB
|
- Based on Alpine 3.17 for a small Docker image of 42MB
|
||||||
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
||||||
- Supports OpenVPN for all providers listed
|
- Supports OpenVPN for all providers listed
|
||||||
- Supports Wireguard
|
- Supports Wireguard both kernelspace and userspace
|
||||||
- For **Mullvad**, **Ivpn** and **Windscribe**
|
- For **Mullvad**, **Ivpn**, **Surfshark** and **Windscribe**
|
||||||
- For **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
||||||
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
||||||
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
||||||
- DNS over TLS baked in with service provider(s) of your choice
|
- DNS over TLS baked in with service provider(s) of your choice
|
||||||
@@ -98,8 +94,12 @@ version: "3"
|
|||||||
services:
|
services:
|
||||||
gluetun:
|
gluetun:
|
||||||
image: qmcgaw/gluetun
|
image: qmcgaw/gluetun
|
||||||
|
# container_name: gluetun
|
||||||
|
# line above must be uncommented to allow external containers to connect. See https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun#external-container-to-gluetun
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
|
devices:
|
||||||
|
- /dev/net/tun:/dev/net/tun
|
||||||
ports:
|
ports:
|
||||||
- 8888:8888/tcp # HTTP proxy
|
- 8888:8888/tcp # HTTP proxy
|
||||||
- 8388:8388/tcp # Shadowsocks
|
- 8388:8388/tcp # Shadowsocks
|
||||||
@@ -108,18 +108,23 @@ services:
|
|||||||
- /yourpath:/gluetun
|
- /yourpath:/gluetun
|
||||||
environment:
|
environment:
|
||||||
# See https://github.com/qdm12/gluetun/wiki
|
# See https://github.com/qdm12/gluetun/wiki
|
||||||
- VPNSP=ivpn
|
- VPN_SERVICE_PROVIDER=ivpn
|
||||||
- VPN_TYPE=openvpn
|
- VPN_TYPE=openvpn
|
||||||
# OpenVPN:
|
# OpenVPN:
|
||||||
- OPENVPN_USER=
|
- OPENVPN_USER=
|
||||||
- OPENVPN_PASSWORD=
|
- OPENVPN_PASSWORD=
|
||||||
# Wireguard:
|
# Wireguard:
|
||||||
# - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
|
# - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
|
||||||
# - WIREGUARD_ADDRESS=10.64.222.21/32
|
# - WIREGUARD_ADDRESSES=10.64.222.21/32
|
||||||
# Timezone for accurate log times
|
# Timezone for accurate log times
|
||||||
- TZ=
|
- TZ=
|
||||||
|
# Server list updater. See https://github.com/qdm12/gluetun/wiki/Updating-Servers#periodic-update
|
||||||
|
- UPDATER_PERIOD=
|
||||||
|
- UPDATER_VPN_SERVICE_PROVIDERS=
|
||||||
```
|
```
|
||||||
|
|
||||||
|
🆕 Image also available as `ghcr.io/qdm12/gluetun`
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[](https://github.com/qdm12/gluetun/master/LICENSE)
|
[](https://github.com/qdm12/gluetun/master/LICENSE)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -17,7 +16,11 @@ import (
|
|||||||
"github.com/qdm12/dns/pkg/unbound"
|
"github.com/qdm12/dns/pkg/unbound"
|
||||||
"github.com/qdm12/gluetun/internal/alpine"
|
"github.com/qdm12/gluetun/internal/alpine"
|
||||||
"github.com/qdm12/gluetun/internal/cli"
|
"github.com/qdm12/gluetun/internal/cli"
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/sources/env"
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||||
|
mux "github.com/qdm12/gluetun/internal/configuration/sources/merge"
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/sources/secrets"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/dns"
|
"github.com/qdm12/gluetun/internal/dns"
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
@@ -26,23 +29,28 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/netlink"
|
"github.com/qdm12/gluetun/internal/netlink"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn"
|
"github.com/qdm12/gluetun/internal/openvpn"
|
||||||
|
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||||
"github.com/qdm12/gluetun/internal/portforward"
|
"github.com/qdm12/gluetun/internal/portforward"
|
||||||
|
"github.com/qdm12/gluetun/internal/pprof"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider"
|
||||||
"github.com/qdm12/gluetun/internal/publicip"
|
"github.com/qdm12/gluetun/internal/publicip"
|
||||||
|
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
|
||||||
"github.com/qdm12/gluetun/internal/routing"
|
"github.com/qdm12/gluetun/internal/routing"
|
||||||
"github.com/qdm12/gluetun/internal/server"
|
"github.com/qdm12/gluetun/internal/server"
|
||||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/gluetun/internal/tun"
|
"github.com/qdm12/gluetun/internal/tun"
|
||||||
"github.com/qdm12/gluetun/internal/updater"
|
updater "github.com/qdm12/gluetun/internal/updater/loop"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/unzip"
|
||||||
"github.com/qdm12/gluetun/internal/vpn"
|
"github.com/qdm12/gluetun/internal/vpn"
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
"github.com/qdm12/goshutdown"
|
"github.com/qdm12/goshutdown"
|
||||||
"github.com/qdm12/goshutdown/goroutine"
|
"github.com/qdm12/goshutdown/goroutine"
|
||||||
"github.com/qdm12/goshutdown/group"
|
"github.com/qdm12/goshutdown/group"
|
||||||
"github.com/qdm12/goshutdown/order"
|
"github.com/qdm12/goshutdown/order"
|
||||||
"github.com/qdm12/gosplash"
|
"github.com/qdm12/gosplash"
|
||||||
|
"github.com/qdm12/log"
|
||||||
"github.com/qdm12/updated/pkg/dnscrypto"
|
"github.com/qdm12/updated/pkg/dnscrypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,11 +61,6 @@ var (
|
|||||||
created = "an unknown date"
|
created = "an unknown date"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
errSetupRouting = errors.New("cannot setup routing")
|
|
||||||
errCreateUser = errors.New("cannot create user")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
buildInfo := models.BuildInformation{
|
buildInfo := models.BuildInformation{
|
||||||
Version: version,
|
Version: version,
|
||||||
@@ -66,33 +69,36 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
background := context.Background()
|
background := context.Background()
|
||||||
signalCtx, stop := signal.NotifyContext(background, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
signalCh := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
|
||||||
ctx, cancel := context.WithCancel(background)
|
ctx, cancel := context.WithCancel(background)
|
||||||
|
|
||||||
logger := logging.New(logging.Settings{
|
logger := log.New(log.SetLevel(log.LevelInfo))
|
||||||
Level: logging.LevelInfo,
|
|
||||||
})
|
|
||||||
|
|
||||||
args := os.Args
|
args := os.Args
|
||||||
tun := tun.New()
|
tun := tun.New()
|
||||||
netLinker := netlink.New()
|
netLinkDebugLogger := logger.New(log.SetComponent("netlink"))
|
||||||
|
netLinker := netlink.New(netLinkDebugLogger)
|
||||||
cli := cli.New()
|
cli := cli.New()
|
||||||
env := params.New()
|
|
||||||
cmder := command.NewCmder()
|
cmder := command.NewCmder()
|
||||||
|
|
||||||
|
envReader := env.New(logger)
|
||||||
|
filesReader := files.New()
|
||||||
|
secretsReader := secrets.New()
|
||||||
|
muxReader := mux.New(envReader, filesReader, secretsReader)
|
||||||
|
|
||||||
errorCh := make(chan error)
|
errorCh := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
errorCh <- _main(ctx, buildInfo, args, logger, env, tun, netLinker, cmder, cli)
|
errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var err error
|
||||||
select {
|
select {
|
||||||
case <-signalCtx.Done():
|
case signal := <-signalCh:
|
||||||
stop()
|
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
logger.Warn("Caught OS signal, shutting down")
|
logger.Warn("Caught OS signal " + signal.String() + ", shutting down")
|
||||||
cancel()
|
cancel()
|
||||||
case err := <-errorCh:
|
case err = <-errorCh:
|
||||||
stop()
|
|
||||||
close(errorCh)
|
close(errorCh)
|
||||||
if err == nil { // expected exit such as healthcheck
|
if err == nil { // expected exit such as healthcheck
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
@@ -104,35 +110,46 @@ func main() {
|
|||||||
const shutdownGracePeriod = 5 * time.Second
|
const shutdownGracePeriod = 5 * time.Second
|
||||||
timer := time.NewTimer(shutdownGracePeriod)
|
timer := time.NewTimer(shutdownGracePeriod)
|
||||||
select {
|
select {
|
||||||
case <-errorCh:
|
case shutdownErr := <-errorCh:
|
||||||
if !timer.Stop() {
|
if !timer.Stop() {
|
||||||
<-timer.C
|
<-timer.C
|
||||||
}
|
}
|
||||||
logger.Info("Shutdown successful")
|
if shutdownErr != nil {
|
||||||
case <-timer.C:
|
logger.Warnf("Shutdown not completed gracefully: %s", shutdownErr)
|
||||||
logger.Warn("Shutdown timed out")
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Info("Shutdown successful")
|
||||||
|
if err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
case <-timer.C:
|
||||||
|
logger.Warn("Shutdown timed out")
|
||||||
|
os.Exit(1)
|
||||||
|
case signal := <-signalCh:
|
||||||
|
logger.Warn("Caught OS signal " + signal.String() + ", forcing shut down")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errCommandUnknown = errors.New("command is unknown")
|
errCommandUnknown = errors.New("command is unknown")
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gocognit,gocyclo
|
//nolint:gocognit,gocyclo,maintidx
|
||||||
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||||
args []string, logger logging.ParentLogger, env params.Interface,
|
args []string, logger log.LoggerInterface, source Source,
|
||||||
tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter,
|
tun Tun, netLinker netLinker, cmder command.RunStarter,
|
||||||
cli cli.CLIer) error {
|
cli clier) error {
|
||||||
if len(args) > 1 { // cli operation
|
if len(args) > 1 { // cli operation
|
||||||
switch args[1] {
|
switch args[1] {
|
||||||
case "healthcheck":
|
case "healthcheck":
|
||||||
return cli.HealthCheck(ctx, env, logger)
|
return cli.HealthCheck(ctx, source, logger)
|
||||||
case "clientkey":
|
case "clientkey":
|
||||||
return cli.ClientKey(args[2:])
|
return cli.ClientKey(args[2:])
|
||||||
case "openvpnconfig":
|
case "openvpnconfig":
|
||||||
return cli.OpenvpnConfig(logger, env)
|
return cli.OpenvpnConfig(logger, source, netLinker)
|
||||||
case "update":
|
case "update":
|
||||||
return cli.Update(ctx, args[2:], logger)
|
return cli.Update(ctx, args[2:], logger)
|
||||||
case "format-servers":
|
case "format-servers":
|
||||||
@@ -142,7 +159,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
announcementExp, err := time.Parse(time.RFC3339, "2021-10-02T00:00:00Z")
|
announcementExp, err := time.Parse(time.RFC3339, "2021-02-15T00:00:00Z")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -153,7 +170,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
Version: buildInfo.Version,
|
Version: buildInfo.Version,
|
||||||
Commit: buildInfo.Commit,
|
Commit: buildInfo.Commit,
|
||||||
BuildDate: buildInfo.Created,
|
BuildDate: buildInfo.Created,
|
||||||
Announcement: "Wireguard is now supported for Mullvad, IVPN and Windscribe!",
|
Announcement: "Large settings parsing refactoring merged on 2022-01-06, please report any issue!",
|
||||||
AnnounceExp: announcementExp,
|
AnnounceExp: announcementExp,
|
||||||
// Sponsor information
|
// Sponsor information
|
||||||
PaypalUser: "qmcgaw",
|
PaypalUser: "qmcgaw",
|
||||||
@@ -163,30 +180,82 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
fmt.Println(line)
|
fmt.Println(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allSettings, err := source.Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: no need to validate minimal settings for the firewall:
|
||||||
|
// - global log level is parsed from source
|
||||||
|
// - firewall Debug and Enabled are booleans parsed from source
|
||||||
|
|
||||||
|
logger.Patch(log.SetLevel(*allSettings.Log.Level))
|
||||||
|
netLinker.PatchLoggerLevel(*allSettings.Log.Level)
|
||||||
|
|
||||||
|
routingLogger := logger.New(log.SetComponent("routing"))
|
||||||
|
if *allSettings.Firewall.Debug { // To remove in v4
|
||||||
|
routingLogger.Patch(log.SetLevel(log.LevelDebug))
|
||||||
|
}
|
||||||
|
routingConf := routing.New(netLinker, routingLogger)
|
||||||
|
|
||||||
|
defaultRoutes, err := routingConf.DefaultRoutes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
localNetworks, err := routingConf.LocalNetworks()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
firewallLogger := logger.New(log.SetComponent("firewall"))
|
||||||
|
if *allSettings.Firewall.Debug { // To remove in v4
|
||||||
|
firewallLogger.Patch(log.SetLevel(log.LevelDebug))
|
||||||
|
}
|
||||||
|
firewallConf, err := firewall.NewConfig(ctx, firewallLogger, cmder,
|
||||||
|
defaultRoutes, localNetworks)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if *allSettings.Firewall.Enabled {
|
||||||
|
err = firewallConf.SetEnabled(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO run this in a loop or in openvpn to reload from file without restarting
|
// TODO run this in a loop or in openvpn to reload from file without restarting
|
||||||
storageLogger := logger.NewChild(logging.Settings{Prefix: "storage: "})
|
storageLogger := logger.New(log.SetComponent("storage"))
|
||||||
storage, err := storage.New(storageLogger, constants.ServersData)
|
storage, err := storage.New(storageLogger, constants.ServersData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
allServers := storage.GetServers()
|
|
||||||
|
|
||||||
var allSettings configuration.Settings
|
ipv6Supported, err := netLinker.IsIPv6Supported()
|
||||||
err = allSettings.Read(env, allServers,
|
if err != nil {
|
||||||
logger.NewChild(logging.Settings{Prefix: "configuration: "}))
|
return fmt.Errorf("checking for IPv6 support: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = allSettings.Validate(storage, ipv6Supported)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.PatchLevel(allSettings.Log.Level)
|
|
||||||
|
|
||||||
puid, pgid := allSettings.System.PUID, allSettings.System.PGID
|
allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof"))
|
||||||
|
pprofServer, err := pprof.New(allSettings.Pprof)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating Pprof server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
|
||||||
|
|
||||||
const clientTimeout = 15 * time.Second
|
const clientTimeout = 15 * time.Second
|
||||||
httpClient := &http.Client{Timeout: clientTimeout}
|
httpClient := &http.Client{Timeout: clientTimeout}
|
||||||
// Create configurators
|
// Create configurators
|
||||||
alpineConf := alpine.New()
|
alpineConf := alpine.New()
|
||||||
ovpnConf := openvpn.New(
|
ovpnConf := openvpn.New(
|
||||||
logger.NewChild(logging.Settings{Prefix: "openvpn configurator: "}),
|
logger.New(log.SetComponent("openvpn configurator")),
|
||||||
cmder, puid, pgid)
|
cmder, puid, pgid)
|
||||||
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
||||||
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
|
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
|
||||||
@@ -208,6 +277,10 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
|
|
||||||
logger.Info(allSettings.String())
|
logger.Info(allSettings.String())
|
||||||
|
|
||||||
|
for _, warning := range allSettings.Warnings() {
|
||||||
|
logger.Warn(warning)
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
|
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -218,62 +291,30 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
const defaultUsername = "nonrootuser"
|
const defaultUsername = "nonrootuser"
|
||||||
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %s", errCreateUser, err)
|
return fmt.Errorf("creating user: %w", err)
|
||||||
}
|
}
|
||||||
if nonRootUsername != defaultUsername {
|
if nonRootUsername != defaultUsername {
|
||||||
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
|
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
|
||||||
}
|
}
|
||||||
// set it for Unbound
|
// set it for Unbound
|
||||||
// TODO remove this when migrating to qdm12/dns v2
|
// TODO remove this when migrating to qdm12/dns v2
|
||||||
allSettings.DNS.Unbound.Username = nonRootUsername
|
allSettings.DNS.DoT.Unbound.Username = nonRootUsername
|
||||||
allSettings.VPN.OpenVPN.ProcUser = nonRootUsername
|
allSettings.VPN.OpenVPN.ProcessUser = nonRootUsername
|
||||||
|
|
||||||
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
firewallLogLevel := allSettings.Log.Level
|
|
||||||
if allSettings.Firewall.Debug {
|
|
||||||
firewallLogLevel = logging.LevelDebug
|
|
||||||
}
|
|
||||||
routingLogger := logger.NewChild(logging.Settings{
|
|
||||||
Prefix: "routing: ",
|
|
||||||
Level: firewallLogLevel,
|
|
||||||
})
|
|
||||||
routingConf := routing.New(netLinker, routingLogger)
|
|
||||||
|
|
||||||
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
localNetworks, err := routingConf.LocalNetworks()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultIP, err := routingConf.DefaultIP()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
firewallLogger := logger.NewChild(logging.Settings{
|
|
||||||
Prefix: "firewall: ",
|
|
||||||
Level: firewallLogLevel,
|
|
||||||
})
|
|
||||||
firewallConf := firewall.NewConfig(firewallLogger, cmder,
|
|
||||||
defaultInterface, defaultGateway, localNetworks, defaultIP)
|
|
||||||
|
|
||||||
if err := routingConf.Setup(); err != nil {
|
if err := routingConf.Setup(); err != nil {
|
||||||
if strings.Contains(err.Error(), "operation not permitted") {
|
if strings.Contains(err.Error(), "operation not permitted") {
|
||||||
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
|
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%w: %s", errSetupRouting, err)
|
return fmt.Errorf("setting up routing: %w", err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
logger.Info("routing cleanup...")
|
routingLogger.Info("routing cleanup...")
|
||||||
if err := routingConf.TearDown(); err != nil {
|
if err := routingConf.TearDown(); err != nil {
|
||||||
logger.Error("cannot teardown routing: " + err.Error())
|
routingLogger.Error("cannot teardown routing: " + err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -284,26 +325,27 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tun.Check(constants.TunnelDevice); err != nil {
|
err = routingConf.AddLocalRules(localNetworks)
|
||||||
logger.Info(err.Error() + "; creating it...")
|
|
||||||
err = tun.Create(constants.TunnelDevice)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("adding local rules: %w", err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if allSettings.Firewall.Enabled {
|
const tunDevice = "/dev/net/tun"
|
||||||
err := firewallConf.SetEnabled(ctx, true) // disabled by default
|
if err := tun.Check(tunDevice); err != nil {
|
||||||
|
logger.Info(err.Error() + "; creating it...")
|
||||||
|
err = tun.Create(tunDevice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, port := range allSettings.Firewall.InputPorts {
|
for _, port := range allSettings.Firewall.InputPorts {
|
||||||
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
|
for _, defaultRoute := range defaultRoutes {
|
||||||
|
err = firewallConf.SetAllowedPort(ctx, port, defaultRoute.NetInterface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} // TODO move inside firewall?
|
} // TODO move inside firewall?
|
||||||
|
|
||||||
// Shutdown settings
|
// Shutdown settings
|
||||||
@@ -323,14 +365,23 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
|
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
|
||||||
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
|
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
|
||||||
|
|
||||||
portForwardLogger := logger.NewChild(logging.Settings{Prefix: "port forwarding: "})
|
if *allSettings.Pprof.Enabled {
|
||||||
|
// TODO run in run loop so this can be patched at runtime
|
||||||
|
pprofReady := make(chan struct{})
|
||||||
|
pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server")
|
||||||
|
go pprofServer.Run(pprofCtx, pprofReady, pprofDone)
|
||||||
|
otherGroupHandler.Add(pprofHandler)
|
||||||
|
<-pprofReady
|
||||||
|
}
|
||||||
|
|
||||||
|
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
|
||||||
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
||||||
httpClient, firewallConf, portForwardLogger)
|
httpClient, firewallConf, portForwardLogger, puid, pgid)
|
||||||
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
|
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
|
||||||
"port forwarding", goroutine.OptionTimeout(time.Second))
|
"port forwarding", goroutine.OptionTimeout(time.Second))
|
||||||
go portForwardLooper.Run(portForwardCtx, portForwardDone)
|
go portForwardLooper.Run(portForwardCtx, portForwardDone)
|
||||||
|
|
||||||
unboundLogger := logger.NewChild(logging.Settings{Prefix: "dns over tls: "})
|
unboundLogger := logger.New(log.SetComponent("dns over tls"))
|
||||||
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
||||||
unboundLogger)
|
unboundLogger)
|
||||||
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
|
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
|
||||||
@@ -344,8 +395,9 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
|
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
|
||||||
controlGroupHandler.Add(dnsTickerHandler)
|
controlGroupHandler.Add(dnsTickerHandler)
|
||||||
|
|
||||||
publicIPLooper := publicip.NewLoop(httpClient,
|
ipFetcher := ipinfo.New(httpClient)
|
||||||
logger.NewChild(logging.Settings{Prefix: "ip getter: "}),
|
publicIPLooper := publicip.NewLoop(ipFetcher,
|
||||||
|
logger.New(log.SetComponent("ip getter")),
|
||||||
allSettings.PublicIP, puid, pgid)
|
allSettings.PublicIP, puid, pgid)
|
||||||
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
|
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
|
||||||
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
@@ -357,18 +409,25 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
|
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
|
||||||
tickersGroupHandler.Add(pubIPTickerHandler)
|
tickersGroupHandler.Add(pubIPTickerHandler)
|
||||||
|
|
||||||
vpnLogger := logger.NewChild(logging.Settings{Prefix: "vpn: "})
|
updaterLogger := logger.New(log.SetComponent("updater"))
|
||||||
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
|
|
||||||
allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
|
unzipper := unzip.New(httpClient)
|
||||||
|
parallelResolver := resolver.NewParallelResolver(allSettings.Updater.DNSAddress)
|
||||||
|
openvpnFileExtractor := extract.New()
|
||||||
|
providers := provider.NewProviders(storage, time.Now, updaterLogger,
|
||||||
|
httpClient, unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
||||||
|
|
||||||
|
vpnLogger := logger.New(log.SetComponent("vpn"))
|
||||||
|
vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6Supported, allSettings.Firewall.VPNInputPorts,
|
||||||
|
providers, storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
|
||||||
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
|
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
|
||||||
buildInfo, allSettings.VersionInformation)
|
buildInfo, *allSettings.Version.Enabled)
|
||||||
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
|
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
|
||||||
"vpn", goroutine.OptionTimeout(time.Second))
|
"vpn", goroutine.OptionTimeout(time.Second))
|
||||||
go vpnLooper.Run(vpnCtx, vpnDone)
|
go vpnLooper.Run(vpnCtx, vpnDone)
|
||||||
|
|
||||||
updaterLooper := updater.NewLooper(allSettings.Updater,
|
updaterLooper := updater.NewLoop(allSettings.Updater,
|
||||||
allServers, storage, vpnLooper.SetServers, httpClient,
|
providers, storage, httpClient, updaterLogger)
|
||||||
logger.NewChild(logging.Settings{Prefix: "updater: "}))
|
|
||||||
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
|
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
|
||||||
"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
||||||
@@ -381,31 +440,37 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
controlGroupHandler.Add(updaterTickerHandler)
|
controlGroupHandler.Add(updaterTickerHandler)
|
||||||
|
|
||||||
httpProxyLooper := httpproxy.NewLoop(
|
httpProxyLooper := httpproxy.NewLoop(
|
||||||
logger.NewChild(logging.Settings{Prefix: "http proxy: "}),
|
logger.New(log.SetComponent("http proxy")),
|
||||||
allSettings.HTTPProxy)
|
allSettings.HTTPProxy)
|
||||||
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
|
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
|
||||||
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
|
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
|
||||||
otherGroupHandler.Add(httpProxyHandler)
|
otherGroupHandler.Add(httpProxyHandler)
|
||||||
|
|
||||||
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks,
|
shadowsocksLooper := shadowsocks.NewLoop(allSettings.Shadowsocks,
|
||||||
logger.NewChild(logging.Settings{Prefix: "shadowsocks: "}))
|
logger.New(log.SetComponent("shadowsocks")))
|
||||||
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
|
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
|
||||||
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
|
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
|
||||||
otherGroupHandler.Add(shadowsocksHandler)
|
otherGroupHandler.Add(shadowsocksHandler)
|
||||||
|
|
||||||
controlServerAddress := ":" + strconv.Itoa(int(allSettings.ControlServer.Port))
|
controlServerAddress := *allSettings.ControlServer.Address
|
||||||
controlServerLogging := allSettings.ControlServer.Log
|
controlServerLogging := *allSettings.ControlServer.Log
|
||||||
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
|
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
|
||||||
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
||||||
logger.NewChild(logging.Settings{Prefix: "http server: "}),
|
logger.New(log.SetComponent("http server")),
|
||||||
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
|
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper,
|
||||||
go httpServer.Run(httpServerCtx, httpServerDone)
|
storage, ipv6Supported)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("setting up control server: %w", err)
|
||||||
|
}
|
||||||
|
httpServerReady := make(chan struct{})
|
||||||
|
go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
|
||||||
|
<-httpServerReady
|
||||||
controlGroupHandler.Add(httpServerHandler)
|
controlGroupHandler.Add(httpServerHandler)
|
||||||
|
|
||||||
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
|
healthLogger := logger.New(log.SetComponent("healthcheck"))
|
||||||
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
|
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
|
||||||
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
||||||
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
@@ -445,10 +510,69 @@ func printVersions(ctx context.Context, logger infoer,
|
|||||||
for _, element := range elements {
|
for _, element := range elements {
|
||||||
version, err := element.getVersion(ctx)
|
version, err := element.getVersion(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("getting %s version: %w", element.name, err)
|
||||||
}
|
}
|
||||||
logger.Info(element.name + " version: " + version)
|
logger.Info(element.name + " version: " + version)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type netLinker interface {
|
||||||
|
Addresser
|
||||||
|
Router
|
||||||
|
Ruler
|
||||||
|
Linker
|
||||||
|
IsWireguardSupported() (ok bool, err error)
|
||||||
|
IsIPv6Supported() (ok bool, err error)
|
||||||
|
PatchLoggerLevel(level log.Level)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Addresser interface {
|
||||||
|
AddrList(link netlink.Link, family int) (
|
||||||
|
addresses []netlink.Addr, err error)
|
||||||
|
AddrAdd(link netlink.Link, addr *netlink.Addr) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Router interface {
|
||||||
|
RouteList(link netlink.Link, family int) (
|
||||||
|
routes []netlink.Route, err error)
|
||||||
|
RouteAdd(route *netlink.Route) error
|
||||||
|
RouteDel(route *netlink.Route) error
|
||||||
|
RouteReplace(route *netlink.Route) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ruler interface {
|
||||||
|
RuleList(family int) (rules []netlink.Rule, err error)
|
||||||
|
RuleAdd(rule *netlink.Rule) error
|
||||||
|
RuleDel(rule *netlink.Rule) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Linker interface {
|
||||||
|
LinkList() (links []netlink.Link, err error)
|
||||||
|
LinkByName(name string) (link netlink.Link, err error)
|
||||||
|
LinkByIndex(index int) (link netlink.Link, err error)
|
||||||
|
LinkAdd(link netlink.Link) (err error)
|
||||||
|
LinkDel(link netlink.Link) (err error)
|
||||||
|
LinkSetUp(link netlink.Link) (err error)
|
||||||
|
LinkSetDown(link netlink.Link) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type clier interface {
|
||||||
|
ClientKey(args []string) error
|
||||||
|
FormatServers(args []string) error
|
||||||
|
OpenvpnConfig(logger cli.OpenvpnConfigLogger, source cli.Source, ipv6Checker cli.IPv6Checker) error
|
||||||
|
HealthCheck(ctx context.Context, source cli.Source, warner cli.Warner) error
|
||||||
|
Update(ctx context.Context, args []string, logger cli.UpdaterLogger) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tun interface {
|
||||||
|
Check(tunDevice string) error
|
||||||
|
Create(tunDevice string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Source interface {
|
||||||
|
Read() (settings settings.Settings, err error)
|
||||||
|
ReadHealth() (health settings.Health, err error)
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|||||||
56
go.mod
56
go.mod
@@ -1,44 +1,50 @@
|
|||||||
module github.com/qdm12/gluetun
|
module github.com/qdm12/gluetun
|
||||||
|
|
||||||
go 1.17
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/breml/rootcerts v0.2.0
|
github.com/breml/rootcerts v0.2.10
|
||||||
github.com/fatih/color v1.13.0
|
github.com/fatih/color v1.15.0
|
||||||
github.com/go-ping/ping v0.0.0-20210911151512-381826476871
|
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/qdm12/dns v1.11.0
|
github.com/qdm12/dns v1.11.0
|
||||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
||||||
github.com/qdm12/goshutdown v0.3.0
|
github.com/qdm12/goshutdown v0.3.0
|
||||||
github.com/qdm12/gosplash v0.1.0
|
github.com/qdm12/gosplash v0.1.0
|
||||||
github.com/qdm12/ss-server v0.3.0
|
github.com/qdm12/gotree v0.2.0
|
||||||
|
github.com/qdm12/govalid v0.1.0
|
||||||
|
github.com/qdm12/log v0.1.0
|
||||||
|
github.com/qdm12/ss-server v0.4.0
|
||||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.8.2
|
||||||
github.com/vishvananda/netlink v1.1.0
|
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
|
golang.org/x/exp v0.0.0-20230519143937-03e91628a987
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
|
golang.org/x/net v0.10.0
|
||||||
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
|
golang.org/x/sys v0.8.0
|
||||||
|
golang.org/x/text v0.9.0
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
|
||||||
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
|
||||||
|
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/google/go-cmp v0.5.5 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/google/uuid v1.2.0 // indirect
|
github.com/josharian/native v1.0.0 // indirect
|
||||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mdlayher/genetlink v1.2.0 // indirect
|
||||||
github.com/mdlayher/genetlink v1.0.0 // indirect
|
github.com/mdlayher/netlink v1.6.2 // indirect
|
||||||
github.com/mdlayher/netlink v1.4.0 // indirect
|
github.com/mdlayher/socket v0.2.3 // indirect
|
||||||
github.com/miekg/dns v1.1.40 // indirect
|
github.com/miekg/dns v1.1.40 // indirect
|
||||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
|
||||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
|
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
golang.org/x/crypto v0.6.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
189
go.sum
189
go.sum
@@ -4,8 +4,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g
|
|||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/breml/rootcerts v0.2.0 h1:bBIgVe8bS0Ec+orgWpZ/GRYt3a0O8yoW+g2kSBY2aLE=
|
github.com/breml/rootcerts v0.2.10 h1:UGVZ193UTSUASpGtg6pbDwzOd7XQP+at0Ssg1/2E4h8=
|
||||||
github.com/breml/rootcerts v0.2.0/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
github.com/breml/rootcerts v0.2.10/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -14,8 +14,8 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
|||||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
@@ -32,36 +32,22 @@ github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsd
|
|||||||
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||||
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||||
github.com/go-ping/ping v0.0.0-20210911151512-381826476871 h1:wtjTfjwAR/BYYMJ+QOLI/3J/qGEI0fgrkZvgsEWK2/Q=
|
|
||||||
github.com/go-ping/ping v0.0.0-20210911151512-381826476871/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
|
||||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
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.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
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/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
|
||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
|
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
|
||||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
|
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
|
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
|
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
|
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
|
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
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/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
@@ -72,29 +58,23 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||||||
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
|
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
|
||||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
|
||||||
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
|
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
|
||||||
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
|
||||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
github.com/mdlayher/netlink v1.6.2 h1:D2zGSkvYsJ6NreeED3JiVTu1lj2sIYATqSaZlhPzUgQ=
|
||||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
github.com/mdlayher/netlink v1.6.2/go.mod h1:O1HXX2sIWSMJ3Qn1BYZk1yZM+7iMki/uYGGiwGyq/iU=
|
||||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
|
||||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
|
||||||
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
|
github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
|
||||||
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
|
||||||
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
|
||||||
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
|
|
||||||
github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0=
|
|
||||||
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
|
|
||||||
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
|
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
|
||||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||||
@@ -115,8 +95,14 @@ github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSE
|
|||||||
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
|
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
|
||||||
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
|
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
|
||||||
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
|
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
|
||||||
github.com/qdm12/ss-server v0.3.0 h1:BfKv4OU6dYb2KcDMYpTc7LIuO2jB73g3JCzy988GrLI=
|
github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
|
||||||
github.com/qdm12/ss-server v0.3.0/go.mod h1:ug+nWfuzKw/h5fxL1B6e9/OhkVuWJX4i2V1Pf0pJU1o=
|
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
|
||||||
|
github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8=
|
||||||
|
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4=
|
||||||
|
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
|
||||||
|
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
|
||||||
|
github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU=
|
||||||
|
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY=
|
||||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
|
||||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
@@ -125,101 +111,104 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
|
|||||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.2.2/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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||||
|
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||||
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
||||||
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||||
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
|
|
||||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
|
||||||
|
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
|
||||||
|
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc=
|
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 h1:QJ/xcIANMLApehfgPCHnfK1hZiaMmbaTVmPv7DAoTbo=
|
||||||
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
|
||||||
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||||
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
|
golang.org/x/exp v0.0.0-20230519143937-03e91628a987 h1:3xJIFvzUFbu4ls0BTBYcgbCGhA63eAOEMxIHugyXJqA=
|
||||||
|
golang.org/x/exp v0.0.0-20230519143937-03e91628a987/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/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-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
|
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
|
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20201020160332-67f06af15bc9/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-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -229,13 +218,13 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-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-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=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 h1:ab2jcw2W91Rz07eHAb8Lic7sFQKO0NhBftjv6m/gL/0=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
|
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
|
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzIUikL9x4LgwEmzhXtzRpKNqngme1VGDWz+Nk=
|
||||||
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -245,8 +234,10 @@ gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQb
|
|||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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.2/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=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY=
|
||||||
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||||
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722 h1:Qws2rZnQudC58cIagVucPQDLmMi3kAXgxscsgD0v6DU=
|
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
|
||||||
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
// Package alpine defines a configurator to interact with the Alpine operating system.
|
|
||||||
package alpine
|
package alpine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os/user"
|
"os/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Alpiner = (*Alpine)(nil)
|
|
||||||
|
|
||||||
type Alpiner interface {
|
|
||||||
UserCreater
|
|
||||||
VersionGetter
|
|
||||||
}
|
|
||||||
|
|
||||||
type Alpine struct {
|
type Alpine struct {
|
||||||
alpineReleasePath string
|
alpineReleasePath string
|
||||||
passwdPath string
|
passwdPath string
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ var (
|
|||||||
ErrUserAlreadyExists = errors.New("user already exists")
|
ErrUserAlreadyExists = errors.New("user already exists")
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserCreater interface {
|
|
||||||
CreateUser(username string, uid int) (createdUsername string, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUser creates a user in Alpine with the given UID.
|
// CreateUser creates a user in Alpine with the given UID.
|
||||||
func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) {
|
func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) {
|
||||||
UIDStr := strconv.Itoa(uid)
|
UIDStr := strconv.Itoa(uid)
|
||||||
|
|||||||
@@ -7,11 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VersionGetter interface {
|
func (a *Alpine) Version(context.Context) (version string, err error) {
|
||||||
Version(ctx context.Context) (version string, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Alpine) Version(ctx context.Context) (version string, err error) {
|
|
||||||
file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0)
|
file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ package cli
|
|||||||
|
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
func (c *CLI) CI(context context.Context) error {
|
func (c *CLI) CI(context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
// Package cli defines an interface CLI to run command line operations.
|
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
var _ CLIer = (*CLI)(nil)
|
|
||||||
|
|
||||||
type CLIer interface {
|
|
||||||
ClientKeyFormatter
|
|
||||||
HealthChecker
|
|
||||||
OpenvpnConfigMaker
|
|
||||||
Updater
|
|
||||||
ServersFormatter
|
|
||||||
}
|
|
||||||
|
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
repoServersPath string
|
repoServersPath string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientKeyFormatter interface {
|
|
||||||
ClientKey(args []string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CLI) ClientKey(args []string) error {
|
func (c *CLI) ClientKey(args []string) error {
|
||||||
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
||||||
filepath := flagSet.String("path", constants.ClientKey, "file path to the client.key file")
|
filepath := flagSet.String("path", files.OpenVPNClientKeyPath, "file path to the client.key file")
|
||||||
if err := flagSet.Parse(args); err != nil {
|
if err := flagSet.Parse(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,51 +6,44 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServersFormatter interface {
|
|
||||||
FormatServers(args []string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrFormatNotRecognized = errors.New("format is not recognized")
|
ErrFormatNotRecognized = errors.New("format is not recognized")
|
||||||
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
|
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
|
||||||
ErrOpenOutputFile = errors.New("cannot open output file")
|
ErrMultipleProvidersToFormat = errors.New("more than one VPN provider to format were specified")
|
||||||
ErrWriteOutput = errors.New("cannot write to output file")
|
|
||||||
ErrCloseOutputFile = errors.New("cannot close output file")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func addProviderFlag(flagSet *flag.FlagSet, providerToFormat map[string]*bool,
|
||||||
|
provider string, titleCaser cases.Caser) {
|
||||||
|
boolPtr, ok := providerToFormat[provider]
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("unknown provider in format map: %s", provider))
|
||||||
|
}
|
||||||
|
flagSet.BoolVar(boolPtr, provider, false, "Format "+titleCaser.String(provider)+" servers")
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CLI) FormatServers(args []string) error {
|
func (c *CLI) FormatServers(args []string) error {
|
||||||
var format, output string
|
var format, output string
|
||||||
var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
|
allProviders := providers.All()
|
||||||
nordvpn, perfectPrivacy, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
|
providersToFormat := make(map[string]*bool, len(allProviders))
|
||||||
torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
|
for _, provider := range allProviders {
|
||||||
|
providersToFormat[provider] = new(bool)
|
||||||
|
}
|
||||||
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
|
||||||
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
|
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
|
||||||
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
|
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
|
||||||
flagSet.BoolVar(&cyberghost, "cyberghost", false, "Format Cyberghost servers")
|
titleCaser := cases.Title(language.English)
|
||||||
flagSet.BoolVar(&expressvpn, "expressvpn", false, "Format ExpressVPN servers")
|
for _, provider := range allProviders {
|
||||||
flagSet.BoolVar(&fastestvpn, "fastestvpn", false, "Format FastestVPN servers")
|
addProviderFlag(flagSet, providersToFormat, provider, titleCaser)
|
||||||
flagSet.BoolVar(&hideMyAss, "hidemyass", false, "Format HideMyAss servers")
|
}
|
||||||
flagSet.BoolVar(&ipvanish, "ipvanish", false, "Format IpVanish servers")
|
|
||||||
flagSet.BoolVar(&ivpn, "ivpn", false, "Format IVPN servers")
|
|
||||||
flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers")
|
|
||||||
flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn servers")
|
|
||||||
flagSet.BoolVar(&perfectPrivacy, "perfectprivacy", false, "Format Perfect Privacy servers")
|
|
||||||
flagSet.BoolVar(&pia, "pia", false, "Format Private Internet Access servers")
|
|
||||||
flagSet.BoolVar(&privado, "privado", false, "Format Privado servers")
|
|
||||||
flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN servers")
|
|
||||||
flagSet.BoolVar(&protonvpn, "protonvpn", false, "Format Protonvpn servers")
|
|
||||||
flagSet.BoolVar(&purevpn, "purevpn", false, "Format Purevpn servers")
|
|
||||||
flagSet.BoolVar(&surfshark, "surfshark", false, "Format Surfshark servers")
|
|
||||||
flagSet.BoolVar(&torguard, "torguard", false, "Format Torguard servers")
|
|
||||||
flagSet.BoolVar(&vpnUnlimited, "vpnunlimited", false, "Format VPN Unlimited servers")
|
|
||||||
flagSet.BoolVar(&vyprvpn, "vyprvpn", false, "Format Vyprvpn servers")
|
|
||||||
flagSet.BoolVar(&wevpn, "wevpn", false, "Format WeVPN servers")
|
|
||||||
flagSet.BoolVar(&windscribe, "windscribe", false, "Format Windscribe servers")
|
|
||||||
if err := flagSet.Parse(args); err != nil {
|
if err := flagSet.Parse(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -59,74 +52,47 @@ func (c *CLI) FormatServers(args []string) error {
|
|||||||
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
|
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify only one provider is set to be formatted.
|
||||||
|
var providers []string
|
||||||
|
for provider, formatPtr := range providersToFormat {
|
||||||
|
if *formatPtr {
|
||||||
|
providers = append(providers, provider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch len(providers) {
|
||||||
|
case 0:
|
||||||
|
return fmt.Errorf("%w", ErrProviderUnspecified)
|
||||||
|
case 1:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: %d specified: %s",
|
||||||
|
ErrMultipleProvidersToFormat, len(providers),
|
||||||
|
strings.Join(providers, ", "))
|
||||||
|
}
|
||||||
|
providerToFormat := providers[0]
|
||||||
|
|
||||||
logger := newNoopLogger()
|
logger := newNoopLogger()
|
||||||
storage, err := storage.New(logger, constants.ServersData)
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %s", ErrNewStorage, err)
|
return fmt.Errorf("creating servers storage: %w", err)
|
||||||
}
|
}
|
||||||
currentServers := storage.GetServers()
|
|
||||||
|
|
||||||
var formatted string
|
formatted := storage.FormatToMarkdown(providerToFormat)
|
||||||
switch {
|
|
||||||
case cyberghost:
|
|
||||||
formatted = currentServers.Cyberghost.ToMarkdown()
|
|
||||||
case expressvpn:
|
|
||||||
formatted = currentServers.Expressvpn.ToMarkdown()
|
|
||||||
case fastestvpn:
|
|
||||||
formatted = currentServers.Fastestvpn.ToMarkdown()
|
|
||||||
case hideMyAss:
|
|
||||||
formatted = currentServers.HideMyAss.ToMarkdown()
|
|
||||||
case ipvanish:
|
|
||||||
formatted = currentServers.Ipvanish.ToMarkdown()
|
|
||||||
case ivpn:
|
|
||||||
formatted = currentServers.Ivpn.ToMarkdown()
|
|
||||||
case mullvad:
|
|
||||||
formatted = currentServers.Mullvad.ToMarkdown()
|
|
||||||
case nordvpn:
|
|
||||||
formatted = currentServers.Nordvpn.ToMarkdown()
|
|
||||||
case perfectPrivacy:
|
|
||||||
formatted = currentServers.Perfectprivacy.ToMarkdown()
|
|
||||||
case pia:
|
|
||||||
formatted = currentServers.Pia.ToMarkdown()
|
|
||||||
case privado:
|
|
||||||
formatted = currentServers.Privado.ToMarkdown()
|
|
||||||
case privatevpn:
|
|
||||||
formatted = currentServers.Privatevpn.ToMarkdown()
|
|
||||||
case protonvpn:
|
|
||||||
formatted = currentServers.Protonvpn.ToMarkdown()
|
|
||||||
case purevpn:
|
|
||||||
formatted = currentServers.Purevpn.ToMarkdown()
|
|
||||||
case surfshark:
|
|
||||||
formatted = currentServers.Surfshark.ToMarkdown()
|
|
||||||
case torguard:
|
|
||||||
formatted = currentServers.Torguard.ToMarkdown()
|
|
||||||
case vpnUnlimited:
|
|
||||||
formatted = currentServers.VPNUnlimited.ToMarkdown()
|
|
||||||
case vyprvpn:
|
|
||||||
formatted = currentServers.Vyprvpn.ToMarkdown()
|
|
||||||
case wevpn:
|
|
||||||
formatted = currentServers.Wevpn.ToMarkdown()
|
|
||||||
case windscribe:
|
|
||||||
formatted = currentServers.Windscribe.ToMarkdown()
|
|
||||||
default:
|
|
||||||
return ErrProviderUnspecified
|
|
||||||
}
|
|
||||||
|
|
||||||
output = filepath.Clean(output)
|
output = filepath.Clean(output)
|
||||||
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
|
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %s", ErrOpenOutputFile, err)
|
return fmt.Errorf("opening output file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = fmt.Fprint(file, formatted)
|
_, err = fmt.Fprint(file, formatted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
return fmt.Errorf("%w: %s", ErrWriteOutput, err)
|
return fmt.Errorf("writing to output file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = file.Close()
|
err = file.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %s", ErrCloseOutputFile, err)
|
return fmt.Errorf("closing output file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -6,23 +6,23 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/healthcheck"
|
"github.com/qdm12/gluetun/internal/healthcheck"
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HealthChecker interface {
|
func (c *CLI) HealthCheck(ctx context.Context, source Source, _ Warner) error {
|
||||||
HealthCheck(ctx context.Context, env params.Interface, warner configuration.Warner) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CLI) HealthCheck(ctx context.Context, env params.Interface,
|
|
||||||
warner configuration.Warner) error {
|
|
||||||
// Extract the health server port from the configuration.
|
// Extract the health server port from the configuration.
|
||||||
config := configuration.Health{}
|
config, err := source.ReadHealth()
|
||||||
err := config.Read(env, warner)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.SetDefaults()
|
||||||
|
|
||||||
|
err = config.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
_, port, err := net.SplitHostPort(config.ServerAddress)
|
_, port, err := net.SplitHostPort(config.ServerAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
9
internal/cli/interfaces.go
Normal file
9
internal/cli/interfaces.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import "github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
|
|
||||||
|
type Source interface {
|
||||||
|
Read() (settings settings.Settings, err error)
|
||||||
|
ReadHealth() (health settings.Health, err error)
|
||||||
|
String() string
|
||||||
|
}
|
||||||
@@ -8,9 +8,9 @@ func newNoopLogger() *noopLogger {
|
|||||||
return new(noopLogger)
|
return new(noopLogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *noopLogger) Debug(s string) {}
|
func (l *noopLogger) Debug(string) {}
|
||||||
func (l *noopLogger) Info(s string) {}
|
func (l *noopLogger) Info(string) {}
|
||||||
func (l *noopLogger) Warn(s string) {}
|
func (l *noopLogger) Warn(string) {}
|
||||||
func (l *noopLogger) Error(s string) {}
|
func (l *noopLogger) Error(string) {}
|
||||||
func (l *noopLogger) PatchLevel(level logging.Level) {}
|
func (l *noopLogger) PatchLevel(logging.Level) {}
|
||||||
func (l *noopLogger) PatchPrefix(prefix string) {}
|
func (l *noopLogger) PatchPrefix(string) {}
|
||||||
|
|||||||
@@ -1,48 +1,85 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
"github.com/qdm12/gluetun/internal/provider"
|
||||||
|
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/golibs/params"
|
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OpenvpnConfigMaker interface {
|
|
||||||
OpenvpnConfig(logger OpenvpnConfigLogger, env params.Interface) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type OpenvpnConfigLogger interface {
|
type OpenvpnConfigLogger interface {
|
||||||
Info(s string)
|
Info(s string)
|
||||||
Warn(s string)
|
Warn(s string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, env params.Interface) error {
|
type Unzipper interface {
|
||||||
|
FetchAndExtract(ctx context.Context, url string) (
|
||||||
|
contents map[string][]byte, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParallelResolver interface {
|
||||||
|
Resolve(ctx context.Context, settings resolver.ParallelSettings) (
|
||||||
|
hostToIPs map[string][]netip.Addr, warnings []string, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPFetcher interface {
|
||||||
|
FetchMultiInfo(ctx context.Context, ips []netip.Addr) (data []ipinfo.Response, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPv6Checker interface {
|
||||||
|
IsIPv6Supported() (supported bool, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source Source,
|
||||||
|
ipv6Checker IPv6Checker) error {
|
||||||
storage, err := storage.New(logger, constants.ServersData)
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
allServers := storage.GetServers()
|
|
||||||
|
|
||||||
var allSettings configuration.Settings
|
allSettings, err := source.Read()
|
||||||
err = allSettings.Read(env, allServers, logger)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
providerConf := provider.New(allSettings.VPN.Provider.Name, allServers, time.Now)
|
|
||||||
connection, err := providerConf.GetConnection(allSettings.VPN.Provider.ServerSelection)
|
ipv6Supported, err := ipv6Checker.IsIPv6Supported()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("checking for IPv6 support: %w", err)
|
||||||
}
|
}
|
||||||
lines, err := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
|
|
||||||
|
if err = allSettings.Validate(storage, ipv6Supported); err != nil {
|
||||||
|
return fmt.Errorf("validating settings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unused by this CLI command
|
||||||
|
unzipper := (Unzipper)(nil)
|
||||||
|
client := (*http.Client)(nil)
|
||||||
|
warner := (Warner)(nil)
|
||||||
|
parallelResolver := (ParallelResolver)(nil)
|
||||||
|
ipFetcher := (IPFetcher)(nil)
|
||||||
|
openvpnFileExtractor := extract.New()
|
||||||
|
|
||||||
|
providers := provider.NewProviders(storage, time.Now, warner, client,
|
||||||
|
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
||||||
|
providerConf := providers.Get(*allSettings.VPN.Provider.Name)
|
||||||
|
connection, err := providerConf.GetConnection(
|
||||||
|
allSettings.VPN.Provider.ServerSelection, ipv6Supported)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lines := providerConf.OpenVPNConfig(connection,
|
||||||
|
allSettings.VPN.OpenVPN, ipv6Supported)
|
||||||
|
|
||||||
fmt.Println(strings.Join(lines, "\n"))
|
fmt.Println(strings.Join(lines, "\n"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,32 +2,30 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
|
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider"
|
||||||
|
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/gluetun/internal/updater"
|
"github.com/qdm12/gluetun/internal/updater"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||||
|
"github.com/qdm12/gluetun/internal/updater/unzip"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
|
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
|
||||||
ErrNewStorage = errors.New("cannot create storage")
|
ErrNoProviderSpecified = errors.New("no provider was specified")
|
||||||
ErrUpdateServerInformation = errors.New("cannot update server information")
|
|
||||||
ErrWriteToFile = errors.New("cannot write updated information to file")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Updater interface {
|
|
||||||
Update(ctx context.Context, args []string, logger UpdaterLogger) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdaterLogger interface {
|
type UpdaterLogger interface {
|
||||||
Info(s string)
|
Info(s string)
|
||||||
Warn(s string)
|
Warn(s string)
|
||||||
@@ -35,87 +33,70 @@ type UpdaterLogger interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
|
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
|
||||||
options := configuration.Updater{CLI: true}
|
options := settings.Updater{}
|
||||||
var endUserMode, maintainerMode, updateAll bool
|
var endUserMode, maintainerMode, updateAll bool
|
||||||
|
var csvProviders string
|
||||||
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
||||||
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
|
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
|
||||||
flagSet.BoolVar(&maintainerMode, "maintainer", false,
|
flagSet.BoolVar(&maintainerMode, "maintainer", false,
|
||||||
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
|
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
|
||||||
flagSet.StringVar(&options.DNSAddress, "dns", "8.8.8.8", "DNS resolver address to use")
|
flagSet.StringVar(&options.DNSAddress, "dns", "8.8.8.8", "DNS resolver address to use")
|
||||||
|
const defaultMinRatio = 0.8
|
||||||
|
flagSet.Float64Var(&options.MinRatio, "minratio", defaultMinRatio,
|
||||||
|
"Minimum ratio of servers to find for the update to succeed")
|
||||||
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
|
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
|
||||||
flagSet.BoolVar(&options.Cyberghost, "cyberghost", false, "Update Cyberghost servers")
|
flagSet.StringVar(&csvProviders, "providers", "", "CSV string of VPN providers to update server data for")
|
||||||
flagSet.BoolVar(&options.Expressvpn, "expressvpn", false, "Update ExpressVPN servers")
|
|
||||||
flagSet.BoolVar(&options.Fastestvpn, "fastestvpn", false, "Update FastestVPN servers")
|
|
||||||
flagSet.BoolVar(&options.HideMyAss, "hidemyass", false, "Update HideMyAss servers")
|
|
||||||
flagSet.BoolVar(&options.Ipvanish, "ipvanish", false, "Update IpVanish servers")
|
|
||||||
flagSet.BoolVar(&options.Ivpn, "ivpn", false, "Update IVPN servers")
|
|
||||||
flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers")
|
|
||||||
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers")
|
|
||||||
flagSet.BoolVar(&options.Perfectprivacy, "perfectprivacy", false, "Update Perfect Privacy servers")
|
|
||||||
flagSet.BoolVar(&options.PIA, "pia", false, "Update Private Internet Access post-summer 2020 servers")
|
|
||||||
flagSet.BoolVar(&options.Privado, "privado", false, "Update Privado servers")
|
|
||||||
flagSet.BoolVar(&options.Privatevpn, "privatevpn", false, "Update Private VPN servers")
|
|
||||||
flagSet.BoolVar(&options.Protonvpn, "protonvpn", false, "Update Protonvpn servers")
|
|
||||||
flagSet.BoolVar(&options.Purevpn, "purevpn", false, "Update Purevpn servers")
|
|
||||||
flagSet.BoolVar(&options.Surfshark, "surfshark", false, "Update Surfshark servers")
|
|
||||||
flagSet.BoolVar(&options.Torguard, "torguard", false, "Update Torguard servers")
|
|
||||||
flagSet.BoolVar(&options.VPNUnlimited, "vpnunlimited", false, "Update VPN Unlimited servers")
|
|
||||||
flagSet.BoolVar(&options.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers")
|
|
||||||
flagSet.BoolVar(&options.Wevpn, "wevpn", false, "Update WeVPN servers")
|
|
||||||
flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers")
|
|
||||||
if err := flagSet.Parse(args); err != nil {
|
if err := flagSet.Parse(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !endUserMode && !maintainerMode {
|
if !endUserMode && !maintainerMode {
|
||||||
return ErrModeUnspecified
|
return fmt.Errorf("%w", ErrModeUnspecified)
|
||||||
}
|
}
|
||||||
|
|
||||||
if updateAll {
|
if updateAll {
|
||||||
options.EnableAll()
|
options.Providers = providers.All()
|
||||||
|
} else {
|
||||||
|
if csvProviders == "" {
|
||||||
|
return fmt.Errorf("%w", ErrNoProviderSpecified)
|
||||||
|
}
|
||||||
|
options.Providers = strings.Split(csvProviders, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
options.SetDefaults(options.Providers[0])
|
||||||
|
|
||||||
|
err := options.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("options validation failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating servers storage: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientTimeout = 10 * time.Second
|
const clientTimeout = 10 * time.Second
|
||||||
httpClient := &http.Client{Timeout: clientTimeout}
|
httpClient := &http.Client{Timeout: clientTimeout}
|
||||||
|
unzipper := unzip.New(httpClient)
|
||||||
|
parallelResolver := resolver.NewParallelResolver(options.DNSAddress)
|
||||||
|
ipFetcher := ipinfo.New(httpClient)
|
||||||
|
openvpnFileExtractor := extract.New()
|
||||||
|
|
||||||
storage, err := storage.New(logger, constants.ServersData)
|
providers := provider.NewProviders(storage, time.Now, logger, httpClient,
|
||||||
|
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
||||||
|
|
||||||
|
updater := updater.New(httpClient, storage, providers, logger)
|
||||||
|
err = updater.UpdateServers(ctx, options.Providers, options.MinRatio)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %s", ErrNewStorage, err)
|
return fmt.Errorf("updating server information: %w", err)
|
||||||
}
|
|
||||||
currentServers := storage.GetServers()
|
|
||||||
|
|
||||||
updater := updater.New(options, httpClient, currentServers, logger)
|
|
||||||
allServers, err := updater.UpdateServers(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrUpdateServerInformation, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if endUserMode {
|
|
||||||
if err := storage.FlushToFile(allServers); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrWriteToFile, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if maintainerMode {
|
if maintainerMode {
|
||||||
if err := writeToEmbeddedJSON(c.repoServersPath, allServers); err != nil {
|
err := storage.FlushToFile(c.repoServersPath)
|
||||||
return fmt.Errorf("%w: %s", ErrWriteToFile, err)
|
if err != nil {
|
||||||
|
return fmt.Errorf("writing servers data to embedded JSON file: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeToEmbeddedJSON(repoServersPath string,
|
|
||||||
allServers models.AllServers) error {
|
|
||||||
const perms = 0600
|
|
||||||
f, err := os.OpenFile(repoServersPath,
|
|
||||||
os.O_TRUNC|os.O_WRONLY|os.O_CREATE, perms)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
encoder := json.NewEncoder(f)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
return encoder.Encode(allServers)
|
|
||||||
}
|
|
||||||
|
|||||||
5
internal/cli/warner.go
Normal file
5
internal/cli/warner.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
type Warner interface {
|
||||||
|
Warn(s string)
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// Package configuration reads initial settings from environment variables
|
|
||||||
// and secret files.
|
|
||||||
package configuration
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
const (
|
|
||||||
lastIndent = "|--"
|
|
||||||
indent = " "
|
|
||||||
)
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errCustomNotSupported = errors.New("custom provider is not supported")
|
|
||||||
errCustomExtractFromFile = errors.New("cannot extract configuration from file")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readCustom(r reader, vpnType string) (err error) {
|
|
||||||
settings.Name = constants.Custom
|
|
||||||
|
|
||||||
switch vpnType {
|
|
||||||
case constants.OpenVPN:
|
|
||||||
return settings.ServerSelection.OpenVPN.readCustom(r)
|
|
||||||
case constants.Wireguard:
|
|
||||||
return settings.ServerSelection.Wireguard.readCustom(r)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%w: for VPN type %s", errCustomNotSupported, vpnType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *OpenVPNSelection) readCustom(r reader) (err error) {
|
|
||||||
configFile, err := r.env.Get("OPENVPN_CUSTOM_CONFIG", params.CaseSensitiveValue(), params.Compulsory())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
|
|
||||||
}
|
|
||||||
settings.ConfFile = configFile
|
|
||||||
|
|
||||||
// For display and consistency purposes only,
|
|
||||||
// these values are not actually used since the file is re-read
|
|
||||||
// before each OpenVPN start.
|
|
||||||
_, connection, err := r.ovpnExt.Data(configFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", errCustomExtractFromFile, err)
|
|
||||||
}
|
|
||||||
settings.TCP = connection.Protocol == constants.TCP
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *OpenVPN) readCustom(r reader) (err error) {
|
|
||||||
settings.ConfFile, err = r.env.Path("OPENVPN_CUSTOM_CONFIG",
|
|
||||||
params.Compulsory(), params.CaseSensitiveValue())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *WireguardSelection) readCustom(r reader) (err error) {
|
|
||||||
settings.PublicKey, err = r.env.Get("WIREGUARD_PUBLIC_KEY",
|
|
||||||
params.CaseSensitiveValue(), params.Compulsory())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable WIREGUARD_PUBLIC_KEY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.EndpointIP, err = readWireguardEndpointIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.EndpointPort, err = r.env.Port("WIREGUARD_ENDPOINT_PORT", params.Compulsory(),
|
|
||||||
params.RetroKeys([]string{"WIREGUARD_PORT"}, r.onRetroActive))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable WIREGUARD_ENDPOINT_PORT: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readWireguardEndpointIP reads and parses the server endpoint IP
|
|
||||||
// address from the environment variable WIREGUARD_ENDPOINT_IP.
|
|
||||||
func readWireguardEndpointIP(env params.Interface) (endpointIP net.IP, err error) {
|
|
||||||
s, err := env.Get("WIREGUARD_ENDPOINT_IP", params.Compulsory())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_IP: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
endpointIP = net.ParseIP(s)
|
|
||||||
if endpointIP == nil {
|
|
||||||
return nil, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_IP: %w: %s",
|
|
||||||
ErrInvalidIP, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
return endpointIP, nil
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readCyberghost(r reader) (err error) {
|
|
||||||
settings.Name = constants.Cyberghost
|
|
||||||
servers := r.servers.GetCyberghost()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY",
|
|
||||||
constants.CyberghostCountryChoices(servers),
|
|
||||||
params.RetroKeys([]string{"REGION"}, r.onRetroActive))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
|
|
||||||
constants.CyberghostHostnameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *OpenVPN) readCyberghost(r reader) (err error) {
|
|
||||||
settings.ClientKey, err = readClientKey(r)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", errClientKey, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ClientCrt, err = readClientCertificate(r)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", errClientCert, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/dns/pkg/blacklist"
|
|
||||||
"github.com/qdm12/dns/pkg/unbound"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DNS contains settings to configure Unbound for DNS over TLS operation.
|
|
||||||
type DNS struct { //nolint:maligned
|
|
||||||
Enabled bool
|
|
||||||
PlaintextAddress net.IP
|
|
||||||
KeepNameserver bool
|
|
||||||
UpdatePeriod time.Duration
|
|
||||||
Unbound unbound.Settings
|
|
||||||
BlacklistBuild blacklist.BuilderSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *DNS) String() string {
|
|
||||||
return strings.Join(settings.lines(), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *DNS) lines() (lines []string) {
|
|
||||||
lines = append(lines, lastIndent+"DNS:")
|
|
||||||
|
|
||||||
if settings.PlaintextAddress != nil {
|
|
||||||
lines = append(lines, indent+lastIndent+"Plaintext address: "+settings.PlaintextAddress.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.KeepNameserver {
|
|
||||||
lines = append(lines, indent+lastIndent+"Keep nameserver (disabled blocking): yes")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !settings.Enabled {
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, indent+lastIndent+"DNS over TLS:")
|
|
||||||
|
|
||||||
lines = append(lines, indent+indent+lastIndent+"Unbound:")
|
|
||||||
for _, line := range settings.Unbound.Lines() {
|
|
||||||
lines = append(lines, indent+indent+indent+line)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, indent+indent+lastIndent+"Blacklist:")
|
|
||||||
for _, line := range settings.BlacklistBuild.Lines(indent, lastIndent) {
|
|
||||||
lines = append(lines, indent+indent+indent+line)
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.UpdatePeriod > 0 {
|
|
||||||
lines = append(lines, indent+indent+lastIndent+"Update: every "+settings.UpdatePeriod.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrUnboundSettings = errors.New("failed getting Unbound settings")
|
|
||||||
ErrBlacklistSettings = errors.New("failed getting DNS blacklist settings")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *DNS) read(r reader) (err error) {
|
|
||||||
settings.Enabled, err = r.env.OnOff("DOT", params.Default("on"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable DOT: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Plain DNS settings
|
|
||||||
if err := settings.readDNSPlaintext(r.env); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
settings.KeepNameserver, err = r.env.OnOff("DNS_KEEP_NAMESERVER", params.Default("off"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable DNS_KEEP_NAMESERVER: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNS over TLS external settings
|
|
||||||
if err := settings.readBlacklistBuilding(r); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrBlacklistSettings, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.UpdatePeriod, err = r.env.Duration("DNS_UPDATE_PERIOD", params.Default("24h"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable DNS_UPDATE_PERIOD: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unbound settings
|
|
||||||
if err := settings.readUnbound(r); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrUnboundSettings, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrDNSAddressNotAnIP = errors.New("DNS plaintext address is not an IP address")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *DNS) readDNSPlaintext(env params.Interface) error {
|
|
||||||
s, err := env.Get("DNS_PLAINTEXT_ADDRESS", params.Default("1.1.1.1"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable DNS_PLAINTEXT_ADDRESS: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.PlaintextAddress = net.ParseIP(s)
|
|
||||||
if settings.PlaintextAddress == nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrDNSAddressNotAnIP, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/dns/pkg/blacklist"
|
|
||||||
"github.com/qdm12/dns/pkg/provider"
|
|
||||||
"github.com/qdm12/dns/pkg/unbound"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_DNS_Lines(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
testCases := map[string]struct {
|
|
||||||
settings DNS
|
|
||||||
lines []string
|
|
||||||
}{
|
|
||||||
"disabled DOT": {
|
|
||||||
settings: DNS{
|
|
||||||
PlaintextAddress: net.IP{1, 1, 1, 1},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--DNS:",
|
|
||||||
" |--Plaintext address: 1.1.1.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"enabled DOT": {
|
|
||||||
settings: DNS{
|
|
||||||
Enabled: true,
|
|
||||||
KeepNameserver: true,
|
|
||||||
Unbound: unbound.Settings{
|
|
||||||
Providers: []provider.Provider{
|
|
||||||
provider.Cloudflare(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
BlacklistBuild: blacklist.BuilderSettings{
|
|
||||||
BlockMalicious: true,
|
|
||||||
BlockAds: true,
|
|
||||||
BlockSurveillance: true,
|
|
||||||
},
|
|
||||||
UpdatePeriod: time.Hour,
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--DNS:",
|
|
||||||
" |--Keep nameserver (disabled blocking): yes",
|
|
||||||
" |--DNS over TLS:",
|
|
||||||
" |--Unbound:",
|
|
||||||
" |--DNS over TLS providers:",
|
|
||||||
" |--Cloudflare",
|
|
||||||
" |--Listening port: 0",
|
|
||||||
" |--Access control:",
|
|
||||||
" |--Allowed:",
|
|
||||||
" |--Caching: disabled",
|
|
||||||
" |--IPv4 resolution: disabled",
|
|
||||||
" |--IPv6 resolution: disabled",
|
|
||||||
" |--Verbosity level: 0/5",
|
|
||||||
" |--Verbosity details level: 0/4",
|
|
||||||
" |--Validation log level: 0/2",
|
|
||||||
" |--Username: ",
|
|
||||||
" |--Blacklist:",
|
|
||||||
" |--Blocked categories: malicious, surveillance, ads",
|
|
||||||
" |--Update: every 1h0m0s",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
lines := testCase.settings.lines()
|
|
||||||
assert.Equal(t, testCase.lines, lines)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
"inet.af/netaddr"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *DNS) readBlacklistBuilding(r reader) (err error) {
|
|
||||||
settings.BlacklistBuild.BlockMalicious, err = r.env.OnOff("BLOCK_MALICIOUS", params.Default("on"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.BlacklistBuild.BlockSurveillance, err = r.env.OnOff("BLOCK_SURVEILLANCE", params.Default("on"),
|
|
||||||
params.RetroKeys([]string{"BLOCK_NSA"}, r.onRetroActive))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable BLOCK_SURVEILLANCE (or BLOCK_NSA): %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.BlacklistBuild.BlockAds, err = r.env.OnOff("BLOCK_ADS", params.Default("off"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable BLOCK_ADS: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.readPrivateAddresses(r.env); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.readBlacklistUnblockedHostnames(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidPrivateAddress = errors.New("private address is not a valid IP or CIDR range")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *DNS) readPrivateAddresses(env params.Interface) (err error) {
|
|
||||||
privateAddresses, err := env.CSV("DOT_PRIVATE_ADDRESS")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable DOT_PRIVATE_ADDRESS: %w", err)
|
|
||||||
} else if len(privateAddresses) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ips := make([]netaddr.IP, 0, len(privateAddresses))
|
|
||||||
ipPrefixes := make([]netaddr.IPPrefix, 0, len(privateAddresses))
|
|
||||||
|
|
||||||
for _, address := range privateAddresses {
|
|
||||||
ip, err := netaddr.ParseIP(address)
|
|
||||||
if err == nil {
|
|
||||||
ips = append(ips, ip)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ipPrefix, err := netaddr.ParseIPPrefix(address)
|
|
||||||
if err == nil {
|
|
||||||
ipPrefixes = append(ipPrefixes, ipPrefix)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("%w: %s", ErrInvalidPrivateAddress, address)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.BlacklistBuild.AddBlockedIPs = append(settings.BlacklistBuild.AddBlockedIPs, ips...)
|
|
||||||
settings.BlacklistBuild.AddBlockedIPPrefixes = append(settings.BlacklistBuild.AddBlockedIPPrefixes, ipPrefixes...)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *DNS) readBlacklistUnblockedHostnames(r reader) (err error) {
|
|
||||||
hostnames, err := r.env.CSV("UNBLOCK")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable UNBLOCK: %w", err)
|
|
||||||
} else if len(hostnames) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, hostname := range hostnames {
|
|
||||||
if !r.regex.MatchHostname(hostname) {
|
|
||||||
return fmt.Errorf("%w: %s", ErrInvalidHostname, hostname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.BlacklistBuild.AllowedHosts = append(settings.BlacklistBuild.AllowedHosts, hostnames...)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readExpressvpn(r reader) (err error) {
|
|
||||||
settings.Name = constants.Expressvpn
|
|
||||||
servers := r.servers.GetExpressvpn()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
|
|
||||||
constants.ExpressvpnHostnameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.ExpressvpnCountriesChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.ExpressvpnCityChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable CITY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.OpenVPN.TCP, err = readOpenVPNProtocol(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readFastestvpn(r reader) (err error) {
|
|
||||||
settings.Name = constants.Fastestvpn
|
|
||||||
servers := r.servers.GetFastestvpn()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
|
|
||||||
constants.FastestvpnHostnameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.FastestvpnCountriesChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Firewall contains settings to customize the firewall operation.
|
|
||||||
type Firewall struct {
|
|
||||||
VPNInputPorts []uint16
|
|
||||||
InputPorts []uint16
|
|
||||||
OutboundSubnets []net.IPNet
|
|
||||||
Enabled bool
|
|
||||||
Debug bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Firewall) String() string {
|
|
||||||
return strings.Join(settings.lines(), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Firewall) lines() (lines []string) {
|
|
||||||
if !settings.Enabled {
|
|
||||||
lines = append(lines, lastIndent+"Firewall: disabled ⚠️")
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, lastIndent+"Firewall:")
|
|
||||||
|
|
||||||
if settings.Debug {
|
|
||||||
lines = append(lines, indent+lastIndent+"Debug: on")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(settings.VPNInputPorts) > 0 {
|
|
||||||
lines = append(lines, indent+lastIndent+"VPN input ports: "+
|
|
||||||
strings.Join(uint16sToStrings(settings.VPNInputPorts), ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(settings.InputPorts) > 0 {
|
|
||||||
lines = append(lines, indent+lastIndent+"Input ports: "+
|
|
||||||
strings.Join(uint16sToStrings(settings.InputPorts), ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(settings.OutboundSubnets) > 0 {
|
|
||||||
lines = append(lines, indent+lastIndent+"Outbound subnets: "+
|
|
||||||
strings.Join(ipNetsToStrings(settings.OutboundSubnets), ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Firewall) read(r reader) (err error) {
|
|
||||||
settings.Enabled, err = r.env.OnOff("FIREWALL", params.Default("on"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable FIREWALL: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Debug, err = r.env.OnOff("FIREWALL_DEBUG", params.Default("off"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable FIREWALL_DEBUG: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.readVPNInputPorts(r.env); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.readInputPorts(r.env); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.readOutboundSubnets(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Firewall) readVPNInputPorts(env params.Interface) (err error) {
|
|
||||||
settings.VPNInputPorts, err = readCSVPorts(env, "FIREWALL_VPN_INPUT_PORTS")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable FIREWALL_VPN_INPUT_PORTS: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Firewall) readInputPorts(env params.Interface) (err error) {
|
|
||||||
settings.InputPorts, err = readCSVPorts(env, "FIREWALL_INPUT_PORTS")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Firewall) readOutboundSubnets(r reader) (err error) {
|
|
||||||
retroOption := params.RetroKeys([]string{"EXTRA_SUBNETS"}, r.onRetroActive)
|
|
||||||
settings.OutboundSubnets, err = readCSVIPNets(r.env, "FIREWALL_OUTBOUND_SUBNETS", retroOption)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable FIREWALL_OUTBOUND_SUBNETS: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Health contains settings for the healthcheck and health server.
|
|
||||||
type Health struct {
|
|
||||||
ServerAddress string
|
|
||||||
AddressToPing string
|
|
||||||
VPN HealthyWait
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Health) String() string {
|
|
||||||
return strings.Join(settings.lines(), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Health) lines() (lines []string) {
|
|
||||||
lines = append(lines, lastIndent+"Health:")
|
|
||||||
|
|
||||||
lines = append(lines, indent+lastIndent+"Server address: "+settings.ServerAddress)
|
|
||||||
|
|
||||||
lines = append(lines, indent+lastIndent+"Address to ping: "+settings.AddressToPing)
|
|
||||||
|
|
||||||
lines = append(lines, indent+lastIndent+"VPN:")
|
|
||||||
for _, line := range settings.VPN.lines() {
|
|
||||||
lines = append(lines, indent+indent+line)
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read is to be used for the healthcheck query mode.
|
|
||||||
func (settings *Health) Read(env params.Interface, warner Warner) (err error) {
|
|
||||||
reader := newReader(env, models.AllServers{}, warner) // note: no need for servers data
|
|
||||||
return settings.read(reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Health) read(r reader) (err error) {
|
|
||||||
var warning string
|
|
||||||
settings.ServerAddress, warning, err = r.env.ListeningAddress(
|
|
||||||
"HEALTH_SERVER_ADDRESS", params.Default("127.0.0.1:9999"))
|
|
||||||
if warning != "" {
|
|
||||||
r.warner.Warn("environment variable HEALTH_SERVER_ADDRESS: " + warning)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable HEALTH_SERVER_ADDRESS: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.AddressToPing, err = r.env.Get("HEALTH_ADDRESS_TO_PING", params.Default("github.com"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable HEALTH_ADDRESS_TO_PING: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
retroKeyOption := params.RetroKeys([]string{"HEALTH_OPENVPN_DURATION_INITIAL"}, r.onRetroActive)
|
|
||||||
settings.VPN.Initial, err = r.env.Duration("HEALTH_VPN_DURATION_INITIAL", params.Default("6s"), retroKeyOption)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable HEALTH_VPN_DURATION_INITIAL: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
retroKeyOption = params.RetroKeys([]string{"HEALTH_OPENVPN_DURATION_ADDITION"}, r.onRetroActive)
|
|
||||||
settings.VPN.Addition, err = r.env.Duration("HEALTH_VPN_DURATION_ADDITION", params.Default("5s"), retroKeyOption)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable HEALTH_VPN_DURATION_ADDITION: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/qdm12/golibs/params/mock_params"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Health_String(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
health := Health{
|
|
||||||
ServerAddress: "a",
|
|
||||||
AddressToPing: "b",
|
|
||||||
}
|
|
||||||
const expected = `|--Health:
|
|
||||||
|--Server address: a
|
|
||||||
|--Address to ping: b
|
|
||||||
|--VPN:
|
|
||||||
|--Initial duration: 0s`
|
|
||||||
|
|
||||||
s := health.String()
|
|
||||||
|
|
||||||
assert.Equal(t, expected, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Health_lines(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
settings Health
|
|
||||||
lines []string
|
|
||||||
}{
|
|
||||||
"empty": {
|
|
||||||
lines: []string{
|
|
||||||
"|--Health:",
|
|
||||||
" |--Server address: ",
|
|
||||||
" |--Address to ping: ",
|
|
||||||
" |--VPN:",
|
|
||||||
" |--Initial duration: 0s",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"filled settings": {
|
|
||||||
settings: Health{
|
|
||||||
ServerAddress: "address:9999",
|
|
||||||
AddressToPing: "github.com",
|
|
||||||
VPN: HealthyWait{
|
|
||||||
Initial: time.Second,
|
|
||||||
Addition: time.Minute,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Health:",
|
|
||||||
" |--Server address: address:9999",
|
|
||||||
" |--Address to ping: github.com",
|
|
||||||
" |--VPN:",
|
|
||||||
" |--Initial duration: 1s",
|
|
||||||
" |--Addition duration: 1m0s",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
lines := testCase.settings.lines()
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.lines, lines)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Health_read(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
errDummy := errors.New("dummy")
|
|
||||||
|
|
||||||
type stringCall struct {
|
|
||||||
call bool
|
|
||||||
s string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringCallWithWarning struct {
|
|
||||||
call bool
|
|
||||||
s string
|
|
||||||
warning string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type durationCall struct {
|
|
||||||
call bool
|
|
||||||
duration time.Duration
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
serverAddress stringCallWithWarning
|
|
||||||
addressToPing stringCall
|
|
||||||
vpnInitial durationCall
|
|
||||||
vpnAddition durationCall
|
|
||||||
expected Health
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
"success": {
|
|
||||||
serverAddress: stringCallWithWarning{
|
|
||||||
call: true,
|
|
||||||
s: "127.0.0.1:9999",
|
|
||||||
},
|
|
||||||
addressToPing: stringCall{
|
|
||||||
call: true,
|
|
||||||
s: "1.2.3.4",
|
|
||||||
},
|
|
||||||
vpnInitial: durationCall{
|
|
||||||
call: true,
|
|
||||||
duration: time.Second,
|
|
||||||
},
|
|
||||||
vpnAddition: durationCall{
|
|
||||||
call: true,
|
|
||||||
duration: time.Minute,
|
|
||||||
},
|
|
||||||
expected: Health{
|
|
||||||
ServerAddress: "127.0.0.1:9999",
|
|
||||||
AddressToPing: "1.2.3.4",
|
|
||||||
VPN: HealthyWait{
|
|
||||||
Initial: time.Second,
|
|
||||||
Addition: time.Minute,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"listening address error": {
|
|
||||||
serverAddress: stringCallWithWarning{
|
|
||||||
call: true,
|
|
||||||
s: "127.0.0.1:9999",
|
|
||||||
warning: "warning",
|
|
||||||
err: errDummy,
|
|
||||||
},
|
|
||||||
expected: Health{
|
|
||||||
ServerAddress: "127.0.0.1:9999",
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable HEALTH_SERVER_ADDRESS: dummy"),
|
|
||||||
},
|
|
||||||
"address to ping error": {
|
|
||||||
serverAddress: stringCallWithWarning{
|
|
||||||
call: true,
|
|
||||||
},
|
|
||||||
addressToPing: stringCall{
|
|
||||||
call: true,
|
|
||||||
s: "address",
|
|
||||||
err: errDummy,
|
|
||||||
},
|
|
||||||
expected: Health{
|
|
||||||
AddressToPing: "address",
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable HEALTH_ADDRESS_TO_PING: dummy"),
|
|
||||||
},
|
|
||||||
"initial error": {
|
|
||||||
serverAddress: stringCallWithWarning{
|
|
||||||
call: true,
|
|
||||||
},
|
|
||||||
addressToPing: stringCall{
|
|
||||||
call: true,
|
|
||||||
},
|
|
||||||
vpnInitial: durationCall{
|
|
||||||
call: true,
|
|
||||||
duration: time.Second,
|
|
||||||
err: errDummy,
|
|
||||||
},
|
|
||||||
expected: Health{
|
|
||||||
VPN: HealthyWait{
|
|
||||||
Initial: time.Second,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable HEALTH_VPN_DURATION_INITIAL: dummy"),
|
|
||||||
},
|
|
||||||
"addition error": {
|
|
||||||
serverAddress: stringCallWithWarning{
|
|
||||||
call: true,
|
|
||||||
},
|
|
||||||
addressToPing: stringCall{
|
|
||||||
call: true,
|
|
||||||
},
|
|
||||||
vpnInitial: durationCall{
|
|
||||||
call: true,
|
|
||||||
duration: time.Second,
|
|
||||||
},
|
|
||||||
vpnAddition: durationCall{
|
|
||||||
call: true,
|
|
||||||
duration: time.Minute,
|
|
||||||
err: errDummy,
|
|
||||||
},
|
|
||||||
expected: Health{
|
|
||||||
VPN: HealthyWait{
|
|
||||||
Initial: time.Second,
|
|
||||||
Addition: time.Minute,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable HEALTH_VPN_DURATION_ADDITION: dummy"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
|
|
||||||
env := mock_params.NewMockInterface(ctrl)
|
|
||||||
warner := NewMockWarner(ctrl)
|
|
||||||
|
|
||||||
if testCase.serverAddress.call {
|
|
||||||
value := testCase.serverAddress.s
|
|
||||||
warning := testCase.serverAddress.warning
|
|
||||||
err := testCase.serverAddress.err
|
|
||||||
env.EXPECT().ListeningAddress("HEALTH_SERVER_ADDRESS", gomock.Any()).
|
|
||||||
Return(value, warning, err)
|
|
||||||
if warning != "" {
|
|
||||||
warner.EXPECT().Warn("environment variable HEALTH_SERVER_ADDRESS: " + warning)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if testCase.addressToPing.call {
|
|
||||||
value := testCase.addressToPing.s
|
|
||||||
err := testCase.addressToPing.err
|
|
||||||
env.EXPECT().Get("HEALTH_ADDRESS_TO_PING", gomock.Any()).
|
|
||||||
Return(value, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if testCase.vpnInitial.call {
|
|
||||||
value := testCase.vpnInitial.duration
|
|
||||||
err := testCase.vpnInitial.err
|
|
||||||
env.EXPECT().
|
|
||||||
Duration("HEALTH_VPN_DURATION_INITIAL", gomock.Any()).
|
|
||||||
Return(value, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if testCase.vpnAddition.call {
|
|
||||||
value := testCase.vpnAddition.duration
|
|
||||||
err := testCase.vpnAddition.err
|
|
||||||
env.EXPECT().
|
|
||||||
Duration("HEALTH_VPN_DURATION_ADDITION", gomock.Any()).
|
|
||||||
Return(value, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := reader{
|
|
||||||
env: env,
|
|
||||||
warner: warner,
|
|
||||||
}
|
|
||||||
|
|
||||||
var health Health
|
|
||||||
|
|
||||||
err := health.read(r)
|
|
||||||
|
|
||||||
if testCase.err != nil {
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.expected, health)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_HealthyWait_String(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var healthyWait HealthyWait
|
|
||||||
const expected = "|--Initial duration: 0s"
|
|
||||||
|
|
||||||
s := healthyWait.String()
|
|
||||||
|
|
||||||
assert.Equal(t, expected, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_HealthyWait_lines(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
settings HealthyWait
|
|
||||||
lines []string
|
|
||||||
}{
|
|
||||||
"empty": {
|
|
||||||
lines: []string{
|
|
||||||
"|--Initial duration: 0s",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"filled settings": {
|
|
||||||
settings: HealthyWait{
|
|
||||||
Initial: time.Second,
|
|
||||||
Addition: time.Minute,
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Initial duration: 1s",
|
|
||||||
"|--Addition duration: 1m0s",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
lines := testCase.settings.lines()
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.lines, lines)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HealthyWait struct {
|
|
||||||
// Initial is the initial duration to wait for the program
|
|
||||||
// to be healthy before taking action.
|
|
||||||
Initial time.Duration
|
|
||||||
// Addition is the duration to add to the Initial duration
|
|
||||||
// after Initial has expired to wait longer for the program
|
|
||||||
// to be healthy.
|
|
||||||
Addition time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *HealthyWait) String() string {
|
|
||||||
return strings.Join(settings.lines(), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *HealthyWait) lines() (lines []string) {
|
|
||||||
lines = append(lines, lastIndent+"Initial duration: "+settings.Initial.String())
|
|
||||||
|
|
||||||
if settings.Addition > 0 {
|
|
||||||
lines = append(lines, lastIndent+"Addition duration: "+settings.Addition.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readHideMyAss(r reader) (err error) {
|
|
||||||
settings.Name = constants.HideMyAss
|
|
||||||
servers := r.servers.GetHideMyAss()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.HideMyAssCountryChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.HideMyAssCountryChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable REGION: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.HideMyAssCityChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable CITY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
|
|
||||||
constants.HideMyAssHostnameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HTTPProxy contains settings to configure the HTTP proxy.
|
|
||||||
type HTTPProxy struct {
|
|
||||||
User string
|
|
||||||
Password string
|
|
||||||
Port uint16
|
|
||||||
Enabled bool
|
|
||||||
Stealth bool
|
|
||||||
Log bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *HTTPProxy) String() string {
|
|
||||||
return strings.Join(settings.lines(), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *HTTPProxy) lines() (lines []string) {
|
|
||||||
if !settings.Enabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, lastIndent+"HTTP proxy:")
|
|
||||||
|
|
||||||
lines = append(lines, indent+lastIndent+"Port: "+strconv.Itoa(int(settings.Port)))
|
|
||||||
|
|
||||||
if settings.User != "" {
|
|
||||||
lines = append(lines, indent+lastIndent+"Authentication: enabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.Log {
|
|
||||||
lines = append(lines, indent+lastIndent+"Log: enabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.Stealth {
|
|
||||||
lines = append(lines, indent+lastIndent+"Stealth: enabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *HTTPProxy) read(r reader) (err error) {
|
|
||||||
settings.Enabled, err = r.env.OnOff("HTTPPROXY", params.Default("off"),
|
|
||||||
params.RetroKeys([]string{"TINYPROXY", "PROXY"}, r.onRetroActive))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable HTTPPROXY (or TINYPROXY, PROXY): %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.User, err = r.getFromEnvOrSecretFile("HTTPPROXY_USER", false, // compulsory
|
|
||||||
[]string{"TINYPROXY_USER", "PROXY_USER"})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable HTTPPROXY_USER (or TINYPROXY_USER, PROXY_USER): %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Password, err = r.getFromEnvOrSecretFile("HTTPPROXY_PASSWORD", false,
|
|
||||||
[]string{"TINYPROXY_PASSWORD", "PROXY_PASSWORD"})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable HTTPPROXY_PASSWORD (or TINYPROXY_PASSWORD, PROXY_PASSWORD): %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Stealth, err = r.env.OnOff("HTTPPROXY_STEALTH", params.Default("off"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable HTTPPROXY_STEALTH: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.readLog(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var warning string
|
|
||||||
settings.Port, warning, err = r.env.ListeningPort("HTTPPROXY_PORT", params.Default("8888"),
|
|
||||||
params.RetroKeys([]string{"TINYPROXY_PORT", "PROXY_PORT"}, r.onRetroActive))
|
|
||||||
if len(warning) > 0 {
|
|
||||||
r.warner.Warn(warning)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable HTTPPROXY_PORT (or TINYPROXY_PORT, PROXY_PORT): %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *HTTPProxy) readLog(r reader) error {
|
|
||||||
s, err := r.env.Get("HTTPPROXY_LOG",
|
|
||||||
params.RetroKeys([]string{"PROXY_LOG_LEVEL", "TINYPROXY_LOG"}, r.onRetroActive))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable HTTPPROXY_LOG (or TINYPROXY_LOG, PROXY_LOG_LEVEL): %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch strings.ToLower(s) {
|
|
||||||
case "on":
|
|
||||||
settings.Log = true
|
|
||||||
// Retro compatibility
|
|
||||||
case "info", "connect", "notice":
|
|
||||||
settings.Log = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readIpvanish(r reader) (err error) {
|
|
||||||
settings.Name = constants.Ipvanish
|
|
||||||
servers := r.servers.GetIpvanish()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.IpvanishCountryChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.IpvanishCityChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable CITY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
|
|
||||||
constants.IpvanishHostnameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/params/mock_params"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Provider_readIpvanish(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var errDummy = errors.New("dummy test error")
|
|
||||||
|
|
||||||
type singleStringCall struct {
|
|
||||||
call bool
|
|
||||||
value string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type sliceStringCall struct {
|
|
||||||
call bool
|
|
||||||
values []string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
targetIP singleStringCall
|
|
||||||
countries sliceStringCall
|
|
||||||
cities sliceStringCall
|
|
||||||
hostnames sliceStringCall
|
|
||||||
protocol singleStringCall
|
|
||||||
settings Provider
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
"target IP error": {
|
|
||||||
targetIP: singleStringCall{call: true, value: "something", err: errDummy},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ipvanish,
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable OPENVPN_TARGET_IP: dummy test error"),
|
|
||||||
},
|
|
||||||
"countries error": {
|
|
||||||
targetIP: singleStringCall{call: true},
|
|
||||||
countries: sliceStringCall{call: true, err: errDummy},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ipvanish,
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable COUNTRY: dummy test error"),
|
|
||||||
},
|
|
||||||
"cities error": {
|
|
||||||
targetIP: singleStringCall{call: true},
|
|
||||||
countries: sliceStringCall{call: true},
|
|
||||||
cities: sliceStringCall{call: true, err: errDummy},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ipvanish,
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable CITY: dummy test error"),
|
|
||||||
},
|
|
||||||
"hostnames error": {
|
|
||||||
targetIP: singleStringCall{call: true},
|
|
||||||
countries: sliceStringCall{call: true},
|
|
||||||
cities: sliceStringCall{call: true},
|
|
||||||
hostnames: sliceStringCall{call: true, err: errDummy},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ipvanish,
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable SERVER_HOSTNAME: dummy test error"),
|
|
||||||
},
|
|
||||||
"protocol error": {
|
|
||||||
targetIP: singleStringCall{call: true},
|
|
||||||
countries: sliceStringCall{call: true},
|
|
||||||
cities: sliceStringCall{call: true},
|
|
||||||
hostnames: sliceStringCall{call: true},
|
|
||||||
protocol: singleStringCall{call: true, err: errDummy},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ipvanish,
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable OPENVPN_PROTOCOL: dummy test error"),
|
|
||||||
},
|
|
||||||
"default settings": {
|
|
||||||
targetIP: singleStringCall{call: true},
|
|
||||||
countries: sliceStringCall{call: true},
|
|
||||||
cities: sliceStringCall{call: true},
|
|
||||||
hostnames: sliceStringCall{call: true},
|
|
||||||
protocol: singleStringCall{call: true},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ipvanish,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"set settings": {
|
|
||||||
targetIP: singleStringCall{call: true, value: "1.2.3.4"},
|
|
||||||
countries: sliceStringCall{call: true, values: []string{"A", "B"}},
|
|
||||||
cities: sliceStringCall{call: true, values: []string{"C", "D"}},
|
|
||||||
hostnames: sliceStringCall{call: true, values: []string{"E", "F"}},
|
|
||||||
protocol: singleStringCall{call: true, value: constants.TCP},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ipvanish,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
OpenVPN: OpenVPNSelection{
|
|
||||||
TCP: true,
|
|
||||||
},
|
|
||||||
TargetIP: net.IPv4(1, 2, 3, 4),
|
|
||||||
Countries: []string{"A", "B"},
|
|
||||||
Cities: []string{"C", "D"},
|
|
||||||
Hostnames: []string{"E", "F"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
|
|
||||||
servers := []models.IpvanishServer{{Hostname: "a"}}
|
|
||||||
allServers := models.AllServers{
|
|
||||||
Ipvanish: models.IpvanishServers{
|
|
||||||
Servers: servers,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
env := mock_params.NewMockInterface(ctrl)
|
|
||||||
if testCase.targetIP.call {
|
|
||||||
env.EXPECT().Get("OPENVPN_TARGET_IP").
|
|
||||||
Return(testCase.targetIP.value, testCase.targetIP.err)
|
|
||||||
}
|
|
||||||
if testCase.countries.call {
|
|
||||||
env.EXPECT().CSVInside("COUNTRY", constants.IpvanishCountryChoices(servers)).
|
|
||||||
Return(testCase.countries.values, testCase.countries.err)
|
|
||||||
}
|
|
||||||
if testCase.cities.call {
|
|
||||||
env.EXPECT().CSVInside("CITY", constants.IpvanishCityChoices(servers)).
|
|
||||||
Return(testCase.cities.values, testCase.cities.err)
|
|
||||||
}
|
|
||||||
if testCase.hostnames.call {
|
|
||||||
env.EXPECT().CSVInside("SERVER_HOSTNAME", constants.IpvanishHostnameChoices(servers)).
|
|
||||||
Return(testCase.hostnames.values, testCase.hostnames.err)
|
|
||||||
}
|
|
||||||
if testCase.protocol.call {
|
|
||||||
env.EXPECT().Inside("OPENVPN_PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
|
|
||||||
Return(testCase.protocol.value, testCase.protocol.err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := reader{
|
|
||||||
servers: allServers,
|
|
||||||
env: env,
|
|
||||||
}
|
|
||||||
|
|
||||||
var settings Provider
|
|
||||||
err := settings.readIpvanish(r)
|
|
||||||
|
|
||||||
if testCase.err != nil {
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.settings, settings)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readIvpn(r reader) (err error) {
|
|
||||||
settings.Name = constants.Ivpn
|
|
||||||
servers := r.servers.GetIvpn()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.IvpnCountryChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.IvpnCityChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable CITY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.ISPs, err = r.env.CSVInside("ISP", constants.IvpnISPChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable ISP: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.IvpnHostnameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = settings.ServerSelection.OpenVPN.readIVPN(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.ServerSelection.Wireguard.readIVPN(r.env)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *OpenVPNSelection) readIVPN(r reader) (err error) {
|
|
||||||
settings.TCP, err = readOpenVPNProtocol(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{
|
|
||||||
tcp: settings.TCP,
|
|
||||||
allowedTCP: []uint16{80, 443, 1443},
|
|
||||||
allowedUDP: []uint16{53, 1194, 2049, 2050},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *WireguardSelection) readIVPN(env params.Interface) (err error) {
|
|
||||||
settings.EndpointPort, err = readWireguardCustomPort(env,
|
|
||||||
[]uint16{2049, 2050, 53, 30587, 41893, 48574, 58237})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/params/mock_params"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Provider_readIvpn(t *testing.T) { //nolint:gocognit
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var errDummy = errors.New("dummy test error")
|
|
||||||
|
|
||||||
type singleStringCall struct {
|
|
||||||
call bool
|
|
||||||
value string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type portCall struct {
|
|
||||||
getCall bool
|
|
||||||
getValue string // "" or "0"
|
|
||||||
getErr error
|
|
||||||
portCall bool
|
|
||||||
portValue uint16
|
|
||||||
portErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
type sliceStringCall struct {
|
|
||||||
call bool
|
|
||||||
values []string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
targetIP singleStringCall
|
|
||||||
countries sliceStringCall
|
|
||||||
cities sliceStringCall
|
|
||||||
isps sliceStringCall
|
|
||||||
hostnames sliceStringCall
|
|
||||||
protocol singleStringCall
|
|
||||||
ovpnPort portCall
|
|
||||||
ovpnOldPort portCall
|
|
||||||
wgPort portCall
|
|
||||||
wgOldPort portCall
|
|
||||||
settings Provider
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
"target IP error": {
|
|
||||||
targetIP: singleStringCall{call: true, value: "something", err: errDummy},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ivpn,
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable OPENVPN_TARGET_IP: dummy test error"),
|
|
||||||
},
|
|
||||||
"countries error": {
|
|
||||||
targetIP: singleStringCall{call: true},
|
|
||||||
countries: sliceStringCall{call: true, err: errDummy},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ivpn,
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable COUNTRY: dummy test error"),
|
|
||||||
},
|
|
||||||
"cities error": {
|
|
||||||
targetIP: singleStringCall{call: true},
|
|
||||||
countries: sliceStringCall{call: true},
|
|
||||||
cities: sliceStringCall{call: true, err: errDummy},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ivpn,
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable CITY: dummy test error"),
|
|
||||||
},
|
|
||||||
"isps error": {
|
|
||||||
targetIP: singleStringCall{call: true},
|
|
||||||
countries: sliceStringCall{call: true},
|
|
||||||
cities: sliceStringCall{call: true},
|
|
||||||
isps: sliceStringCall{call: true, err: errDummy},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ivpn,
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable ISP: dummy test error"),
|
|
||||||
},
|
|
||||||
"hostnames error": {
|
|
||||||
targetIP: singleStringCall{call: true},
|
|
||||||
countries: sliceStringCall{call: true},
|
|
||||||
cities: sliceStringCall{call: true},
|
|
||||||
isps: sliceStringCall{call: true},
|
|
||||||
hostnames: sliceStringCall{call: true, err: errDummy},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ivpn,
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable SERVER_HOSTNAME: dummy test error"),
|
|
||||||
},
|
|
||||||
"openvpn protocol error": {
|
|
||||||
targetIP: singleStringCall{call: true},
|
|
||||||
countries: sliceStringCall{call: true},
|
|
||||||
cities: sliceStringCall{call: true},
|
|
||||||
isps: sliceStringCall{call: true},
|
|
||||||
hostnames: sliceStringCall{call: true},
|
|
||||||
protocol: singleStringCall{call: true, err: errDummy},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ivpn,
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable OPENVPN_PROTOCOL: dummy test error"),
|
|
||||||
},
|
|
||||||
"openvpn custom port error": {
|
|
||||||
targetIP: singleStringCall{call: true},
|
|
||||||
countries: sliceStringCall{call: true},
|
|
||||||
cities: sliceStringCall{call: true},
|
|
||||||
isps: sliceStringCall{call: true},
|
|
||||||
hostnames: sliceStringCall{call: true},
|
|
||||||
protocol: singleStringCall{call: true},
|
|
||||||
ovpnPort: portCall{getCall: true, getErr: errDummy},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ivpn,
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable OPENVPN_PORT: dummy test error"),
|
|
||||||
},
|
|
||||||
"wireguard custom port error": {
|
|
||||||
targetIP: singleStringCall{call: true},
|
|
||||||
countries: sliceStringCall{call: true},
|
|
||||||
cities: sliceStringCall{call: true},
|
|
||||||
isps: sliceStringCall{call: true},
|
|
||||||
hostnames: sliceStringCall{call: true},
|
|
||||||
protocol: singleStringCall{call: true},
|
|
||||||
ovpnPort: portCall{getCall: true, getValue: "0"},
|
|
||||||
ovpnOldPort: portCall{getCall: true, getValue: "0"},
|
|
||||||
wgPort: portCall{getCall: true, getErr: errDummy},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ivpn,
|
|
||||||
},
|
|
||||||
err: errors.New("environment variable WIREGUARD_ENDPOINT_PORT: dummy test error"),
|
|
||||||
},
|
|
||||||
"default settings": {
|
|
||||||
targetIP: singleStringCall{call: true},
|
|
||||||
countries: sliceStringCall{call: true},
|
|
||||||
cities: sliceStringCall{call: true},
|
|
||||||
isps: sliceStringCall{call: true},
|
|
||||||
hostnames: sliceStringCall{call: true},
|
|
||||||
protocol: singleStringCall{call: true},
|
|
||||||
ovpnPort: portCall{getCall: true, getValue: "0"},
|
|
||||||
ovpnOldPort: portCall{getCall: true, getValue: "0"},
|
|
||||||
wgPort: portCall{getCall: true, getValue: "0"},
|
|
||||||
wgOldPort: portCall{getCall: true, getValue: "0"},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ivpn,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"set settings": {
|
|
||||||
targetIP: singleStringCall{call: true, value: "1.2.3.4"},
|
|
||||||
countries: sliceStringCall{call: true, values: []string{"A", "B"}},
|
|
||||||
cities: sliceStringCall{call: true, values: []string{"C", "D"}},
|
|
||||||
isps: sliceStringCall{call: true, values: []string{"ISP 1"}},
|
|
||||||
hostnames: sliceStringCall{call: true, values: []string{"E", "F"}},
|
|
||||||
protocol: singleStringCall{call: true, value: constants.TCP},
|
|
||||||
ovpnPort: portCall{getCall: true, portCall: true, portValue: 443},
|
|
||||||
wgPort: portCall{getCall: true, portCall: true, portValue: 2049},
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ivpn,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
OpenVPN: OpenVPNSelection{
|
|
||||||
TCP: true,
|
|
||||||
CustomPort: 443,
|
|
||||||
},
|
|
||||||
Wireguard: WireguardSelection{
|
|
||||||
EndpointPort: 2049,
|
|
||||||
},
|
|
||||||
TargetIP: net.IPv4(1, 2, 3, 4),
|
|
||||||
Countries: []string{"A", "B"},
|
|
||||||
Cities: []string{"C", "D"},
|
|
||||||
ISPs: []string{"ISP 1"},
|
|
||||||
Hostnames: []string{"E", "F"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
|
|
||||||
env := mock_params.NewMockInterface(ctrl)
|
|
||||||
|
|
||||||
servers := []models.IvpnServer{{Hostname: "a"}}
|
|
||||||
allServers := models.AllServers{
|
|
||||||
Ivpn: models.IvpnServers{
|
|
||||||
Servers: servers,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if testCase.targetIP.call {
|
|
||||||
env.EXPECT().Get("OPENVPN_TARGET_IP").
|
|
||||||
Return(testCase.targetIP.value, testCase.targetIP.err)
|
|
||||||
}
|
|
||||||
if testCase.countries.call {
|
|
||||||
env.EXPECT().CSVInside("COUNTRY", constants.IvpnCountryChoices(servers)).
|
|
||||||
Return(testCase.countries.values, testCase.countries.err)
|
|
||||||
}
|
|
||||||
if testCase.cities.call {
|
|
||||||
env.EXPECT().CSVInside("CITY", constants.IvpnCityChoices(servers)).
|
|
||||||
Return(testCase.cities.values, testCase.cities.err)
|
|
||||||
}
|
|
||||||
if testCase.isps.call {
|
|
||||||
env.EXPECT().CSVInside("ISP", constants.IvpnISPChoices(servers)).
|
|
||||||
Return(testCase.isps.values, testCase.isps.err)
|
|
||||||
}
|
|
||||||
if testCase.hostnames.call {
|
|
||||||
env.EXPECT().CSVInside("SERVER_HOSTNAME", constants.IvpnHostnameChoices(servers)).
|
|
||||||
Return(testCase.hostnames.values, testCase.hostnames.err)
|
|
||||||
}
|
|
||||||
if testCase.protocol.call {
|
|
||||||
env.EXPECT().Inside("OPENVPN_PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
|
|
||||||
Return(testCase.protocol.value, testCase.protocol.err)
|
|
||||||
}
|
|
||||||
if testCase.ovpnPort.getCall {
|
|
||||||
env.EXPECT().Get("OPENVPN_PORT", gomock.Any()).
|
|
||||||
Return(testCase.ovpnPort.getValue, testCase.ovpnPort.getErr)
|
|
||||||
}
|
|
||||||
if testCase.ovpnPort.portCall {
|
|
||||||
env.EXPECT().Port("OPENVPN_PORT").
|
|
||||||
Return(testCase.ovpnPort.portValue, testCase.ovpnPort.portErr)
|
|
||||||
}
|
|
||||||
if testCase.ovpnOldPort.getCall {
|
|
||||||
env.EXPECT().Get("PORT", gomock.Any()).
|
|
||||||
Return(testCase.ovpnOldPort.getValue, testCase.ovpnOldPort.getErr)
|
|
||||||
}
|
|
||||||
if testCase.ovpnOldPort.portCall {
|
|
||||||
env.EXPECT().Port("PORT").
|
|
||||||
Return(testCase.ovpnOldPort.portValue, testCase.ovpnOldPort.portErr)
|
|
||||||
}
|
|
||||||
if testCase.wgPort.getCall {
|
|
||||||
env.EXPECT().Get("WIREGUARD_ENDPOINT_PORT", gomock.Any()).
|
|
||||||
Return(testCase.wgPort.getValue, testCase.wgPort.getErr)
|
|
||||||
}
|
|
||||||
if testCase.wgPort.portCall {
|
|
||||||
env.EXPECT().Port("WIREGUARD_ENDPOINT_PORT").
|
|
||||||
Return(testCase.wgPort.portValue, testCase.wgPort.portErr)
|
|
||||||
}
|
|
||||||
if testCase.wgOldPort.getCall {
|
|
||||||
env.EXPECT().Get("WIREGUARD_PORT", gomock.Any()).
|
|
||||||
Return(testCase.wgOldPort.getValue, testCase.wgOldPort.getErr)
|
|
||||||
}
|
|
||||||
if testCase.wgOldPort.portCall {
|
|
||||||
env.EXPECT().Port("WIREGUARD_PORT").
|
|
||||||
Return(testCase.wgOldPort.portValue, testCase.wgOldPort.portErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := reader{
|
|
||||||
servers: allServers,
|
|
||||||
env: env,
|
|
||||||
}
|
|
||||||
|
|
||||||
var settings Provider
|
|
||||||
err := settings.readIvpn(r)
|
|
||||||
|
|
||||||
if testCase.err != nil {
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.settings, settings)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/openvpn/parse"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errClientCert = errors.New("cannot read client certificate")
|
|
||||||
errClientKey = errors.New("cannot read client key")
|
|
||||||
)
|
|
||||||
|
|
||||||
func readClientKey(r reader) (clientKey string, err error) {
|
|
||||||
b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTKEY", constants.ClientKey)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return parse.ExtractPrivateKey(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readClientCertificate(r reader) (clientCertificate string, err error) {
|
|
||||||
b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTCRT", constants.ClientCertificate)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return parse.ExtractCert(b)
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func uint16sToStrings(uint16s []uint16) (strings []string) {
|
|
||||||
strings = make([]string, len(uint16s))
|
|
||||||
for i := range uint16s {
|
|
||||||
strings[i] = strconv.Itoa(int(uint16s[i]))
|
|
||||||
}
|
|
||||||
return strings
|
|
||||||
}
|
|
||||||
|
|
||||||
func ipNetsToStrings(ipNets []net.IPNet) (strings []string) {
|
|
||||||
strings = make([]string, len(ipNets))
|
|
||||||
for i := range ipNets {
|
|
||||||
strings[i] = ipNets[i].String()
|
|
||||||
}
|
|
||||||
return strings
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Log struct {
|
|
||||||
Level logging.Level `json:"level"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Log) lines() (lines []string) {
|
|
||||||
lines = append(lines, lastIndent+"Log:")
|
|
||||||
|
|
||||||
lines = append(lines, indent+lastIndent+"Level: "+settings.Level.String())
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Log) read(env params.Interface) (err error) {
|
|
||||||
defaultLevel := logging.LevelInfo.String()
|
|
||||||
settings.Level, err = env.LogLevel("LOG_LEVEL", params.Default(defaultLevel))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable LOG_LEVEL: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readMullvad(r reader) (err error) {
|
|
||||||
settings.Name = constants.Mullvad
|
|
||||||
servers := r.servers.GetMullvad()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.MullvadCountryChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.MullvadCityChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable CITY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.MullvadHostnameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.ISPs, err = r.env.CSVInside("ISP", constants.MullvadISPChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable ISP: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Owned, err = r.env.YesNo("OWNED", params.Default("no"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable OWNED: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = settings.ServerSelection.OpenVPN.readMullvad(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.ServerSelection.Wireguard.readMullvad(r.env)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *OpenVPNSelection) readMullvad(r reader) (err error) {
|
|
||||||
settings.TCP, err = readOpenVPNProtocol(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{
|
|
||||||
tcp: settings.TCP,
|
|
||||||
allowedTCP: []uint16{80, 443, 1401},
|
|
||||||
allowedUDP: []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *WireguardSelection) readMullvad(env params.Interface) (err error) {
|
|
||||||
settings.EndpointPort, err = readWireguardCustomPort(env, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readNordvpn(r reader) (err error) {
|
|
||||||
settings.Name = constants.Nordvpn
|
|
||||||
servers := r.servers.GetNordvpn()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.NordvpnRegionChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable REGION: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.NordvpnHostnameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Numbers, err = readNordVPNServerNumbers(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readNordVPNServerNumbers(env params.Interface) (numbers []uint16, err error) {
|
|
||||||
const possiblePortsCount = 65537
|
|
||||||
possibilities := make([]string, possiblePortsCount)
|
|
||||||
for i := range possibilities {
|
|
||||||
possibilities[i] = fmt.Sprintf("%d", i)
|
|
||||||
}
|
|
||||||
possibilities[65536] = ""
|
|
||||||
values, err := env.CSVInside("SERVER_NUMBER", possibilities)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
numbers = make([]uint16, len(values))
|
|
||||||
for i := range values {
|
|
||||||
n, err := strconv.Atoi(values[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
numbers[i] = uint16(n)
|
|
||||||
}
|
|
||||||
return numbers, nil
|
|
||||||
}
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpenVPN contains settings to configure the OpenVPN client.
|
|
||||||
type OpenVPN struct {
|
|
||||||
User string `json:"user"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Verbosity int `json:"verbosity"`
|
|
||||||
Flags []string `json:"flags"`
|
|
||||||
MSSFix uint16 `json:"mssfix"`
|
|
||||||
Root bool `json:"run_as_root"`
|
|
||||||
Ciphers []string `json:"ciphers"`
|
|
||||||
Auth string `json:"auth"`
|
|
||||||
ConfFile string `json:"conf_file"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
ClientCrt string `json:"-"` // Cyberghost
|
|
||||||
ClientKey string `json:"-"` // Cyberghost, VPNUnlimited
|
|
||||||
EncPreset string `json:"encryption_preset"` // PIA
|
|
||||||
IPv6 bool `json:"ipv6"` // Mullvad
|
|
||||||
ProcUser string `json:"procuser"` // Process username
|
|
||||||
Interface string `json:"interface"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *OpenVPN) String() string {
|
|
||||||
return strings.Join(settings.lines(), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *OpenVPN) lines() (lines []string) {
|
|
||||||
lines = append(lines, lastIndent+"OpenVPN:")
|
|
||||||
|
|
||||||
lines = append(lines, indent+lastIndent+"Version: "+settings.Version)
|
|
||||||
|
|
||||||
lines = append(lines, indent+lastIndent+"Verbosity level: "+strconv.Itoa(settings.Verbosity))
|
|
||||||
|
|
||||||
lines = append(lines, indent+lastIndent+"Network interface: "+settings.Interface)
|
|
||||||
|
|
||||||
if len(settings.Flags) > 0 {
|
|
||||||
lines = append(lines, indent+lastIndent+"Flags: "+strings.Join(settings.Flags, " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.Root {
|
|
||||||
lines = append(lines, indent+lastIndent+"Run as root: enabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(settings.Ciphers) > 0 {
|
|
||||||
lines = append(lines, indent+lastIndent+"Custom ciphers: "+commaJoin(settings.Ciphers))
|
|
||||||
}
|
|
||||||
if len(settings.Auth) > 0 {
|
|
||||||
lines = append(lines, indent+lastIndent+"Custom auth algorithm: "+settings.Auth)
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.ConfFile != "" {
|
|
||||||
lines = append(lines, indent+lastIndent+"Configuration file: "+settings.ConfFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.ClientKey != "" {
|
|
||||||
lines = append(lines, indent+lastIndent+"Client key is set")
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.ClientCrt != "" {
|
|
||||||
lines = append(lines, indent+lastIndent+"Client certificate is set")
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.IPv6 {
|
|
||||||
lines = append(lines, indent+lastIndent+"IPv6: enabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.EncPreset != "" { // PIA only
|
|
||||||
lines = append(lines, indent+lastIndent+"Encryption preset: "+settings.EncPreset)
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
|
|
||||||
credentialsRequired := false
|
|
||||||
switch serviceProvider {
|
|
||||||
case constants.Custom:
|
|
||||||
case constants.VPNUnlimited:
|
|
||||||
default:
|
|
||||||
credentialsRequired = true
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.User, err = r.getFromEnvOrSecretFile("OPENVPN_USER", credentialsRequired, []string{"USER"})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable OPENVPN_USER: %w", err)
|
|
||||||
}
|
|
||||||
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
|
|
||||||
settings.User = strings.ReplaceAll(settings.User, " ", "")
|
|
||||||
|
|
||||||
if serviceProvider == constants.Mullvad {
|
|
||||||
settings.Password = "m"
|
|
||||||
} else {
|
|
||||||
settings.Password, err = r.getFromEnvOrSecretFile("OPENVPN_PASSWORD", credentialsRequired, []string{"PASSWORD"})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Version, err = r.env.Inside("OPENVPN_VERSION",
|
|
||||||
[]string{constants.Openvpn24, constants.Openvpn25}, params.Default(constants.Openvpn25))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable OPENVPN_VERSION: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Verbosity, err = r.env.IntRange("OPENVPN_VERBOSITY", 0, 6, params.Default("1")) //nolint:gomnd
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Flags = []string{}
|
|
||||||
flagsStr, err := r.env.Get("OPENVPN_FLAGS")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable OPENVPN_FLAGS: %w", err)
|
|
||||||
}
|
|
||||||
if flagsStr != "" {
|
|
||||||
settings.Flags = strings.Fields(flagsStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Root, err = r.env.YesNo("OPENVPN_ROOT", params.Default("no"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable OPENVPN_ROOT: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Ciphers, err = r.env.CSV("OPENVPN_CIPHER")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable OPENVPN_CIPHER: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Auth, err = r.env.Get("OPENVPN_AUTH")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable OPENVPN_AUTH: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxMSSFix = 10000
|
|
||||||
mssFix, err := r.env.IntRange("OPENVPN_MSSFIX", 0, maxMSSFix, params.Default("0"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
|
|
||||||
}
|
|
||||||
settings.MSSFix = uint16(mssFix)
|
|
||||||
|
|
||||||
settings.IPv6, err = r.env.OnOff("OPENVPN_IPV6", params.Default("off"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable OPENVPN_IPV6: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Interface, err = readInterface(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch serviceProvider {
|
|
||||||
case constants.Custom:
|
|
||||||
err = settings.readCustom(r) // read OPENVPN_CUSTOM_CONFIG
|
|
||||||
case constants.Cyberghost:
|
|
||||||
err = settings.readCyberghost(r)
|
|
||||||
case constants.PrivateInternetAccess:
|
|
||||||
settings.EncPreset, err = getPIAEncryptionPreset(r)
|
|
||||||
case constants.VPNUnlimited:
|
|
||||||
err = settings.readVPNUnlimited(r)
|
|
||||||
case constants.Wevpn:
|
|
||||||
err = settings.readWevpn(r)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readOpenVPNProtocol(r reader) (tcp bool, err error) {
|
|
||||||
protocol, err := r.env.Inside("OPENVPN_PROTOCOL", []string{constants.TCP, constants.UDP},
|
|
||||||
params.Default(constants.UDP), params.RetroKeys([]string{"PROTOCOL"}, r.onRetroActive))
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("environment variable OPENVPN_PROTOCOL: %w", err)
|
|
||||||
}
|
|
||||||
return protocol == constants.TCP, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const openvpnIntfRegexString = `^.*[0-9]$`
|
|
||||||
|
|
||||||
var openvpnIntfRegexp = regexp.MustCompile(openvpnIntfRegexString)
|
|
||||||
var errInterfaceNameNotValid = errors.New("interface name is not valid")
|
|
||||||
|
|
||||||
func readInterface(env params.Interface) (intf string, err error) {
|
|
||||||
intf, err = env.Get("OPENVPN_INTERFACE", params.Default("tun0"))
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("environment variable OPENVPN_INTERFACE: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !openvpnIntfRegexp.MatchString(intf) {
|
|
||||||
return "", fmt.Errorf("%w: does not match regex %s: %s",
|
|
||||||
errInterfaceNameNotValid, openvpnIntfRegexString, intf)
|
|
||||||
}
|
|
||||||
|
|
||||||
return intf, nil
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_OpenVPN_JSON(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
in := OpenVPN{
|
|
||||||
Root: true,
|
|
||||||
Flags: []string{},
|
|
||||||
Ciphers: []string{},
|
|
||||||
}
|
|
||||||
data, err := json.MarshalIndent(in, "", " ")
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, `{
|
|
||||||
"user": "",
|
|
||||||
"password": "",
|
|
||||||
"verbosity": 0,
|
|
||||||
"flags": [],
|
|
||||||
"mssfix": 0,
|
|
||||||
"run_as_root": true,
|
|
||||||
"ciphers": [],
|
|
||||||
"auth": "",
|
|
||||||
"conf_file": "",
|
|
||||||
"version": "",
|
|
||||||
"encryption_preset": "",
|
|
||||||
"ipv6": false,
|
|
||||||
"procuser": "",
|
|
||||||
"interface": ""
|
|
||||||
}`, string(data))
|
|
||||||
var out OpenVPN
|
|
||||||
err = json.Unmarshal(data, &out)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, in, out)
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readPerfectPrivacy(r reader) (err error) {
|
|
||||||
settings.Name = constants.Perfectprivacy
|
|
||||||
servers := r.servers.GetPerfectprivacy()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PerfectprivacyCityChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable CITY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.ServerSelection.OpenVPN.readPerfectPrivacy(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *OpenVPNSelection) readPerfectPrivacy(r reader) (err error) {
|
|
||||||
settings.TCP, err = readOpenVPNProtocol(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
portValidation := openvpnPortValidation{
|
|
||||||
tcp: settings.TCP,
|
|
||||||
allowedTCP: []uint16{44, 443, 4433},
|
|
||||||
allowedUDP: []uint16{44, 443, 4433},
|
|
||||||
}
|
|
||||||
settings.CustomPort, err = readOpenVPNCustomPort(r, portValidation)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readPrivado(r reader) (err error) {
|
|
||||||
settings.Name = constants.Privado
|
|
||||||
servers := r.servers.GetPrivado()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.PrivadoCountryChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PrivadoRegionChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable REGION: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PrivadoCityChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable CITY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.PrivadoHostnameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readPrivateInternetAccess(r reader) (err error) {
|
|
||||||
settings.Name = constants.PrivateInternetAccess
|
|
||||||
servers := r.servers.GetPia()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PIAGeoChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable REGION: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.PIAHostnameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Names, err = r.env.CSVInside("SERVER_NAME", constants.PIANameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_NAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.PortForwarding.Enabled, err = r.env.OnOff("PORT_FORWARDING", params.Default("off"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable PORT_FORWARDING: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.PortForwarding.Enabled {
|
|
||||||
settings.PortForwarding.Filepath, err = r.env.Path("PORT_FORWARDING_STATUS_FILE",
|
|
||||||
params.Default("/tmp/gluetun/forwarded_port"), params.CaseSensitiveValue())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable PORT_FORWARDING_STATUS_FILE: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.ServerSelection.OpenVPN.readPrivateInternetAccess(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *OpenVPNSelection) readPrivateInternetAccess(r reader) (err error) {
|
|
||||||
settings.EncPreset, err = getPIAEncryptionPreset(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{allAllowed: true})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPIAEncryptionPreset(r reader) (encryptionPreset string, err error) {
|
|
||||||
encryptionPreset, err = r.env.Inside("PIA_ENCRYPTION",
|
|
||||||
[]string{constants.PIAEncryptionPresetNone, constants.PIAEncryptionPresetNormal, constants.PIAEncryptionPresetStrong},
|
|
||||||
params.RetroKeys([]string{"ENCRYPTION"}, r.onRetroActive),
|
|
||||||
params.Default(constants.PIAEncryptionPresetStrong),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("environment variable PIA_ENCRYPTION: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return encryptionPreset, nil
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readPrivatevpn(r reader) (err error) {
|
|
||||||
settings.Name = constants.Privatevpn
|
|
||||||
servers := r.servers.GetPrivatevpn()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.PrivatevpnCountryChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PrivatevpnCityChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable CITY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
|
|
||||||
constants.PrivatevpnHostnameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readProtonvpn(r reader) (err error) {
|
|
||||||
settings.Name = constants.Protonvpn
|
|
||||||
servers := r.servers.GetProtonvpn()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.ProtonvpnCountryChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.ProtonvpnRegionChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable REGION: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.ProtonvpnCityChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable CITY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Names, err = r.env.CSVInside("SERVER_NAME", constants.ProtonvpnNameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_NAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
|
|
||||||
constants.ProtonvpnHostnameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.FreeOnly, err = r.env.YesNo("FREE_ONLY", params.Default("no"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable FREE_ONLY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Provider contains settings specific to a VPN provider.
|
|
||||||
type Provider struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
ServerSelection ServerSelection `json:"server_selection"`
|
|
||||||
PortForwarding PortForwarding `json:"port_forwarding"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Provider) lines() (lines []string) {
|
|
||||||
if settings.Name == "" { // custom OpenVPN configuration
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, lastIndent+strings.Title(settings.Name)+" settings:")
|
|
||||||
|
|
||||||
for _, line := range settings.ServerSelection.toLines() {
|
|
||||||
lines = append(lines, indent+line)
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.PortForwarding.Enabled { // PIA
|
|
||||||
lines = append(lines, indent+lastIndent+"Port forwarding:")
|
|
||||||
for _, line := range settings.PortForwarding.lines() {
|
|
||||||
lines = append(lines, indent+indent+line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidVPNProvider = errors.New("invalid VPN provider")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) read(r reader, vpnType string) error {
|
|
||||||
err := settings.readVPNServiceProvider(r, vpnType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch settings.Name {
|
|
||||||
case constants.Custom:
|
|
||||||
err = settings.readCustom(r, vpnType)
|
|
||||||
case constants.Cyberghost:
|
|
||||||
err = settings.readCyberghost(r)
|
|
||||||
case constants.Expressvpn:
|
|
||||||
err = settings.readExpressvpn(r)
|
|
||||||
case constants.Fastestvpn:
|
|
||||||
err = settings.readFastestvpn(r)
|
|
||||||
case constants.HideMyAss:
|
|
||||||
err = settings.readHideMyAss(r)
|
|
||||||
case constants.Ipvanish:
|
|
||||||
err = settings.readIpvanish(r)
|
|
||||||
case constants.Ivpn:
|
|
||||||
err = settings.readIvpn(r)
|
|
||||||
case constants.Mullvad:
|
|
||||||
err = settings.readMullvad(r)
|
|
||||||
case constants.Nordvpn:
|
|
||||||
err = settings.readNordvpn(r)
|
|
||||||
case constants.Perfectprivacy:
|
|
||||||
err = settings.readPerfectPrivacy(r)
|
|
||||||
case constants.Privado:
|
|
||||||
err = settings.readPrivado(r)
|
|
||||||
case constants.PrivateInternetAccess:
|
|
||||||
err = settings.readPrivateInternetAccess(r)
|
|
||||||
case constants.Privatevpn:
|
|
||||||
err = settings.readPrivatevpn(r)
|
|
||||||
case constants.Protonvpn:
|
|
||||||
err = settings.readProtonvpn(r)
|
|
||||||
case constants.Purevpn:
|
|
||||||
err = settings.readPurevpn(r)
|
|
||||||
case constants.Surfshark:
|
|
||||||
err = settings.readSurfshark(r)
|
|
||||||
case constants.Torguard:
|
|
||||||
err = settings.readTorguard(r)
|
|
||||||
case constants.VPNUnlimited:
|
|
||||||
err = settings.readVPNUnlimited(r)
|
|
||||||
case constants.Vyprvpn:
|
|
||||||
err = settings.readVyprvpn(r)
|
|
||||||
case constants.Wevpn:
|
|
||||||
err = settings.readWevpn(r)
|
|
||||||
case constants.Windscribe:
|
|
||||||
err = settings.readWindscribe(r)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Name)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.VPN = vpnType
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err error) {
|
|
||||||
var allowedVPNServiceProviders []string
|
|
||||||
switch vpnType {
|
|
||||||
case constants.OpenVPN:
|
|
||||||
allowedVPNServiceProviders = []string{
|
|
||||||
constants.Custom,
|
|
||||||
"cyberghost", constants.Expressvpn, "fastestvpn", "hidemyass", "ipvanish",
|
|
||||||
"ivpn", "mullvad", "nordvpn",
|
|
||||||
constants.Perfectprivacy, "privado", "pia", "private internet access", "privatevpn", "protonvpn",
|
|
||||||
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn",
|
|
||||||
constants.Wevpn, "windscribe"}
|
|
||||||
case constants.Wireguard:
|
|
||||||
allowedVPNServiceProviders = []string{
|
|
||||||
constants.Custom, constants.Ivpn,
|
|
||||||
constants.Mullvad, constants.Windscribe,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vpnsp, err := r.env.Inside("VPNSP", allowedVPNServiceProviders,
|
|
||||||
params.Default("private internet access"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable VPNSP: %w", err)
|
|
||||||
}
|
|
||||||
if vpnsp == "pia" { // retro compatibility
|
|
||||||
vpnsp = "private internet access"
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.isOpenVPNCustomConfig(r.env) { // retro compatibility
|
|
||||||
vpnsp = constants.Custom
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Name = vpnsp
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func commaJoin(slice []string) string {
|
|
||||||
return strings.Join(slice, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func protoToString(tcp bool) string {
|
|
||||||
if tcp {
|
|
||||||
return constants.TCP
|
|
||||||
}
|
|
||||||
return constants.UDP
|
|
||||||
}
|
|
||||||
|
|
||||||
func readTargetIP(env params.Interface) (targetIP net.IP, err error) {
|
|
||||||
targetIP, err = readIP(env, "OPENVPN_TARGET_IP")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("environment variable OPENVPN_TARGET_IP: %w", err)
|
|
||||||
}
|
|
||||||
return targetIP, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type openvpnPortValidation struct {
|
|
||||||
allAllowed bool
|
|
||||||
tcp bool
|
|
||||||
allowedTCP []uint16
|
|
||||||
allowedUDP []uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func readOpenVPNCustomPort(r reader, validation openvpnPortValidation) (
|
|
||||||
port uint16, err error) {
|
|
||||||
port, err = readPortOrZero(r.env, "OPENVPN_PORT")
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("environment variable OPENVPN_PORT: %w", err)
|
|
||||||
} else if port == 0 {
|
|
||||||
// Try using old variable name
|
|
||||||
port, err = readPortOrZero(r.env, "PORT")
|
|
||||||
if err != nil {
|
|
||||||
r.onRetroActive("PORT", "OPENVPN_PORT")
|
|
||||||
return 0, fmt.Errorf("environment variable PORT: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if port == 0 || validation.allAllowed {
|
|
||||||
return port, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if validation.tcp {
|
|
||||||
for _, allowedPort := range validation.allowedTCP {
|
|
||||||
if port == allowedPort {
|
|
||||||
return port, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf(
|
|
||||||
"environment variable PORT: %w: port %d for TCP protocol, can only be one of %s",
|
|
||||||
ErrInvalidPort, port, portsToString(validation.allowedTCP))
|
|
||||||
}
|
|
||||||
for _, allowedPort := range validation.allowedUDP {
|
|
||||||
if port == allowedPort {
|
|
||||||
return port, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf(
|
|
||||||
"environment variable PORT: %w: port %d for UDP protocol, can only be one of %s",
|
|
||||||
ErrInvalidPort, port, portsToString(validation.allowedUDP))
|
|
||||||
}
|
|
||||||
|
|
||||||
// note: set allowed to an empty slice to allow all valid ports
|
|
||||||
func readWireguardCustomPort(env params.Interface, allowed []uint16) (port uint16, err error) {
|
|
||||||
port, err = readPortOrZero(env, "WIREGUARD_ENDPOINT_PORT")
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_PORT: %w", err)
|
|
||||||
} else if port == 0 {
|
|
||||||
port, _ = readPortOrZero(env, "WIREGUARD_PORT")
|
|
||||||
if err == nil {
|
|
||||||
return port, nil // 0 or WIREGUARD_PORT value
|
|
||||||
}
|
|
||||||
return 0, nil // default 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(allowed) == 0 {
|
|
||||||
return port, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range allowed {
|
|
||||||
if allowed[i] == port {
|
|
||||||
return port, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, fmt.Errorf(
|
|
||||||
"environment variable WIREGUARD_PORT: %w: port %d, can only be one of %s",
|
|
||||||
ErrInvalidPort, port, portsToString(allowed))
|
|
||||||
}
|
|
||||||
|
|
||||||
func portsToString(ports []uint16) string {
|
|
||||||
slice := make([]string, len(ports))
|
|
||||||
for i := range ports {
|
|
||||||
slice[i] = fmt.Sprint(ports[i])
|
|
||||||
}
|
|
||||||
return strings.Join(slice, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// isOpenVPNCustomConfig is for retro compatibility to set VPNSP=custom
|
|
||||||
// if OPENVPN_CUSTOM_CONFIG is set.
|
|
||||||
func (settings Provider) isOpenVPNCustomConfig(env params.Interface) (ok bool) {
|
|
||||||
s, _ := env.Get("VPN_TYPE")
|
|
||||||
isOpenVPN := s == constants.OpenVPN
|
|
||||||
s, _ = env.Get("OPENVPN_CUSTOM_CONFIG")
|
|
||||||
return isOpenVPN && s != ""
|
|
||||||
}
|
|
||||||
@@ -1,462 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/params/mock_params"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errDummy = errors.New("dummy")
|
|
||||||
|
|
||||||
func Test_Provider_lines(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
settings Provider
|
|
||||||
lines []string
|
|
||||||
}{
|
|
||||||
"cyberghost": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Cyberghost,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Countries: []string{"a", "El country"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Cyberghost settings:",
|
|
||||||
" |--Countries: a, El country",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"expressvpn": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Expressvpn,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Hostnames: []string{"a", "b"},
|
|
||||||
Countries: []string{"c", "d"},
|
|
||||||
Cities: []string{"e", "f"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Expressvpn settings:",
|
|
||||||
" |--Countries: c, d",
|
|
||||||
" |--Cities: e, f",
|
|
||||||
" |--Hostnames: a, b",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"fastestvpn": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Fastestvpn,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Hostnames: []string{"a", "b"},
|
|
||||||
Countries: []string{"c", "d"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Fastestvpn settings:",
|
|
||||||
" |--Countries: c, d",
|
|
||||||
" |--Hostnames: a, b",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"hidemyass": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.HideMyAss,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Countries: []string{"a", "b"},
|
|
||||||
Cities: []string{"c", "d"},
|
|
||||||
Hostnames: []string{"e", "f"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Hidemyass settings:",
|
|
||||||
" |--Countries: a, b",
|
|
||||||
" |--Cities: c, d",
|
|
||||||
" |--Hostnames: e, f",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"ipvanish": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ipvanish,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Countries: []string{"a", "b"},
|
|
||||||
Cities: []string{"c", "d"},
|
|
||||||
Hostnames: []string{"e", "f"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Ipvanish settings:",
|
|
||||||
" |--Countries: a, b",
|
|
||||||
" |--Cities: c, d",
|
|
||||||
" |--Hostnames: e, f",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"ivpn": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Ivpn,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Countries: []string{"a", "b"},
|
|
||||||
Cities: []string{"c", "d"},
|
|
||||||
Hostnames: []string{"e", "f"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Ivpn settings:",
|
|
||||||
" |--Countries: a, b",
|
|
||||||
" |--Cities: c, d",
|
|
||||||
" |--Hostnames: e, f",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"mullvad": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Mullvad,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Countries: []string{"a", "b"},
|
|
||||||
Cities: []string{"c", "d"},
|
|
||||||
ISPs: []string{"e", "f"},
|
|
||||||
OpenVPN: OpenVPNSelection{
|
|
||||||
CustomPort: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Mullvad settings:",
|
|
||||||
" |--Countries: a, b",
|
|
||||||
" |--Cities: c, d",
|
|
||||||
" |--ISPs: e, f",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
" |--Custom port: 1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nordvpn": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Nordvpn,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Regions: []string{"a", "b"},
|
|
||||||
Numbers: []uint16{1, 2},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Nordvpn settings:",
|
|
||||||
" |--Regions: a, b",
|
|
||||||
" |--Numbers: 1, 2",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"perfectprivacy": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Perfectprivacy,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Cities: []string{"a", "b"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Perfect Privacy settings:",
|
|
||||||
" |--Cities: a, b",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"privado": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Privado,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Hostnames: []string{"a", "b"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Privado settings:",
|
|
||||||
" |--Hostnames: a, b",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"privatevpn": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Privatevpn,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Hostnames: []string{"a", "b"},
|
|
||||||
Countries: []string{"c", "d"},
|
|
||||||
Cities: []string{"e", "f"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Privatevpn settings:",
|
|
||||||
" |--Countries: c, d",
|
|
||||||
" |--Cities: e, f",
|
|
||||||
" |--Hostnames: a, b",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"protonvpn": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Protonvpn,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Countries: []string{"a", "b"},
|
|
||||||
Regions: []string{"c", "d"},
|
|
||||||
Cities: []string{"e", "f"},
|
|
||||||
Names: []string{"g", "h"},
|
|
||||||
Hostnames: []string{"i", "j"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Protonvpn settings:",
|
|
||||||
" |--Countries: a, b",
|
|
||||||
" |--Regions: c, d",
|
|
||||||
" |--Cities: e, f",
|
|
||||||
" |--Hostnames: i, j",
|
|
||||||
" |--Names: g, h",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"private internet access": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.PrivateInternetAccess,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Regions: []string{"a", "b"},
|
|
||||||
OpenVPN: OpenVPNSelection{
|
|
||||||
CustomPort: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PortForwarding: PortForwarding{
|
|
||||||
Enabled: true,
|
|
||||||
Filepath: string("/here"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Private Internet Access settings:",
|
|
||||||
" |--Regions: a, b",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
" |--Custom port: 1",
|
|
||||||
" |--Port forwarding:",
|
|
||||||
" |--File path: /here",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"purevpn": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Purevpn,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Regions: []string{"a", "b"},
|
|
||||||
Countries: []string{"c", "d"},
|
|
||||||
Cities: []string{"e", "f"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Purevpn settings:",
|
|
||||||
" |--Countries: c, d",
|
|
||||||
" |--Regions: a, b",
|
|
||||||
" |--Cities: e, f",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"surfshark": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Surfshark,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Regions: []string{"a", "b"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Surfshark settings:",
|
|
||||||
" |--Regions: a, b",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"torguard": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Torguard,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Countries: []string{"a", "b"},
|
|
||||||
Cities: []string{"c", "d"},
|
|
||||||
Hostnames: []string{"e"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Torguard settings:",
|
|
||||||
" |--Countries: a, b",
|
|
||||||
" |--Cities: c, d",
|
|
||||||
" |--Hostnames: e",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
constants.VPNUnlimited: {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.VPNUnlimited,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Countries: []string{"a", "b"},
|
|
||||||
Cities: []string{"c", "d"},
|
|
||||||
Hostnames: []string{"e", "f"},
|
|
||||||
FreeOnly: true,
|
|
||||||
StreamOnly: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Vpn Unlimited settings:",
|
|
||||||
" |--Countries: a, b",
|
|
||||||
" |--Cities: c, d",
|
|
||||||
" |--Free servers only",
|
|
||||||
" |--Stream servers only",
|
|
||||||
" |--Hostnames: e, f",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"vyprvpn": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Vyprvpn,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Regions: []string{"a", "b"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Vyprvpn settings:",
|
|
||||||
" |--Regions: a, b",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"wevpn": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Wevpn,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Cities: []string{"a", "b"},
|
|
||||||
Hostnames: []string{"c", "d"},
|
|
||||||
OpenVPN: OpenVPNSelection{
|
|
||||||
CustomPort: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Wevpn settings:",
|
|
||||||
" |--Cities: a, b",
|
|
||||||
" |--Hostnames: c, d",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
" |--Custom port: 1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"windscribe": {
|
|
||||||
settings: Provider{
|
|
||||||
Name: constants.Windscribe,
|
|
||||||
ServerSelection: ServerSelection{
|
|
||||||
VPN: constants.OpenVPN,
|
|
||||||
Regions: []string{"a", "b"},
|
|
||||||
Cities: []string{"c", "d"},
|
|
||||||
Hostnames: []string{"e", "f"},
|
|
||||||
OpenVPN: OpenVPNSelection{
|
|
||||||
CustomPort: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
"|--Windscribe settings:",
|
|
||||||
" |--Regions: a, b",
|
|
||||||
" |--Cities: c, d",
|
|
||||||
" |--Hostnames: e, f",
|
|
||||||
" |--OpenVPN selection:",
|
|
||||||
" |--Protocol: udp",
|
|
||||||
" |--Custom port: 1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
lines := testCase.settings.lines()
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.lines, lines)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_readProtocol(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
mockStr string
|
|
||||||
mockErr error
|
|
||||||
tcp bool
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
"error": {
|
|
||||||
mockErr: errDummy,
|
|
||||||
err: errors.New("environment variable OPENVPN_PROTOCOL: dummy"),
|
|
||||||
},
|
|
||||||
"success": {
|
|
||||||
mockStr: "tcp",
|
|
||||||
tcp: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
|
|
||||||
env := mock_params.NewMockInterface(ctrl)
|
|
||||||
env.EXPECT().
|
|
||||||
Inside("OPENVPN_PROTOCOL", []string{"tcp", "udp"}, gomock.Any(), gomock.Any()).
|
|
||||||
Return(testCase.mockStr, testCase.mockErr)
|
|
||||||
reader := reader{
|
|
||||||
env: env,
|
|
||||||
}
|
|
||||||
|
|
||||||
tcp, err := readOpenVPNProtocol(reader)
|
|
||||||
|
|
||||||
if testCase.err != nil {
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.tcp, tcp)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PublicIP struct {
|
|
||||||
Period time.Duration `json:"period"`
|
|
||||||
IPFilepath string `json:"ip_filepath"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *PublicIP) String() string {
|
|
||||||
return strings.Join(settings.lines(), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *PublicIP) lines() (lines []string) {
|
|
||||||
if settings.Period == 0 {
|
|
||||||
lines = append(lines, lastIndent+"Public IP getter: disabled")
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, lastIndent+"Public IP getter:")
|
|
||||||
lines = append(lines, indent+lastIndent+"Fetch period: "+settings.Period.String())
|
|
||||||
lines = append(lines, indent+lastIndent+"IP file: "+settings.IPFilepath)
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *PublicIP) read(r reader) (err error) {
|
|
||||||
settings.Period, err = r.env.Duration("PUBLICIP_PERIOD", params.Default("12h"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable PUBLICIP_PERIOD: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.IPFilepath, err = r.env.Path("PUBLICIP_FILE", params.CaseSensitiveValue(),
|
|
||||||
params.Default("/tmp/gluetun/ip"),
|
|
||||||
params.RetroKeys([]string{"IP_STATUS_FILE"}, r.onRetroActive))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable PUBLICIP_FILE (or IP_STATUS_FILE): %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (settings *Provider) readPurevpn(r reader) (err error) {
|
|
||||||
settings.Name = constants.Purevpn
|
|
||||||
servers := r.servers.GetPurevpn()
|
|
||||||
|
|
||||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PurevpnRegionChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable REGION: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.PurevpnCountryChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable COUNTRY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PurevpnCityChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable CITY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.PurevpnHostnameChoices(servers))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
ovpnextract "github.com/qdm12/gluetun/internal/openvpn/extract"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
"github.com/qdm12/golibs/verification"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate mockgen -destination=warner_mock_test.go -package configuration . Warner
|
|
||||||
|
|
||||||
type reader struct {
|
|
||||||
servers models.AllServers
|
|
||||||
env params.Interface
|
|
||||||
warner Warner
|
|
||||||
regex verification.Regex
|
|
||||||
ovpnExt ovpnextract.Interface
|
|
||||||
}
|
|
||||||
|
|
||||||
type Warner interface {
|
|
||||||
Warn(s string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newReader(env params.Interface,
|
|
||||||
servers models.AllServers, warner Warner) reader {
|
|
||||||
return reader{
|
|
||||||
servers: servers,
|
|
||||||
env: env,
|
|
||||||
warner: warner,
|
|
||||||
regex: verification.NewRegex(),
|
|
||||||
ovpnExt: ovpnextract.New(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *reader) onRetroActive(oldKey, newKey string) {
|
|
||||||
r.warner.Warn(
|
|
||||||
"You are using the old environment variable " + oldKey +
|
|
||||||
", please consider changing it to " + newKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidPort = errors.New("invalid port")
|
|
||||||
)
|
|
||||||
|
|
||||||
func readCSVPorts(env params.Interface, key string) (ports []uint16, err error) {
|
|
||||||
s, err := env.Get(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if s == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
portsStr := strings.Split(s, ",")
|
|
||||||
ports = make([]uint16, len(portsStr))
|
|
||||||
for i, portStr := range portsStr {
|
|
||||||
portInt, err := strconv.Atoi(portStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%w: %s: %s", ErrInvalidPort, portStr, err)
|
|
||||||
} else if portInt <= 0 || portInt > 65535 {
|
|
||||||
return nil, fmt.Errorf("%w: %d: must be between 1 and 65535", ErrInvalidPort, portInt)
|
|
||||||
}
|
|
||||||
ports[i] = uint16(portInt)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ports, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidIPNet = errors.New("invalid IP network")
|
|
||||||
)
|
|
||||||
|
|
||||||
func readCSVIPNets(env params.Interface, key string, options ...params.OptionSetter) (
|
|
||||||
ipNets []net.IPNet, err error) {
|
|
||||||
s, err := env.Get(key, options...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if s == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ipNetsStr := strings.Split(s, ",")
|
|
||||||
ipNets = make([]net.IPNet, len(ipNetsStr))
|
|
||||||
for i, ipNetStr := range ipNetsStr {
|
|
||||||
_, ipNet, err := net.ParseCIDR(ipNetStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%w: %s: %s",
|
|
||||||
ErrInvalidIPNet, ipNetStr, err)
|
|
||||||
} else if ipNet == nil {
|
|
||||||
return nil, fmt.Errorf("%w: %s: subnet is nil", ErrInvalidIPNet, ipNetStr)
|
|
||||||
}
|
|
||||||
ipNets[i] = *ipNet
|
|
||||||
}
|
|
||||||
|
|
||||||
return ipNets, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidIP = errors.New("invalid IP address")
|
|
||||||
)
|
|
||||||
|
|
||||||
func readIP(env params.Interface, key string) (ip net.IP, err error) {
|
|
||||||
s, err := env.Get(key)
|
|
||||||
if s == "" {
|
|
||||||
return nil, nil
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ip = net.ParseIP(s)
|
|
||||||
if ip == nil {
|
|
||||||
return nil, fmt.Errorf("%w: %s", ErrInvalidIP, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ip, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readPortOrZero(env params.Interface, key string) (port uint16, err error) {
|
|
||||||
s, err := env.Get(key, params.Default("0"))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s == "0" {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return env.Port(key)
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrGetSecretFilepath = errors.New("cannot get secret file path from env")
|
|
||||||
ErrReadSecretFile = errors.New("cannot read secret file")
|
|
||||||
ErrSecretFileIsEmpty = errors.New("secret file is empty")
|
|
||||||
ErrReadNonSecretFile = errors.New("cannot read non secret file")
|
|
||||||
ErrFilesDoNotExist = errors.New("files do not exist")
|
|
||||||
)
|
|
||||||
|
|
||||||
func cleanSuffix(value string) string {
|
|
||||||
value = strings.TrimSuffix(value, "\n")
|
|
||||||
value = strings.TrimSuffix(value, "\r")
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *reader) getFromEnvOrSecretFile(envKey string, compulsory bool, retroKeys []string) (value string, err error) {
|
|
||||||
envOptions := []params.OptionSetter{
|
|
||||||
params.Compulsory(), // to fallback on file reading
|
|
||||||
params.CaseSensitiveValue(),
|
|
||||||
params.Unset(),
|
|
||||||
params.RetroKeys(retroKeys, r.onRetroActive),
|
|
||||||
}
|
|
||||||
value, envErr := r.env.Get(envKey, envOptions...)
|
|
||||||
if envErr == nil {
|
|
||||||
value = cleanSuffix(value)
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
secretFilepathEnvKey := envKey + "_SECRETFILE"
|
|
||||||
defaultSecretFile := "/run/secrets/" + strings.ToLower(envKey)
|
|
||||||
filepath, err := r.env.Get(secretFilepathEnvKey,
|
|
||||||
params.CaseSensitiveValue(),
|
|
||||||
params.Default(defaultSecretFile),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("%w: environment variable %s: %s",
|
|
||||||
ErrGetSecretFilepath, secretFilepathEnvKey, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, fileErr := os.OpenFile(filepath, os.O_RDONLY, 0)
|
|
||||||
if os.IsNotExist(fileErr) {
|
|
||||||
if compulsory {
|
|
||||||
return "", fmt.Errorf("environment variable %s: %w", envKey, envErr)
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
} else if fileErr != nil {
|
|
||||||
return "", fmt.Errorf("%w: %s: %s", ErrReadSecretFile, filepath, fileErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := io.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("%w: %s: %s", ErrReadSecretFile, filepath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
value = string(b)
|
|
||||||
value = cleanSuffix(value)
|
|
||||||
if compulsory && value == "" {
|
|
||||||
return "", fmt.Errorf("%s: %w", filepath, ErrSecretFileIsEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tries to read from the secret file then the non secret file.
|
|
||||||
func (r *reader) getFromFileOrSecretFile(secretName, filepath string) (
|
|
||||||
b []byte, err error) {
|
|
||||||
defaultSecretFile := "/run/secrets/" + strings.ToLower(secretName)
|
|
||||||
key := strings.ToUpper(secretName) + "_SECRETFILE"
|
|
||||||
secretFilepath, err := r.env.Get(key,
|
|
||||||
params.CaseSensitiveValue(),
|
|
||||||
params.Default(defaultSecretFile),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return b, fmt.Errorf("environment variable %s: %w: %s", key, ErrGetSecretFilepath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err = readFromFile(secretFilepath)
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return b, fmt.Errorf("%w: %s", ErrReadSecretFile, err)
|
|
||||||
} else if err == nil {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Secret file does not exist, try the non secret file
|
|
||||||
b, err = readFromFile(filepath)
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return nil, fmt.Errorf("%w: %s", ErrReadSecretFile, err)
|
|
||||||
} else if err == nil {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("%w: %s and %s", ErrFilesDoNotExist, secretFilepath, filepath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readFromFile(filepath string) (b []byte, err error) {
|
|
||||||
file, err := os.Open(filepath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b, err = io.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
_ = file.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := file.Close(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ServerSelection struct { //nolint:maligned
|
|
||||||
// Common
|
|
||||||
VPN string `json:"vpn"` // note: this is required
|
|
||||||
TargetIP net.IP `json:"target_ip,omitempty"`
|
|
||||||
// TODO comments
|
|
||||||
// Cyberghost, PIA, Protonvpn, Surfshark, Windscribe, Vyprvpn, NordVPN
|
|
||||||
Regions []string `json:"regions"`
|
|
||||||
|
|
||||||
// Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
|
|
||||||
Countries []string `json:"countries"`
|
|
||||||
// Expressvpn, HideMyAss, IPVanish, IVPN, Mullvad, Perfectprivacy, PrivateVPN, Protonvpn,
|
|
||||||
// PureVPN, VPNUnlimited, WeVPN, Windscribe
|
|
||||||
Cities []string `json:"cities"`
|
|
||||||
// Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited, WeVPN
|
|
||||||
Hostnames []string `json:"hostnames"`
|
|
||||||
Names []string `json:"names"` // Protonvpn
|
|
||||||
|
|
||||||
// Mullvad
|
|
||||||
ISPs []string `json:"isps"`
|
|
||||||
Owned bool `json:"owned"`
|
|
||||||
|
|
||||||
// NordVPN
|
|
||||||
Numbers []uint16 `json:"numbers"`
|
|
||||||
|
|
||||||
// ProtonVPN
|
|
||||||
FreeOnly bool `json:"free_only"`
|
|
||||||
|
|
||||||
// VPNUnlimited
|
|
||||||
StreamOnly bool `json:"stream_only"`
|
|
||||||
|
|
||||||
// Surfshark
|
|
||||||
MultiHopOnly bool `json:"multihop_only"`
|
|
||||||
|
|
||||||
OpenVPN OpenVPNSelection `json:"openvpn"`
|
|
||||||
Wireguard WireguardSelection `json:"wireguard"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (selection ServerSelection) toLines() (lines []string) {
|
|
||||||
if selection.TargetIP != nil {
|
|
||||||
lines = append(lines, lastIndent+"Target IP address: "+selection.TargetIP.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Countries) > 0 {
|
|
||||||
lines = append(lines, lastIndent+"Countries: "+commaJoin(selection.Countries))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Regions) > 0 {
|
|
||||||
lines = append(lines, lastIndent+"Regions: "+commaJoin(selection.Regions))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Cities) > 0 {
|
|
||||||
lines = append(lines, lastIndent+"Cities: "+commaJoin(selection.Cities))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.ISPs) > 0 {
|
|
||||||
lines = append(lines, lastIndent+"ISPs: "+commaJoin(selection.ISPs))
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.FreeOnly {
|
|
||||||
lines = append(lines, lastIndent+"Free servers only")
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.StreamOnly {
|
|
||||||
lines = append(lines, lastIndent+"Stream servers only")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Hostnames) > 0 {
|
|
||||||
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(selection.Hostnames))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Names) > 0 {
|
|
||||||
lines = append(lines, lastIndent+"Names: "+commaJoin(selection.Names))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selection.Numbers) > 0 {
|
|
||||||
numbersString := make([]string, len(selection.Numbers))
|
|
||||||
for i, numberUint16 := range selection.Numbers {
|
|
||||||
numbersString[i] = fmt.Sprint(numberUint16)
|
|
||||||
}
|
|
||||||
lines = append(lines, lastIndent+"Numbers: "+commaJoin(numbersString))
|
|
||||||
}
|
|
||||||
|
|
||||||
if selection.VPN == constants.OpenVPN {
|
|
||||||
lines = append(lines, selection.OpenVPN.lines()...)
|
|
||||||
} else { // wireguard
|
|
||||||
lines = append(lines, selection.Wireguard.lines()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
type OpenVPNSelection struct {
|
|
||||||
ConfFile string `json:"conf_file"` // Custom configuration file path
|
|
||||||
TCP bool `json:"tcp"` // UDP if TCP is false
|
|
||||||
CustomPort uint16 `json:"custom_port"` // HideMyAss, Mullvad, PIA, ProtonVPN, WeVPN, Windscribe
|
|
||||||
EncPreset string `json:"encryption_preset"` // PIA - needed to get the port number
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *OpenVPNSelection) lines() (lines []string) {
|
|
||||||
lines = append(lines, lastIndent+"OpenVPN selection:")
|
|
||||||
|
|
||||||
if settings.ConfFile != "" {
|
|
||||||
lines = append(lines, indent+lastIndent+"Custom configuration file: "+settings.ConfFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, indent+lastIndent+"Protocol: "+protoToString(settings.TCP))
|
|
||||||
|
|
||||||
if settings.CustomPort != 0 {
|
|
||||||
lines = append(lines, indent+lastIndent+"Custom port: "+fmt.Sprint(settings.CustomPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.EncPreset != "" {
|
|
||||||
lines = append(lines, indent+lastIndent+"PIA encryption preset: "+settings.EncPreset)
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *OpenVPNSelection) readProtocolOnly(r reader) (err error) {
|
|
||||||
settings.TCP, err = readOpenVPNProtocol(r)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *OpenVPNSelection) readProtocolAndPort(r reader) (err error) {
|
|
||||||
settings.TCP, err = readOpenVPNProtocol(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{allAllowed: true})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type WireguardSelection struct {
|
|
||||||
// EndpointPort is a the server port to use for the VPN server.
|
|
||||||
// It is optional for Wireguard VPN providers IVPN, Mullvad
|
|
||||||
// and Windscribe, and compulsory for the others
|
|
||||||
EndpointPort uint16 `json:"port,omitempty"`
|
|
||||||
// PublicKey is the server public key.
|
|
||||||
// It is only used with VPN providers generating Wireguard
|
|
||||||
// configurations specific to each server and user.
|
|
||||||
PublicKey string `json:"publickey,omitempty"`
|
|
||||||
// EndpointIP is the server endpoint IP address.
|
|
||||||
// It is only used with VPN providers generating Wireguard
|
|
||||||
// configurations specific to each server and user.
|
|
||||||
EndpointIP net.IP `json:"endpoint_ip,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *WireguardSelection) lines() (lines []string) {
|
|
||||||
lines = append(lines, lastIndent+"Wireguard selection:")
|
|
||||||
|
|
||||||
if settings.PublicKey != "" {
|
|
||||||
lines = append(lines, indent+lastIndent+"Public key: "+settings.PublicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.EndpointIP != nil {
|
|
||||||
endpoint := settings.EndpointIP.String() + ":" + fmt.Sprint(settings.EndpointPort)
|
|
||||||
lines = append(lines, indent+lastIndent+"Server endpoint: "+endpoint)
|
|
||||||
} else if settings.EndpointPort != 0 {
|
|
||||||
lines = append(lines, indent+lastIndent+"Custom port: "+fmt.Sprint(settings.EndpointPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
// PortForwarding contains settings for port forwarding.
|
|
||||||
type PortForwarding struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
Filepath string `json:"filepath"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PortForwarding) lines() (lines []string) {
|
|
||||||
return []string{
|
|
||||||
lastIndent + "File path: " + p.Filepath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ControlServer contains settings to customize the control server operation.
|
|
||||||
type ControlServer struct {
|
|
||||||
Port uint16
|
|
||||||
Log bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *ControlServer) String() string {
|
|
||||||
return strings.Join(settings.lines(), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *ControlServer) lines() (lines []string) {
|
|
||||||
lines = append(lines, lastIndent+"HTTP control server:")
|
|
||||||
|
|
||||||
lines = append(lines, indent+lastIndent+"Listening port: "+strconv.Itoa(int(settings.Port)))
|
|
||||||
|
|
||||||
if settings.Log {
|
|
||||||
lines = append(lines, indent+lastIndent+"Logging: enabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *ControlServer) read(r reader) (err error) {
|
|
||||||
settings.Log, err = r.env.OnOff("HTTP_CONTROL_SERVER_LOG", params.Default("on"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable HTTP_CONTROL_SERVER_LOG: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var warning string
|
|
||||||
settings.Port, warning, err = r.env.ListeningPort(
|
|
||||||
"HTTP_CONTROL_SERVER_PORT", params.Default("8000"))
|
|
||||||
if len(warning) > 0 {
|
|
||||||
r.warner.Warn(warning)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable HTTP_CONTROL_SERVER_PORT: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/golibs/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Settings contains all settings for the program to run.
|
|
||||||
type Settings struct {
|
|
||||||
VPN VPN
|
|
||||||
System System
|
|
||||||
DNS DNS
|
|
||||||
Firewall Firewall
|
|
||||||
HTTPProxy HTTPProxy
|
|
||||||
ShadowSocks ShadowSocks
|
|
||||||
Updater Updater
|
|
||||||
PublicIP PublicIP
|
|
||||||
VersionInformation bool
|
|
||||||
ControlServer ControlServer
|
|
||||||
Health Health
|
|
||||||
Log Log
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Settings) String() string {
|
|
||||||
return strings.Join(settings.lines(), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (settings *Settings) lines() (lines []string) {
|
|
||||||
lines = append(lines, "Settings summary below:")
|
|
||||||
lines = append(lines, settings.VPN.lines()...)
|
|
||||||
lines = append(lines, settings.DNS.lines()...)
|
|
||||||
lines = append(lines, settings.Firewall.lines()...)
|
|
||||||
lines = append(lines, settings.Log.lines()...)
|
|
||||||
lines = append(lines, settings.System.lines()...)
|
|
||||||
lines = append(lines, settings.HTTPProxy.lines()...)
|
|
||||||
lines = append(lines, settings.ShadowSocks.lines()...)
|
|
||||||
lines = append(lines, settings.Health.lines()...)
|
|
||||||
lines = append(lines, settings.ControlServer.lines()...)
|
|
||||||
lines = append(lines, settings.Updater.lines()...)
|
|
||||||
lines = append(lines, settings.PublicIP.lines()...)
|
|
||||||
if settings.VersionInformation {
|
|
||||||
lines = append(lines, lastIndent+"Github version information: enabled")
|
|
||||||
}
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrVPN = errors.New("cannot read VPN settings")
|
|
||||||
ErrSystem = errors.New("cannot read System settings")
|
|
||||||
ErrDNS = errors.New("cannot read DNS settings")
|
|
||||||
ErrFirewall = errors.New("cannot read firewall settings")
|
|
||||||
ErrHTTPProxy = errors.New("cannot read HTTP proxy settings")
|
|
||||||
ErrShadowsocks = errors.New("cannot read Shadowsocks settings")
|
|
||||||
ErrControlServer = errors.New("cannot read control server settings")
|
|
||||||
ErrUpdater = errors.New("cannot read Updater settings")
|
|
||||||
ErrPublicIP = errors.New("cannot read Public IP getter settings")
|
|
||||||
ErrHealth = errors.New("cannot read health settings")
|
|
||||||
ErrLog = errors.New("cannot read log settings")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Read obtains all configuration options for the program and returns an error as soon
|
|
||||||
// as an error is encountered reading them.
|
|
||||||
func (settings *Settings) Read(env params.Interface, servers models.AllServers,
|
|
||||||
warner Warner) (err error) {
|
|
||||||
r := newReader(env, servers, warner)
|
|
||||||
|
|
||||||
settings.VersionInformation, err = r.env.OnOff("VERSION_INFORMATION", params.Default("on"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("environment variable VERSION_INFORMATION: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.VPN.read(r); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrVPN, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.System.read(r); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrSystem, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.DNS.read(r); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrDNS, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.Firewall.read(r); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrFirewall, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.HTTPProxy.read(r); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrHTTPProxy, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.ShadowSocks.read(r); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrShadowsocks, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.ControlServer.read(r); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrControlServer, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.Updater.read(r); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrUpdater, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip := settings.DNS.PlaintextAddress; ip != nil {
|
|
||||||
settings.Updater.DNSAddress = ip.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.PublicIP.read(r); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrPublicIP, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.Health.read(r); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrHealth, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.Log.read(r.env); err != nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrLog, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
82
internal/configuration/settings/dns.go
Normal file
82
internal/configuration/settings/dns.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNS contains settings to configure DNS.
|
||||||
|
type DNS struct {
|
||||||
|
// ServerAddress is the DNS server to use inside
|
||||||
|
// the Go program and for the system.
|
||||||
|
// It defaults to '127.0.0.1' to be used with the
|
||||||
|
// DoT server. It cannot be the zero value in the internal
|
||||||
|
// state.
|
||||||
|
ServerAddress netip.Addr
|
||||||
|
// KeepNameserver is true if the Docker DNS server
|
||||||
|
// found in /etc/resolv.conf should be kept.
|
||||||
|
// Note settings this to true will go around the
|
||||||
|
// DoT server blocking.
|
||||||
|
// It defaults to false and cannot be nil in the
|
||||||
|
// internal state.
|
||||||
|
KeepNameserver *bool
|
||||||
|
// DOT contains settings to configure the DoT
|
||||||
|
// server.
|
||||||
|
DoT DoT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DNS) validate() (err error) {
|
||||||
|
err = d.DoT.validate()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("validating DoT settings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNS) Copy() (copied DNS) {
|
||||||
|
return DNS{
|
||||||
|
ServerAddress: d.ServerAddress,
|
||||||
|
KeepNameserver: helpers.CopyPointer(d.KeepNameserver),
|
||||||
|
DoT: d.DoT.copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (d *DNS) mergeWith(other DNS) {
|
||||||
|
d.ServerAddress = helpers.MergeWithIP(d.ServerAddress, other.ServerAddress)
|
||||||
|
d.KeepNameserver = helpers.MergeWithPointer(d.KeepNameserver, other.KeepNameserver)
|
||||||
|
d.DoT.mergeWith(other.DoT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (d *DNS) overrideWith(other DNS) {
|
||||||
|
d.ServerAddress = helpers.OverrideWithIP(d.ServerAddress, other.ServerAddress)
|
||||||
|
d.KeepNameserver = helpers.OverrideWithPointer(d.KeepNameserver, other.KeepNameserver)
|
||||||
|
d.DoT.overrideWith(other.DoT)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNS) setDefaults() {
|
||||||
|
localhost := netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
||||||
|
d.ServerAddress = helpers.DefaultIP(d.ServerAddress, localhost)
|
||||||
|
d.KeepNameserver = helpers.DefaultPointer(d.KeepNameserver, false)
|
||||||
|
d.DoT.setDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DNS) String() string {
|
||||||
|
return d.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DNS) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("DNS settings:")
|
||||||
|
node.Appendf("DNS server address to use: %s", d.ServerAddress)
|
||||||
|
node.Appendf("Keep existing nameserver(s): %s", helpers.BoolPtrToYesNo(d.KeepNameserver))
|
||||||
|
node.AppendNode(d.DoT.toLinesNode())
|
||||||
|
return node
|
||||||
|
}
|
||||||
138
internal/configuration/settings/dnsblacklist.go
Normal file
138
internal/configuration/settings/dnsblacklist.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/qdm12/dns/pkg/blacklist"
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNSBlacklist is settings for the DNS blacklist building.
|
||||||
|
type DNSBlacklist struct {
|
||||||
|
BlockMalicious *bool
|
||||||
|
BlockAds *bool
|
||||||
|
BlockSurveillance *bool
|
||||||
|
AllowedHosts []string
|
||||||
|
AddBlockedHosts []string
|
||||||
|
AddBlockedIPs []netip.Addr
|
||||||
|
AddBlockedIPPrefixes []netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DNSBlacklist) setDefaults() {
|
||||||
|
b.BlockMalicious = helpers.DefaultPointer(b.BlockMalicious, true)
|
||||||
|
b.BlockAds = helpers.DefaultPointer(b.BlockAds, false)
|
||||||
|
b.BlockSurveillance = helpers.DefaultPointer(b.BlockSurveillance, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9_])(\.([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9]))*$`) //nolint:lll
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrAllowedHostNotValid = errors.New("allowed host is not valid")
|
||||||
|
ErrBlockedHostNotValid = errors.New("blocked host is not valid")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b DNSBlacklist) validate() (err error) {
|
||||||
|
for _, host := range b.AllowedHosts {
|
||||||
|
if !hostRegex.MatchString(host) {
|
||||||
|
return fmt.Errorf("%w: %s", ErrAllowedHostNotValid, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, host := range b.AddBlockedHosts {
|
||||||
|
if !hostRegex.MatchString(host) {
|
||||||
|
return fmt.Errorf("%w: %s", ErrBlockedHostNotValid, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b DNSBlacklist) copy() (copied DNSBlacklist) {
|
||||||
|
return DNSBlacklist{
|
||||||
|
BlockMalicious: helpers.CopyPointer(b.BlockMalicious),
|
||||||
|
BlockAds: helpers.CopyPointer(b.BlockAds),
|
||||||
|
BlockSurveillance: helpers.CopyPointer(b.BlockSurveillance),
|
||||||
|
AllowedHosts: helpers.CopySlice(b.AllowedHosts),
|
||||||
|
AddBlockedHosts: helpers.CopySlice(b.AddBlockedHosts),
|
||||||
|
AddBlockedIPs: helpers.CopySlice(b.AddBlockedIPs),
|
||||||
|
AddBlockedIPPrefixes: helpers.CopySlice(b.AddBlockedIPPrefixes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
|
||||||
|
b.BlockMalicious = helpers.MergeWithPointer(b.BlockMalicious, other.BlockMalicious)
|
||||||
|
b.BlockAds = helpers.MergeWithPointer(b.BlockAds, other.BlockAds)
|
||||||
|
b.BlockSurveillance = helpers.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance)
|
||||||
|
b.AllowedHosts = helpers.MergeSlices(b.AllowedHosts, other.AllowedHosts)
|
||||||
|
b.AddBlockedHosts = helpers.MergeSlices(b.AddBlockedHosts, other.AddBlockedHosts)
|
||||||
|
b.AddBlockedIPs = helpers.MergeSlices(b.AddBlockedIPs, other.AddBlockedIPs)
|
||||||
|
b.AddBlockedIPPrefixes = helpers.MergeSlices(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
|
||||||
|
b.BlockMalicious = helpers.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
|
||||||
|
b.BlockAds = helpers.OverrideWithPointer(b.BlockAds, other.BlockAds)
|
||||||
|
b.BlockSurveillance = helpers.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance)
|
||||||
|
b.AllowedHosts = helpers.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts)
|
||||||
|
b.AddBlockedHosts = helpers.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
|
||||||
|
b.AddBlockedIPs = helpers.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
|
||||||
|
b.AddBlockedIPPrefixes = helpers.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) {
|
||||||
|
return blacklist.BuilderSettings{
|
||||||
|
BlockMalicious: *b.BlockMalicious,
|
||||||
|
BlockAds: *b.BlockAds,
|
||||||
|
BlockSurveillance: *b.BlockSurveillance,
|
||||||
|
AllowedHosts: b.AllowedHosts,
|
||||||
|
AddBlockedHosts: b.AddBlockedHosts,
|
||||||
|
AddBlockedIPs: netipAddressesToNetaddrIPs(b.AddBlockedIPs),
|
||||||
|
AddBlockedIPPrefixes: netipPrefixesToNetaddrIPPrefixes(b.AddBlockedIPPrefixes),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b DNSBlacklist) String() string {
|
||||||
|
return b.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("DNS filtering settings:")
|
||||||
|
|
||||||
|
node.Appendf("Block malicious: %s", helpers.BoolPtrToYesNo(b.BlockMalicious))
|
||||||
|
node.Appendf("Block ads: %s", helpers.BoolPtrToYesNo(b.BlockAds))
|
||||||
|
node.Appendf("Block surveillance: %s", helpers.BoolPtrToYesNo(b.BlockSurveillance))
|
||||||
|
|
||||||
|
if len(b.AllowedHosts) > 0 {
|
||||||
|
allowedHostsNode := node.Appendf("Allowed hosts:")
|
||||||
|
for _, host := range b.AllowedHosts {
|
||||||
|
allowedHostsNode.Appendf(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.AddBlockedHosts) > 0 {
|
||||||
|
blockedHostsNode := node.Appendf("Blocked hosts:")
|
||||||
|
for _, host := range b.AddBlockedHosts {
|
||||||
|
blockedHostsNode.Appendf(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.AddBlockedIPs) > 0 {
|
||||||
|
blockedIPsNode := node.Appendf("Blocked IP addresses:")
|
||||||
|
for _, ip := range b.AddBlockedIPs {
|
||||||
|
blockedIPsNode.Appendf(ip.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.AddBlockedIPPrefixes) > 0 {
|
||||||
|
blockedIPPrefixesNode := node.Appendf("Blocked IP networks:")
|
||||||
|
for _, ipNetwork := range b.AddBlockedIPPrefixes {
|
||||||
|
blockedIPPrefixesNode.Appendf(ipNetwork.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
113
internal/configuration/settings/dot.go
Normal file
113
internal/configuration/settings/dot.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DoT contains settings to configure the DoT server.
|
||||||
|
type DoT struct {
|
||||||
|
// Enabled is true if the DoT server should be running
|
||||||
|
// and used. It defaults to true, and cannot be nil
|
||||||
|
// in the internal state.
|
||||||
|
Enabled *bool
|
||||||
|
// UpdatePeriod is the period to update DNS block
|
||||||
|
// lists and cryptographic files for DNSSEC validation.
|
||||||
|
// It can be set to 0 to disable the update.
|
||||||
|
// It defaults to 24h and cannot be nil in
|
||||||
|
// the internal state.
|
||||||
|
UpdatePeriod *time.Duration
|
||||||
|
// Unbound contains settings to configure Unbound.
|
||||||
|
Unbound Unbound
|
||||||
|
// Blacklist contains settings to configure the filter
|
||||||
|
// block lists.
|
||||||
|
Blacklist DNSBlacklist
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDoTUpdatePeriodTooShort = errors.New("update period is too short")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d DoT) validate() (err error) {
|
||||||
|
const minUpdatePeriod = 30 * time.Second
|
||||||
|
if *d.UpdatePeriod != 0 && *d.UpdatePeriod < minUpdatePeriod {
|
||||||
|
return fmt.Errorf("%w: %s must be bigger than %s",
|
||||||
|
ErrDoTUpdatePeriodTooShort, *d.UpdatePeriod, minUpdatePeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Unbound.validate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Blacklist.validate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoT) copy() (copied DoT) {
|
||||||
|
return DoT{
|
||||||
|
Enabled: helpers.CopyPointer(d.Enabled),
|
||||||
|
UpdatePeriod: helpers.CopyPointer(d.UpdatePeriod),
|
||||||
|
Unbound: d.Unbound.copy(),
|
||||||
|
Blacklist: d.Blacklist.copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (d *DoT) mergeWith(other DoT) {
|
||||||
|
d.Enabled = helpers.MergeWithPointer(d.Enabled, other.Enabled)
|
||||||
|
d.UpdatePeriod = helpers.MergeWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
||||||
|
d.Unbound.mergeWith(other.Unbound)
|
||||||
|
d.Blacklist.mergeWith(other.Blacklist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (d *DoT) overrideWith(other DoT) {
|
||||||
|
d.Enabled = helpers.OverrideWithPointer(d.Enabled, other.Enabled)
|
||||||
|
d.UpdatePeriod = helpers.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
||||||
|
d.Unbound.overrideWith(other.Unbound)
|
||||||
|
d.Blacklist.overrideWith(other.Blacklist)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoT) setDefaults() {
|
||||||
|
d.Enabled = helpers.DefaultPointer(d.Enabled, true)
|
||||||
|
const defaultUpdatePeriod = 24 * time.Hour
|
||||||
|
d.UpdatePeriod = helpers.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
|
||||||
|
d.Unbound.setDefaults()
|
||||||
|
d.Blacklist.setDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DoT) String() string {
|
||||||
|
return d.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d DoT) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("DNS over TLS settings:")
|
||||||
|
|
||||||
|
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(d.Enabled))
|
||||||
|
if !*d.Enabled {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
update := "disabled"
|
||||||
|
if *d.UpdatePeriod > 0 {
|
||||||
|
update = "every " + d.UpdatePeriod.String()
|
||||||
|
}
|
||||||
|
node.Appendf("Update period: %s", update)
|
||||||
|
|
||||||
|
node.AppendNode(d.Unbound.toLinesNode())
|
||||||
|
node.AppendNode(d.Blacklist.toLinesNode())
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
49
internal/configuration/settings/errors.go
Normal file
49
internal/configuration/settings/errors.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrCityNotValid = errors.New("the city specified is not valid")
|
||||||
|
ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root")
|
||||||
|
ErrCountryNotValid = errors.New("the country specified is not valid")
|
||||||
|
ErrFilepathMissing = errors.New("filepath is missing")
|
||||||
|
ErrFirewallZeroPort = errors.New("cannot have a zero port to block")
|
||||||
|
ErrHostnameNotValid = errors.New("the hostname specified is not valid")
|
||||||
|
ErrISPNotValid = errors.New("the ISP specified is not valid")
|
||||||
|
ErrMinRatioNotValid = errors.New("minimum ratio is not valid")
|
||||||
|
ErrMissingValue = errors.New("missing value")
|
||||||
|
ErrNameNotValid = errors.New("the server name specified is not valid")
|
||||||
|
ErrOpenVPNClientKeyMissing = errors.New("client key is missing")
|
||||||
|
ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed")
|
||||||
|
ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid")
|
||||||
|
ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid")
|
||||||
|
ErrOpenVPNKeyPassphraseIsEmpty = errors.New("key passphrase is empty")
|
||||||
|
ErrOpenVPNMSSFixIsTooHigh = errors.New("mssfix option value is too high")
|
||||||
|
ErrOpenVPNPasswordIsEmpty = errors.New("password is empty")
|
||||||
|
ErrOpenVPNTCPNotSupported = errors.New("TCP protocol is not supported")
|
||||||
|
ErrOpenVPNUserIsEmpty = errors.New("user is empty")
|
||||||
|
ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds")
|
||||||
|
ErrOpenVPNVersionIsNotValid = errors.New("version is not valid")
|
||||||
|
ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled")
|
||||||
|
ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short")
|
||||||
|
ErrRegionNotValid = errors.New("the region specified is not valid")
|
||||||
|
ErrServerAddressNotValid = errors.New("server listening address is not valid")
|
||||||
|
ErrSystemPGIDNotValid = errors.New("process group id is not valid")
|
||||||
|
ErrSystemPUIDNotValid = errors.New("process user id is not valid")
|
||||||
|
ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
|
||||||
|
ErrUpdaterPeriodTooSmall = errors.New("VPN server data updater period is too small")
|
||||||
|
ErrVPNProviderNameNotValid = errors.New("VPN provider name is not valid")
|
||||||
|
ErrVPNTypeNotValid = errors.New("VPN type is not valid")
|
||||||
|
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
|
||||||
|
ErrWireguardEndpointPortNotAllowed = errors.New("endpoint port is not allowed")
|
||||||
|
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
|
||||||
|
ErrWireguardEndpointPortSet = errors.New("endpoint port is set")
|
||||||
|
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
|
||||||
|
ErrWireguardInterfaceAddressIPv6 = errors.New("interface address is IPv6 but IPv6 is not supported")
|
||||||
|
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
|
||||||
|
ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set")
|
||||||
|
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
|
||||||
|
ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
|
||||||
|
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
|
||||||
|
ErrWireguardImplementationNotValid = errors.New("implementation is not valid")
|
||||||
|
)
|
||||||
118
internal/configuration/settings/firewall.go
Normal file
118
internal/configuration/settings/firewall.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Firewall contains settings to customize the firewall operation.
|
||||||
|
type Firewall struct {
|
||||||
|
VPNInputPorts []uint16
|
||||||
|
InputPorts []uint16
|
||||||
|
OutboundSubnets []netip.Prefix
|
||||||
|
Enabled *bool
|
||||||
|
Debug *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Firewall) validate() (err error) {
|
||||||
|
if hasZeroPort(f.VPNInputPorts) {
|
||||||
|
return fmt.Errorf("VPN input ports: %w", ErrFirewallZeroPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasZeroPort(f.InputPorts) {
|
||||||
|
return fmt.Errorf("input ports: %w", ErrFirewallZeroPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasZeroPort(ports []uint16) (has bool) {
|
||||||
|
for _, port := range ports {
|
||||||
|
if port == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firewall) copy() (copied Firewall) {
|
||||||
|
return Firewall{
|
||||||
|
VPNInputPorts: helpers.CopySlice(f.VPNInputPorts),
|
||||||
|
InputPorts: helpers.CopySlice(f.InputPorts),
|
||||||
|
OutboundSubnets: helpers.CopySlice(f.OutboundSubnets),
|
||||||
|
Enabled: helpers.CopyPointer(f.Enabled),
|
||||||
|
Debug: helpers.CopyPointer(f.Debug),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
// It merges values of slices together, even if they
|
||||||
|
// are set in the receiver settings.
|
||||||
|
func (f *Firewall) mergeWith(other Firewall) {
|
||||||
|
f.VPNInputPorts = helpers.MergeSlices(f.VPNInputPorts, other.VPNInputPorts)
|
||||||
|
f.InputPorts = helpers.MergeSlices(f.InputPorts, other.InputPorts)
|
||||||
|
f.OutboundSubnets = helpers.MergeSlices(f.OutboundSubnets, other.OutboundSubnets)
|
||||||
|
f.Enabled = helpers.MergeWithPointer(f.Enabled, other.Enabled)
|
||||||
|
f.Debug = helpers.MergeWithPointer(f.Debug, other.Debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (f *Firewall) overrideWith(other Firewall) {
|
||||||
|
f.VPNInputPorts = helpers.OverrideWithSlice(f.VPNInputPorts, other.VPNInputPorts)
|
||||||
|
f.InputPorts = helpers.OverrideWithSlice(f.InputPorts, other.InputPorts)
|
||||||
|
f.OutboundSubnets = helpers.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets)
|
||||||
|
f.Enabled = helpers.OverrideWithPointer(f.Enabled, other.Enabled)
|
||||||
|
f.Debug = helpers.OverrideWithPointer(f.Debug, other.Debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firewall) setDefaults() {
|
||||||
|
f.Enabled = helpers.DefaultPointer(f.Enabled, true)
|
||||||
|
f.Debug = helpers.DefaultPointer(f.Debug, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Firewall) String() string {
|
||||||
|
return f.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Firewall) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Firewall settings:")
|
||||||
|
|
||||||
|
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(f.Enabled))
|
||||||
|
if !*f.Enabled {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
if *f.Debug {
|
||||||
|
node.Appendf("Debug mode: on")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.VPNInputPorts) > 0 {
|
||||||
|
vpnInputPortsNode := node.Appendf("VPN input ports:")
|
||||||
|
for _, port := range f.VPNInputPorts {
|
||||||
|
vpnInputPortsNode.Appendf("%d", port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.InputPorts) > 0 {
|
||||||
|
inputPortsNode := node.Appendf("Input ports:")
|
||||||
|
for _, port := range f.InputPorts {
|
||||||
|
inputPortsNode.Appendf("%d", port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.OutboundSubnets) > 0 {
|
||||||
|
outboundSubnets := node.Appendf("Outbound subnets:")
|
||||||
|
for _, subnet := range f.OutboundSubnets {
|
||||||
|
subnet := subnet
|
||||||
|
outboundSubnets.Appendf("%s", &subnet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
113
internal/configuration/settings/health.go
Normal file
113
internal/configuration/settings/health.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
"github.com/qdm12/govalid/address"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Health contains settings for the healthcheck and health server.
|
||||||
|
type Health struct {
|
||||||
|
// ServerAddress is the listening address
|
||||||
|
// for the health check server.
|
||||||
|
// It cannot be the empty string in the internal state.
|
||||||
|
ServerAddress string
|
||||||
|
// ReadHeaderTimeout is the HTTP server header read timeout
|
||||||
|
// duration of the HTTP server. It defaults to 100 milliseconds.
|
||||||
|
ReadHeaderTimeout time.Duration
|
||||||
|
// ReadTimeout is the HTTP read timeout duration of the
|
||||||
|
// HTTP server. It defaults to 500 milliseconds.
|
||||||
|
ReadTimeout time.Duration
|
||||||
|
// TargetAddress is the address (host or host:port)
|
||||||
|
// to TCP dial to periodically for the health check.
|
||||||
|
// It cannot be the empty string in the internal state.
|
||||||
|
TargetAddress string
|
||||||
|
// SuccessWait is the duration to wait to re-run the
|
||||||
|
// healthcheck after a successful healthcheck.
|
||||||
|
// It defaults to 5 seconds and cannot be zero in
|
||||||
|
// the internal state.
|
||||||
|
SuccessWait time.Duration
|
||||||
|
// VPN has health settings specific to the VPN loop.
|
||||||
|
VPN HealthyWait
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Health) Validate() (err error) {
|
||||||
|
uid := os.Getuid()
|
||||||
|
_, err = address.Validate(h.ServerAddress,
|
||||||
|
address.OptionListening(uid))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("server listening address is not valid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.VPN.validate()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("health VPN settings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Health) copy() (copied Health) {
|
||||||
|
return Health{
|
||||||
|
ServerAddress: h.ServerAddress,
|
||||||
|
ReadHeaderTimeout: h.ReadHeaderTimeout,
|
||||||
|
ReadTimeout: h.ReadTimeout,
|
||||||
|
TargetAddress: h.TargetAddress,
|
||||||
|
SuccessWait: h.SuccessWait,
|
||||||
|
VPN: h.VPN.copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (h *Health) MergeWith(other Health) {
|
||||||
|
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
|
||||||
|
h.ReadHeaderTimeout = helpers.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||||
|
h.ReadTimeout = helpers.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||||
|
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
|
||||||
|
h.SuccessWait = helpers.MergeWithNumber(h.SuccessWait, other.SuccessWait)
|
||||||
|
h.VPN.mergeWith(other.VPN)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OverrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (h *Health) OverrideWith(other Health) {
|
||||||
|
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress)
|
||||||
|
h.ReadHeaderTimeout = helpers.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||||
|
h.ReadTimeout = helpers.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||||
|
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
|
||||||
|
h.SuccessWait = helpers.OverrideWithNumber(h.SuccessWait, other.SuccessWait)
|
||||||
|
h.VPN.overrideWith(other.VPN)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Health) SetDefaults() {
|
||||||
|
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
|
||||||
|
const defaultReadHeaderTimeout = 100 * time.Millisecond
|
||||||
|
h.ReadHeaderTimeout = helpers.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||||
|
const defaultReadTimeout = 500 * time.Millisecond
|
||||||
|
h.ReadTimeout = helpers.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
|
||||||
|
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
|
||||||
|
const defaultSuccessWait = 5 * time.Second
|
||||||
|
h.SuccessWait = helpers.DefaultNumber(h.SuccessWait, defaultSuccessWait)
|
||||||
|
h.VPN.setDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Health) String() string {
|
||||||
|
return h.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Health) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Health settings:")
|
||||||
|
node.Appendf("Server listening address: %s", h.ServerAddress)
|
||||||
|
node.Appendf("Target address: %s", h.TargetAddress)
|
||||||
|
node.Appendf("Duration to wait after success: %s", h.SuccessWait)
|
||||||
|
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
|
||||||
|
node.Appendf("Read timeout: %s", h.ReadTimeout)
|
||||||
|
node.AppendNode(h.VPN.toLinesNode("VPN"))
|
||||||
|
return node
|
||||||
|
}
|
||||||
66
internal/configuration/settings/healthywait.go
Normal file
66
internal/configuration/settings/healthywait.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HealthyWait struct {
|
||||||
|
// Initial is the initial duration to wait for the program
|
||||||
|
// to be healthy before taking action.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Initial *time.Duration
|
||||||
|
// Addition is the duration to add to the Initial duration
|
||||||
|
// after Initial has expired to wait longer for the program
|
||||||
|
// to be healthy.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Addition *time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HealthyWait) validate() (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (h *HealthyWait) copy() (copied HealthyWait) {
|
||||||
|
return HealthyWait{
|
||||||
|
Initial: helpers.CopyPointer(h.Initial),
|
||||||
|
Addition: helpers.CopyPointer(h.Addition),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (h *HealthyWait) mergeWith(other HealthyWait) {
|
||||||
|
h.Initial = helpers.MergeWithPointer(h.Initial, other.Initial)
|
||||||
|
h.Addition = helpers.MergeWithPointer(h.Addition, other.Addition)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (h *HealthyWait) overrideWith(other HealthyWait) {
|
||||||
|
h.Initial = helpers.OverrideWithPointer(h.Initial, other.Initial)
|
||||||
|
h.Addition = helpers.OverrideWithPointer(h.Addition, other.Addition)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HealthyWait) setDefaults() {
|
||||||
|
const initialDurationDefault = 6 * time.Second
|
||||||
|
const additionDurationDefault = 5 * time.Second
|
||||||
|
h.Initial = helpers.DefaultPointer(h.Initial, initialDurationDefault)
|
||||||
|
h.Addition = helpers.DefaultPointer(h.Addition, additionDurationDefault)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HealthyWait) String() string {
|
||||||
|
return h.toLinesNode("Health").String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HealthyWait) toLinesNode(kind string) (node *gotree.Node) {
|
||||||
|
node = gotree.New(kind + " wait durations:")
|
||||||
|
node.Appendf("Initial duration: %s", *h.Initial)
|
||||||
|
node.Appendf("Additional duration: %s", *h.Addition)
|
||||||
|
return node
|
||||||
|
}
|
||||||
52
internal/configuration/settings/helpers/belong.go
Normal file
52
internal/configuration/settings/helpers/belong.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsOneOf(value string, choices ...string) (ok bool) {
|
||||||
|
for _, choice := range choices {
|
||||||
|
if value == choice {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoChoice = errors.New("one or more values is set but there is no possible value available")
|
||||||
|
ErrValueNotOneOf = errors.New("value is not one of the possible choices")
|
||||||
|
)
|
||||||
|
|
||||||
|
func AreAllOneOf(values, choices []string) (err error) {
|
||||||
|
if len(values) > 0 && len(choices) == 0 {
|
||||||
|
return fmt.Errorf("%w", ErrNoChoice)
|
||||||
|
}
|
||||||
|
|
||||||
|
set := make(map[string]struct{}, len(choices))
|
||||||
|
for _, choice := range choices {
|
||||||
|
choice = strings.ToLower(choice)
|
||||||
|
set[choice] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, value := range values {
|
||||||
|
_, ok := set[value]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: value %q, choices available are %s",
|
||||||
|
ErrValueNotOneOf, value, strings.Join(choices, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Uint16IsOneOf(port uint16, choices []uint16) (ok bool) {
|
||||||
|
for _, choice := range choices {
|
||||||
|
if port == choice {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
20
internal/configuration/settings/helpers/copy.go
Normal file
20
internal/configuration/settings/helpers/copy.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CopyPointer[T any](original *T) (copied *T) {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied = new(T)
|
||||||
|
*copied = *original
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopySlice[T string | uint16 | netip.Addr | netip.Prefix](original []T) (copied []T) {
|
||||||
|
return slices.Clone(original)
|
||||||
|
}
|
||||||
39
internal/configuration/settings/helpers/default.go
Normal file
39
internal/configuration/settings/helpers/default.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DefaultPointer[T any](existing *T, defaultValue T) (
|
||||||
|
result *T) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(T)
|
||||||
|
*result = defaultValue
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultString(existing string, defaultValue string) (
|
||||||
|
result string) {
|
||||||
|
if existing != "" {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultNumber[T Number](existing T, defaultValue T) ( //nolint:ireturn
|
||||||
|
result T) {
|
||||||
|
if existing != 0 {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultIP(existing netip.Addr, defaultValue netip.Addr) (
|
||||||
|
result netip.Addr) {
|
||||||
|
if existing.IsValid() {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
31
internal/configuration/settings/helpers/files.go
Normal file
31
internal/configuration/settings/helpers/files.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFileDoesNotExist = errors.New("file does not exist")
|
||||||
|
ErrFileRead = errors.New("cannot read file")
|
||||||
|
ErrFileClose = errors.New("cannot close file")
|
||||||
|
)
|
||||||
|
|
||||||
|
func FileExists(path string) (err error) {
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return fmt.Errorf("%w: %s", ErrFileDoesNotExist, path)
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrFileRead, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrFileClose, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
10
internal/configuration/settings/helpers/generics.go
Normal file
10
internal/configuration/settings/helpers/generics.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Number interface {
|
||||||
|
uint8 | uint16 | uint32 | uint64 | uint |
|
||||||
|
int8 | int16 | int32 | int64 | int |
|
||||||
|
float32 | float64 |
|
||||||
|
time.Duration
|
||||||
|
}
|
||||||
69
internal/configuration/settings/helpers/merge.go
Normal file
69
internal/configuration/settings/helpers/merge.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MergeWithPointer[T any](existing, other *T) (result *T) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
} else if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = new(T)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithString(existing, other string) (result string) {
|
||||||
|
if existing != "" {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithNumber[T Number](existing, other T) (result T) { //nolint:ireturn
|
||||||
|
if existing != 0 {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithIP(existing, other netip.Addr) (result netip.Addr) {
|
||||||
|
if existing.IsValid() {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
|
||||||
|
if existing != nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeSlices[T comparable](a, b []T) (result []T) {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[T]struct{}, len(a)+len(b))
|
||||||
|
result = make([]T, 0, len(a)+len(b))
|
||||||
|
for _, s := range a {
|
||||||
|
if _, ok := seen[s]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, s)
|
||||||
|
seen[s] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, s := range b {
|
||||||
|
if _, ok := seen[s]; ok {
|
||||||
|
continue // duplicate
|
||||||
|
}
|
||||||
|
result = append(result, s)
|
||||||
|
seen[s] = struct{}{}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
29
internal/configuration/settings/helpers/messages.go
Normal file
29
internal/configuration/settings/helpers/messages.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ChoicesOrString(choices []string) string {
|
||||||
|
return strings.Join(
|
||||||
|
choices[:len(choices)-1], ", ") +
|
||||||
|
" or " + choices[len(choices)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func PortChoicesOrString(ports []uint16) (s string) {
|
||||||
|
switch len(ports) {
|
||||||
|
case 0:
|
||||||
|
return "there is no allowed port"
|
||||||
|
case 1:
|
||||||
|
return "allowed port is " + fmt.Sprint(ports[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
s = "allowed ports are "
|
||||||
|
portStrings := make([]string, len(ports))
|
||||||
|
for i := range ports {
|
||||||
|
portStrings[i] = fmt.Sprint(ports[i])
|
||||||
|
}
|
||||||
|
s += ChoicesOrString(portStrings)
|
||||||
|
return s
|
||||||
|
}
|
||||||
25
internal/configuration/settings/helpers/obfuscate.go
Normal file
25
internal/configuration/settings/helpers/obfuscate.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
func ObfuscateWireguardKey(fullKey string) (obfuscatedKey string) {
|
||||||
|
const minKeyLength = 10
|
||||||
|
if len(fullKey) < minKeyLength {
|
||||||
|
return "(too short)"
|
||||||
|
}
|
||||||
|
|
||||||
|
lastIndex := len(fullKey) - 1
|
||||||
|
return fullKey[0:2] + "..." + fullKey[lastIndex-2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func ObfuscatePassword(password string) (obfuscatedPassword string) {
|
||||||
|
if password != "" {
|
||||||
|
return "[set]"
|
||||||
|
}
|
||||||
|
return "[not set]"
|
||||||
|
}
|
||||||
|
|
||||||
|
func ObfuscateData(data string) (obfuscated string) {
|
||||||
|
if data != "" {
|
||||||
|
return "[set]"
|
||||||
|
}
|
||||||
|
return "[not set]"
|
||||||
|
}
|
||||||
52
internal/configuration/settings/helpers/override.go
Normal file
52
internal/configuration/settings/helpers/override.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
func OverrideWithPointer[T any](existing, other *T) (result *T) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = new(T)
|
||||||
|
*result = *other
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithString(existing, other string) (result string) {
|
||||||
|
if other == "" {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithNumber[T Number](existing, other T) (result T) { //nolint:ireturn
|
||||||
|
if other == 0 {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithIP(existing, other netip.Addr) (result netip.Addr) {
|
||||||
|
if !other.IsValid() {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
|
||||||
|
if other != nil {
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
|
||||||
|
func OverrideWithSlice[T any](existing, other []T) (result []T) {
|
||||||
|
if other == nil {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
result = make([]T, len(other))
|
||||||
|
copy(result, other)
|
||||||
|
return result
|
||||||
|
}
|
||||||
15
internal/configuration/settings/helpers/string.go
Normal file
15
internal/configuration/settings/helpers/string.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
func BoolPtrToYesNo(b *bool) string {
|
||||||
|
if *b {
|
||||||
|
return "yes"
|
||||||
|
}
|
||||||
|
return "no"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TCPPtrToString(tcp *bool) string {
|
||||||
|
if *tcp {
|
||||||
|
return "TCP"
|
||||||
|
}
|
||||||
|
return "UDP"
|
||||||
|
}
|
||||||
4
internal/configuration/settings/helpers_test.go
Normal file
4
internal/configuration/settings/helpers_test.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
func boolPtr(b bool) *bool { return &b }
|
||||||
|
func uint8Ptr(n uint8) *uint8 { return &n }
|
||||||
130
internal/configuration/settings/httpproxy.go
Normal file
130
internal/configuration/settings/httpproxy.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
"github.com/qdm12/govalid/address"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPProxy contains settings to configure the HTTP proxy.
|
||||||
|
type HTTPProxy struct {
|
||||||
|
// User is the username to use for the HTTP proxy.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
User *string
|
||||||
|
// Password is the password to use for the HTTP proxy.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Password *string
|
||||||
|
// ListeningAddress is the listening address
|
||||||
|
// of the HTTP proxy server.
|
||||||
|
// It cannot be the empty string in the internal state.
|
||||||
|
ListeningAddress string
|
||||||
|
// Enabled is true if the HTTP proxy server should run,
|
||||||
|
// and false otherwise. It cannot be nil in the
|
||||||
|
// internal state.
|
||||||
|
Enabled *bool
|
||||||
|
// Stealth is true if the HTTP proxy server should hide
|
||||||
|
// each request has been proxied to the destination.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Stealth *bool
|
||||||
|
// Log is true if the HTTP proxy server should log
|
||||||
|
// each request/response. It cannot be nil in the
|
||||||
|
// internal state.
|
||||||
|
Log *bool
|
||||||
|
// ReadHeaderTimeout is the HTTP header read timeout duration
|
||||||
|
// of the HTTP server. It defaults to 1 second if left unset.
|
||||||
|
ReadHeaderTimeout time.Duration
|
||||||
|
// ReadTimeout is the HTTP read timeout duration
|
||||||
|
// of the HTTP server. It defaults to 3 seconds if left unset.
|
||||||
|
ReadTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HTTPProxy) validate() (err error) {
|
||||||
|
// Do not validate user and password
|
||||||
|
|
||||||
|
uid := os.Getuid()
|
||||||
|
_, err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HTTPProxy) copy() (copied HTTPProxy) {
|
||||||
|
return HTTPProxy{
|
||||||
|
User: helpers.CopyPointer(h.User),
|
||||||
|
Password: helpers.CopyPointer(h.Password),
|
||||||
|
ListeningAddress: h.ListeningAddress,
|
||||||
|
Enabled: helpers.CopyPointer(h.Enabled),
|
||||||
|
Stealth: helpers.CopyPointer(h.Stealth),
|
||||||
|
Log: helpers.CopyPointer(h.Log),
|
||||||
|
ReadHeaderTimeout: h.ReadHeaderTimeout,
|
||||||
|
ReadTimeout: h.ReadTimeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (h *HTTPProxy) mergeWith(other HTTPProxy) {
|
||||||
|
h.User = helpers.MergeWithPointer(h.User, other.User)
|
||||||
|
h.Password = helpers.MergeWithPointer(h.Password, other.Password)
|
||||||
|
h.ListeningAddress = helpers.MergeWithString(h.ListeningAddress, other.ListeningAddress)
|
||||||
|
h.Enabled = helpers.MergeWithPointer(h.Enabled, other.Enabled)
|
||||||
|
h.Stealth = helpers.MergeWithPointer(h.Stealth, other.Stealth)
|
||||||
|
h.Log = helpers.MergeWithPointer(h.Log, other.Log)
|
||||||
|
h.ReadHeaderTimeout = helpers.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||||
|
h.ReadTimeout = helpers.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (h *HTTPProxy) overrideWith(other HTTPProxy) {
|
||||||
|
h.User = helpers.OverrideWithPointer(h.User, other.User)
|
||||||
|
h.Password = helpers.OverrideWithPointer(h.Password, other.Password)
|
||||||
|
h.ListeningAddress = helpers.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
|
||||||
|
h.Enabled = helpers.OverrideWithPointer(h.Enabled, other.Enabled)
|
||||||
|
h.Stealth = helpers.OverrideWithPointer(h.Stealth, other.Stealth)
|
||||||
|
h.Log = helpers.OverrideWithPointer(h.Log, other.Log)
|
||||||
|
h.ReadHeaderTimeout = helpers.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||||
|
h.ReadTimeout = helpers.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HTTPProxy) setDefaults() {
|
||||||
|
h.User = helpers.DefaultPointer(h.User, "")
|
||||||
|
h.Password = helpers.DefaultPointer(h.Password, "")
|
||||||
|
h.ListeningAddress = helpers.DefaultString(h.ListeningAddress, ":8888")
|
||||||
|
h.Enabled = helpers.DefaultPointer(h.Enabled, false)
|
||||||
|
h.Stealth = helpers.DefaultPointer(h.Stealth, false)
|
||||||
|
h.Log = helpers.DefaultPointer(h.Log, false)
|
||||||
|
const defaultReadHeaderTimeout = time.Second
|
||||||
|
h.ReadHeaderTimeout = helpers.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||||
|
const defaultReadTimeout = 3 * time.Second
|
||||||
|
h.ReadTimeout = helpers.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HTTPProxy) String() string {
|
||||||
|
return h.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("HTTP proxy settings:")
|
||||||
|
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(h.Enabled))
|
||||||
|
if !*h.Enabled {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Appendf("Listening address: %s", h.ListeningAddress)
|
||||||
|
node.Appendf("User: %s", *h.User)
|
||||||
|
node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password))
|
||||||
|
node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth))
|
||||||
|
node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log))
|
||||||
|
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
|
||||||
|
node.Appendf("Read timeout: %s", h.ReadTimeout)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
51
internal/configuration/settings/log.go
Normal file
51
internal/configuration/settings/log.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
"github.com/qdm12/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log contains settings to configure the logger.
|
||||||
|
type Log struct {
|
||||||
|
// Level is the log level of the logger.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Level *log.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Log) validate() (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Log) copy() (copied Log) {
|
||||||
|
return Log{
|
||||||
|
Level: helpers.CopyPointer(l.Level),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (l *Log) mergeWith(other Log) {
|
||||||
|
l.Level = helpers.MergeWithPointer(l.Level, other.Level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (l *Log) overrideWith(other Log) {
|
||||||
|
l.Level = helpers.OverrideWithPointer(l.Level, other.Level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Log) setDefaults() {
|
||||||
|
l.Level = helpers.DefaultPointer(l.Level, log.LevelInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Log) String() string {
|
||||||
|
return l.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Log) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("Log settings:")
|
||||||
|
node.Appendf("Log level: %s", l.Level.String())
|
||||||
|
return node
|
||||||
|
}
|
||||||
36
internal/configuration/settings/netaddr.go
Normal file
36
internal/configuration/settings/netaddr.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"inet.af/netaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func netipAddressToNetaddrIP(address netip.Addr) (ip netaddr.IP) {
|
||||||
|
if address.Is4() {
|
||||||
|
return netaddr.IPFrom4(address.As4())
|
||||||
|
}
|
||||||
|
return netaddr.IPFrom16(address.As16())
|
||||||
|
}
|
||||||
|
|
||||||
|
func netipAddressesToNetaddrIPs(addresses []netip.Addr) (ips []netaddr.IP) {
|
||||||
|
ips = make([]netaddr.IP, len(addresses))
|
||||||
|
for i := range addresses {
|
||||||
|
ips[i] = netipAddressToNetaddrIP(addresses[i])
|
||||||
|
}
|
||||||
|
return ips
|
||||||
|
}
|
||||||
|
|
||||||
|
func netipPrefixToNetaddrIPPrefix(prefix netip.Prefix) (ipPrefix netaddr.IPPrefix) {
|
||||||
|
netaddrIP := netipAddressToNetaddrIP(prefix.Addr())
|
||||||
|
bits := prefix.Bits()
|
||||||
|
return netaddr.IPPrefixFrom(netaddrIP, uint8(bits))
|
||||||
|
}
|
||||||
|
|
||||||
|
func netipPrefixesToNetaddrIPPrefixes(prefixes []netip.Prefix) (ipPrefixes []netaddr.IPPrefix) {
|
||||||
|
ipPrefixes = make([]netaddr.IPPrefix, len(prefixes))
|
||||||
|
for i := range ipPrefixes {
|
||||||
|
ipPrefixes[i] = netipPrefixToNetaddrIPPrefix(prefixes[i])
|
||||||
|
}
|
||||||
|
return ipPrefixes
|
||||||
|
}
|
||||||
398
internal/configuration/settings/openvpn.go
Normal file
398
internal/configuration/settings/openvpn.go
Normal file
@@ -0,0 +1,398 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
|
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenVPN contains settings to configure the OpenVPN client.
|
||||||
|
type OpenVPN struct {
|
||||||
|
// Version is the OpenVPN version to run.
|
||||||
|
// It can only be "2.4" or "2.5".
|
||||||
|
Version string
|
||||||
|
// User is the OpenVPN authentication username.
|
||||||
|
// It cannot be nil in the internal state if OpenVPN is used.
|
||||||
|
// It is usually required but in some cases can be the empty string
|
||||||
|
// to indicate no user+password authentication is needed.
|
||||||
|
User *string
|
||||||
|
// Password is the OpenVPN authentication password.
|
||||||
|
// It cannot be nil in the internal state if OpenVPN is used.
|
||||||
|
// It is usually required but in some cases can be the empty string
|
||||||
|
// to indicate no user+password authentication is needed.
|
||||||
|
Password *string
|
||||||
|
// ConfFile is a custom OpenVPN configuration file path.
|
||||||
|
// It can be set to the empty string for it to be ignored.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
ConfFile *string
|
||||||
|
// Ciphers is a list of ciphers to use for OpenVPN,
|
||||||
|
// different from the ones specified by the VPN
|
||||||
|
// service provider configuration files.
|
||||||
|
Ciphers []string
|
||||||
|
// Auth is an auth algorithm to use in OpenVPN instead
|
||||||
|
// of the one specified by the VPN service provider.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
// It is ignored if it is set to the empty string.
|
||||||
|
Auth *string
|
||||||
|
// Cert is the base64 encoded DER of an OpenVPN certificate for the <cert> block.
|
||||||
|
// This is notably used by Cyberghost and VPN secure.
|
||||||
|
// It can be set to the empty string to be ignored.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Cert *string
|
||||||
|
// Key is the base64 encoded DER of an OpenVPN key.
|
||||||
|
// This is used by Cyberghost and VPN Unlimited.
|
||||||
|
// It can be set to the empty string to be ignored.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Key *string
|
||||||
|
// EncryptedKey is the base64 encoded DER of an encrypted key for OpenVPN.
|
||||||
|
// It is used by VPN secure.
|
||||||
|
// It defaults to the empty string meaning it is not
|
||||||
|
// to be used. KeyPassphrase must be set if this one is set.
|
||||||
|
EncryptedKey *string
|
||||||
|
// KeyPassphrase is the key passphrase to be used by OpenVPN
|
||||||
|
// to decrypt the EncryptedPrivateKey. It defaults to the
|
||||||
|
// empty string and must be set if EncryptedPrivateKey is set.
|
||||||
|
KeyPassphrase *string
|
||||||
|
// PIAEncPreset is the encryption preset for
|
||||||
|
// Private Internet Access. It can be set to an
|
||||||
|
// empty string for other providers.
|
||||||
|
PIAEncPreset *string
|
||||||
|
// MSSFix is the value (1 to 10000) to set for the
|
||||||
|
// mssfix option for OpenVPN. It is ignored if set to 0.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
MSSFix *uint16
|
||||||
|
// Interface is the OpenVPN device interface name.
|
||||||
|
// It cannot be an empty string in the internal state.
|
||||||
|
Interface string
|
||||||
|
// ProcessUser is the OpenVPN process OS username
|
||||||
|
// to use. It cannot be empty in the internal state.
|
||||||
|
// It defaults to 'root'.
|
||||||
|
ProcessUser string
|
||||||
|
// Verbosity is the OpenVPN verbosity level from 0 to 6.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Verbosity *int
|
||||||
|
// Flags is a slice of additional flags to be passed
|
||||||
|
// to the OpenVPN program.
|
||||||
|
Flags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var ivpnAccountID = regexp.MustCompile(`^(i|ivpn)\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}$`)
|
||||||
|
|
||||||
|
func (o OpenVPN) validate(vpnProvider string) (err error) {
|
||||||
|
// Validate version
|
||||||
|
validVersions := []string{openvpn.Openvpn24, openvpn.Openvpn25}
|
||||||
|
if !helpers.IsOneOf(o.Version, validVersions...) {
|
||||||
|
return fmt.Errorf("%w: %q can only be one of %s",
|
||||||
|
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
isCustom := vpnProvider == providers.Custom
|
||||||
|
isUserRequired := !isCustom &&
|
||||||
|
vpnProvider != providers.Airvpn &&
|
||||||
|
vpnProvider != providers.VPNSecure
|
||||||
|
|
||||||
|
if isUserRequired && *o.User == "" {
|
||||||
|
return fmt.Errorf("%w", ErrOpenVPNUserIsEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordRequired := isUserRequired &&
|
||||||
|
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(*o.User))
|
||||||
|
|
||||||
|
if passwordRequired && *o.Password == "" {
|
||||||
|
return fmt.Errorf("%w", ErrOpenVPNPasswordIsEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateOpenVPNConfigFilepath(isCustom, *o.ConfFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("custom configuration file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateOpenVPNClientCertificate(vpnProvider, *o.Cert)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("client certificate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateOpenVPNClientKey(vpnProvider, *o.Key)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("client key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateOpenVPNEncryptedKey(vpnProvider, *o.EncryptedKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encrypted key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.EncryptedKey != "" && *o.KeyPassphrase == "" {
|
||||||
|
return fmt.Errorf("%w", ErrOpenVPNKeyPassphraseIsEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxMSSFix = 10000
|
||||||
|
if *o.MSSFix > maxMSSFix {
|
||||||
|
return fmt.Errorf("%w: %d is over the maximum value of %d",
|
||||||
|
ErrOpenVPNMSSFixIsTooHigh, *o.MSSFix, maxMSSFix)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !regexpInterfaceName.MatchString(o.Interface) {
|
||||||
|
return fmt.Errorf("%w: '%s' does not match regex '%s'",
|
||||||
|
ErrOpenVPNInterfaceNotValid, o.Interface, regexpInterfaceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.Verbosity < 0 || *o.Verbosity > 6 {
|
||||||
|
return fmt.Errorf("%w: %d can only be between 0 and 5",
|
||||||
|
ErrOpenVPNVerbosityIsOutOfBounds, o.Verbosity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOpenVPNConfigFilepath(isCustom bool,
|
||||||
|
confFile string) (err error) {
|
||||||
|
if !isCustom {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if confFile == "" {
|
||||||
|
return fmt.Errorf("%w", ErrFilepathMissing)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = helpers.FileExists(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
extractor := extract.New()
|
||||||
|
_, _, err = extractor.Data(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("extracting information from custom configuration file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOpenVPNClientCertificate(vpnProvider,
|
||||||
|
clientCert string) (err error) {
|
||||||
|
switch vpnProvider {
|
||||||
|
case
|
||||||
|
providers.Airvpn,
|
||||||
|
providers.Cyberghost,
|
||||||
|
providers.VPNSecure,
|
||||||
|
providers.VPNUnlimited:
|
||||||
|
if clientCert == "" {
|
||||||
|
return fmt.Errorf("%w", ErrMissingValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientCert == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = base64.StdEncoding.DecodeString(clientCert)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
|
||||||
|
switch vpnProvider {
|
||||||
|
case
|
||||||
|
providers.Airvpn,
|
||||||
|
providers.Cyberghost,
|
||||||
|
providers.VPNUnlimited,
|
||||||
|
providers.Wevpn:
|
||||||
|
if clientKey == "" {
|
||||||
|
return fmt.Errorf("%w", ErrMissingValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientKey == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = base64.StdEncoding.DecodeString(clientKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateOpenVPNEncryptedKey(vpnProvider,
|
||||||
|
encryptedPrivateKey string) (err error) {
|
||||||
|
if vpnProvider == providers.VPNSecure && encryptedPrivateKey == "" {
|
||||||
|
return fmt.Errorf("%w", ErrMissingValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encryptedPrivateKey == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = base64.StdEncoding.DecodeString(encryptedPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OpenVPN) copy() (copied OpenVPN) {
|
||||||
|
return OpenVPN{
|
||||||
|
Version: o.Version,
|
||||||
|
User: helpers.CopyPointer(o.User),
|
||||||
|
Password: helpers.CopyPointer(o.Password),
|
||||||
|
ConfFile: helpers.CopyPointer(o.ConfFile),
|
||||||
|
Ciphers: helpers.CopySlice(o.Ciphers),
|
||||||
|
Auth: helpers.CopyPointer(o.Auth),
|
||||||
|
Cert: helpers.CopyPointer(o.Cert),
|
||||||
|
Key: helpers.CopyPointer(o.Key),
|
||||||
|
EncryptedKey: helpers.CopyPointer(o.EncryptedKey),
|
||||||
|
KeyPassphrase: helpers.CopyPointer(o.KeyPassphrase),
|
||||||
|
PIAEncPreset: helpers.CopyPointer(o.PIAEncPreset),
|
||||||
|
MSSFix: helpers.CopyPointer(o.MSSFix),
|
||||||
|
Interface: o.Interface,
|
||||||
|
ProcessUser: o.ProcessUser,
|
||||||
|
Verbosity: helpers.CopyPointer(o.Verbosity),
|
||||||
|
Flags: helpers.CopySlice(o.Flags),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeWith merges the other settings into any
|
||||||
|
// unset field of the receiver settings object.
|
||||||
|
func (o *OpenVPN) mergeWith(other OpenVPN) {
|
||||||
|
o.Version = helpers.MergeWithString(o.Version, other.Version)
|
||||||
|
o.User = helpers.MergeWithPointer(o.User, other.User)
|
||||||
|
o.Password = helpers.MergeWithPointer(o.Password, other.Password)
|
||||||
|
o.ConfFile = helpers.MergeWithPointer(o.ConfFile, other.ConfFile)
|
||||||
|
o.Ciphers = helpers.MergeSlices(o.Ciphers, other.Ciphers)
|
||||||
|
o.Auth = helpers.MergeWithPointer(o.Auth, other.Auth)
|
||||||
|
o.Cert = helpers.MergeWithPointer(o.Cert, other.Cert)
|
||||||
|
o.Key = helpers.MergeWithPointer(o.Key, other.Key)
|
||||||
|
o.EncryptedKey = helpers.MergeWithPointer(o.EncryptedKey, other.EncryptedKey)
|
||||||
|
o.KeyPassphrase = helpers.MergeWithPointer(o.KeyPassphrase, other.KeyPassphrase)
|
||||||
|
o.PIAEncPreset = helpers.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
|
o.MSSFix = helpers.MergeWithPointer(o.MSSFix, other.MSSFix)
|
||||||
|
o.Interface = helpers.MergeWithString(o.Interface, other.Interface)
|
||||||
|
o.ProcessUser = helpers.MergeWithString(o.ProcessUser, other.ProcessUser)
|
||||||
|
o.Verbosity = helpers.MergeWithPointer(o.Verbosity, other.Verbosity)
|
||||||
|
o.Flags = helpers.MergeSlices(o.Flags, other.Flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideWith overrides fields of the receiver
|
||||||
|
// settings object with any field set in the other
|
||||||
|
// settings.
|
||||||
|
func (o *OpenVPN) overrideWith(other OpenVPN) {
|
||||||
|
o.Version = helpers.OverrideWithString(o.Version, other.Version)
|
||||||
|
o.User = helpers.OverrideWithPointer(o.User, other.User)
|
||||||
|
o.Password = helpers.OverrideWithPointer(o.Password, other.Password)
|
||||||
|
o.ConfFile = helpers.OverrideWithPointer(o.ConfFile, other.ConfFile)
|
||||||
|
o.Ciphers = helpers.OverrideWithSlice(o.Ciphers, other.Ciphers)
|
||||||
|
o.Auth = helpers.OverrideWithPointer(o.Auth, other.Auth)
|
||||||
|
o.Cert = helpers.OverrideWithPointer(o.Cert, other.Cert)
|
||||||
|
o.Key = helpers.OverrideWithPointer(o.Key, other.Key)
|
||||||
|
o.EncryptedKey = helpers.OverrideWithPointer(o.EncryptedKey, other.EncryptedKey)
|
||||||
|
o.KeyPassphrase = helpers.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase)
|
||||||
|
o.PIAEncPreset = helpers.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
|
o.MSSFix = helpers.OverrideWithPointer(o.MSSFix, other.MSSFix)
|
||||||
|
o.Interface = helpers.OverrideWithString(o.Interface, other.Interface)
|
||||||
|
o.ProcessUser = helpers.OverrideWithString(o.ProcessUser, other.ProcessUser)
|
||||||
|
o.Verbosity = helpers.OverrideWithPointer(o.Verbosity, other.Verbosity)
|
||||||
|
o.Flags = helpers.OverrideWithSlice(o.Flags, other.Flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
||||||
|
o.Version = helpers.DefaultString(o.Version, openvpn.Openvpn25)
|
||||||
|
o.User = helpers.DefaultPointer(o.User, "")
|
||||||
|
if vpnProvider == providers.Mullvad {
|
||||||
|
o.Password = helpers.DefaultPointer(o.Password, "m")
|
||||||
|
} else {
|
||||||
|
o.Password = helpers.DefaultPointer(o.Password, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
o.ConfFile = helpers.DefaultPointer(o.ConfFile, "")
|
||||||
|
o.Auth = helpers.DefaultPointer(o.Auth, "")
|
||||||
|
o.Cert = helpers.DefaultPointer(o.Cert, "")
|
||||||
|
o.Key = helpers.DefaultPointer(o.Key, "")
|
||||||
|
o.EncryptedKey = helpers.DefaultPointer(o.EncryptedKey, "")
|
||||||
|
o.KeyPassphrase = helpers.DefaultPointer(o.KeyPassphrase, "")
|
||||||
|
|
||||||
|
var defaultEncPreset string
|
||||||
|
if vpnProvider == providers.PrivateInternetAccess {
|
||||||
|
defaultEncPreset = presets.Strong
|
||||||
|
}
|
||||||
|
o.PIAEncPreset = helpers.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
|
||||||
|
o.MSSFix = helpers.DefaultPointer(o.MSSFix, 0)
|
||||||
|
o.Interface = helpers.DefaultString(o.Interface, "tun0")
|
||||||
|
o.ProcessUser = helpers.DefaultString(o.ProcessUser, "root")
|
||||||
|
o.Verbosity = helpers.DefaultPointer(o.Verbosity, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OpenVPN) String() string {
|
||||||
|
return o.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("OpenVPN settings:")
|
||||||
|
node.Appendf("OpenVPN version: %s", o.Version)
|
||||||
|
node.Appendf("User: %s", helpers.ObfuscatePassword(*o.User))
|
||||||
|
node.Appendf("Password: %s", helpers.ObfuscatePassword(*o.Password))
|
||||||
|
|
||||||
|
if *o.ConfFile != "" {
|
||||||
|
node.Appendf("Custom configuration file: %s", *o.ConfFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(o.Ciphers) > 0 {
|
||||||
|
node.Appendf("Ciphers: %s", o.Ciphers)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.Auth != "" {
|
||||||
|
node.Appendf("Auth: %s", *o.Auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.Cert != "" {
|
||||||
|
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.Cert))
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.Key != "" {
|
||||||
|
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.Key))
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.EncryptedKey != "" {
|
||||||
|
node.Appendf("Encrypted key: %s (key passhrapse %s)",
|
||||||
|
helpers.ObfuscateData(*o.EncryptedKey), helpers.ObfuscatePassword(*o.KeyPassphrase))
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.PIAEncPreset != "" {
|
||||||
|
node.Appendf("Private Internet Access encryption preset: %s", *o.PIAEncPreset)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.MSSFix > 0 {
|
||||||
|
node.Appendf("MSS Fix: %d", *o.MSSFix)
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Interface != "" {
|
||||||
|
node.Appendf("Network interface: %s", o.Interface)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Appendf("Run OpenVPN as: %s", o.ProcessUser)
|
||||||
|
|
||||||
|
node.Appendf("Verbosity level: %d", *o.Verbosity)
|
||||||
|
|
||||||
|
if len(o.Flags) > 0 {
|
||||||
|
node.Appendf("Flags: %s", o.Flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaults is a shorthand using setDefaults.
|
||||||
|
// It's used in unit tests in other packages.
|
||||||
|
func (o OpenVPN) WithDefaults(provider string) OpenVPN {
|
||||||
|
o.setDefaults(provider)
|
||||||
|
return o
|
||||||
|
}
|
||||||
44
internal/configuration/settings/openvpn_test.go
Normal file
44
internal/configuration/settings/openvpn_test.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ivpnAccountID(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
s string
|
||||||
|
match bool
|
||||||
|
}{
|
||||||
|
{},
|
||||||
|
{s: "abc"},
|
||||||
|
{s: "i"},
|
||||||
|
{s: "ivpn"},
|
||||||
|
{s: "ivpn-aaaa"},
|
||||||
|
{s: "ivpn-aaaa-aaaa"},
|
||||||
|
{s: "ivpn-aaaa-aaaa-aaa"},
|
||||||
|
{s: "ivpn-aaaa-aaaa-aaaa", match: true},
|
||||||
|
{s: "ivpn-aaaa-aaaa-aaaaa"},
|
||||||
|
{s: "ivpn-a6B7-fP91-Zh6Y", match: true},
|
||||||
|
{s: "i-aaaa"},
|
||||||
|
{s: "i-aaaa-aaaa"},
|
||||||
|
{s: "i-aaaa-aaaa-aaa"},
|
||||||
|
{s: "i-aaaa-aaaa-aaaa", match: true},
|
||||||
|
{s: "i-aaaa-aaaa-aaaaa"},
|
||||||
|
{s: "i-a6B7-fP91-Zh6Y", match: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(testCase.s, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
match := ivpnAccountID.MatchString(testCase.s)
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.match, match)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
187
internal/configuration/settings/openvpnselection.go
Normal file
187
internal/configuration/settings/openvpnselection.go
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OpenVPNSelection struct {
|
||||||
|
// ConfFile is the custom configuration file path.
|
||||||
|
// It can be set to an empty string to indicate to
|
||||||
|
// NOT use a custom configuration file.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
ConfFile *string
|
||||||
|
// TCP is true if the OpenVPN protocol is TCP,
|
||||||
|
// and false for UDP.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
TCP *bool
|
||||||
|
// CustomPort is the OpenVPN server endpoint port.
|
||||||
|
// It can be set to 0 to indicate no custom port should
|
||||||
|
// be used. It cannot be nil in the internal state.
|
||||||
|
CustomPort *uint16 // HideMyAss, Mullvad, PIA, ProtonVPN, WeVPN, Windscribe
|
||||||
|
// PIAEncPreset is the encryption preset for
|
||||||
|
// Private Internet Access. It can be set to an
|
||||||
|
// empty string for other providers.
|
||||||
|
PIAEncPreset *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||||
|
// Validate ConfFile
|
||||||
|
if confFile := *o.ConfFile; confFile != "" {
|
||||||
|
err := helpers.FileExists(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("configuration file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate TCP
|
||||||
|
if *o.TCP && helpers.IsOneOf(vpnProvider,
|
||||||
|
providers.Ipvanish,
|
||||||
|
providers.Perfectprivacy,
|
||||||
|
providers.Privado,
|
||||||
|
providers.VPNUnlimited,
|
||||||
|
providers.Vyprvpn,
|
||||||
|
) {
|
||||||
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
|
ErrOpenVPNTCPNotSupported, vpnProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate CustomPort
|
||||||
|
if *o.CustomPort != 0 {
|
||||||
|
switch vpnProvider {
|
||||||
|
// no restriction on port
|
||||||
|
case providers.Cyberghost, providers.HideMyAss,
|
||||||
|
providers.Privatevpn, providers.Torguard:
|
||||||
|
// no custom port allowed
|
||||||
|
case providers.Expressvpn, providers.Fastestvpn,
|
||||||
|
providers.Ipvanish, providers.Nordvpn,
|
||||||
|
providers.Privado, providers.Purevpn,
|
||||||
|
providers.Surfshark, providers.VPNSecure,
|
||||||
|
providers.VPNUnlimited, providers.Vyprvpn:
|
||||||
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
|
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
|
||||||
|
default:
|
||||||
|
var allowedTCP, allowedUDP []uint16
|
||||||
|
switch vpnProvider {
|
||||||
|
case providers.Airvpn:
|
||||||
|
allowedTCP = []uint16{
|
||||||
|
53, 80, 443, // IP in 1, 3
|
||||||
|
1194, 2018, 41185, // IP in 1, 2, 3, 4
|
||||||
|
}
|
||||||
|
allowedUDP = []uint16{53, 80, 443, 1194, 2018, 41185}
|
||||||
|
case providers.Ivpn:
|
||||||
|
allowedTCP = []uint16{80, 443, 1143}
|
||||||
|
allowedUDP = []uint16{53, 1194, 2049, 2050}
|
||||||
|
case providers.Mullvad:
|
||||||
|
allowedTCP = []uint16{80, 443, 1401}
|
||||||
|
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
|
||||||
|
case providers.Perfectprivacy:
|
||||||
|
allowedTCP = []uint16{44, 443, 4433}
|
||||||
|
allowedUDP = []uint16{44, 443, 4433}
|
||||||
|
case providers.PrivateInternetAccess:
|
||||||
|
allowedTCP = []uint16{80, 110, 443}
|
||||||
|
allowedUDP = []uint16{53, 1194, 1197, 1198, 8080, 9201}
|
||||||
|
case providers.Protonvpn:
|
||||||
|
allowedTCP = []uint16{443, 5995, 8443}
|
||||||
|
allowedUDP = []uint16{80, 443, 1194, 4569, 5060}
|
||||||
|
case providers.SlickVPN:
|
||||||
|
allowedTCP = []uint16{443, 8080, 8888}
|
||||||
|
allowedUDP = []uint16{443, 8080, 8888}
|
||||||
|
case providers.Wevpn:
|
||||||
|
allowedTCP = []uint16{53, 1195, 1199, 2018}
|
||||||
|
allowedUDP = []uint16{80, 1194, 1198}
|
||||||
|
case providers.Windscribe:
|
||||||
|
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
|
||||||
|
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedTCP) {
|
||||||
|
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
|
||||||
|
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider,
|
||||||
|
helpers.PortChoicesOrString(allowedTCP))
|
||||||
|
} else if !*o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedUDP) {
|
||||||
|
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
|
||||||
|
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider,
|
||||||
|
helpers.PortChoicesOrString(allowedUDP))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate EncPreset
|
||||||
|
if vpnProvider == providers.PrivateInternetAccess {
|
||||||
|
validEncryptionPresets := []string{
|
||||||
|
presets.None,
|
||||||
|
presets.Normal,
|
||||||
|
presets.Strong,
|
||||||
|
}
|
||||||
|
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) {
|
||||||
|
return fmt.Errorf("%w: %s; valid presets are %s",
|
||||||
|
ErrOpenVPNEncryptionPresetNotValid, *o.PIAEncPreset,
|
||||||
|
helpers.ChoicesOrString(validEncryptionPresets))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) {
|
||||||
|
return OpenVPNSelection{
|
||||||
|
ConfFile: helpers.CopyPointer(o.ConfFile),
|
||||||
|
TCP: helpers.CopyPointer(o.TCP),
|
||||||
|
CustomPort: helpers.CopyPointer(o.CustomPort),
|
||||||
|
PIAEncPreset: helpers.CopyPointer(o.PIAEncPreset),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) {
|
||||||
|
o.ConfFile = helpers.MergeWithPointer(o.ConfFile, other.ConfFile)
|
||||||
|
o.TCP = helpers.MergeWithPointer(o.TCP, other.TCP)
|
||||||
|
o.CustomPort = helpers.MergeWithPointer(o.CustomPort, other.CustomPort)
|
||||||
|
o.PIAEncPreset = helpers.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) {
|
||||||
|
o.ConfFile = helpers.OverrideWithPointer(o.ConfFile, other.ConfFile)
|
||||||
|
o.TCP = helpers.OverrideWithPointer(o.TCP, other.TCP)
|
||||||
|
o.CustomPort = helpers.OverrideWithPointer(o.CustomPort, other.CustomPort)
|
||||||
|
o.PIAEncPreset = helpers.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
|
||||||
|
o.ConfFile = helpers.DefaultPointer(o.ConfFile, "")
|
||||||
|
o.TCP = helpers.DefaultPointer(o.TCP, false)
|
||||||
|
o.CustomPort = helpers.DefaultPointer(o.CustomPort, 0)
|
||||||
|
|
||||||
|
var defaultEncPreset string
|
||||||
|
if vpnProvider == providers.PrivateInternetAccess {
|
||||||
|
defaultEncPreset = presets.Strong
|
||||||
|
}
|
||||||
|
o.PIAEncPreset = helpers.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OpenVPNSelection) String() string {
|
||||||
|
return o.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("OpenVPN server selection settings:")
|
||||||
|
node.Appendf("Protocol: %s", helpers.TCPPtrToString(o.TCP))
|
||||||
|
|
||||||
|
if *o.CustomPort != 0 {
|
||||||
|
node.Appendf("Custom port: %d", *o.CustomPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.PIAEncPreset != "" {
|
||||||
|
node.Appendf("Private Internet Access encryption preset: %s", *o.PIAEncPreset)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *o.ConfFile != "" {
|
||||||
|
node.Appendf("Custom configuration file: %s", *o.ConfFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
89
internal/configuration/settings/portforward.go
Normal file
89
internal/configuration/settings/portforward.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PortForwarding contains settings for port forwarding.
|
||||||
|
type PortForwarding struct {
|
||||||
|
// Enabled is true if port forwarding should be activated.
|
||||||
|
// It cannot be nil for the internal state.
|
||||||
|
Enabled *bool
|
||||||
|
// Filepath is the port forwarding status file path
|
||||||
|
// to use. It can be the empty string to indicate not
|
||||||
|
// to write to a file. It cannot be nil for the
|
||||||
|
// internal state
|
||||||
|
Filepath *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PortForwarding) validate(vpnProvider string) (err error) {
|
||||||
|
if !*p.Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Enabled
|
||||||
|
validProviders := []string{providers.PrivateInternetAccess}
|
||||||
|
if !helpers.IsOneOf(vpnProvider, validProviders...) {
|
||||||
|
return fmt.Errorf("%w: for provider %s, it is only available for %s",
|
||||||
|
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Filepath
|
||||||
|
if *p.Filepath != "" { // optional
|
||||||
|
_, err := filepath.Abs(*p.Filepath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("filepath is not valid: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PortForwarding) copy() (copied PortForwarding) {
|
||||||
|
return PortForwarding{
|
||||||
|
Enabled: helpers.CopyPointer(p.Enabled),
|
||||||
|
Filepath: helpers.CopyPointer(p.Filepath),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PortForwarding) mergeWith(other PortForwarding) {
|
||||||
|
p.Enabled = helpers.MergeWithPointer(p.Enabled, other.Enabled)
|
||||||
|
p.Filepath = helpers.MergeWithPointer(p.Filepath, other.Filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PortForwarding) overrideWith(other PortForwarding) {
|
||||||
|
p.Enabled = helpers.OverrideWithPointer(p.Enabled, other.Enabled)
|
||||||
|
p.Filepath = helpers.OverrideWithPointer(p.Filepath, other.Filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PortForwarding) setDefaults() {
|
||||||
|
p.Enabled = helpers.DefaultPointer(p.Enabled, false)
|
||||||
|
p.Filepath = helpers.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PortForwarding) String() string {
|
||||||
|
return p.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PortForwarding) toLinesNode() (node *gotree.Node) {
|
||||||
|
if !*p.Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
node = gotree.New("Automatic port forwarding settings:")
|
||||||
|
node.Appendf("Enabled: yes")
|
||||||
|
|
||||||
|
filepath := *p.Filepath
|
||||||
|
if filepath == "" {
|
||||||
|
filepath = "[not set]"
|
||||||
|
}
|
||||||
|
node.Appendf("Forwarded port file path: %s", filepath)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
19
internal/configuration/settings/portforward_test.go
Normal file
19
internal/configuration/settings/portforward_test.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_PortForwarding_String(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
settings := PortForwarding{
|
||||||
|
Enabled: boolPtr(false),
|
||||||
|
}
|
||||||
|
|
||||||
|
s := settings.String()
|
||||||
|
|
||||||
|
assert.Empty(t, s)
|
||||||
|
}
|
||||||
95
internal/configuration/settings/provider.go
Normal file
95
internal/configuration/settings/provider.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||||
|
"github.com/qdm12/gotree"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider contains settings specific to a VPN provider.
|
||||||
|
type Provider struct {
|
||||||
|
// Name is the VPN service provider name.
|
||||||
|
// It cannot be nil in the internal state.
|
||||||
|
Name *string
|
||||||
|
// ServerSelection is the settings to
|
||||||
|
// select the VPN server.
|
||||||
|
ServerSelection ServerSelection
|
||||||
|
// PortForwarding is the settings about port forwarding.
|
||||||
|
PortForwarding PortForwarding
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
|
func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
||||||
|
// Validate Name
|
||||||
|
var validNames []string
|
||||||
|
if vpnType == vpn.OpenVPN {
|
||||||
|
validNames = providers.AllWithCustom()
|
||||||
|
validNames = append(validNames, "pia") // Retro-compatibility
|
||||||
|
} else { // Wireguard
|
||||||
|
validNames = []string{
|
||||||
|
providers.Airvpn,
|
||||||
|
providers.Custom,
|
||||||
|
providers.Ivpn,
|
||||||
|
providers.Mullvad,
|
||||||
|
providers.Surfshark,
|
||||||
|
providers.Windscribe,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !helpers.IsOneOf(*p.Name, validNames...) {
|
||||||
|
return fmt.Errorf("%w for Wireguard: %q can only be one of %s",
|
||||||
|
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.ServerSelection.validate(*p.Name, storage)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("server selection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.PortForwarding.validate(*p.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("port forwarding: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) copy() (copied Provider) {
|
||||||
|
return Provider{
|
||||||
|
Name: helpers.CopyPointer(p.Name),
|
||||||
|
ServerSelection: p.ServerSelection.copy(),
|
||||||
|
PortForwarding: p.PortForwarding.copy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) mergeWith(other Provider) {
|
||||||
|
p.Name = helpers.MergeWithPointer(p.Name, other.Name)
|
||||||
|
p.ServerSelection.mergeWith(other.ServerSelection)
|
||||||
|
p.PortForwarding.mergeWith(other.PortForwarding)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) overrideWith(other Provider) {
|
||||||
|
p.Name = helpers.OverrideWithPointer(p.Name, other.Name)
|
||||||
|
p.ServerSelection.overrideWith(other.ServerSelection)
|
||||||
|
p.PortForwarding.overrideWith(other.PortForwarding)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) setDefaults() {
|
||||||
|
p.Name = helpers.DefaultPointer(p.Name, providers.PrivateInternetAccess)
|
||||||
|
p.ServerSelection.setDefaults(*p.Name)
|
||||||
|
p.PortForwarding.setDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Provider) String() string {
|
||||||
|
return p.toLinesNode().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Provider) toLinesNode() (node *gotree.Node) {
|
||||||
|
node = gotree.New("VPN provider settings:")
|
||||||
|
node.Appendf("Name: %s", *p.Name)
|
||||||
|
node.AppendNode(p.ServerSelection.toLinesNode())
|
||||||
|
node.AppendNode(p.PortForwarding.toLinesNode())
|
||||||
|
return node
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user