Compare commits
23 Commits
dependabot
...
ivp6-level
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be935e70e6 | ||
|
|
5ca13021e7 | ||
|
|
dae44051f6 | ||
|
|
ffb0bec4da | ||
|
|
4d2b8787e0 | ||
|
|
d4831ad4a6 | ||
|
|
9e1b53a732 | ||
|
|
d0113849d6 | ||
|
|
7b25fdfee8 | ||
|
|
5ed6e82922 | ||
|
|
7dbd14df27 | ||
|
|
96d8b53338 | ||
|
|
2bd19640d9 | ||
|
|
1047508bd7 | ||
|
|
eb49306b80 | ||
|
|
43da9ddbb3 | ||
|
|
7fbc5c3c07 | ||
|
|
e03f545e07 | ||
|
|
942f1f2c0f | ||
|
|
baf566d7a5 | ||
|
|
6712adfe6b | ||
|
|
2e2e5f9df5 | ||
|
|
35e9b2365d |
37
.github/ISSUE_TEMPLATE/provider.md
vendored
37
.github/ISSUE_TEMPLATE/provider.md
vendored
@@ -6,12 +6,35 @@ labels: ":bulb: New provider"
|
||||
|
||||
---
|
||||
|
||||
One of the following is required:
|
||||
Important notes:
|
||||
|
||||
- Publicly accessible URL to a zip file containing the Openvpn configuration files
|
||||
- Publicly accessible URL to a structured (JSON etc.) list of servers **and attach** an example Openvpn configuration file for both TCP and UDP
|
||||
- There is no need to support both OpenVPN and Wireguard for a provider, but it's better to support both if possible
|
||||
- We do **not** implement authentication to access servers information behind a login. This is way too time consuming unfortunately
|
||||
- If it's not possible to support a provider natively, you can still use the [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||
|
||||
## For Wireguard
|
||||
|
||||
Wireguard can be natively supported ONLY if:
|
||||
|
||||
- the `PrivateKey` field value is the same across all servers for one user account
|
||||
- the `Address` field value is:
|
||||
- can be found in a structured (JSON etc.) list of servers publicly available; OR
|
||||
- the same across all servers for one user account
|
||||
- the `PublicKey` field value is:
|
||||
- can be found in a structured (JSON etc.) list of servers publicly available; OR
|
||||
- the same across all servers for one user account
|
||||
- the `Endpoint` field value:
|
||||
- can be found in a structured (JSON etc.) list of servers publicly available
|
||||
- can be determined using a pattern, for example using country codes in hostnames
|
||||
|
||||
If any of these conditions are not met, Wireguard cannot be natively supported or there is no advantage compared to using a custom Wireguard configuration file.
|
||||
|
||||
If **all** of these conditions are met, please provide an answer for each of them.
|
||||
|
||||
## For OpenVPN
|
||||
|
||||
OpenVPN can be natively supported ONLY if one of the following can be provided, by preference in this order:
|
||||
|
||||
- Publicly accessible URL to a structured (JSON etc.) list of servers **and attach** an example Openvpn configuration file for both TCP and UDP; OR
|
||||
- Publicly accessible URL to a zip file containing the Openvpn configuration files; OR
|
||||
- Publicly accessible URL to the list of servers **and attach** an example Openvpn configuration file for both TCP and UDP
|
||||
|
||||
If the list of servers requires to login **or** is hidden behind an interactive configurator,
|
||||
you can only use a custom Openvpn configuration file.
|
||||
[The Wiki's OpenVPN configuration file page](https://github.com/qdm12/gluetun-wiki/blob/main/setup/openvpn-configuration-file.md) describes how to do so.
|
||||
|
||||
3
.github/workflows/configs/mlc-config.json
vendored
3
.github/workflows/configs/mlc-config.json
vendored
@@ -8,6 +8,7 @@
|
||||
"retryOn429": false,
|
||||
"fallbackRetryDelay": "30s",
|
||||
"aliveStatusCodes": [
|
||||
200
|
||||
200,
|
||||
429
|
||||
]
|
||||
}
|
||||
20
Dockerfile
20
Dockerfile
@@ -159,23 +159,27 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
FIREWALL_INPUT_PORTS= \
|
||||
FIREWALL_OUTBOUND_SUBNETS= \
|
||||
FIREWALL_DEBUG=off \
|
||||
# IPv6
|
||||
IPV6_CHECK_ADDRESS=[2606:4700::6810:84e5]:443 \
|
||||
# Logging
|
||||
LOG_LEVEL=info \
|
||||
# Health
|
||||
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
||||
HEALTH_TARGET_ADDRESS=cloudflare.com:443 \
|
||||
HEALTH_ICMP_TARGET_IP=0.0.0.0 \
|
||||
HEALTH_ICMP_TARGET_IP=1.1.1.1 \
|
||||
HEALTH_RESTART_VPN=on \
|
||||
# DNS over TLS
|
||||
DOT=on \
|
||||
DOT_PROVIDERS=cloudflare \
|
||||
DOT_PRIVATE_ADDRESS=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:7f00:1/104,::ffff:a00:0/104,::ffff:a9fe:0/112,::ffff:ac10:0/108,::ffff:c0a8:0/112 \
|
||||
DOT_CACHING=on \
|
||||
DOT_IPV6=off \
|
||||
# DNS
|
||||
DNS_SERVER=on \
|
||||
DNS_UPSTREAM_RESOLVER_TYPE=DoT \
|
||||
DNS_UPSTREAM_RESOLVERS=cloudflare \
|
||||
DNS_BLOCK_IPS= \
|
||||
DNS_BLOCK_IP_PREFIXES=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:7f00:1/104,::ffff:a00:0/104,::ffff:a9fe:0/112,::ffff:ac10:0/108,::ffff:c0a8:0/112 \
|
||||
DNS_CACHING=on \
|
||||
DNS_UPSTREAM_IPV6=off \
|
||||
BLOCK_MALICIOUS=on \
|
||||
BLOCK_SURVEILLANCE=off \
|
||||
BLOCK_ADS=off \
|
||||
UNBLOCK= \
|
||||
DNS_UNBLOCK_HOSTNAMES= \
|
||||
DNS_UPDATE_PERIOD=24h \
|
||||
DNS_ADDRESS=127.0.0.1 \
|
||||
DNS_KEEP_NAMESERVER=off \
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
@@ -242,10 +243,13 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
return err
|
||||
}
|
||||
|
||||
ipv6Supported, err := netLinker.IsIPv6Supported()
|
||||
ipv6SupportLevel, err := netLinker.FindIPv6SupportLevel(ctx,
|
||||
allSettings.IPv6.CheckAddress, firewallConf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking for IPv6 support: %w", err)
|
||||
}
|
||||
ipv6Supported := ipv6SupportLevel == netlink.IPv6Supported ||
|
||||
ipv6SupportLevel == netlink.IPv6Internet
|
||||
|
||||
err = allSettings.Validate(storage, ipv6Supported, logger)
|
||||
if err != nil {
|
||||
@@ -430,7 +434,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
httpClient, unzipper, parallelResolver, publicIPLooper.Fetcher(), openvpnFileExtractor)
|
||||
|
||||
vpnLogger := logger.New(log.SetComponent("vpn"))
|
||||
vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6Supported, allSettings.Firewall.VPNInputPorts,
|
||||
vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6SupportLevel, allSettings.Firewall.VPNInputPorts,
|
||||
providers, storage, allSettings.Health, healthChecker, healthcheckServer, ovpnConf, netLinker, firewallConf,
|
||||
routingConf, portForwardLooper, cmder, publicIPLooper, dnsLooper, vpnLogger, httpClient,
|
||||
buildInfo, *allSettings.Version.Enabled)
|
||||
@@ -474,7 +478,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
logger.New(log.SetComponent("http server")),
|
||||
allSettings.ControlServer.AuthFilePath,
|
||||
buildInfo, vpnLooper, portForwardLooper, dnsLooper, updaterLooper, publicIPLooper,
|
||||
storage, ipv6Supported)
|
||||
storage, ipv6SupportLevel.IsSupported())
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up control server: %w", err)
|
||||
}
|
||||
@@ -550,7 +554,9 @@ type netLinker interface {
|
||||
Ruler
|
||||
Linker
|
||||
IsWireguardSupported() (ok bool, err error)
|
||||
IsIPv6Supported() (ok bool, err error)
|
||||
FindIPv6SupportLevel(ctx context.Context,
|
||||
checkAddress netip.AddrPort, firewall netlink.Firewall,
|
||||
) (level netlink.IPv6SupportLevel, err error)
|
||||
PatchLoggerLevel(level log.Level)
|
||||
}
|
||||
|
||||
|
||||
12
go.mod
12
go.mod
@@ -3,20 +3,20 @@ module github.com/qdm12/gluetun
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/breml/rootcerts v0.3.2
|
||||
github.com/breml/rootcerts v0.3.3
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/klauspost/compress v1.17.11
|
||||
github.com/klauspost/compress v1.18.1
|
||||
github.com/klauspost/pgzip v1.2.6
|
||||
github.com/pelletier/go-toml/v2 v2.2.3
|
||||
github.com/qdm12/dns/v2 v2.0.0-rc8
|
||||
github.com/pelletier/go-toml/v2 v2.2.4
|
||||
github.com/qdm12/dns/v2 v2.0.0-rc9
|
||||
github.com/qdm12/gosettings v0.4.4
|
||||
github.com/qdm12/goshutdown v0.3.0
|
||||
github.com/qdm12/gosplash v0.2.0
|
||||
github.com/qdm12/gotree v0.3.0
|
||||
github.com/qdm12/log v0.1.0
|
||||
github.com/qdm12/ss-server v0.6.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||
@@ -47,7 +47,7 @@ require (
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.60.1 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/qdm12/goservices v0.1.0 // indirect
|
||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
|
||||
24
go.sum
24
go.sum
@@ -1,7 +1,7 @@
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/breml/rootcerts v0.3.2 h1:gm11iClhK8wFn/GDdINoDaqPkiaGXyVvSwoXINZN+z4=
|
||||
github.com/breml/rootcerts v0.3.2/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
||||
github.com/breml/rootcerts v0.3.3 h1://GnaRtQ/9BY2+GtMk2wtWxVdCRysiaPr5/xBwl7NKw=
|
||||
github.com/breml/rootcerts v0.3.3/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -16,8 +16,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -41,8 +41,8 @@ github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE9
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
@@ -53,10 +53,10 @@ github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPA
|
||||
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/qdm12/dns/v2 v2.0.0-rc8 h1:kbgKPkbT+79nScfuZ0ZcVhksTGo8IUqQ8TTQGnQlZ18=
|
||||
github.com/qdm12/dns/v2 v2.0.0-rc8/go.mod h1:VaF02KWEL7xNV4oKfG4N9nEv/kR6bqyIcBReCV5NJhw=
|
||||
github.com/qdm12/goservices v0.1.0 h1:9sODefm/yuIGS7ynCkEnNlMTAYn9GzPhtcK4F69JWvc=
|
||||
github.com/qdm12/goservices v0.1.0/go.mod h1:/JOFsAnHFiSjyoXxa5FlfX903h20K5u/3rLzCjYVMck=
|
||||
github.com/qdm12/dns/v2 v2.0.0-rc9 h1:qDzRkHr6993jknNB/ZOCnZOyIG6bsZcl2MIfdeUd0kI=
|
||||
github.com/qdm12/dns/v2 v2.0.0-rc9/go.mod h1:98foWgXJZ+g8gJIuO+fdO+oWpFei5WShMFTeN4Im2lE=
|
||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 h1:TRGpCU1l0lNwtogEUSs5U+RFceYxkAJUmrGabno7J5c=
|
||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978/go.mod h1:D1Po4CRQLYjccnAR2JsVlN1sBMgQrcNLONbvyuzcdTg=
|
||||
github.com/qdm12/gosettings v0.4.4 h1:SM6tOZDf6k8qbjWU8KWyBF4mWIixfsKCfh9DGRLHlj4=
|
||||
github.com/qdm12/gosettings v0.4.4/go.mod h1:CPrt2YC4UsURTrslmhxocVhMCW03lIrqdH2hzIf5prg=
|
||||
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
|
||||
@@ -73,8 +73,8 @@ github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||
|
||||
14
internal/cli/noopfirewall.go
Normal file
14
internal/cli/noopfirewall.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type noopFirewall struct{}
|
||||
|
||||
func (f *noopFirewall) AcceptOutput(_ context.Context, _, _ string, _ netip.Addr,
|
||||
_ uint16, _ bool,
|
||||
) (err error) {
|
||||
return nil
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/netlink"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||
"github.com/qdm12/gluetun/internal/provider"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
@@ -40,7 +41,9 @@ type IPFetcher interface {
|
||||
}
|
||||
|
||||
type IPv6Checker interface {
|
||||
IsIPv6Supported() (supported bool, err error)
|
||||
FindIPv6SupportLevel(ctx context.Context,
|
||||
checkAddress netip.AddrPort, firewall netlink.Firewall,
|
||||
) (level netlink.IPv6SupportLevel, err error)
|
||||
}
|
||||
|
||||
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
||||
@@ -58,12 +61,14 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
||||
}
|
||||
allSettings.SetDefaults()
|
||||
|
||||
ipv6Supported, err := ipv6Checker.IsIPv6Supported()
|
||||
ipv6SupportLevel, err := ipv6Checker.FindIPv6SupportLevel(context.Background(),
|
||||
allSettings.IPv6.CheckAddress, &noopFirewall{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking for IPv6 support: %w", err)
|
||||
}
|
||||
|
||||
if err = allSettings.Validate(storage, ipv6Supported, logger); err != nil {
|
||||
err = allSettings.Validate(storage, ipv6SupportLevel.IsSupported(), logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating settings: %w", err)
|
||||
}
|
||||
|
||||
@@ -79,13 +84,13 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
||||
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
||||
providerConf := providers.Get(allSettings.VPN.Provider.Name)
|
||||
connection, err := providerConf.GetConnection(
|
||||
allSettings.VPN.Provider.ServerSelection, ipv6Supported)
|
||||
allSettings.VPN.Provider.ServerSelection, ipv6SupportLevel == netlink.IPv6Internet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lines := providerConf.OpenVPNConfig(connection,
|
||||
allSettings.VPN.OpenVPN, ipv6Supported)
|
||||
allSettings.VPN.OpenVPN, ipv6SupportLevel.IsSupported())
|
||||
|
||||
fmt.Println(strings.Join(lines, "\n"))
|
||||
return nil
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/dns/v2/pkg/provider"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
@@ -11,10 +15,31 @@ import (
|
||||
|
||||
// DNS contains settings to configure DNS.
|
||||
type DNS struct {
|
||||
// ServerEnabled is true if the server should be running
|
||||
// and used. It defaults to true, and cannot be nil
|
||||
// in the internal state.
|
||||
ServerEnabled *bool
|
||||
// UpstreamType can be dot or plain, and defaults to dot.
|
||||
UpstreamType string `json:"upstream_type"`
|
||||
// UpdatePeriod is the period to update DNS block lists.
|
||||
// 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
|
||||
// Providers is a list of DNS providers
|
||||
Providers []string `json:"providers"`
|
||||
// Caching is true if the server should cache
|
||||
// DNS responses.
|
||||
Caching *bool `json:"caching"`
|
||||
// IPv6 is true if the server should connect over IPv6.
|
||||
IPv6 *bool `json:"ipv6"`
|
||||
// Blacklist contains settings to configure the filter
|
||||
// block lists.
|
||||
Blacklist DNSBlacklist
|
||||
// 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
|
||||
// local server. It cannot be the zero value in the internal
|
||||
// state.
|
||||
ServerAddress netip.Addr
|
||||
// KeepNameserver is true if the existing DNS server
|
||||
@@ -23,20 +48,40 @@ type DNS struct {
|
||||
// outside the VPN tunnel since it would go through
|
||||
// the local DNS server of your Docker/Kubernetes
|
||||
// configuration, which is likely not going through the tunnel.
|
||||
// This will also disable the DNS over TLS server and the
|
||||
// This will also disable the DNS forwarder server and the
|
||||
// `ServerAddress` field will be ignored.
|
||||
// It defaults to false and cannot be nil in the
|
||||
// internal state.
|
||||
KeepNameserver *bool
|
||||
// DOT contains settings to configure the DoT
|
||||
// server.
|
||||
DoT DoT
|
||||
}
|
||||
|
||||
var (
|
||||
ErrDNSUpstreamTypeNotValid = errors.New("DNS upstream type is not valid")
|
||||
ErrDNSUpdatePeriodTooShort = errors.New("update period is too short")
|
||||
)
|
||||
|
||||
func (d DNS) validate() (err error) {
|
||||
err = d.DoT.validate()
|
||||
if !helpers.IsOneOf(d.UpstreamType, "dot", "doh", "plain") {
|
||||
return fmt.Errorf("%w: %s", ErrDNSUpstreamTypeNotValid, d.UpstreamType)
|
||||
}
|
||||
|
||||
const minUpdatePeriod = 30 * time.Second
|
||||
if *d.UpdatePeriod != 0 && *d.UpdatePeriod < minUpdatePeriod {
|
||||
return fmt.Errorf("%w: %s must be bigger than %s",
|
||||
ErrDNSUpdatePeriodTooShort, *d.UpdatePeriod, minUpdatePeriod)
|
||||
}
|
||||
|
||||
providers := provider.NewProviders()
|
||||
for _, providerName := range d.Providers {
|
||||
_, err := providers.Get(providerName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = d.Blacklist.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating DoT settings: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -44,9 +89,15 @@ func (d DNS) validate() (err error) {
|
||||
|
||||
func (d *DNS) Copy() (copied DNS) {
|
||||
return DNS{
|
||||
ServerEnabled: gosettings.CopyPointer(d.ServerEnabled),
|
||||
UpstreamType: d.UpstreamType,
|
||||
UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod),
|
||||
Providers: gosettings.CopySlice(d.Providers),
|
||||
Caching: gosettings.CopyPointer(d.Caching),
|
||||
IPv6: gosettings.CopyPointer(d.IPv6),
|
||||
Blacklist: d.Blacklist.copy(),
|
||||
ServerAddress: d.ServerAddress,
|
||||
KeepNameserver: gosettings.CopyPointer(d.KeepNameserver),
|
||||
DoT: d.DoT.copy(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,16 +105,48 @@ func (d *DNS) Copy() (copied DNS) {
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (d *DNS) overrideWith(other DNS) {
|
||||
d.ServerEnabled = gosettings.OverrideWithPointer(d.ServerEnabled, other.ServerEnabled)
|
||||
d.UpstreamType = gosettings.OverrideWithComparable(d.UpstreamType, other.UpstreamType)
|
||||
d.UpdatePeriod = gosettings.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
||||
d.Providers = gosettings.OverrideWithSlice(d.Providers, other.Providers)
|
||||
d.Caching = gosettings.OverrideWithPointer(d.Caching, other.Caching)
|
||||
d.IPv6 = gosettings.OverrideWithPointer(d.IPv6, other.IPv6)
|
||||
d.Blacklist.overrideWith(other.Blacklist)
|
||||
d.ServerAddress = gosettings.OverrideWithValidator(d.ServerAddress, other.ServerAddress)
|
||||
d.KeepNameserver = gosettings.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 = gosettings.DefaultValidator(d.ServerAddress, localhost)
|
||||
d.ServerEnabled = gosettings.DefaultPointer(d.ServerEnabled, true)
|
||||
d.UpstreamType = gosettings.DefaultComparable(d.UpstreamType, "dot")
|
||||
const defaultUpdatePeriod = 24 * time.Hour
|
||||
d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
|
||||
d.Providers = gosettings.DefaultSlice(d.Providers, []string{
|
||||
provider.Cloudflare().Name,
|
||||
})
|
||||
d.Caching = gosettings.DefaultPointer(d.Caching, true)
|
||||
d.IPv6 = gosettings.DefaultPointer(d.IPv6, false)
|
||||
d.Blacklist.setDefaults()
|
||||
d.ServerAddress = gosettings.DefaultValidator(d.ServerAddress,
|
||||
netip.AddrFrom4([4]byte{127, 0, 0, 1}))
|
||||
d.KeepNameserver = gosettings.DefaultPointer(d.KeepNameserver, false)
|
||||
d.DoT.setDefaults()
|
||||
}
|
||||
|
||||
func (d DNS) GetFirstPlaintextIPv4() (ipv4 netip.Addr) {
|
||||
localhost := netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
||||
if d.ServerAddress.Compare(localhost) != 0 && d.ServerAddress.Is4() {
|
||||
return d.ServerAddress
|
||||
}
|
||||
|
||||
providers := provider.NewProviders()
|
||||
provider, err := providers.Get(d.Providers[0])
|
||||
if err != nil {
|
||||
// Settings should be validated before calling this function,
|
||||
// so an error happening here is a programming error.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return provider.Plain.IPv4[0].Addr()
|
||||
}
|
||||
|
||||
func (d DNS) String() string {
|
||||
@@ -77,11 +160,63 @@ func (d DNS) toLinesNode() (node *gotree.Node) {
|
||||
return node
|
||||
}
|
||||
node.Appendf("DNS server address to use: %s", d.ServerAddress)
|
||||
node.AppendNode(d.DoT.toLinesNode())
|
||||
|
||||
node.Appendf("DNS forwarder server enabled: %s", gosettings.BoolToYesNo(d.ServerEnabled))
|
||||
if !*d.ServerEnabled {
|
||||
return node
|
||||
}
|
||||
|
||||
node.Appendf("Upstream resolver type: %s", d.UpstreamType)
|
||||
|
||||
upstreamResolvers := node.Append("Upstream resolvers:")
|
||||
for _, provider := range d.Providers {
|
||||
upstreamResolvers.Append(provider)
|
||||
}
|
||||
|
||||
node.Appendf("Caching: %s", gosettings.BoolToYesNo(d.Caching))
|
||||
node.Appendf("IPv6: %s", gosettings.BoolToYesNo(d.IPv6))
|
||||
|
||||
update := "disabled"
|
||||
if *d.UpdatePeriod > 0 {
|
||||
update = "every " + d.UpdatePeriod.String()
|
||||
}
|
||||
node.Appendf("Update period: %s", update)
|
||||
|
||||
node.AppendNode(d.Blacklist.toLinesNode())
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (d *DNS) read(r *reader.Reader) (err error) {
|
||||
d.ServerEnabled, err = r.BoolPtr("DNS_SERVER", reader.RetroKeys("DOT"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.UpstreamType = r.String("DNS_UPSTREAM_RESOLVER_TYPE")
|
||||
|
||||
d.UpdatePeriod, err = r.DurationPtr("DNS_UPDATE_PERIOD")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.Providers = r.CSV("DNS_UPSTREAM_RESOLVERS", reader.RetroKeys("DOT_PROVIDERS"))
|
||||
|
||||
d.Caching, err = r.BoolPtr("DNS_CACHING", reader.RetroKeys("DOT_CACHING"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.IPv6, err = r.BoolPtr("DNS_UPSTREAM_IPV6", reader.RetroKeys("DOT_IPV6"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.Blacklist.read(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.ServerAddress, err = r.NetipAddr("DNS_ADDRESS", reader.RetroKeys("DNS_PLAINTEXT_ADDRESS"))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -92,10 +227,5 @@ func (d *DNS) read(r *reader.Reader) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.DoT.read(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DNS over TLS settings: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -149,23 +149,45 @@ func (b *DNSBlacklist) read(r *reader.Reader) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
b.AddBlockedIPs, b.AddBlockedIPPrefixes,
|
||||
err = readDoTPrivateAddresses(r) // TODO v4 split in 2
|
||||
b.AddBlockedIPs, b.AddBlockedIPPrefixes, err = readDNSBlockedIPs(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.AllowedHosts = r.CSV("UNBLOCK") // TODO v4 change name
|
||||
b.AllowedHosts = r.CSV("DNS_UNBLOCK_HOSTNAMES", reader.RetroKeys("UNBLOCK"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
|
||||
|
||||
func readDoTPrivateAddresses(reader *reader.Reader) (ips []netip.Addr,
|
||||
func readDNSBlockedIPs(r *reader.Reader) (ips []netip.Addr,
|
||||
ipPrefixes []netip.Prefix, err error,
|
||||
) {
|
||||
privateAddresses := reader.CSV("DOT_PRIVATE_ADDRESS")
|
||||
ips, err = r.CSVNetipAddresses("DNS_BLOCK_IPS")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ipPrefixes, err = r.CSVNetipPrefixes("DNS_BLOCK_IP_PREFIXES")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// TODO v4 remove this block below
|
||||
privateIPs, privateIPPrefixes, err := readDNSPrivateAddresses(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ips = append(ips, privateIPs...)
|
||||
ipPrefixes = append(ipPrefixes, privateIPPrefixes...)
|
||||
|
||||
return ips, ipPrefixes, nil
|
||||
}
|
||||
|
||||
var ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
|
||||
|
||||
func readDNSPrivateAddresses(r *reader.Reader) (ips []netip.Addr,
|
||||
ipPrefixes []netip.Prefix, err error,
|
||||
) {
|
||||
privateAddresses := r.CSV("DOT_PRIVATE_ADDRESS", reader.IsRetro("DNS_BLOCK_IP_PREFIXES"))
|
||||
if len(privateAddresses) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/dns/v2/pkg/provider"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"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.
|
||||
// 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
|
||||
// Providers is a list of DNS over TLS providers
|
||||
Providers []string `json:"providers"`
|
||||
// Caching is true if the DoT server should cache
|
||||
// DNS responses.
|
||||
Caching *bool `json:"caching"`
|
||||
// IPv6 is true if the DoT server should connect over IPv6.
|
||||
IPv6 *bool `json:"ipv6"`
|
||||
// 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)
|
||||
}
|
||||
|
||||
providers := provider.NewProviders()
|
||||
for _, providerName := range d.Providers {
|
||||
_, err := providers.Get(providerName)
|
||||
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: gosettings.CopyPointer(d.Enabled),
|
||||
UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod),
|
||||
Providers: gosettings.CopySlice(d.Providers),
|
||||
Caching: gosettings.CopyPointer(d.Caching),
|
||||
IPv6: gosettings.CopyPointer(d.IPv6),
|
||||
Blacklist: d.Blacklist.copy(),
|
||||
}
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (d *DoT) overrideWith(other DoT) {
|
||||
d.Enabled = gosettings.OverrideWithPointer(d.Enabled, other.Enabled)
|
||||
d.UpdatePeriod = gosettings.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
||||
d.Providers = gosettings.OverrideWithSlice(d.Providers, other.Providers)
|
||||
d.Caching = gosettings.OverrideWithPointer(d.Caching, other.Caching)
|
||||
d.IPv6 = gosettings.OverrideWithPointer(d.IPv6, other.IPv6)
|
||||
d.Blacklist.overrideWith(other.Blacklist)
|
||||
}
|
||||
|
||||
func (d *DoT) setDefaults() {
|
||||
d.Enabled = gosettings.DefaultPointer(d.Enabled, true)
|
||||
const defaultUpdatePeriod = 24 * time.Hour
|
||||
d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
|
||||
d.Providers = gosettings.DefaultSlice(d.Providers, []string{
|
||||
provider.Cloudflare().Name,
|
||||
})
|
||||
d.Caching = gosettings.DefaultPointer(d.Caching, true)
|
||||
d.IPv6 = gosettings.DefaultPointer(d.IPv6, false)
|
||||
d.Blacklist.setDefaults()
|
||||
}
|
||||
|
||||
func (d DoT) GetFirstPlaintextIPv4() (ipv4 netip.Addr) {
|
||||
providers := provider.NewProviders()
|
||||
provider, err := providers.Get(d.Providers[0])
|
||||
if err != nil {
|
||||
// Settings should be validated before calling this function,
|
||||
// so an error happening here is a programming error.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return provider.DoT.IPv4[0].Addr()
|
||||
}
|
||||
|
||||
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", gosettings.BoolToYesNo(d.Enabled))
|
||||
if !*d.Enabled {
|
||||
return node
|
||||
}
|
||||
|
||||
update := "disabled"
|
||||
if *d.UpdatePeriod > 0 {
|
||||
update = "every " + d.UpdatePeriod.String()
|
||||
}
|
||||
node.Appendf("Update period: %s", update)
|
||||
|
||||
upstreamResolvers := node.Append("Upstream resolvers:")
|
||||
for _, provider := range d.Providers {
|
||||
upstreamResolvers.Append(provider)
|
||||
}
|
||||
|
||||
node.Appendf("Caching: %s", gosettings.BoolToYesNo(d.Caching))
|
||||
node.Appendf("IPv6: %s", gosettings.BoolToYesNo(d.IPv6))
|
||||
|
||||
node.AppendNode(d.Blacklist.toLinesNode())
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (d *DoT) read(reader *reader.Reader) (err error) {
|
||||
d.Enabled, err = reader.BoolPtr("DOT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.UpdatePeriod, err = reader.DurationPtr("DNS_UPDATE_PERIOD")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.Providers = reader.CSV("DOT_PROVIDERS")
|
||||
|
||||
d.Caching, err = reader.BoolPtr("DOT_CACHING")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.IPv6, err = reader.BoolPtr("DOT_IPV6")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.Blacklist.read(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -29,7 +29,7 @@ type Health struct {
|
||||
// It cannot be the empty string in the internal state.
|
||||
TargetAddress string
|
||||
// ICMPTargetIP is the IP address to use for ICMP echo requests
|
||||
// in the health checker. It can be set to an unspecified address
|
||||
// in the health checker. It can be set to an unspecified address (0.0.0.0)
|
||||
// such that the VPN server IP is used, which is also the default behavior.
|
||||
ICMPTargetIP netip.Addr
|
||||
// RestartVPN indicates whether to restart the VPN connection
|
||||
|
||||
51
internal/configuration/settings/ipv6.go
Normal file
51
internal/configuration/settings/ipv6.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
// IPv6 contains settings regarding IPv6 configuration.
|
||||
type IPv6 struct {
|
||||
// CheckAddress is the TCP ip:port address to dial to check
|
||||
// IPv6 is supported, in case a default IPv6 route is found.
|
||||
// It defaults to cloudflare.com address [2606:4700::6810:84e5]:443
|
||||
CheckAddress netip.AddrPort
|
||||
}
|
||||
|
||||
func (i IPv6) validate() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *IPv6) copy() (copied IPv6) {
|
||||
return IPv6{
|
||||
CheckAddress: i.CheckAddress,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *IPv6) overrideWith(other IPv6) {
|
||||
i.CheckAddress = gosettings.OverrideWithValidator(i.CheckAddress, other.CheckAddress)
|
||||
}
|
||||
|
||||
func (i *IPv6) setDefaults() {
|
||||
defaultCheckAddress := netip.MustParseAddrPort("[2606:4700::6810:84e5]:443")
|
||||
i.CheckAddress = gosettings.DefaultComparable(i.CheckAddress, defaultCheckAddress)
|
||||
}
|
||||
|
||||
func (i IPv6) String() string {
|
||||
return i.toLinesNode().String()
|
||||
}
|
||||
|
||||
func (i IPv6) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("IPv6 settings:")
|
||||
node.Appendf("Check address: %s", i.CheckAddress)
|
||||
return node
|
||||
}
|
||||
|
||||
func (i *IPv6) read(r *reader.Reader) (err error) {
|
||||
i.CheckAddress, err = r.NetipAddrPort("IPV6_CHECK_ADDRESS")
|
||||
return err
|
||||
}
|
||||
@@ -27,6 +27,7 @@ type Settings struct {
|
||||
Updater Updater
|
||||
Version Version
|
||||
VPN VPN
|
||||
IPv6 IPv6
|
||||
Pprof pprof.Settings
|
||||
}
|
||||
|
||||
@@ -53,6 +54,7 @@ func (s *Settings) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Support
|
||||
"system": s.System.validate,
|
||||
"updater": s.Updater.Validate,
|
||||
"version": s.Version.validate,
|
||||
"ipv6": s.IPv6.validate,
|
||||
// Pprof validation done in pprof constructor
|
||||
"VPN": func() error {
|
||||
return s.VPN.Validate(filterChoicesGetter, ipv6Supported, warner)
|
||||
@@ -85,6 +87,7 @@ func (s *Settings) copy() (copied Settings) {
|
||||
Version: s.Version.copy(),
|
||||
VPN: s.VPN.Copy(),
|
||||
Pprof: s.Pprof.Copy(),
|
||||
IPv6: s.IPv6.copy(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +109,7 @@ func (s *Settings) OverrideWith(other Settings,
|
||||
patchedSettings.Version.overrideWith(other.Version)
|
||||
patchedSettings.VPN.OverrideWith(other.VPN)
|
||||
patchedSettings.Pprof.OverrideWith(other.Pprof)
|
||||
patchedSettings.IPv6.overrideWith(other.IPv6)
|
||||
err = patchedSettings.Validate(filterChoicesGetter, ipv6Supported, warner)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -121,6 +125,7 @@ func (s *Settings) SetDefaults() {
|
||||
s.Health.SetDefaults()
|
||||
s.HTTPProxy.setDefaults()
|
||||
s.Log.setDefaults()
|
||||
s.IPv6.setDefaults()
|
||||
s.PublicIP.setDefaults()
|
||||
s.Shadowsocks.setDefaults()
|
||||
s.Storage.setDefaults()
|
||||
@@ -142,6 +147,7 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
|
||||
node.AppendNode(s.DNS.toLinesNode())
|
||||
node.AppendNode(s.Firewall.toLinesNode())
|
||||
node.AppendNode(s.Log.toLinesNode())
|
||||
node.AppendNode(s.IPv6.toLinesNode())
|
||||
node.AppendNode(s.Health.toLinesNode())
|
||||
node.AppendNode(s.Shadowsocks.toLinesNode())
|
||||
node.AppendNode(s.HTTPProxy.toLinesNode())
|
||||
@@ -177,10 +183,10 @@ func (s Settings) Warnings() (warnings []string) {
|
||||
// TODO remove in v4
|
||||
if s.DNS.ServerAddress.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
|
||||
warnings = append(warnings, "DNS address is set to "+s.DNS.ServerAddress.String()+
|
||||
" so the DNS over TLS (DoT) server will not be used."+
|
||||
" The default value changed to 127.0.0.1 so it uses the internal DoT serves."+
|
||||
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server"+
|
||||
" corresponding to the first DoT provider chosen is used.")
|
||||
" so the local forwarding DNS server will not be used."+
|
||||
" The default value changed to 127.0.0.1 so it uses the internal DNS server."+
|
||||
" If this server fails to start, the IPv4 address of the first plaintext DNS server"+
|
||||
" corresponding to the first DNS provider chosen is used.")
|
||||
}
|
||||
|
||||
return warnings
|
||||
@@ -208,6 +214,7 @@ func (s *Settings) Read(r *reader.Reader, warner Warner) (err error) {
|
||||
"updater": s.Updater.read,
|
||||
"version": s.Version.read,
|
||||
"VPN": s.VPN.read,
|
||||
"IPv6": s.IPv6.read,
|
||||
"profiling": s.Pprof.Read,
|
||||
}
|
||||
|
||||
|
||||
@@ -40,21 +40,23 @@ func Test_Settings_String(t *testing.T) {
|
||||
├── DNS settings:
|
||||
| ├── Keep existing nameserver(s): no
|
||||
| ├── DNS server address to use: 127.0.0.1
|
||||
| └── DNS over TLS settings:
|
||||
| ├── Enabled: yes
|
||||
| ├── Update period: every 24h0m0s
|
||||
| ├── Upstream resolvers:
|
||||
| | └── Cloudflare
|
||||
| ├── Caching: yes
|
||||
| ├── IPv6: no
|
||||
| └── DNS filtering settings:
|
||||
| ├── Block malicious: yes
|
||||
| ├── Block ads: no
|
||||
| └── Block surveillance: yes
|
||||
| ├── DNS forwarder server enabled: yes
|
||||
| ├── Upstream resolver type: dot
|
||||
| ├── Upstream resolvers:
|
||||
| | └── Cloudflare
|
||||
| ├── Caching: yes
|
||||
| ├── IPv6: no
|
||||
| ├── Update period: every 24h0m0s
|
||||
| └── DNS filtering settings:
|
||||
| ├── Block malicious: yes
|
||||
| ├── Block ads: no
|
||||
| └── Block surveillance: yes
|
||||
├── Firewall settings:
|
||||
| └── Enabled: yes
|
||||
├── Log settings:
|
||||
| └── Log level: INFO
|
||||
├── IPv6 settings:
|
||||
| └── Check address: [2606:4700::6810:84e5]:443
|
||||
├── Health settings:
|
||||
| ├── Server listening address: 127.0.0.1:9999
|
||||
| ├── Target address: cloudflare.com:443
|
||||
|
||||
@@ -10,15 +10,7 @@ import (
|
||||
func (l *Loop) useUnencryptedDNS(fallback bool) {
|
||||
settings := l.GetSettings()
|
||||
|
||||
// Try with user provided plaintext ip address
|
||||
// if it's not 127.0.0.1 (default for DoT), otherwise
|
||||
// use the first DoT provider ipv4 address found.
|
||||
var targetIP netip.Addr
|
||||
if settings.ServerAddress.Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
|
||||
targetIP = settings.ServerAddress
|
||||
} else {
|
||||
targetIP = settings.DoT.GetFirstPlaintextIPv4()
|
||||
}
|
||||
targetIP := settings.GetFirstPlaintextIPv4()
|
||||
|
||||
if fallback {
|
||||
l.logger.Info("falling back on plaintext DNS at address " + targetIP.String())
|
||||
@@ -27,14 +19,15 @@ func (l *Loop) useUnencryptedDNS(fallback bool) {
|
||||
}
|
||||
|
||||
const dialTimeout = 3 * time.Second
|
||||
const defaultDNSPort = 53
|
||||
settingsInternalDNS := nameserver.SettingsInternalDNS{
|
||||
IP: targetIP,
|
||||
Timeout: dialTimeout,
|
||||
AddrPort: netip.AddrPortFrom(targetIP, defaultDNSPort),
|
||||
Timeout: dialTimeout,
|
||||
}
|
||||
nameserver.UseDNSInternally(settingsInternalDNS)
|
||||
|
||||
settingsSystemWide := nameserver.SettingsSystemDNS{
|
||||
IP: targetIP,
|
||||
IPs: []netip.Addr{targetIP},
|
||||
ResolvPath: l.resolvConf,
|
||||
}
|
||||
err := nameserver.UseDNSSystemWide(settingsSystemWide)
|
||||
|
||||
@@ -26,12 +26,12 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
||||
}
|
||||
|
||||
for ctx.Err() == nil {
|
||||
// Upper scope variables for the DNS over TLS server only
|
||||
// Upper scope variables for the DNS forwarder server only
|
||||
// Their values are to be used if DOT=off
|
||||
var runError <-chan error
|
||||
|
||||
settings := l.GetSettings()
|
||||
for !*settings.KeepNameserver && *settings.DoT.Enabled {
|
||||
for !*settings.KeepNameserver && *settings.ServerEnabled {
|
||||
var err error
|
||||
runError, err = l.setupServer(ctx)
|
||||
if err == nil {
|
||||
@@ -56,7 +56,7 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
||||
}
|
||||
|
||||
settings = l.GetSettings()
|
||||
if !*settings.KeepNameserver && !*settings.DoT.Enabled {
|
||||
if !*settings.KeepNameserver && !*settings.ServerEnabled {
|
||||
const fallback = false
|
||||
l.useUnencryptedDNS(fallback)
|
||||
}
|
||||
@@ -101,6 +101,6 @@ func (l *Loop) runWait(ctx context.Context, runError <-chan error) (exitLoop boo
|
||||
func (l *Loop) stopServer() {
|
||||
stopErr := l.server.Stop()
|
||||
if stopErr != nil {
|
||||
l.logger.Error("stopping DoT server: " + stopErr.Error())
|
||||
l.logger.Error("stopping server: " + stopErr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/dns/v2/pkg/doh"
|
||||
"github.com/qdm12/dns/v2/pkg/dot"
|
||||
cachemiddleware "github.com/qdm12/dns/v2/pkg/middlewares/cache"
|
||||
"github.com/qdm12/dns/v2/pkg/middlewares/cache/lru"
|
||||
filtermiddleware "github.com/qdm12/dns/v2/pkg/middlewares/filter"
|
||||
"github.com/qdm12/dns/v2/pkg/middlewares/filter/mapfilter"
|
||||
"github.com/qdm12/dns/v2/pkg/plain"
|
||||
"github.com/qdm12/dns/v2/pkg/provider"
|
||||
"github.com/qdm12/dns/v2/pkg/server"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
@@ -22,33 +24,62 @@ func (l *Loop) SetSettings(ctx context.Context, settings settings.DNS) (
|
||||
return l.state.SetSettings(ctx, settings)
|
||||
}
|
||||
|
||||
func buildDoTSettings(settings settings.DNS,
|
||||
func buildServerSettings(settings settings.DNS,
|
||||
filter *mapfilter.Filter, logger Logger) (
|
||||
serverSettings server.Settings, err error,
|
||||
) {
|
||||
serverSettings.Logger = logger
|
||||
|
||||
var dotSettings dot.Settings
|
||||
providersData := provider.NewProviders()
|
||||
dotSettings.UpstreamResolvers = make([]provider.Provider, len(settings.DoT.Providers))
|
||||
for i := range settings.DoT.Providers {
|
||||
upstreamResolvers := make([]provider.Provider, len(settings.Providers))
|
||||
for i := range settings.Providers {
|
||||
var err error
|
||||
dotSettings.UpstreamResolvers[i], err = providersData.Get(settings.DoT.Providers[i])
|
||||
upstreamResolvers[i], err = providersData.Get(settings.Providers[i])
|
||||
if err != nil {
|
||||
panic(err) // this should already had been checked
|
||||
}
|
||||
}
|
||||
dotSettings.IPVersion = "ipv4"
|
||||
if *settings.DoT.IPv6 {
|
||||
dotSettings.IPVersion = "ipv6"
|
||||
|
||||
ipVersion := "ipv4"
|
||||
if *settings.IPv6 {
|
||||
ipVersion = "ipv6"
|
||||
}
|
||||
|
||||
serverSettings.Dialer, err = dot.New(dotSettings)
|
||||
if err != nil {
|
||||
return server.Settings{}, fmt.Errorf("creating DNS over TLS dialer: %w", err)
|
||||
var dialer server.Dialer
|
||||
switch settings.UpstreamType {
|
||||
case "dot":
|
||||
dialerSettings := dot.Settings{
|
||||
UpstreamResolvers: upstreamResolvers,
|
||||
IPVersion: ipVersion,
|
||||
}
|
||||
dialer, err = dot.New(dialerSettings)
|
||||
if err != nil {
|
||||
return server.Settings{}, fmt.Errorf("creating DNS over TLS dialer: %w", err)
|
||||
}
|
||||
case "doh":
|
||||
dialerSettings := doh.Settings{
|
||||
UpstreamResolvers: upstreamResolvers,
|
||||
IPVersion: ipVersion,
|
||||
}
|
||||
dialer, err = doh.New(dialerSettings)
|
||||
if err != nil {
|
||||
return server.Settings{}, fmt.Errorf("creating DNS over HTTPS dialer: %w", err)
|
||||
}
|
||||
case "plain":
|
||||
dialerSettings := plain.Settings{
|
||||
UpstreamResolvers: upstreamResolvers,
|
||||
IPVersion: ipVersion,
|
||||
}
|
||||
dialer, err = plain.New(dialerSettings)
|
||||
if err != nil {
|
||||
return server.Settings{}, fmt.Errorf("creating plain DNS dialer: %w", err)
|
||||
}
|
||||
default:
|
||||
panic("unknown upstream type: " + settings.UpstreamType)
|
||||
}
|
||||
serverSettings.Dialer = dialer
|
||||
|
||||
if *settings.DoT.Caching {
|
||||
if *settings.Caching {
|
||||
lruCache, err := lru.New(lru.Settings{})
|
||||
if err != nil {
|
||||
return server.Settings{}, fmt.Errorf("creating LRU cache: %w", err)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/dns/v2/pkg/check"
|
||||
"github.com/qdm12/dns/v2/pkg/nameserver"
|
||||
@@ -20,14 +21,14 @@ func (l *Loop) setupServer(ctx context.Context) (runError <-chan error, err erro
|
||||
|
||||
settings := l.GetSettings()
|
||||
|
||||
dotSettings, err := buildDoTSettings(settings, l.filter, l.logger)
|
||||
serverSettings, err := buildServerSettings(settings, l.filter, l.logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building DoT settings: %w", err)
|
||||
return nil, fmt.Errorf("building server settings: %w", err)
|
||||
}
|
||||
|
||||
server, err := server.New(dotSettings)
|
||||
server, err := server.New(serverSettings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating DoT server: %w", err)
|
||||
return nil, fmt.Errorf("creating server: %w", err)
|
||||
}
|
||||
|
||||
runError, err = server.Start(ctx)
|
||||
@@ -37,11 +38,12 @@ func (l *Loop) setupServer(ctx context.Context) (runError <-chan error, err erro
|
||||
l.server = server
|
||||
|
||||
// use internal DNS server
|
||||
const defaultDNSPort = 53
|
||||
nameserver.UseDNSInternally(nameserver.SettingsInternalDNS{
|
||||
IP: settings.ServerAddress,
|
||||
AddrPort: netip.AddrPortFrom(settings.ServerAddress, defaultDNSPort),
|
||||
})
|
||||
err = nameserver.UseDNSSystemWide(nameserver.SettingsSystemDNS{
|
||||
IP: settings.ServerAddress,
|
||||
IPs: []netip.Addr{settings.ServerAddress},
|
||||
ResolvPath: l.resolvConf,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -27,7 +27,7 @@ func (s *State) SetSettings(ctx context.Context, settings settings.DNS) (
|
||||
|
||||
// Check for only update period change
|
||||
tempSettings := s.settings.Copy()
|
||||
*tempSettings.DoT.UpdatePeriod = *settings.DoT.UpdatePeriod
|
||||
*tempSettings.UpdatePeriod = *settings.UpdatePeriod
|
||||
onlyUpdatePeriodChanged := reflect.DeepEqual(tempSettings, settings)
|
||||
|
||||
s.settings = settings
|
||||
@@ -40,7 +40,7 @@ func (s *State) SetSettings(ctx context.Context, settings settings.DNS) (
|
||||
|
||||
// Restart
|
||||
_, _ = s.statusApplier.ApplyStatus(ctx, constants.Stopped)
|
||||
if *settings.DoT.Enabled {
|
||||
if *settings.ServerEnabled {
|
||||
outcome, _ = s.statusApplier.ApplyStatus(ctx, constants.Running)
|
||||
}
|
||||
return outcome
|
||||
|
||||
@@ -14,7 +14,7 @@ func (l *Loop) RunRestartTicker(ctx context.Context, done chan<- struct{}) {
|
||||
timer.Stop()
|
||||
timerIsStopped := true
|
||||
settings := l.GetSettings()
|
||||
if period := *settings.DoT.UpdatePeriod; period > 0 {
|
||||
if period := *settings.UpdatePeriod; period > 0 {
|
||||
timer.Reset(period)
|
||||
timerIsStopped = false
|
||||
}
|
||||
@@ -43,14 +43,14 @@ func (l *Loop) RunRestartTicker(ctx context.Context, done chan<- struct{}) {
|
||||
_, _ = l.statusManager.ApplyStatus(ctx, constants.Running)
|
||||
|
||||
settings := l.GetSettings()
|
||||
timer.Reset(*settings.DoT.UpdatePeriod)
|
||||
timer.Reset(*settings.UpdatePeriod)
|
||||
case <-l.updateTicker:
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
timerIsStopped = true
|
||||
settings := l.GetSettings()
|
||||
newUpdatePeriod := *settings.DoT.UpdatePeriod
|
||||
newUpdatePeriod := *settings.UpdatePeriod
|
||||
if newUpdatePeriod == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ func (l *Loop) updateFiles(ctx context.Context) (err error) {
|
||||
settings := l.GetSettings()
|
||||
|
||||
l.logger.Info("downloading hostnames and IP block lists")
|
||||
blacklistSettings := settings.DoT.Blacklist.ToBlockBuilderSettings(l.client)
|
||||
blacklistSettings := settings.Blacklist.ToBlockBuilderSettings(l.client)
|
||||
|
||||
blockBuilder, err := blockbuilder.New(blacklistSettings)
|
||||
if err != nil {
|
||||
|
||||
@@ -162,6 +162,24 @@ func (c *Config) acceptOutputTrafficToVPN(ctx context.Context,
|
||||
return c.runIP6tablesInstruction(ctx, instruction)
|
||||
}
|
||||
|
||||
func (c *Config) AcceptOutput(ctx context.Context,
|
||||
protocol, intf string, ip netip.Addr, port uint16, remove bool,
|
||||
) error {
|
||||
interfaceFlag := "-o " + intf
|
||||
if intf == "*" { // all interfaces
|
||||
interfaceFlag = ""
|
||||
}
|
||||
|
||||
instruction := fmt.Sprintf("%s OUTPUT -d %s %s -p %s -m %s --dport %d -j ACCEPT",
|
||||
appendOrDelete(remove), ip, interfaceFlag, protocol, protocol, port)
|
||||
if ip.Is4() {
|
||||
return c.runIptablesInstruction(ctx, instruction)
|
||||
} else if c.ip6Tables == "" {
|
||||
return fmt.Errorf("accept output to VPN server: %w", ErrNeedIP6Tables)
|
||||
}
|
||||
return c.runIP6tablesInstruction(ctx, instruction)
|
||||
}
|
||||
|
||||
// Thanks to @npawelek.
|
||||
func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context,
|
||||
intf string, sourceIP netip.Addr, destinationSubnet netip.Prefix, remove bool,
|
||||
|
||||
@@ -323,12 +323,12 @@ var ErrProtocolUnknown = errors.New("unknown protocol")
|
||||
|
||||
func parseProtocol(s string) (protocol string, err error) {
|
||||
switch s {
|
||||
case "0":
|
||||
case "1":
|
||||
case "0", "all":
|
||||
case "1", "icmp":
|
||||
protocol = "icmp"
|
||||
case "6":
|
||||
case "6", "tcp":
|
||||
protocol = "tcp"
|
||||
case "17":
|
||||
case "17", "udp":
|
||||
protocol = "udp"
|
||||
default:
|
||||
return "", fmt.Errorf("%w: %s", ErrProtocolUnknown, s)
|
||||
|
||||
@@ -58,6 +58,7 @@ num pkts bytes target prot opt in out source destinati
|
||||
2 0 0 ACCEPT 6 -- tun0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:55405
|
||||
3 0 0 ACCEPT 1 -- tun0 * 0.0.0.0/0 0.0.0.0/0
|
||||
4 0 0 DROP 0 -- tun0 * 1.2.3.4 0.0.0.0/0
|
||||
5 0 0 ACCEPT all -- tun0 * 1.2.3.4 0.0.0.0/0
|
||||
`,
|
||||
table: chain{
|
||||
name: "INPUT",
|
||||
@@ -111,6 +112,17 @@ num pkts bytes target prot opt in out source destinati
|
||||
source: netip.MustParsePrefix("1.2.3.4/32"),
|
||||
destination: netip.MustParsePrefix("0.0.0.0/0"),
|
||||
},
|
||||
{
|
||||
lineNumber: 5,
|
||||
packets: 0,
|
||||
bytes: 0,
|
||||
target: "ACCEPT",
|
||||
protocol: "",
|
||||
inputInterface: "tun0",
|
||||
outputInterface: "*",
|
||||
source: netip.MustParsePrefix("1.2.3.4/32"),
|
||||
destination: netip.MustParsePrefix("0.0.0.0/0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,33 +5,60 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/dns/v2/pkg/provider"
|
||||
)
|
||||
|
||||
// Client is a simple plaintext UDP DNS client, to be used for healthchecks.
|
||||
// Note the client connects to a DNS server only over UDP on port 53,
|
||||
// because we don't want to use DoT or DoH and impact the TCP connections
|
||||
// when running a healthcheck.
|
||||
type Client struct{}
|
||||
type Client struct {
|
||||
serverAddrs []netip.AddrPort
|
||||
dnsIPIndex int
|
||||
}
|
||||
|
||||
func New() *Client {
|
||||
return &Client{}
|
||||
return &Client{
|
||||
serverAddrs: concatAddrPorts([][]netip.AddrPort{
|
||||
provider.Cloudflare().Plain.IPv4,
|
||||
provider.Google().Plain.IPv4,
|
||||
provider.Quad9().Plain.IPv4,
|
||||
provider.OpenDNS().Plain.IPv4,
|
||||
provider.LibreDNS().Plain.IPv4,
|
||||
provider.Quadrant().Plain.IPv4,
|
||||
provider.CiraProtected().Plain.IPv4,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func concatAddrPorts(addrs [][]netip.AddrPort) []netip.AddrPort {
|
||||
var result []netip.AddrPort
|
||||
for _, addrList := range addrs {
|
||||
result = append(result, addrList...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var ErrLookupNoIPs = errors.New("no IPs found from DNS lookup")
|
||||
|
||||
func (c *Client) Check(ctx context.Context) error {
|
||||
dnsAddr := c.serverAddrs[c.dnsIPIndex].Addr()
|
||||
resolver := &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
dialer := net.Dialer{}
|
||||
return dialer.DialContext(ctx, "udp", "1.1.1.1:53")
|
||||
return dialer.DialContext(ctx, "udp", dnsAddr.String())
|
||||
},
|
||||
}
|
||||
ips, err := resolver.LookupIP(ctx, "ip", "github.com")
|
||||
switch {
|
||||
case err != nil:
|
||||
c.dnsIPIndex = (c.dnsIPIndex + 1) % len(c.serverAddrs)
|
||||
return err
|
||||
case len(ips) == 0:
|
||||
c.dnsIPIndex = (c.dnsIPIndex + 1) % len(c.serverAddrs)
|
||||
return fmt.Errorf("%w", ErrLookupNoIPs)
|
||||
default:
|
||||
return nil
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
package netlink
|
||||
|
||||
import "github.com/qdm12/log"
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/log"
|
||||
)
|
||||
|
||||
type DebugLogger interface {
|
||||
Debug(message string)
|
||||
Debugf(format string, args ...any)
|
||||
Patch(options ...log.Option)
|
||||
}
|
||||
|
||||
type Firewall interface {
|
||||
AcceptOutput(ctx context.Context, protocol, intf string, ip netip.Addr,
|
||||
port uint16, remove bool) (err error)
|
||||
}
|
||||
|
||||
@@ -1,37 +1,106 @@
|
||||
package netlink
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (n *NetLink) IsIPv6Supported() (supported bool, err error) {
|
||||
type IPv6SupportLevel uint8
|
||||
|
||||
const (
|
||||
IPv6Unsupported = iota
|
||||
// IPv6Supported indicates the host supports IPv6 but has no access to the
|
||||
// Internet via IPv6. It is true if one IPv6 route is found and no default
|
||||
// IPv6 route is found.
|
||||
IPv6Supported
|
||||
// IPv6Internet indicates the host has access to the Internet via IPv6,
|
||||
// which is detected when a default IPv6 route is found.
|
||||
IPv6Internet
|
||||
)
|
||||
|
||||
func (i IPv6SupportLevel) IsSupported() bool {
|
||||
return i == IPv6Supported || i == IPv6Internet
|
||||
}
|
||||
|
||||
func (n *NetLink) FindIPv6SupportLevel(ctx context.Context,
|
||||
checkAddress netip.AddrPort, firewall Firewall,
|
||||
) (level IPv6SupportLevel, err error) {
|
||||
routes, err := n.RouteList(FamilyV6)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("listing IPv6 routes: %w", err)
|
||||
return IPv6Unsupported, fmt.Errorf("listing IPv6 routes: %w", err)
|
||||
}
|
||||
|
||||
// Check each route for IPv6 due to Podman bug listing IPv4 routes
|
||||
// as IPv6 routes at container start, see:
|
||||
// https://github.com/qdm12/gluetun/issues/1241#issuecomment-1333405949
|
||||
level = IPv6Unsupported
|
||||
for _, route := range routes {
|
||||
link, err := n.LinkByIndex(route.LinkIndex)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("finding link corresponding to route: %w", err)
|
||||
return IPv6Unsupported, fmt.Errorf("finding link corresponding to route: %w", err)
|
||||
}
|
||||
|
||||
sourceIsIPv6 := route.Src.IsValid() && route.Src.Is6()
|
||||
sourceIsIPv4 := route.Src.IsValid() && route.Src.Is4()
|
||||
destinationIsIPv4 := route.Dst.IsValid() && route.Dst.Addr().Is4()
|
||||
destinationIsIPv6 := route.Dst.IsValid() && route.Dst.Addr().Is6()
|
||||
switch {
|
||||
case !sourceIsIPv6 && !destinationIsIPv6,
|
||||
case sourceIsIPv4 && destinationIsIPv4,
|
||||
destinationIsIPv6 && route.Dst.Addr().IsLoopback():
|
||||
continue
|
||||
case route.Dst.Addr().IsUnspecified(): // default ipv6 route
|
||||
n.debugLogger.Debugf("IPv6 default route found on link %s", link.Name)
|
||||
err = dialAddrThroughFirewall(ctx, link.Name, checkAddress, firewall)
|
||||
if err != nil {
|
||||
n.debugLogger.Debugf("IPv6 query failed on %s: %w", link.Name, err)
|
||||
level = IPv6Supported
|
||||
continue
|
||||
}
|
||||
n.debugLogger.Debugf("IPv6 internet is accessible through link %s", link.Name)
|
||||
return IPv6Internet, nil
|
||||
default: // non-default ipv6 route found
|
||||
n.debugLogger.Debugf("IPv6 is supported by link %s", link.Name)
|
||||
level = IPv6Supported
|
||||
}
|
||||
|
||||
n.debugLogger.Debugf("IPv6 is supported by link %s", link.Name)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
n.debugLogger.Debugf("IPv6 is not supported after searching %d routes",
|
||||
len(routes))
|
||||
return false, nil
|
||||
if level == IPv6Unsupported {
|
||||
n.debugLogger.Debugf("no IPv6 route found in %d routes", len(routes))
|
||||
}
|
||||
return level, nil
|
||||
}
|
||||
|
||||
func dialAddrThroughFirewall(ctx context.Context, intf string,
|
||||
checkAddress netip.AddrPort, firewall Firewall,
|
||||
) (err error) {
|
||||
const protocol = "tcp"
|
||||
remove := false
|
||||
err = firewall.AcceptOutput(ctx, protocol, intf,
|
||||
checkAddress.Addr(), checkAddress.Port(), remove)
|
||||
if err != nil {
|
||||
return fmt.Errorf("accepting output traffic: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
remove = true
|
||||
firewallErr := firewall.AcceptOutput(ctx, protocol, intf,
|
||||
checkAddress.Addr(), checkAddress.Port(), remove)
|
||||
if err == nil && firewallErr != nil {
|
||||
err = fmt.Errorf("removing output traffic rule: %w", firewallErr)
|
||||
}
|
||||
}()
|
||||
|
||||
dialer := &net.Dialer{
|
||||
Timeout: time.Second,
|
||||
}
|
||||
conn, err := dialer.DialContext(ctx, protocol, checkAddress.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("dialing: %w", err)
|
||||
}
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("closing connection: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
167
internal/netlink/ipv6_test.go
Normal file
167
internal/netlink/ipv6_test.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package netlink
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func isIPv6LocallySupported() bool {
|
||||
dialer := net.Dialer{Timeout: time.Millisecond}
|
||||
_, err := dialer.Dial("tcp6", "[::1]:9999")
|
||||
return !strings.HasSuffix(err.Error(), "connect: cannot assign requested address")
|
||||
}
|
||||
|
||||
// Susceptible to TOCTOU but it should be fine for the use case.
|
||||
func findAvailableTCPPort(t *testing.T) (port uint16) {
|
||||
t.Helper()
|
||||
|
||||
config := &net.ListenConfig{}
|
||||
listener, err := config.Listen(context.Background(), "tcp", "localhost:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
addr := listener.Addr().String()
|
||||
err = listener.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
addrPort, err := netip.ParseAddrPort(addr)
|
||||
require.NoError(t, err)
|
||||
|
||||
return addrPort.Port()
|
||||
}
|
||||
|
||||
func Test_dialAddrThroughFirewall(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
errTest := errors.New("test error")
|
||||
|
||||
const ipv6InternetWorks = false
|
||||
|
||||
testCases := map[string]struct {
|
||||
getIPv6CheckAddr func(t *testing.T) netip.AddrPort
|
||||
firewallAddErr error
|
||||
firewallRemoveErr error
|
||||
errMessageRegex func() string
|
||||
}{
|
||||
"cloudflare.com": {
|
||||
getIPv6CheckAddr: func(_ *testing.T) netip.AddrPort {
|
||||
return netip.MustParseAddrPort("[2606:4700::6810:84e5]:443")
|
||||
},
|
||||
errMessageRegex: func() string {
|
||||
if ipv6InternetWorks {
|
||||
return ""
|
||||
}
|
||||
return "dialing: dial tcp \\[2606:4700::6810:84e5\\]:443: " +
|
||||
"connect: (cannot assign requested address|network is unreachable)"
|
||||
},
|
||||
},
|
||||
"local_server": {
|
||||
getIPv6CheckAddr: func(t *testing.T) netip.AddrPort {
|
||||
t.Helper()
|
||||
|
||||
network := "tcp6"
|
||||
loopback := netip.MustParseAddr("::1")
|
||||
if !isIPv6LocallySupported() {
|
||||
network = "tcp4"
|
||||
loopback = netip.MustParseAddr("127.0.0.1")
|
||||
}
|
||||
|
||||
listener, err := net.ListenTCP(network, nil)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := listener.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
addrPort := netip.MustParseAddrPort(listener.Addr().String())
|
||||
return netip.AddrPortFrom(loopback, addrPort.Port())
|
||||
},
|
||||
},
|
||||
"no_local_server": {
|
||||
getIPv6CheckAddr: func(t *testing.T) netip.AddrPort {
|
||||
t.Helper()
|
||||
|
||||
loopback := netip.MustParseAddr("::1")
|
||||
if !ipv6InternetWorks {
|
||||
loopback = netip.MustParseAddr("127.0.0.1")
|
||||
}
|
||||
|
||||
availablePort := findAvailableTCPPort(t)
|
||||
return netip.AddrPortFrom(loopback, availablePort)
|
||||
},
|
||||
errMessageRegex: func() string {
|
||||
return "dialing: dial tcp (\\[::1\\]|127\\.0\\.0\\.1):[1-9][0-9]{1,4}: " +
|
||||
"connect: connection refused"
|
||||
},
|
||||
},
|
||||
"firewall_add_error": {
|
||||
firewallAddErr: errTest,
|
||||
errMessageRegex: func() string {
|
||||
return "accepting output traffic: test error"
|
||||
},
|
||||
},
|
||||
"firewall_remove_error": {
|
||||
getIPv6CheckAddr: func(t *testing.T) netip.AddrPort {
|
||||
t.Helper()
|
||||
|
||||
network := "tcp4"
|
||||
loopback := netip.MustParseAddr("127.0.0.1")
|
||||
listener, err := net.ListenTCP(network, nil)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := listener.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
addrPort := netip.MustParseAddrPort(listener.Addr().String())
|
||||
return netip.AddrPortFrom(loopback, addrPort.Port())
|
||||
},
|
||||
firewallRemoveErr: errTest,
|
||||
errMessageRegex: func() string {
|
||||
return "removing output traffic rule: test error"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
var checkAddr netip.AddrPort
|
||||
if testCase.getIPv6CheckAddr != nil {
|
||||
checkAddr = testCase.getIPv6CheckAddr(t)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
const intf = "eth0"
|
||||
firewall := NewMockFirewall(ctrl)
|
||||
call := firewall.EXPECT().AcceptOutput(ctx, "tcp", intf,
|
||||
checkAddr.Addr(), checkAddr.Port(), false).
|
||||
Return(testCase.firewallAddErr)
|
||||
if testCase.firewallAddErr == nil {
|
||||
firewall.EXPECT().AcceptOutput(ctx, "tcp", intf,
|
||||
checkAddr.Addr(), checkAddr.Port(), true).
|
||||
Return(testCase.firewallRemoveErr).After(call)
|
||||
}
|
||||
|
||||
err := dialAddrThroughFirewall(ctx, intf, checkAddr, firewall)
|
||||
var errMessageRegex string
|
||||
if testCase.errMessageRegex != nil {
|
||||
errMessageRegex = testCase.errMessageRegex()
|
||||
}
|
||||
if errMessageRegex == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
assert.Regexp(t, errMessageRegex, err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
3
internal/netlink/mocks_generate_test.go
Normal file
3
internal/netlink/mocks_generate_test.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package netlink
|
||||
|
||||
//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Firewall
|
||||
50
internal/netlink/mocks_test.go
Normal file
50
internal/netlink/mocks_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/netlink (interfaces: Firewall)
|
||||
|
||||
// Package netlink is a generated GoMock package.
|
||||
package netlink
|
||||
|
||||
import (
|
||||
context "context"
|
||||
netip "net/netip"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockFirewall is a mock of Firewall interface.
|
||||
type MockFirewall struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockFirewallMockRecorder
|
||||
}
|
||||
|
||||
// MockFirewallMockRecorder is the mock recorder for MockFirewall.
|
||||
type MockFirewallMockRecorder struct {
|
||||
mock *MockFirewall
|
||||
}
|
||||
|
||||
// NewMockFirewall creates a new mock instance.
|
||||
func NewMockFirewall(ctrl *gomock.Controller) *MockFirewall {
|
||||
mock := &MockFirewall{ctrl: ctrl}
|
||||
mock.recorder = &MockFirewallMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockFirewall) EXPECT() *MockFirewallMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AcceptOutput mocks base method.
|
||||
func (m *MockFirewall) AcceptOutput(arg0 context.Context, arg1, arg2 string, arg3 netip.Addr, arg4 uint16, arg5 bool) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AcceptOutput", arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AcceptOutput indicates an expected call of AcceptOutput.
|
||||
func (mr *MockFirewallMockRecorder) AcceptOutput(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptOutput", reflect.TypeOf((*MockFirewall)(nil).AcceptOutput), arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
}
|
||||
@@ -15,12 +15,12 @@ type Provider struct {
|
||||
}
|
||||
|
||||
func New(storage common.Storage, randSource rand.Source,
|
||||
parallelResolver common.ParallelResolver,
|
||||
updaterWarner common.Warner, parallelResolver common.ParallelResolver,
|
||||
) *Provider {
|
||||
return &Provider{
|
||||
storage: storage,
|
||||
randSource: randSource,
|
||||
Fetcher: updater.New(parallelResolver),
|
||||
Fetcher: updater.New(parallelResolver, updaterWarner),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,10 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
||||
|
||||
possibleHosts := possibleServers.hostsSlice()
|
||||
resolveSettings := parallelResolverSettings(possibleHosts)
|
||||
hostToIPs, _, err := u.parallelResolver.Resolve(ctx, resolveSettings)
|
||||
hostToIPs, warnings, err := u.parallelResolver.Resolve(ctx, resolveSettings)
|
||||
for _, warning := range warnings {
|
||||
u.warner.Warn(warning)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import (
|
||||
|
||||
type Updater struct {
|
||||
parallelResolver common.ParallelResolver
|
||||
warner common.Warner
|
||||
}
|
||||
|
||||
func New(parallelResolver common.ParallelResolver) *Updater {
|
||||
func New(parallelResolver common.ParallelResolver, warner common.Warner) *Updater {
|
||||
return &Updater{
|
||||
parallelResolver: parallelResolver,
|
||||
warner: warner,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func NewProviders(storage Storage, timeNow func() time.Time,
|
||||
providerNameToProvider := map[string]Provider{
|
||||
providers.Airvpn: airvpn.New(storage, randSource, client),
|
||||
providers.Custom: custom.New(extractor),
|
||||
providers.Cyberghost: cyberghost.New(storage, randSource, parallelResolver),
|
||||
providers.Cyberghost: cyberghost.New(storage, randSource, updaterWarner, parallelResolver),
|
||||
providers.Expressvpn: expressvpn.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
||||
providers.Fastestvpn: fastestvpn.New(storage, randSource, client, updaterWarner, parallelResolver),
|
||||
providers.Giganews: giganews.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
||||
|
||||
@@ -15,6 +15,7 @@ type infoWarner interface {
|
||||
|
||||
type infoer interface {
|
||||
Info(s string)
|
||||
Infof(format string, args ...any)
|
||||
}
|
||||
|
||||
type warner interface {
|
||||
|
||||
@@ -13,9 +13,6 @@ import (
|
||||
func Read(filepath string) (settings Settings, err error) {
|
||||
file, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return Settings{}, nil
|
||||
}
|
||||
return settings, fmt.Errorf("opening file: %w", err)
|
||||
}
|
||||
decoder := toml.NewDecoder(file)
|
||||
|
||||
@@ -2,7 +2,9 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/httpserver"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
@@ -17,8 +19,12 @@ func New(ctx context.Context, address string, logEnabled bool, logger Logger,
|
||||
server *httpserver.Server, err error,
|
||||
) {
|
||||
authSettings, err := auth.Read(authConfigPath)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, os.ErrNotExist): // no auth file present
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("reading auth settings: %w", err)
|
||||
default:
|
||||
logger.Infof("read %d roles from authentication file", len(authSettings.Roles))
|
||||
}
|
||||
authSettings.SetDefaults()
|
||||
err = authSettings.Validate()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/loopstate"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/netlink"
|
||||
"github.com/qdm12/gluetun/internal/vpn/state"
|
||||
"github.com/qdm12/log"
|
||||
)
|
||||
@@ -21,10 +22,10 @@ type Loop struct {
|
||||
healthChecker HealthChecker
|
||||
healthServer HealthServer
|
||||
// Fixed parameters
|
||||
buildInfo models.BuildInformation
|
||||
versionInfo bool
|
||||
ipv6Supported bool
|
||||
vpnInputPorts []uint16 // TODO make changeable through stateful firewall
|
||||
buildInfo models.BuildInformation
|
||||
versionInfo bool
|
||||
ipv6SupportLevel netlink.IPv6SupportLevel
|
||||
vpnInputPorts []uint16 // TODO make changeable through stateful firewall
|
||||
// Configurators
|
||||
openvpnConf OpenVPN
|
||||
netLinker NetLinker
|
||||
@@ -51,7 +52,7 @@ const (
|
||||
defaultBackoffTime = 15 * time.Second
|
||||
)
|
||||
|
||||
func NewLoop(vpnSettings settings.VPN, ipv6Supported bool, vpnInputPorts []uint16,
|
||||
func NewLoop(vpnSettings settings.VPN, ipv6SupportLevel netlink.IPv6SupportLevel, vpnInputPorts []uint16,
|
||||
providers Providers, storage Storage, healthSettings settings.Health,
|
||||
healthChecker HealthChecker, healthServer HealthServer, openvpnConf OpenVPN,
|
||||
netLinker NetLinker, fw Firewall, routing Routing,
|
||||
@@ -69,32 +70,32 @@ func NewLoop(vpnSettings settings.VPN, ipv6Supported bool, vpnInputPorts []uint1
|
||||
state := state.New(statusManager, vpnSettings)
|
||||
|
||||
return &Loop{
|
||||
statusManager: statusManager,
|
||||
state: state,
|
||||
providers: providers,
|
||||
storage: storage,
|
||||
healthSettings: healthSettings,
|
||||
healthChecker: healthChecker,
|
||||
healthServer: healthServer,
|
||||
buildInfo: buildInfo,
|
||||
versionInfo: versionInfo,
|
||||
ipv6Supported: ipv6Supported,
|
||||
vpnInputPorts: vpnInputPorts,
|
||||
openvpnConf: openvpnConf,
|
||||
netLinker: netLinker,
|
||||
fw: fw,
|
||||
routing: routing,
|
||||
portForward: portForward,
|
||||
publicip: publicip,
|
||||
dnsLooper: dnsLooper,
|
||||
starter: starter,
|
||||
logger: logger,
|
||||
client: client,
|
||||
start: start,
|
||||
running: running,
|
||||
stop: stop,
|
||||
stopped: stopped,
|
||||
userTrigger: true,
|
||||
backoffTime: defaultBackoffTime,
|
||||
statusManager: statusManager,
|
||||
state: state,
|
||||
providers: providers,
|
||||
storage: storage,
|
||||
healthSettings: healthSettings,
|
||||
healthChecker: healthChecker,
|
||||
healthServer: healthServer,
|
||||
buildInfo: buildInfo,
|
||||
versionInfo: versionInfo,
|
||||
ipv6SupportLevel: ipv6SupportLevel,
|
||||
vpnInputPorts: vpnInputPorts,
|
||||
openvpnConf: openvpnConf,
|
||||
netLinker: netLinker,
|
||||
fw: fw,
|
||||
routing: routing,
|
||||
portForward: portForward,
|
||||
publicip: publicip,
|
||||
dnsLooper: dnsLooper,
|
||||
starter: starter,
|
||||
logger: logger,
|
||||
client: client,
|
||||
start: start,
|
||||
running: running,
|
||||
stop: stop,
|
||||
stopped: stopped,
|
||||
userTrigger: true,
|
||||
backoffTime: defaultBackoffTime,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/netlink"
|
||||
"github.com/qdm12/gluetun/internal/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/provider"
|
||||
)
|
||||
@@ -14,15 +15,16 @@ import (
|
||||
// It returns a serverName for port forwarding (PIA) and an error if it fails.
|
||||
func setupOpenVPN(ctx context.Context, fw Firewall,
|
||||
openvpnConf OpenVPN, providerConf provider.Provider,
|
||||
settings settings.VPN, ipv6Supported bool, starter CmdStarter,
|
||||
settings settings.VPN, ipv6SupportLevel netlink.IPv6SupportLevel, starter CmdStarter,
|
||||
logger openvpn.Logger) (runner *openvpn.Runner, connection models.Connection, err error,
|
||||
) {
|
||||
connection, err = providerConf.GetConnection(settings.Provider.ServerSelection, ipv6Supported)
|
||||
ipv6Internet := ipv6SupportLevel == netlink.IPv6Internet
|
||||
connection, err = providerConf.GetConnection(settings.Provider.ServerSelection, ipv6Internet)
|
||||
if err != nil {
|
||||
return nil, models.Connection{}, fmt.Errorf("finding a valid server connection: %w", err)
|
||||
}
|
||||
|
||||
lines := providerConf.OpenVPNConfig(connection, settings.OpenVPN, ipv6Supported)
|
||||
lines := providerConf.OpenVPNConfig(connection, settings.OpenVPN, ipv6SupportLevel.IsSupported())
|
||||
|
||||
if err := openvpnConf.WriteConfig(lines); err != nil {
|
||||
return nil, models.Connection{}, fmt.Errorf("writing configuration to file: %w", err)
|
||||
|
||||
@@ -36,11 +36,11 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
||||
if settings.Type == vpn.OpenVPN {
|
||||
vpnInterface = settings.OpenVPN.Interface
|
||||
vpnRunner, connection, err = setupOpenVPN(ctx, l.fw,
|
||||
l.openvpnConf, providerConf, settings, l.ipv6Supported, l.starter, subLogger)
|
||||
l.openvpnConf, providerConf, settings, l.ipv6SupportLevel, l.starter, subLogger)
|
||||
} else { // Wireguard
|
||||
vpnInterface = settings.Wireguard.Interface
|
||||
vpnRunner, connection, err = setupWireguard(ctx, l.netLinker, l.fw,
|
||||
providerConf, settings, l.ipv6Supported, subLogger)
|
||||
providerConf, settings, l.ipv6SupportLevel, subLogger)
|
||||
}
|
||||
if err != nil {
|
||||
l.crashed(ctx, err)
|
||||
@@ -56,14 +56,14 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
||||
password: settings.Provider.PortForwarding.Password,
|
||||
}
|
||||
|
||||
openvpnCtx, openvpnCancel := context.WithCancel(context.Background())
|
||||
vpnCtx, vpnCancel := context.WithCancel(context.Background())
|
||||
waitError := make(chan error)
|
||||
tunnelReady := make(chan struct{})
|
||||
|
||||
go vpnRunner.Run(openvpnCtx, waitError, tunnelReady)
|
||||
go vpnRunner.Run(vpnCtx, waitError, tunnelReady)
|
||||
|
||||
if err := l.waitForError(ctx, waitError); err != nil {
|
||||
openvpnCancel()
|
||||
vpnCancel()
|
||||
l.crashed(ctx, err)
|
||||
continue
|
||||
}
|
||||
@@ -75,10 +75,10 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
||||
for stayHere {
|
||||
select {
|
||||
case <-tunnelReady:
|
||||
go l.onTunnelUp(openvpnCtx, ctx, tunnelUpData)
|
||||
go l.onTunnelUp(vpnCtx, ctx, tunnelUpData)
|
||||
case <-ctx.Done():
|
||||
l.cleanup()
|
||||
openvpnCancel()
|
||||
vpnCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
return
|
||||
@@ -86,7 +86,7 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
||||
l.userTrigger = true
|
||||
l.logger.Info("stopping")
|
||||
l.cleanup()
|
||||
openvpnCancel()
|
||||
vpnCancel()
|
||||
<-waitError
|
||||
// do not close waitError or the waitError
|
||||
// select case will trigger
|
||||
@@ -99,7 +99,7 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
||||
l.statusManager.Lock() // prevent SetStatus from running in parallel
|
||||
|
||||
l.cleanup()
|
||||
openvpnCancel()
|
||||
vpnCancel()
|
||||
l.statusManager.SetStatus(constants.Crashed)
|
||||
l.logAndWait(ctx, err)
|
||||
stayHere = false
|
||||
@@ -107,6 +107,6 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
||||
l.statusManager.Unlock()
|
||||
}
|
||||
}
|
||||
openvpnCancel()
|
||||
vpnCancel()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (l *Loop) onTunnelUp(ctx, loopCtx context.Context, data tunnelUpData) {
|
||||
return
|
||||
}
|
||||
|
||||
if *l.dnsLooper.GetSettings().DoT.Enabled {
|
||||
if *l.dnsLooper.GetSettings().ServerEnabled {
|
||||
_, _ = l.dnsLooper.ApplyStatus(ctx, constants.Running)
|
||||
} else {
|
||||
err := check.WaitForDNS(ctx, check.Settings{})
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/netlink"
|
||||
"github.com/qdm12/gluetun/internal/provider"
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
"github.com/qdm12/gluetun/internal/wireguard"
|
||||
@@ -16,15 +17,16 @@ import (
|
||||
// It returns a serverName for port forwarding (PIA) and an error if it fails.
|
||||
func setupWireguard(ctx context.Context, netlinker NetLinker,
|
||||
fw Firewall, providerConf provider.Provider,
|
||||
settings settings.VPN, ipv6Supported bool, logger wireguard.Logger) (
|
||||
settings settings.VPN, ipv6SupportLevel netlink.IPv6SupportLevel, logger wireguard.Logger) (
|
||||
wireguarder *wireguard.Wireguard, connection models.Connection, err error,
|
||||
) {
|
||||
connection, err = providerConf.GetConnection(settings.Provider.ServerSelection, ipv6Supported)
|
||||
ipv6Internet := ipv6SupportLevel == netlink.IPv6Internet
|
||||
connection, err = providerConf.GetConnection(settings.Provider.ServerSelection, ipv6Internet)
|
||||
if err != nil {
|
||||
return nil, models.Connection{}, fmt.Errorf("finding a VPN server: %w", err)
|
||||
}
|
||||
|
||||
wireguardSettings := utils.BuildWireguardSettings(connection, settings.Wireguard, ipv6Supported)
|
||||
wireguardSettings := utils.BuildWireguardSettings(connection, settings.Wireguard, ipv6SupportLevel.IsSupported())
|
||||
|
||||
logger.Debug("Wireguard server public key: " + wireguardSettings.PublicKey)
|
||||
logger.Debug("Wireguard client private key: " + gosettings.ObfuscateKey(wireguardSettings.PrivateKey))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Maintenance
|
||||
|
||||
- Rename `UNBLOCK` to `DNS_HOSTNAMES_UNBLOCKED`
|
||||
- Change `Run` methods to `Start`+`Stop`, returning channels rather than injecting them
|
||||
- Go 1.18
|
||||
- gofumpt
|
||||
|
||||
Reference in New Issue
Block a user