Merge branch 'master' into pmtu
This commit is contained in:
@@ -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,9 +29,12 @@ 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
|
||||
// when the healthcheck fails.
|
||||
RestartVPN *bool
|
||||
}
|
||||
|
||||
func (h Health) Validate() (err error) {
|
||||
@@ -50,6 +53,7 @@ func (h *Health) copy() (copied Health) {
|
||||
ReadTimeout: h.ReadTimeout,
|
||||
TargetAddress: h.TargetAddress,
|
||||
ICMPTargetIP: h.ICMPTargetIP,
|
||||
RestartVPN: gosettings.CopyPointer(h.RestartVPN),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +66,7 @@ func (h *Health) OverrideWith(other Health) {
|
||||
h.ReadTimeout = gosettings.OverrideWithComparable(h.ReadTimeout, other.ReadTimeout)
|
||||
h.TargetAddress = gosettings.OverrideWithComparable(h.TargetAddress, other.TargetAddress)
|
||||
h.ICMPTargetIP = gosettings.OverrideWithComparable(h.ICMPTargetIP, other.ICMPTargetIP)
|
||||
h.RestartVPN = gosettings.OverrideWithPointer(h.RestartVPN, other.RestartVPN)
|
||||
}
|
||||
|
||||
func (h *Health) SetDefaults() {
|
||||
@@ -72,6 +77,7 @@ func (h *Health) SetDefaults() {
|
||||
h.ReadTimeout = gosettings.DefaultComparable(h.ReadTimeout, defaultReadTimeout)
|
||||
h.TargetAddress = gosettings.DefaultComparable(h.TargetAddress, "cloudflare.com:443")
|
||||
h.ICMPTargetIP = gosettings.DefaultComparable(h.ICMPTargetIP, netip.IPv4Unspecified()) // use the VPN server IP
|
||||
h.RestartVPN = gosettings.DefaultPointer(h.RestartVPN, true)
|
||||
}
|
||||
|
||||
func (h Health) String() string {
|
||||
@@ -87,6 +93,7 @@ func (h Health) toLinesNode() (node *gotree.Node) {
|
||||
icmpTarget = h.ICMPTargetIP.String()
|
||||
}
|
||||
node.Appendf("ICMP target IP: %s", icmpTarget)
|
||||
node.Appendf("Restart VPN on healthcheck failure: %s", gosettings.BoolToYesNo(h.RestartVPN))
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -98,5 +105,9 @@ func (h *Health) Read(r *reader.Reader) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.RestartVPN, err = r.BoolPtr("HEALTH_RESTART_VPN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -177,10 +177,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
|
||||
|
||||
@@ -40,17 +40,17 @@ 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:
|
||||
@@ -58,7 +58,8 @@ func Test_Settings_String(t *testing.T) {
|
||||
├── Health settings:
|
||||
| ├── Server listening address: 127.0.0.1:9999
|
||||
| ├── Target address: cloudflare.com:443
|
||||
| └── ICMP target IP: VPN server IP
|
||||
| ├── ICMP target IP: VPN server IP
|
||||
| └── Restart VPN on healthcheck failure: yes
|
||||
├── Shadowsocks server settings:
|
||||
| └── Enabled: no
|
||||
├── HTTP proxy settings:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,6 +25,7 @@ type Checker struct {
|
||||
configMutex sync.Mutex
|
||||
|
||||
icmpNotPermitted bool
|
||||
smallCheckName string
|
||||
|
||||
// Internal periodic service signals
|
||||
stop context.CancelFunc
|
||||
@@ -58,8 +59,9 @@ func (c *Checker) SetConfig(tlsDialAddr string, icmpTarget netip.Addr) {
|
||||
// and, on success, starts the periodic checks in a separate goroutine:
|
||||
// - a "small" ICMP echo check every 15 seconds
|
||||
// - a "full" TCP+TLS check every 5 minutes
|
||||
// It returns a channel `runError` that receives an error if one of the periodic checks fail.
|
||||
// It returns a channel `runError` that receives an error (nil or not) when a periodic check is performed.
|
||||
// It returns an error if the initial TCP+TLS check fails.
|
||||
// The Checker has to be ultimately stopped by calling [Checker.Stop].
|
||||
func (c *Checker) Start(ctx context.Context) (runError <-chan error, err error) {
|
||||
if c.tlsDialAddr == "" || c.icmpTarget.IsUnspecified() {
|
||||
panic("call Checker.SetConfig with non empty values before Checker.Start")
|
||||
@@ -81,7 +83,8 @@ func (c *Checker) Start(ctx context.Context) (runError <-chan error, err error)
|
||||
c.stop = cancel
|
||||
done := make(chan struct{})
|
||||
c.done = done
|
||||
const smallCheckPeriod = 15 * time.Second
|
||||
c.smallCheckName = "ICMP echo"
|
||||
const smallCheckPeriod = time.Minute
|
||||
smallCheckTimer := time.NewTimer(smallCheckPeriod)
|
||||
const fullCheckPeriod = 5 * time.Minute
|
||||
fullCheckTimer := time.NewTimer(fullCheckPeriod)
|
||||
@@ -99,16 +102,16 @@ func (c *Checker) Start(ctx context.Context) (runError <-chan error, err error)
|
||||
case <-smallCheckTimer.C:
|
||||
err := c.smallPeriodicCheck(ctx)
|
||||
if err != nil {
|
||||
runErrorCh <- fmt.Errorf("periodic small check: %w", err)
|
||||
return
|
||||
err = fmt.Errorf("small periodic check: %w", err)
|
||||
}
|
||||
runErrorCh <- err
|
||||
smallCheckTimer.Reset(smallCheckPeriod)
|
||||
case <-fullCheckTimer.C:
|
||||
err := c.fullPeriodicCheck(ctx)
|
||||
if err != nil {
|
||||
runErrorCh <- fmt.Errorf("periodic full check: %w", err)
|
||||
return
|
||||
err = fmt.Errorf("full periodic check: %w", err)
|
||||
}
|
||||
runErrorCh <- err
|
||||
fullCheckTimer.Reset(fullCheckPeriod)
|
||||
}
|
||||
}
|
||||
@@ -129,8 +132,8 @@ func (c *Checker) smallPeriodicCheck(ctx context.Context) error {
|
||||
ip := c.icmpTarget
|
||||
c.configMutex.Unlock()
|
||||
const maxTries = 3
|
||||
const timeout = 3 * time.Second
|
||||
const extraTryTime = time.Second // 1s added for each subsequent retry
|
||||
const timeout = 10 * time.Second
|
||||
const extraTryTime = 10 * time.Second // 10s added for each subsequent retry
|
||||
check := func(ctx context.Context) error {
|
||||
if c.icmpNotPermitted {
|
||||
return c.dnsClient.Check(ctx)
|
||||
@@ -138,20 +141,21 @@ func (c *Checker) smallPeriodicCheck(ctx context.Context) error {
|
||||
err := c.echoer.Echo(ctx, ip)
|
||||
if errors.Is(err, icmp.ErrNotPermitted) {
|
||||
c.icmpNotPermitted = true
|
||||
c.logger.Warnf("%s; permanently falling back to plaintext DNS checks.", err)
|
||||
c.smallCheckName = "plain DNS over UDP"
|
||||
c.logger.Infof("%s; permanently falling back to %s checks.", c.smallCheckName, err)
|
||||
return c.dnsClient.Check(ctx)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return withRetries(ctx, maxTries, timeout, extraTryTime, c.logger, "ICMP echo", check)
|
||||
return withRetries(ctx, maxTries, timeout, extraTryTime, c.logger, c.smallCheckName, check)
|
||||
}
|
||||
|
||||
func (c *Checker) fullPeriodicCheck(ctx context.Context) error {
|
||||
const maxTries = 2
|
||||
// 10s timeout in case the connection is under stress
|
||||
// 20s timeout in case the connection is under stress
|
||||
// See https://github.com/qdm12/gluetun/issues/2270
|
||||
const timeout = 10 * time.Second
|
||||
const extraTryTime = 3 * time.Second // 3s added for each subsequent retry
|
||||
const timeout = 20 * time.Second
|
||||
const extraTryTime = 10 * time.Second // 10s added for each subsequent retry
|
||||
check := func(ctx context.Context) error {
|
||||
return tcpTLSCheck(ctx, c.dialer, c.tlsDialAddr)
|
||||
}
|
||||
@@ -215,9 +219,10 @@ func makeAddressToDial(address string) (addressToDial string, err error) {
|
||||
var ErrAllCheckTriesFailed = errors.New("all check tries failed")
|
||||
|
||||
func withRetries(ctx context.Context, maxTries uint, tryTimeout, extraTryTime time.Duration,
|
||||
warner Logger, checkName string, check func(ctx context.Context) error,
|
||||
logger Logger, checkName string, check func(ctx context.Context) error,
|
||||
) error {
|
||||
try := uint(0)
|
||||
var errs []error
|
||||
for {
|
||||
timeout := tryTimeout + time.Duration(try)*extraTryTime //nolint:gosec
|
||||
checkCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
@@ -227,13 +232,19 @@ func withRetries(ctx context.Context, maxTries uint, tryTimeout, extraTryTime ti
|
||||
case err == nil:
|
||||
return nil
|
||||
case ctx.Err() != nil:
|
||||
return fmt.Errorf("%s context error: %w", checkName, ctx.Err())
|
||||
default:
|
||||
warner.Warnf("%s attempt %d/%d failed: %v", checkName, try+1, maxTries, err)
|
||||
try++
|
||||
if try == maxTries {
|
||||
return fmt.Errorf("%w: %s: after %d attempts", ErrAllCheckTriesFailed, checkName, maxTries)
|
||||
}
|
||||
return fmt.Errorf("%s: %w", checkName, ctx.Err())
|
||||
}
|
||||
logger.Debugf("%s attempt %d/%d failed: %s", checkName, try+1, maxTries, err)
|
||||
errs = append(errs, err)
|
||||
try++
|
||||
if try < maxTries {
|
||||
continue
|
||||
}
|
||||
errStrings := make([]string, len(errs))
|
||||
for i, err := range errs {
|
||||
errStrings[i] = fmt.Sprintf("attempt %d: %s", i+1, err.Error())
|
||||
}
|
||||
return fmt.Errorf("%w: after %d %s attempts (%s)",
|
||||
ErrAllCheckTriesFailed, maxTries, checkName, strings.Join(errStrings, "; "))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func Test_Checker_fullcheck(t *testing.T) {
|
||||
err := checker.fullPeriodicCheck(canceledCtx)
|
||||
|
||||
require.Error(t, err)
|
||||
assert.EqualError(t, err, "TCP+TLS dial context error: context canceled")
|
||||
assert.EqualError(t, err, "TCP+TLS dial: context canceled")
|
||||
})
|
||||
|
||||
t.Run("dial localhost:0", func(t *testing.T) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -153,7 +153,7 @@ func receiveEchoReply(conn net.PacketConn, id int, buffer []byte, ipVersion stri
|
||||
// Note we need to read the whole packet in one call to ReadFrom, so the buffer
|
||||
// must be large enough to read the entire reply packet. See:
|
||||
// https://groups.google.com/g/golang-nuts/c/5dy2Q4nPs08/m/KmuSQAGEtG4J
|
||||
bytesRead, _, err := conn.ReadFrom(buffer)
|
||||
bytesRead, returnAddr, err := conn.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading from ICMP connection: %w", err)
|
||||
}
|
||||
@@ -168,23 +168,26 @@ func receiveEchoReply(conn net.PacketConn, id int, buffer []byte, ipVersion stri
|
||||
switch body := message.Body.(type) {
|
||||
case *icmp.Echo:
|
||||
if id != body.ID {
|
||||
logger.Warnf("ignoring ICMP echo reply mismatching expected id %d (id: %d, type: %d, code: %d, length: %d)",
|
||||
id, body.ID, message.Type, message.Code, len(packetBytes))
|
||||
logger.Warnf("ignoring ICMP echo reply mismatching expected id %d "+
|
||||
"(id: %d, type: %d, code: %d, length: %d, return address %s)",
|
||||
id, body.ID, message.Type, message.Code, len(packetBytes), returnAddr)
|
||||
continue // not the ID we are looking for
|
||||
}
|
||||
return body.Data, nil
|
||||
case *icmp.DstUnreach:
|
||||
logger.Debugf("ignoring ICMP destination unreachable message (type: 3, code: %d, expected-id %d)", message.Code, id)
|
||||
logger.Debugf("ignoring ICMP destination unreachable message (type: 3, code: %d, return address %s, expected-id %d)",
|
||||
message.Code, returnAddr, id)
|
||||
// See https://github.com/qdm12/gluetun/pull/2923#issuecomment-3377532249
|
||||
// on why we ignore this message. If it is actually unreachable, the timeout on waiting for
|
||||
// the echo reply will do instead of returning an error error.
|
||||
continue
|
||||
case *icmp.TimeExceeded:
|
||||
logger.Debugf("ignoring ICMP time exceeded message (type: 11, code: %d, expected-id %d)", message.Code, id)
|
||||
logger.Debugf("ignoring ICMP time exceeded message (type: 11, code: %d, return address %s, expected-id %d)",
|
||||
message.Code, returnAddr, id)
|
||||
continue
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %T (type %d, code %d, expected-id %d)",
|
||||
ErrICMPBodyUnsupported, body, message.Type, message.Code, id)
|
||||
return nil, fmt.Errorf("%w: %T (type %d, code %d, return address %s, expected-id %d)",
|
||||
ErrICMPBodyUnsupported, body, message.Type, message.Code, returnAddr, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package healthcheck
|
||||
type Logger interface {
|
||||
Debugf(format string, args ...any)
|
||||
Info(s string)
|
||||
Infof(format string, args ...any)
|
||||
Warnf(format string, args ...any)
|
||||
Error(s string)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -61,10 +60,10 @@ func (s *Service) cleanup() (err error) {
|
||||
s.ports = nil
|
||||
|
||||
filepath := s.settings.Filepath
|
||||
s.logger.Info("removing port file " + filepath)
|
||||
err = os.Remove(filepath)
|
||||
s.logger.Info("clearing port file " + filepath)
|
||||
err = s.writePortForwardedFile(nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("removing port file: %w", err)
|
||||
return fmt.Errorf("clearing port file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -114,6 +114,11 @@ func (l *Loop) run(runCtx context.Context, runDone chan<- struct{},
|
||||
continue
|
||||
}
|
||||
|
||||
if !*l.settings.Enabled {
|
||||
singleRunResult <- nil
|
||||
continue
|
||||
}
|
||||
|
||||
result, err := l.fetcher.FetchInfo(singleRunCtx, netip.Addr{})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("fetching information: %w", err)
|
||||
|
||||
@@ -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
@@ -57,14 +57,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
|
||||
}
|
||||
@@ -76,10 +76,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
|
||||
@@ -87,7 +87,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
|
||||
@@ -100,7 +100,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
|
||||
@@ -108,6 +108,6 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
||||
l.statusManager.Unlock()
|
||||
}
|
||||
}
|
||||
openvpnCancel()
|
||||
vpnCancel()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,9 +53,6 @@ func (l *Loop) onTunnelUp(ctx, loopCtx context.Context, data tunnelUpData) {
|
||||
l.restartVPN(loopCtx, err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = l.healthChecker.Stop()
|
||||
}()
|
||||
|
||||
mtuLogger := l.logger.New(log.SetComponent("MTU discovery"))
|
||||
err = updateToMaxMTU(ctx, data.vpnIntf, data.vpnType,
|
||||
@@ -64,7 +61,7 @@ func (l *Loop) onTunnelUp(ctx, loopCtx context.Context, data tunnelUpData) {
|
||||
mtuLogger.Error(err.Error())
|
||||
}
|
||||
|
||||
if *l.dnsLooper.GetSettings().DoT.Enabled {
|
||||
if *l.dnsLooper.GetSettings().ServerEnabled {
|
||||
_, _ = l.dnsLooper.ApplyStatus(ctx, constants.Running)
|
||||
} else {
|
||||
err := check.WaitForDNS(ctx, check.Settings{})
|
||||
@@ -93,13 +90,33 @@ func (l *Loop) onTunnelUp(ctx, loopCtx context.Context, data tunnelUpData) {
|
||||
l.logger.Error(err.Error())
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case healthErr := <-healthErrCh:
|
||||
l.healthServer.SetError(healthErr)
|
||||
// Note this restart call must be done in a separate goroutine
|
||||
// from the VPN loop goroutine.
|
||||
l.restartVPN(loopCtx, healthErr)
|
||||
l.collectHealthErrors(ctx, loopCtx, healthErrCh)
|
||||
}
|
||||
|
||||
func (l *Loop) collectHealthErrors(ctx, loopCtx context.Context, healthErrCh <-chan error) {
|
||||
var previousHealthErr error
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
_ = l.healthChecker.Stop()
|
||||
return
|
||||
case healthErr := <-healthErrCh:
|
||||
l.healthServer.SetError(healthErr)
|
||||
if healthErr != nil {
|
||||
if *l.healthSettings.RestartVPN {
|
||||
// Note this restart call must be done in a separate goroutine
|
||||
// from the VPN loop goroutine.
|
||||
_ = l.healthChecker.Stop()
|
||||
l.restartVPN(loopCtx, healthErr)
|
||||
return
|
||||
}
|
||||
l.logger.Warnf("healthcheck failed: %s", healthErr)
|
||||
l.logger.Info("👉 See https://github.com/qdm12/gluetun-wiki/blob/main/faq/healthcheck.md")
|
||||
} else if previousHealthErr != nil {
|
||||
l.logger.Info("healthcheck passed successfully after previous failure(s)")
|
||||
}
|
||||
previousHealthErr = healthErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,14 @@ func (w *Wireguard) addRoutes(link netlink.Link, destinations []netip.Prefix,
|
||||
func (w *Wireguard) addRoute(link netlink.Link, dst netip.Prefix,
|
||||
firewallMark uint32,
|
||||
) (err error) {
|
||||
family := netlink.FamilyV4
|
||||
if dst.Addr().Is6() {
|
||||
family = netlink.FamilyV6
|
||||
}
|
||||
route := netlink.Route{
|
||||
LinkIndex: link.Index,
|
||||
Dst: dst,
|
||||
Family: family,
|
||||
Table: int(firewallMark),
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ func Test_Wireguard_addRoute(t *testing.T) {
|
||||
expectedRoute: netlink.Route{
|
||||
LinkIndex: linkIndex,
|
||||
Dst: ipPrefix,
|
||||
Family: netlink.FamilyV4,
|
||||
Table: firewallMark,
|
||||
},
|
||||
},
|
||||
@@ -49,6 +50,7 @@ func Test_Wireguard_addRoute(t *testing.T) {
|
||||
expectedRoute: netlink.Route{
|
||||
LinkIndex: linkIndex,
|
||||
Dst: ipPrefix,
|
||||
Family: netlink.FamilyV4,
|
||||
Table: firewallMark,
|
||||
},
|
||||
routeAddErr: errDummy,
|
||||
|
||||
Reference in New Issue
Block a user