diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index ce7b3ee5..ce48feed 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -14,6 +14,7 @@ import ( "github.com/qdm12/dns/pkg/unbound" "github.com/qdm12/gluetun/internal/alpine" "github.com/qdm12/gluetun/internal/cli" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/dns" "github.com/qdm12/gluetun/internal/firewall" @@ -22,11 +23,9 @@ import ( gluetunLogging "github.com/qdm12/gluetun/internal/logging" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/openvpn" - "github.com/qdm12/gluetun/internal/params" "github.com/qdm12/gluetun/internal/publicip" "github.com/qdm12/gluetun/internal/routing" "github.com/qdm12/gluetun/internal/server" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/gluetun/internal/shadowsocks" "github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/gluetun/internal/unix" @@ -35,6 +34,7 @@ import ( "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" "github.com/qdm12/golibs/os/user" + "github.com/qdm12/golibs/params" "github.com/qdm12/updated/pkg/dnscrypto" ) @@ -140,7 +140,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, routingConf := routing.NewRouting(logger) firewallConf := firewall.NewConfigurator(logger, routingConf, os.OpenFile) - paramsReader := params.NewReader(logger, os) fmt.Println(gluetunLogging.Splash(buildInfo)) printVersions(ctx, logger, map[string]func(ctx context.Context) (string, error){ @@ -149,10 +148,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, "IPtables": firewallConf.Version, }) - allSettings, warnings, err := settings.GetAllSettings(paramsReader) - for _, warning := range warnings { - logger.Warn(warning) - } + var allSettings configuration.Settings + err := allSettings.Read(params.NewEnv(), os, logger.WithPrefix("configuration: ")) if err != nil { return err } diff --git a/go.mod b/go.mod index e6dd6b29..da0b42af 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/golang/mock v1.4.4 github.com/kyokomi/emoji v2.2.4+incompatible github.com/qdm12/dns v1.4.0 - github.com/qdm12/golibs v0.0.0-20210124192933-79a950eaf217 + github.com/qdm12/golibs v0.0.0-20210206072445-35759e951561 github.com/qdm12/ss-server v0.1.0 github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 8fc7e900..55b9e98a 100644 --- a/go.sum +++ b/go.sum @@ -97,6 +97,8 @@ github.com/qdm12/dns v1.4.0/go.mod h1:WUY4/U8Z2O8888DPrahrIBv8GdYeoIcEy4aUDecZ+U github.com/qdm12/golibs v0.0.0-20201227203847-2fd99ffdfdba/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc= github.com/qdm12/golibs v0.0.0-20210124192933-79a950eaf217 h1:/eMBq0vbc/KmVPXbwLfssp547pp6APRS1x/JNmPvm0s= github.com/qdm12/golibs v0.0.0-20210124192933-79a950eaf217/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc= +github.com/qdm12/golibs v0.0.0-20210206072445-35759e951561 h1:YgdQYYj4cEq8jK9TWCItlOOLfmDMVMajcp0YGVCW7cA= +github.com/qdm12/golibs v0.0.0-20210206072445-35759e951561/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc= github.com/qdm12/ss-server v0.1.0 h1:WV9MkHCDEWRwe4WpnYFeR/zcZAxYoTbfntLDnw9AQ50= github.com/qdm12/ss-server v0.1.0/go.mod h1:ABVUkxubboL3vqBkOwDV9glX1/x7SnYrckBe5d+M/zw= github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a h1:gkyP+gMEeBgMgyRYGrVNcoy6cL1065IvXsyfB6xboIc= diff --git a/internal/cli/openvpnconfig.go b/internal/cli/openvpnconfig.go index 27871b27..061a5b58 100644 --- a/internal/cli/openvpnconfig.go +++ b/internal/cli/openvpnconfig.go @@ -5,13 +5,13 @@ import ( "strings" "time" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" - "github.com/qdm12/gluetun/internal/params" "github.com/qdm12/gluetun/internal/provider" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" + "github.com/qdm12/golibs/params" ) func (c *cli) OpenvpnConfig(os os.OS) error { @@ -19,8 +19,9 @@ func (c *cli) OpenvpnConfig(os os.OS) error { if err != nil { return err } - paramsReader := params.NewReader(logger, os) - allSettings, _, err := settings.GetAllSettings(paramsReader) + + var allSettings configuration.Settings + err = allSettings.Read(params.NewEnv(), os, logger) if err != nil { return err } diff --git a/internal/cli/update.go b/internal/cli/update.go index 611c7305..d9c49ea9 100644 --- a/internal/cli/update.go +++ b/internal/cli/update.go @@ -7,8 +7,8 @@ import ( "net/http" "time" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/gluetun/internal/updater" "github.com/qdm12/golibs/logging" @@ -16,7 +16,7 @@ import ( ) func (c *cli) Update(ctx context.Context, args []string, os os.OS) error { - options := settings.Updater{CLI: true} + options := configuration.Updater{CLI: true} var flushToFile bool flagSet := flag.NewFlagSet("update", flag.ExitOnError) flagSet.BoolVar(&flushToFile, "file", false, "Write results to /gluetun/servers.json (for end users)") diff --git a/internal/configuration/configuration.go b/internal/configuration/configuration.go new file mode 100644 index 00000000..2db2e5dc --- /dev/null +++ b/internal/configuration/configuration.go @@ -0,0 +1,3 @@ +// Package configuration reads initial settings from environment variables +// and secret files. +package configuration diff --git a/internal/configuration/constants.go b/internal/configuration/constants.go new file mode 100644 index 00000000..7610d498 --- /dev/null +++ b/internal/configuration/constants.go @@ -0,0 +1,6 @@ +package configuration + +const ( + lastIndent = "|--" + indent = " " +) diff --git a/internal/configuration/cyberghost.go b/internal/configuration/cyberghost.go new file mode 100644 index 00000000..74dfcda5 --- /dev/null +++ b/internal/configuration/cyberghost.go @@ -0,0 +1,107 @@ +package configuration + +import ( + "encoding/pem" + "fmt" + "strings" + + "github.com/qdm12/gluetun/internal/constants" + "github.com/qdm12/golibs/params" +) + +func (settings *Provider) cyberghostLines() (lines []string) { + lines = append(lines, lastIndent+"Server group: "+settings.ServerSelection.Group) + + if len(settings.ServerSelection.Regions) > 0 { + lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions)) + } + + if settings.ExtraConfigOptions.ClientKey != "" { + lines = append(lines, lastIndent+"Client key is set") + } + + if settings.ExtraConfigOptions.ClientCertificate != "" { + lines = append(lines, lastIndent+"Client certificate is set") + } + + return lines +} + +func (settings *Provider) readCyberghost(r reader) (err error) { + settings.Name = constants.Cyberghost + + settings.ServerSelection.Protocol, err = readProtocol(r.env) + if err != nil { + return err + } + + settings.ServerSelection.TargetIP, err = readTargetIP(r.env) + if err != nil { + return err + } + + settings.ExtraConfigOptions.ClientKey, err = readCyberghostClientKey(r) + if err != nil { + return err + } + + settings.ExtraConfigOptions.ClientCertificate, err = readCyberghostClientCertificate(r) + if err != nil { + return err + } + + settings.ServerSelection.Group, err = r.env.Inside("CYBERGHOST_GROUP", + constants.CyberghostGroupChoices(), params.Default("Premium UDP Europe")) + if err != nil { + return err + } + + settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.CyberghostRegionChoices()) + if err != nil { + return err + } + + return nil +} + +func readCyberghostClientKey(r reader) (clientKey string, err error) { + b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTKEY", string(constants.ClientKey)) + if err != nil { + return "", err + } + return extractClientKey(b) +} + +func extractClientKey(b []byte) (key string, err error) { + pemBlock, _ := pem.Decode(b) + if pemBlock == nil { + return "", fmt.Errorf("cannot decode PEM block from client key") + } + parsedBytes := pem.EncodeToMemory(pemBlock) + s := string(parsedBytes) + s = strings.ReplaceAll(s, "\n", "") + s = strings.TrimPrefix(s, "-----BEGIN PRIVATE KEY-----") + s = strings.TrimSuffix(s, "-----END PRIVATE KEY-----") + return s, nil +} + +func readCyberghostClientCertificate(r reader) (clientCertificate string, err error) { + b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTCRT", string(constants.ClientCertificate)) + if err != nil { + return "", err + } + return extractClientCertificate(b) +} + +func extractClientCertificate(b []byte) (certificate string, err error) { + pemBlock, _ := pem.Decode(b) + if pemBlock == nil { + return "", fmt.Errorf("cannot decode PEM block from client certificate") + } + parsedBytes := pem.EncodeToMemory(pemBlock) + s := string(parsedBytes) + s = strings.ReplaceAll(s, "\n", "") + s = strings.TrimPrefix(s, "-----BEGIN CERTIFICATE-----") + s = strings.TrimSuffix(s, "-----END CERTIFICATE-----") + return s, nil +} diff --git a/internal/params/cyberghost_test.go b/internal/configuration/cyberghost_test.go similarity index 99% rename from internal/params/cyberghost_test.go rename to internal/configuration/cyberghost_test.go index c714819e..54476c9f 100644 --- a/internal/params/cyberghost_test.go +++ b/internal/configuration/cyberghost_test.go @@ -1,4 +1,4 @@ -package params +package configuration import ( "fmt" diff --git a/internal/configuration/dns.go b/internal/configuration/dns.go new file mode 100644 index 00000000..969e9211 --- /dev/null +++ b/internal/configuration/dns.go @@ -0,0 +1,154 @@ +package configuration + +import ( + "errors" + "fmt" + "net" + "strings" + "time" + + unboundmodels "github.com/qdm12/dns/pkg/models" + unbound "github.com/qdm12/dns/pkg/unbound" + "github.com/qdm12/golibs/params" +) + +// DNS contains settings to configure Unbound for DNS over TLS operation. +type DNS struct { //nolint:maligned + Enabled bool + PlaintextAddress net.IP + KeepNameserver bool + BlockMalicious bool + BlockAds bool + BlockSurveillance bool + UpdatePeriod time.Duration + Unbound unboundmodels.Settings +} + +func (settings *DNS) String() string { + return strings.Join(settings.lines(), "\n") +} + +func (settings *DNS) lines() (lines []string) { + lines = append(lines, lastIndent+"DNS:") + + if settings.PlaintextAddress != nil { + lines = append(lines, indent+lastIndent+"Plaintext address: "+settings.PlaintextAddress.String()) + } + + if settings.KeepNameserver { + lines = append(lines, indent+lastIndent+"Keep nameserver (disabled blocking): yes") + } + + if !settings.Enabled { + return lines + } + + lines = append(lines, indent+lastIndent+"DNS over TLS:") + + lines = append(lines, indent+indent+lastIndent+"Unbound:") + for _, line := range settings.Unbound.Lines() { + lines = append(lines, indent+indent+indent+line) + } + + if settings.BlockMalicious { + lines = append(lines, indent+indent+lastIndent+"Block malicious: enabled") + } + + if settings.BlockAds { + lines = append(lines, indent+indent+lastIndent+"Block ads: enabled") + } + + if settings.BlockSurveillance { + lines = append(lines, indent+indent+lastIndent+"Block surveillance: enabled") + } + + if settings.UpdatePeriod > 0 { + lines = append(lines, indent+indent+lastIndent+"Update: every "+settings.UpdatePeriod.String()) + } + + return lines +} + +var ( + ErrUnboundSettings = errors.New("failed getting Unbound settings") + ErrDNSProviderNoData = errors.New("DNS provider has no associated data") + ErrDNSProviderNoTLS = errors.New("DNS provider does not support DNS over TLS") + ErrDNSNoIPv6Support = errors.New("no DNS provider supports IPv6") +) + +func (settings *DNS) read(r reader) (err error) { + settings.Enabled, err = r.env.OnOff("DOT", params.Default("on")) + if err != nil { + return err + } + + // Plain DNS settings + if err := settings.readDNSPlaintext(r.env); err != nil { + return err + } + settings.KeepNameserver, err = r.env.OnOff("DNS_KEEP_NAMESERVER", params.Default("off")) + if err != nil { + return err + } + + // DNS over TLS external settings + settings.BlockMalicious, err = r.env.OnOff("BLOCK_MALICIOUS", params.Default("on")) + if err != nil { + return err + } + settings.BlockSurveillance, err = r.env.OnOff("BLOCK_SURVEILLANCE", params.Default("on"), + params.RetroKeys([]string{"BLOCK_NSA"}, r.onRetroActive)) + if err != nil { + return err + } + settings.BlockAds, err = r.env.OnOff("BLOCK_ADS", params.Default("off")) + if err != nil { + return err + } + settings.UpdatePeriod, err = r.env.Duration("DNS_UPDATE_PERIOD", params.Default("24h")) + if err != nil { + return err + } + + if err := settings.readUnbound(r); err != nil { + return fmt.Errorf("%w: %s", ErrUnboundSettings, err) + } + + // Consistency check + IPv6Support := false + for _, provider := range settings.Unbound.Providers { + providerData, ok := unbound.GetProviderData(provider) + switch { + case !ok: + return fmt.Errorf("%w: %s", ErrDNSProviderNoData, provider) + case !providerData.SupportsTLS: + return fmt.Errorf("%w: %s", ErrDNSProviderNoTLS, provider) + case providerData.SupportsIPv6: + IPv6Support = true + } + } + + if settings.Unbound.IPv6 && !IPv6Support { + return ErrDNSNoIPv6Support + } + + return nil +} + +var ( + ErrDNSAddressNotAnIP = errors.New("DNS plaintext address is not an IP address") +) + +func (settings *DNS) readDNSPlaintext(env params.Env) error { + s, err := env.Get("DNS_PLAINTEXT_ADDRESS", params.Default("1.1.1.1")) + if err != nil { + return err + } + + settings.PlaintextAddress = net.ParseIP(s) + if settings.PlaintextAddress == nil { + return fmt.Errorf("%w: %s", ErrDNSAddressNotAnIP, s) + } + + return nil +} diff --git a/internal/configuration/dns_test.go b/internal/configuration/dns_test.go new file mode 100644 index 00000000..ccfb4e7f --- /dev/null +++ b/internal/configuration/dns_test.go @@ -0,0 +1,73 @@ +package configuration + +import ( + "net" + "testing" + "time" + + "github.com/qdm12/dns/pkg/models" + "github.com/stretchr/testify/assert" +) + +func Test_DNS_Lines(t *testing.T) { + t.Parallel() + testCases := map[string]struct { + settings DNS + lines []string + }{ + "disabled DOT": { + settings: DNS{ + PlaintextAddress: net.IP{1, 1, 1, 1}, + }, + lines: []string{ + "|--DNS:", + " |--Plaintext address: 1.1.1.1", + }, + }, + "enabled DOT": { + settings: DNS{ + Enabled: true, + KeepNameserver: true, + Unbound: models.Settings{ + Providers: []string{"cloudflare"}, + }, + BlockMalicious: true, + BlockAds: true, + BlockSurveillance: true, + UpdatePeriod: time.Hour, + }, + lines: []string{ + "|--DNS:", + " |--Keep nameserver (disabled blocking): yes", + " |--DNS over TLS:", + " |--Unbound:", + " |--DNS over TLS providers:", + " |--cloudflare", + " |--Listening port: 0", + " |--Access control:", + " |--Allowed:", + " |--Caching: disabled", + " |--IPv4 resolution: disabled", + " |--IPv6 resolution: disabled", + " |--Verbosity level: 0/5", + " |--Verbosity details level: 0/4", + " |--Validation log level: 0/2", + " |--Blocked hostnames:", + " |--Blocked IP addresses:", + " |--Allowed hostnames:", + " |--Block malicious: enabled", + " |--Block ads: enabled", + " |--Block surveillance: enabled", + " |--Update: every 1h0m0s", + }, + }, + } + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + lines := testCase.settings.lines() + assert.Equal(t, testCase.lines, lines) + }) + } +} diff --git a/internal/configuration/firewall.go b/internal/configuration/firewall.go new file mode 100644 index 00000000..b7049d22 --- /dev/null +++ b/internal/configuration/firewall.go @@ -0,0 +1,93 @@ +package configuration + +import ( + "net" + "strings" + + "github.com/qdm12/golibs/params" +) + +// Firewall contains settings to customize the firewall operation. +type Firewall struct { + VPNInputPorts []uint16 + InputPorts []uint16 + OutboundSubnets []net.IPNet + Enabled bool + Debug bool +} + +func (settings *Firewall) String() string { + return strings.Join(settings.lines(), "\n") +} + +func (settings *Firewall) lines() (lines []string) { + if !settings.Enabled { + lines = append(lines, lastIndent+"Firewall: disabled ⚠️") + return lines + } + + lines = append(lines, lastIndent+"Firewall:") + + if settings.Debug { + lines = append(lines, indent+lastIndent+"Debug: on") + } + + if len(settings.VPNInputPorts) > 0 { + lines = append(lines, indent+lastIndent+"VPN input ports: "+ + strings.Join(uint16sToStrings(settings.VPNInputPorts), ", ")) + } + + if len(settings.InputPorts) > 0 { + lines = append(lines, indent+lastIndent+"Input ports: "+ + strings.Join(uint16sToStrings(settings.InputPorts), ", ")) + } + + if len(settings.OutboundSubnets) > 0 { + lines = append(lines, indent+lastIndent+"Outbound subnets: "+ + strings.Join(ipNetsToStrings(settings.OutboundSubnets), ", ")) + } + + return lines +} + +func (settings *Firewall) read(r reader) (err error) { + settings.Enabled, err = r.env.OnOff("FIREWALL", params.Default("on")) + if err != nil { + return err + } + + settings.Debug, err = r.env.OnOff("FIREWALL_DEBUG", params.Default("off")) + if err != nil { + return err + } + + if err := settings.readVPNInputPorts(r.env); err != nil { + return err + } + + if err := settings.readInputPorts(r.env); err != nil { + return err + } + + if err := settings.readOutboundSubnets(r); err != nil { + return err + } + + return nil +} + +func (settings *Firewall) readVPNInputPorts(env params.Env) (err error) { + settings.VPNInputPorts, err = readCSVPorts(env, "FIREWALL_VPN_INPUT_PORTS") + return err +} + +func (settings *Firewall) readInputPorts(env params.Env) (err error) { + settings.VPNInputPorts, err = readCSVPorts(env, "FIREWALL_INPUT_PORTS") + return err +} + +func (settings *Firewall) readOutboundSubnets(r reader) (err error) { + retroOption := params.RetroKeys([]string{"EXTRA_SUBNETS"}, r.onRetroActive) + settings.OutboundSubnets, err = readCSVIPNets(r.env, "FIREWALL_OUTBOUND_SUBNETS", retroOption) + return err +} diff --git a/internal/configuration/httpproxy.go b/internal/configuration/httpproxy.go new file mode 100644 index 00000000..6f8f4e2d --- /dev/null +++ b/internal/configuration/httpproxy.go @@ -0,0 +1,105 @@ +package configuration + +import ( + "strconv" + "strings" + + "github.com/qdm12/golibs/params" +) + +// HTTPProxy contains settings to configure the HTTP proxy. +type HTTPProxy struct { + User string + Password string + Port uint16 + Enabled bool + Stealth bool + Log bool +} + +func (settings *HTTPProxy) String() string { + return strings.Join(settings.lines(), "\n") +} + +func (settings *HTTPProxy) lines() (lines []string) { + if !settings.Enabled { + return nil + } + + lines = append(lines, lastIndent+"HTTP proxy:") + + lines = append(lines, indent+lastIndent+"Port: "+strconv.Itoa(int(settings.Port))) + + if settings.User != "" { + lines = append(lines, indent+lastIndent+"Authentication: enabled") + } + + if settings.Log { + lines = append(lines, indent+lastIndent+"Log: enabled") + } + + if settings.Stealth { + lines = append(lines, indent+lastIndent+"Stealth: enabled") + } + + return lines +} + +func (settings *HTTPProxy) read(r reader) (err error) { + settings.Enabled, err = r.env.OnOff("HTTPPROXY", params.Default("off"), + params.RetroKeys([]string{"TINYPROXY", "PROXY"}, r.onRetroActive)) + if err != nil { + return err + } + + settings.User, err = r.getFromEnvOrSecretFile("HTTPPROXY_USER", false, // compulsory + []string{"TINYPROXY_USER", "PROXY_USER"}) + if err != nil { + return err + } + + settings.Password, err = r.getFromEnvOrSecretFile("HTTPPROXY_USER", false, + []string{"TINYPROXY_PASSWORD", "PROXY_PASSWORD"}) + if err != nil { + return err + } + + settings.Stealth, err = r.env.OnOff("HTTPPROXY_STEALTH", params.Default("off")) + if err != nil { + return err + } + + if err := settings.readLog(r); err != nil { + return err + } + + var warning string + settings.Port, warning, err = r.env.ListeningPort("HTTPPROXY_PORT", params.Default("8888"), + params.RetroKeys([]string{"TINYPROXY_PORT", "PROXY_PORT"}, r.onRetroActive)) + if len(warning) > 0 { + r.logger.Warn(warning) + } + if err != nil { + return err + } + + return nil +} + +func (settings *HTTPProxy) readLog(r reader) error { + s, err := r.env.Get("HTTPPROXY_LOG", + params.RetroKeys([]string{"PROXY_LOG_LEVEL", "TINYPROXY_LOG"}, r.onRetroActive)) + if err != nil { + return err + } + + switch strings.ToLower(s) { + case "on": + settings.Enabled = true + // Retro compatibility + case "info", "connect", "notice": + settings.Enabled = true + } + + return nil +} diff --git a/internal/configuration/lines.go b/internal/configuration/lines.go new file mode 100644 index 00000000..8f7c00e9 --- /dev/null +++ b/internal/configuration/lines.go @@ -0,0 +1,22 @@ +package configuration + +import ( + "net" + "strconv" +) + +func uint16sToStrings(uint16s []uint16) (strings []string) { + strings = make([]string, len(uint16s)) + for i := range uint16s { + strings[i] = strconv.Itoa(int(uint16s[i])) + } + return strings +} + +func ipNetsToStrings(ipNets []net.IPNet) (strings []string) { + strings = make([]string, len(ipNets)) + for i := range ipNets { + strings[i] = ipNets[i].String() + } + return strings +} diff --git a/internal/configuration/mullvad.go b/internal/configuration/mullvad.go new file mode 100644 index 00000000..311d2551 --- /dev/null +++ b/internal/configuration/mullvad.go @@ -0,0 +1,79 @@ +package configuration + +import ( + "strconv" + + "github.com/qdm12/gluetun/internal/constants" + "github.com/qdm12/golibs/params" +) + +func (settings *Provider) mullvadLines() (lines []string) { + if len(settings.ServerSelection.Countries) > 0 { + lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries)) + } + + if len(settings.ServerSelection.Cities) > 0 { + lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities)) + } + + if len(settings.ServerSelection.ISPs) > 0 { + lines = append(lines, lastIndent+"ISPs: "+commaJoin(settings.ServerSelection.ISPs)) + } + + if settings.ServerSelection.CustomPort > 0 { + lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort))) + } + + if settings.ExtraConfigOptions.OpenVPNIPv6 { + lines = append(lines, lastIndent+"IPv6: enabled") + } + + return lines +} + +func (settings *Provider) readMullvad(r reader) (err error) { + settings.Name = constants.Mullvad + + settings.ServerSelection.Protocol, err = readProtocol(r.env) + if err != nil { + return err + } + + settings.ServerSelection.TargetIP, err = readTargetIP(r.env) + if err != nil { + return err + } + + settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.MullvadCountryChoices()) + if err != nil { + return err + } + + settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.MullvadCityChoices()) + if err != nil { + return err + } + + settings.ServerSelection.ISPs, err = r.env.CSVInside("ISP", constants.MullvadISPChoices()) + if err != nil { + return err + } + + settings.ServerSelection.CustomPort, err = readCustomPort(r.env, settings.ServerSelection.Protocol, + []uint16{80, 443, 1401}, []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}) + if err != nil { + return err + } + + settings.ServerSelection.Owned, err = r.env.YesNo("OWNED", params.Default("no")) + if err != nil { + return err + } + + settings.ExtraConfigOptions.OpenVPNIPv6, err = r.env.OnOff("OPENVPN_IPV6", params.Default("off")) + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/nordvpn.go b/internal/configuration/nordvpn.go new file mode 100644 index 00000000..bd1f4b85 --- /dev/null +++ b/internal/configuration/nordvpn.go @@ -0,0 +1,72 @@ +package configuration + +import ( + "fmt" + "strconv" + + "github.com/qdm12/gluetun/internal/constants" + "github.com/qdm12/golibs/params" +) + +func (settings *Provider) nordvpnLines() (lines []string) { + if len(settings.ServerSelection.Regions) > 0 { + lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions)) + } + + if numbersUint16 := settings.ServerSelection.Numbers; len(numbersUint16) > 0 { + numbersString := make([]string, len(numbersUint16)) + for i, numberUint16 := range numbersUint16 { + numbersString[i] = strconv.Itoa(int(numberUint16)) + } + lines = append(lines, lastIndent+"Numbers: "+commaJoin(numbersString)) + } + + return lines +} + +func (settings *Provider) readNordvpn(r reader) (err error) { + settings.Name = constants.Nordvpn + + settings.ServerSelection.Protocol, err = readProtocol(r.env) + if err != nil { + return err + } + + settings.ServerSelection.TargetIP, err = readTargetIP(r.env) + if err != nil { + return err + } + + settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.NordvpnRegionChoices()) + if err != nil { + return err + } + + settings.ServerSelection.Numbers, err = readNordVPNServerNumbers(r.env) + if err != nil { + return err + } + + return nil +} + +func readNordVPNServerNumbers(env params.Env) (numbers []uint16, err error) { + possibilities := make([]string, 65537) + for i := range possibilities { + possibilities[i] = fmt.Sprintf("%d", i) + } + possibilities[65536] = "" + values, err := env.CSVInside("SERVER_NUMBER", possibilities) + if err != nil { + return nil, err + } + numbers = make([]uint16, len(values)) + for i := range values { + n, err := strconv.Atoi(values[i]) + if err != nil { + return nil, err + } + numbers[i] = uint16(n) + } + return numbers, nil +} diff --git a/internal/configuration/openvpn.go b/internal/configuration/openvpn.go new file mode 100644 index 00000000..75e3eb9c --- /dev/null +++ b/internal/configuration/openvpn.go @@ -0,0 +1,139 @@ +package configuration + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/qdm12/gluetun/internal/constants" + "github.com/qdm12/gluetun/internal/models" + "github.com/qdm12/golibs/params" +) + +// OpenVPN contains settings to configure the OpenVPN client. +type OpenVPN struct { + User string `json:"user"` + Password string `json:"password"` + Verbosity int `json:"verbosity"` + MSSFix uint16 `json:"mssfix"` + Root bool `json:"run_as_root"` + Cipher string `json:"cipher"` + Auth string `json:"auth"` + Provider Provider `json:"provider"` +} + +func (settings *OpenVPN) String() string { + return strings.Join(settings.lines(), "\n") +} + +func (settings *OpenVPN) lines() (lines []string) { + lines = append(lines, lastIndent+"OpenVPN:") + + lines = append(lines, indent+lastIndent+"Verbosity level: "+strconv.Itoa(settings.Verbosity)) + + if settings.Root { + lines = append(lines, indent+lastIndent+"Run as root: enabled") + } + + if len(settings.Cipher) > 0 { + lines = append(lines, indent+lastIndent+"Custom cipher: "+settings.Cipher) + } + if len(settings.Auth) > 0 { + lines = append(lines, indent+lastIndent+"Custom auth algorithm: "+settings.Auth) + } + + lines = append(lines, indent+lastIndent+"Provider:") + for _, line := range settings.Provider.lines() { + lines = append(lines, indent+indent+line) + } + + return lines +} + +var ( + ErrInvalidVPNProvider = errors.New("invalid VPN provider") +) + +func (settings *OpenVPN) read(r reader) (err error) { + vpnsp, err := r.env.Inside("VPNSP", []string{ + "pia", "private internet access", "mullvad", "windscribe", "surfshark", + "cyberghost", "vyprvpn", "nordvpn", "purevpn", "privado"}, + params.Default("private internet access")) + if err != nil { + return err + } + if vpnsp == "pia" { // retro compatibility + vpnsp = "private internet access" + } + + settings.Provider.Name = models.VPNProvider(vpnsp) + + settings.User, err = r.getFromEnvOrSecretFile("OPENVPN_USER", true, []string{"USER"}) + if err != nil { + return err + } + // Remove spaces in user ID to simplify user's life, thanks @JeordyR + settings.User = strings.ReplaceAll(settings.User, " ", "") + + if settings.Provider.Name == constants.Mullvad { + settings.Password = "m" + } else { + settings.Password, err = r.getFromEnvOrSecretFile("OPENVPN_PASSWORD", true, []string{"PASSWORD"}) + if err != nil { + return err + } + } + + settings.Verbosity, err = r.env.IntRange("OPENVPN_VERBOSITY", 0, 6, params.Default("1")) + if err != nil { + return err + } + + settings.Root, err = r.env.YesNo("OPENVPN_ROOT", params.Default("no")) + if err != nil { + return err + } + + settings.Cipher, err = r.env.Get("OPENVPN_CIPHER") + if err != nil { + return err + } + + settings.Auth, err = r.env.Get("OPENVPN_AUTH") + if err != nil { + return err + } + + mssFix, err := r.env.IntRange("OPENVPN_MSSFIX", 0, 10000, params.Default("0")) + if err != nil { + return err + } + settings.MSSFix = uint16(mssFix) + + var readProvider func(r reader) error + switch settings.Provider.Name { + case constants.PrivateInternetAccess: + readProvider = settings.Provider.readPrivateInternetAccess + case constants.Mullvad: + readProvider = settings.Provider.readMullvad + case constants.Windscribe: + readProvider = settings.Provider.readWindscribe + case constants.Surfshark: + readProvider = settings.Provider.readSurfshark + case constants.Cyberghost: + readProvider = settings.Provider.readCyberghost + case constants.Vyprvpn: + readProvider = settings.Provider.readVyprvpn + case constants.Nordvpn: + readProvider = settings.Provider.readNordvpn + case constants.Purevpn: + readProvider = settings.Provider.readPurevpn + case constants.Privado: + readProvider = settings.Provider.readPrivado + default: + return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Provider.Name) + } + + return readProvider(r) +} diff --git a/internal/settings/openvpn_test.go b/internal/configuration/openvpn_test.go similarity index 89% rename from internal/settings/openvpn_test.go rename to internal/configuration/openvpn_test.go index c3b0d8d6..7f672216 100644 --- a/internal/settings/openvpn_test.go +++ b/internal/configuration/openvpn_test.go @@ -1,10 +1,9 @@ -package settings +package configuration import ( "encoding/json" "testing" - "github.com/qdm12/gluetun/internal/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -13,7 +12,7 @@ func Test_OpenVPN_JSON(t *testing.T) { t.Parallel() in := OpenVPN{ Root: true, - Provider: models.ProviderSettings{ + Provider: Provider{ Name: "name", }, } diff --git a/internal/configuration/privado.go b/internal/configuration/privado.go new file mode 100644 index 00000000..5c03fbf3 --- /dev/null +++ b/internal/configuration/privado.go @@ -0,0 +1,36 @@ +package configuration + +import ( + "github.com/qdm12/gluetun/internal/constants" + "github.com/qdm12/golibs/params" +) + +func (settings *Provider) privadoLines() (lines []string) { + if len(settings.ServerSelection.Hostnames) > 0 { + lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames)) + } + + return lines +} + +func (settings *Provider) readPrivado(r reader) (err error) { + settings.Name = constants.Privado + + settings.ServerSelection.Protocol, err = readProtocol(r.env) + if err != nil { + return err + } + + settings.ServerSelection.TargetIP, err = readTargetIP(r.env) + if err != nil { + return err + } + + settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", + constants.PrivadoHostnameChoices(), params.RetroKeys([]string{"HOSTNAME"}, r.onRetroActive)) + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/privateinternetaccess.go b/internal/configuration/privateinternetaccess.go new file mode 100644 index 00000000..ae969a4d --- /dev/null +++ b/internal/configuration/privateinternetaccess.go @@ -0,0 +1,79 @@ +package configuration + +import ( + "strconv" + + "github.com/qdm12/gluetun/internal/constants" + "github.com/qdm12/gluetun/internal/models" + "github.com/qdm12/golibs/params" +) + +func (settings *Provider) privateinternetaccessLines() (lines []string) { + if len(settings.ServerSelection.Regions) > 0 { + lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions)) + } + + lines = append(lines, lastIndent+"Encryption preset: "+settings.ServerSelection.EncryptionPreset) + + lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort))) + + if settings.PortForwarding.Enabled { + lines = append(lines, lastIndent+"Port forwarding:") + for _, line := range settings.PortForwarding.lines() { + lines = append(lines, indent+line) + } + } + + return lines +} + +func (settings *Provider) readPrivateInternetAccess(r reader) (err error) { + settings.Name = constants.PrivateInternetAccess + + settings.ServerSelection.Protocol, err = readProtocol(r.env) + if err != nil { + return err + } + + settings.ServerSelection.TargetIP, err = readTargetIP(r.env) + if err != nil { + return err + } + + encryptionPreset, err := r.env.Inside("PIA_ENCRYPTION", + []string{constants.PIAEncryptionPresetNormal, constants.PIAEncryptionPresetStrong}, + params.RetroKeys([]string{"ENCRYPTION"}, r.onRetroActive), + params.Default(constants.PIACertificateStrong), + ) + if err != nil { + return err + } + settings.ServerSelection.EncryptionPreset = encryptionPreset + settings.ExtraConfigOptions.EncryptionPreset = encryptionPreset + + settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PIAGeoChoices()) + if err != nil { + return err + } + + settings.ServerSelection.CustomPort, err = readPortOrZero(r.env, "PORT") + if err != nil { + return err + } + + settings.PortForwarding.Enabled, err = r.env.OnOff("PORT_FORWARDING", params.Default("off")) + if err != nil { + return err + } + + if settings.PortForwarding.Enabled { + filepathStr, err := r.env.Path("PORT_FORWARDING_STATUS_FILE", + params.Default("/tmp/gluetun/forwarded_port"), params.CaseSensitiveValue()) + if err != nil { + return err + } + settings.PortForwarding.Filepath = models.Filepath(filepathStr) + } + + return nil +} diff --git a/internal/configuration/provider.go b/internal/configuration/provider.go new file mode 100644 index 00000000..d96cefd7 --- /dev/null +++ b/internal/configuration/provider.go @@ -0,0 +1,112 @@ +package configuration + +import ( + "errors" + "fmt" + "net" + "strings" + + "github.com/qdm12/gluetun/internal/constants" + "github.com/qdm12/gluetun/internal/models" + "github.com/qdm12/golibs/params" +) + +// Provider contains settings specific to a VPN provider. +type Provider struct { + Name models.VPNProvider `json:"name"` + ServerSelection ServerSelection `json:"server_selection"` + ExtraConfigOptions ExtraConfigOptions `json:"extra_config"` + PortForwarding PortForwarding `json:"port_forwarding"` +} + +func (settings *Provider) lines() (lines []string) { + lines = append(lines, lastIndent+strings.Title(string(settings.Name))+" settings:") + + lines = append(lines, indent+lastIndent+"Network protocol: "+string(settings.ServerSelection.Protocol)) + + if settings.ServerSelection.TargetIP != nil { + lines = append(lines, indent+lastIndent+"Target IP address: "+settings.ServerSelection.TargetIP.String()) + } + + var providerLines []string + switch strings.ToLower(string(settings.Name)) { + case "cyberghost": + providerLines = settings.cyberghostLines() + case "mullvad": + providerLines = settings.mullvadLines() + case "nordvpn": + providerLines = settings.nordvpnLines() + case "privado": + providerLines = settings.privadoLines() + case "private internet access": + providerLines = settings.privateinternetaccessLines() + case "purevpn": + providerLines = settings.purevpnLines() + case "surfshark": + providerLines = settings.surfsharkLines() + case "vyprvpn": + providerLines = settings.vyprvpnLines() + case "windscribe": + providerLines = settings.windscribeLines() + default: + panic(`Missing lines method for provider "` + + settings.Name + `"! Please create a Github issue.`) + } + + for _, line := range providerLines { + lines = append(lines, indent+line) + } + + return lines +} + +func commaJoin(slice []string) string { + return strings.Join(slice, ", ") +} + +func readProtocol(env params.Env) (protocol models.NetworkProtocol, err error) { + s, err := env.Inside("PROTOCOL", + []string{string(constants.TCP), string(constants.UDP)}, + params.Default(string(constants.UDP))) + if err != nil { + return "", err + } + return models.NetworkProtocol(s), nil +} + +func readTargetIP(env params.Env) (targetIP net.IP, err error) { + return readIP(env, "OPENVPN_TARGET_IP") +} + +var ( + ErrInvalidProtocol = errors.New("invalid network protocol") +) + +func readCustomPort(env params.Env, protocol models.NetworkProtocol, + allowedTCP, allowedUDP []uint16) (port uint16, err error) { + port, err = readPortOrZero(env, "PORT") + if err != nil { + return 0, err + } else if port == 0 { + return 0, nil + } + + switch protocol { + case constants.TCP: + for i := range allowedTCP { + if allowedTCP[i] == port { + return port, nil + } + } + return 0, fmt.Errorf("%w: port %d for TCP protocol", ErrInvalidPort, port) + case constants.UDP: + for i := range allowedUDP { + if allowedTCP[i] == port { + return port, nil + } + } + return 0, fmt.Errorf("%w: port %d for UDP protocol", ErrInvalidPort, port) + default: + return 0, fmt.Errorf("%w: %s", ErrInvalidProtocol, protocol) + } +} diff --git a/internal/configuration/provider_test.go b/internal/configuration/provider_test.go new file mode 100644 index 00000000..13973905 --- /dev/null +++ b/internal/configuration/provider_test.go @@ -0,0 +1,246 @@ +package configuration + +import ( + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/qdm12/gluetun/internal/constants" + "github.com/qdm12/gluetun/internal/models" + "github.com/qdm12/golibs/params/mock_params" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var errDummy = errors.New("dummy") + +func Test_Provider_lines(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + settings Provider + lines []string + }{ + "cyberghost": { + settings: Provider{ + Name: constants.Cyberghost, + ServerSelection: ServerSelection{ + Protocol: constants.UDP, + Group: "group", + Regions: []string{"a", "El country"}, + }, + ExtraConfigOptions: ExtraConfigOptions{ + ClientKey: "a", + ClientCertificate: "a", + }, + }, + lines: []string{ + "|--Cyberghost settings:", + " |--Network protocol: udp", + " |--Server group: group", + " |--Regions: a, El country", + " |--Client key is set", + " |--Client certificate is set", + }, + }, + "mullvad": { + settings: Provider{ + Name: constants.Mullvad, + ServerSelection: ServerSelection{ + Protocol: constants.UDP, + Countries: []string{"a", "b"}, + Cities: []string{"c", "d"}, + ISPs: []string{"e", "f"}, + CustomPort: 1, + }, + ExtraConfigOptions: ExtraConfigOptions{ + OpenVPNIPv6: true, + }, + }, + lines: []string{ + "|--Mullvad settings:", + " |--Network protocol: udp", + " |--Countries: a, b", + " |--Cities: c, d", + " |--ISPs: e, f", + " |--Custom port: 1", + " |--IPv6: enabled", + }, + }, + "nordvpn": { + settings: Provider{ + Name: constants.Nordvpn, + ServerSelection: ServerSelection{ + Protocol: constants.UDP, + Regions: []string{"a", "b"}, + Numbers: []uint16{1, 2}, + }, + }, + lines: []string{ + "|--Nordvpn settings:", + " |--Network protocol: udp", + " |--Regions: a, b", + " |--Numbers: 1, 2", + }, + }, + "privado": { + settings: Provider{ + Name: constants.Privado, + ServerSelection: ServerSelection{ + Protocol: constants.UDP, + Hostnames: []string{"a", "b"}, + }, + }, + lines: []string{ + "|--Privado settings:", + " |--Network protocol: udp", + " |--Hostnames: a, b", + }, + }, + "private internet access": { + settings: Provider{ + Name: constants.PrivateInternetAccess, + ServerSelection: ServerSelection{ + Protocol: constants.UDP, + Regions: []string{"a", "b"}, + EncryptionPreset: constants.PIAEncryptionPresetStrong, + CustomPort: 1, + }, + PortForwarding: PortForwarding{ + Enabled: true, + Filepath: models.Filepath("/here"), + }, + }, + lines: []string{ + "|--Private Internet Access settings:", + " |--Network protocol: udp", + " |--Regions: a, b", + " |--Encryption preset: strong", + " |--Custom port: 1", + " |--Port forwarding:", + " |--File path: /here", + }, + }, + "purevpn": { + settings: Provider{ + Name: constants.Purevpn, + ServerSelection: ServerSelection{ + Protocol: constants.UDP, + Regions: []string{"a", "b"}, + Countries: []string{"c", "d"}, + Cities: []string{"e", "f"}, + }, + }, + lines: []string{ + "|--Purevpn settings:", + " |--Network protocol: udp", + " |--Regions: a, b", + " |--Countries: c, d", + " |--Cities: e, f", + }, + }, + "surfshark": { + settings: Provider{ + Name: constants.Surfshark, + ServerSelection: ServerSelection{ + Protocol: constants.UDP, + Regions: []string{"a", "b"}, + }, + }, + lines: []string{ + "|--Surfshark settings:", + " |--Network protocol: udp", + " |--Regions: a, b", + }, + }, + "vyprvpn": { + settings: Provider{ + Name: constants.Vyprvpn, + ServerSelection: ServerSelection{ + Protocol: constants.UDP, + Regions: []string{"a", "b"}, + }, + }, + lines: []string{ + "|--Vyprvpn settings:", + " |--Network protocol: udp", + " |--Regions: a, b", + }, + }, + "windscribe": { + settings: Provider{ + Name: constants.Windscribe, + ServerSelection: ServerSelection{ + Protocol: constants.UDP, + Regions: []string{"a", "b"}, + Cities: []string{"c", "d"}, + Hostnames: []string{"e", "f"}, + CustomPort: 1, + }, + }, + lines: []string{ + "|--Windscribe settings:", + " |--Network protocol: udp", + " |--Regions: a, b", + " |--Cities: c, d", + " |--Hostnames: e, f", + " |--Custom port: 1", + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + lines := testCase.settings.lines() + + assert.Equal(t, testCase.lines, lines) + }) + } +} + +func Test_readProtocol(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + mockStr string + mockErr error + protocol models.NetworkProtocol + err error + }{ + "error": { + mockErr: errDummy, + err: errDummy, + }, + "success": { + mockStr: "tcp", + protocol: constants.TCP, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + env := mock_params.NewMockEnv(ctrl) + env.EXPECT(). + Inside("PROTOCOL", []string{"tcp", "udp"}, gomock.Any()). + Return(testCase.mockStr, testCase.mockErr) + + protocol, err := readProtocol(env) + + if testCase.err != nil { + require.Error(t, err) + assert.Equal(t, testCase.err.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, testCase.protocol, protocol) + }) + } +} diff --git a/internal/configuration/publicip.go b/internal/configuration/publicip.go new file mode 100644 index 00000000..ecc3acc8 --- /dev/null +++ b/internal/configuration/publicip.go @@ -0,0 +1,48 @@ +package configuration + +import ( + "strings" + "time" + + "github.com/qdm12/gluetun/internal/models" + "github.com/qdm12/golibs/params" +) + +type PublicIP struct { + Period time.Duration `json:"period"` + IPFilepath models.Filepath `json:"ip_filepath"` +} + +func (settings *PublicIP) String() string { + return strings.Join(settings.lines(), "\n") +} + +func (settings *PublicIP) lines() (lines []string) { + if settings.Period == 0 { + lines = append(lines, lastIndent+"Public IP getter: disabled") + return lines + } + + lines = append(lines, lastIndent+"Public IP getter:") + lines = append(lines, indent+lastIndent+"Fetch period: "+settings.Period.String()) + lines = append(lines, indent+lastIndent+"IP file: "+string(settings.IPFilepath)) + + return lines +} + +func (settings *PublicIP) read(r reader) (err error) { + settings.Period, err = r.env.Duration("PUBLICIP_PERIOD", params.Default("12h")) + if err != nil { + return err + } + + filepathStr, err := r.env.Path("PUBLICIP_FILE", params.CaseSensitiveValue(), + params.Default("/tmp/gluetun/ip"), + params.RetroKeys([]string{"IP_STATUS_FILE"}, r.onRetroActive)) + if err != nil { + return err + } + settings.IPFilepath = models.Filepath(filepathStr) + + return nil +} diff --git a/internal/configuration/purevpn.go b/internal/configuration/purevpn.go new file mode 100644 index 00000000..75d89792 --- /dev/null +++ b/internal/configuration/purevpn.go @@ -0,0 +1,52 @@ +package configuration + +import ( + "github.com/qdm12/gluetun/internal/constants" +) + +func (settings *Provider) purevpnLines() (lines []string) { + if len(settings.ServerSelection.Regions) > 0 { + lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions)) + } + + if len(settings.ServerSelection.Countries) > 0 { + lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries)) + } + + if len(settings.ServerSelection.Cities) > 0 { + lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities)) + } + + return lines +} + +func (settings *Provider) readPurevpn(r reader) (err error) { + settings.Name = constants.Purevpn + + settings.ServerSelection.Protocol, err = readProtocol(r.env) + if err != nil { + return err + } + + settings.ServerSelection.TargetIP, err = readTargetIP(r.env) + if err != nil { + return err + } + + settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PurevpnRegionChoices()) + if err != nil { + return err + } + + settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.PurevpnCountryChoices()) + if err != nil { + return err + } + + settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PurevpnCityChoices()) + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/reader.go b/internal/configuration/reader.go new file mode 100644 index 00000000..f36f141f --- /dev/null +++ b/internal/configuration/reader.go @@ -0,0 +1,129 @@ +package configuration + +import ( + "errors" + "fmt" + "net" + "strconv" + "strings" + + "github.com/qdm12/golibs/logging" + "github.com/qdm12/golibs/os" + "github.com/qdm12/golibs/params" + "github.com/qdm12/golibs/verification" +) + +type reader struct { + env params.Env + logger logging.Logger + regex verification.Regex + os os.OS +} + +func newReader(env params.Env, os os.OS, logger logging.Logger) reader { + return reader{ + env: env, + logger: logger, + regex: verification.NewRegex(), + os: os, + } +} + +func (r *reader) onRetroActive(oldKey, newKey string) { + r.logger.Warn( + "You are using the old environment variable %s, please consider changing it to %s", + oldKey, newKey, + ) +} + +var ( + ErrInvalidPort = errors.New("invalid port") +) + +func readCSVPorts(env params.Env, key string) (ports []uint16, err error) { + s, err := env.Get(key) + if err != nil { + return nil, err + } else if len(s) == 0 { + return nil, nil + } + + portsStr := strings.Split(s, ",") + ports = make([]uint16, len(portsStr)) + for i, portStr := range portsStr { + portInt, err := strconv.Atoi(portStr) + if err != nil { + return nil, fmt.Errorf("%w: %q from environment variable %s: %s", + ErrInvalidPort, portStr, key, err) + } else if portInt <= 0 || portInt > 65535 { + return nil, fmt.Errorf("%w: %d from environment variable %s: must be between 1 and 65535", + ErrInvalidPort, portInt, key) + } + ports[i] = uint16(portInt) + } + + return ports, nil +} + +var ( + ErrInvalidIPNet = errors.New("invalid IP network") +) + +func readCSVIPNets(env params.Env, key string, options ...params.OptionSetter) ( + ipNets []net.IPNet, err error) { + s, err := env.Get(key, options...) + if err != nil { + return nil, err + } else if s == "" { + return nil, nil + } + + ipNetsStr := strings.Split(s, ",") + ipNets = make([]net.IPNet, len(ipNetsStr)) + for i, ipNetStr := range ipNetsStr { + _, ipNet, err := net.ParseCIDR(ipNetStr) + if err != nil { + return nil, fmt.Errorf("%w: %q from environment variable %s: %s", + ErrInvalidIPNet, ipNetStr, key, err) + } else if ipNet == nil { + return nil, fmt.Errorf("%w: %q from environment variable %s: subnet is nil", + ErrInvalidIPNet, ipNetStr, key) + } + ipNets[i] = *ipNet + } + + return ipNets, nil +} + +var ( + ErrInvalidIP = errors.New("invalid IP address") +) + +func readIP(env params.Env, key string) (ip net.IP, err error) { + s, err := env.Get(key) + if len(s) == 0 { + return nil, nil + } else if err != nil { + return nil, err + } + + ip = net.ParseIP(s) + if ip == nil { + return nil, fmt.Errorf("%w: %s", ErrInvalidIP, s) + } + + return ip, nil +} + +func readPortOrZero(env params.Env, key string) (port uint16, err error) { + s, err := env.Get(key) + if err != nil { + return 0, err + } + + if len(s) == 0 || s == "0" { + return 0, nil + } + + return env.Port(key) +} diff --git a/internal/params/secrets.go b/internal/configuration/secrets.go similarity index 86% rename from internal/params/secrets.go rename to internal/configuration/secrets.go index 86031326..610bdb69 100644 --- a/internal/params/secrets.go +++ b/internal/configuration/secrets.go @@ -1,4 +1,4 @@ -package params +package configuration import ( "errors" @@ -7,7 +7,7 @@ import ( "strings" "github.com/qdm12/golibs/os" - libparams "github.com/qdm12/golibs/params" + "github.com/qdm12/golibs/params" ) var ( @@ -19,11 +19,11 @@ var ( ) func (r *reader) getFromEnvOrSecretFile(envKey string, compulsory bool, retroKeys []string) (value string, err error) { - envOptions := []libparams.OptionSetter{ - libparams.Compulsory(), // to fallback on file reading - libparams.CaseSensitiveValue(), - libparams.Unset(), - libparams.RetroKeys(retroKeys, r.onRetroActive), + envOptions := []params.OptionSetter{ + params.Compulsory(), // to fallback on file reading + params.CaseSensitiveValue(), + params.Unset(), + params.RetroKeys(retroKeys, r.onRetroActive), } value, envErr := r.env.Get(envKey, envOptions...) if envErr == nil { @@ -32,8 +32,8 @@ func (r *reader) getFromEnvOrSecretFile(envKey string, compulsory bool, retroKey defaultSecretFile := "/run/secrets/" + strings.ToLower(envKey) filepath, err := r.env.Get(envKey+"_SECRETFILE", - libparams.CaseSensitiveValue(), - libparams.Default(defaultSecretFile), + params.CaseSensitiveValue(), + params.Default(defaultSecretFile), ) if err != nil { return "", fmt.Errorf("%w: %s", ErrGetSecretFilepath, err) @@ -68,8 +68,8 @@ func (r *reader) getFromFileOrSecretFile(secretName, filepath string) ( b []byte, err error) { defaultSecretFile := "/run/secrets/" + strings.ToLower(secretName) secretFilepath, err := r.env.Get(strings.ToUpper(secretName)+"_SECRETFILE", - libparams.CaseSensitiveValue(), - libparams.Default(defaultSecretFile), + params.CaseSensitiveValue(), + params.Default(defaultSecretFile), ) if err != nil { return b, fmt.Errorf("%w: %s", ErrGetSecretFilepath, err) diff --git a/internal/configuration/selection.go b/internal/configuration/selection.go new file mode 100644 index 00000000..1b951f47 --- /dev/null +++ b/internal/configuration/selection.go @@ -0,0 +1,55 @@ +package configuration + +import ( + "net" + + "github.com/qdm12/gluetun/internal/models" +) + +type ServerSelection struct { + // Common + Protocol models.NetworkProtocol `json:"network_protocol"` + TargetIP net.IP `json:"target_ip,omitempty"` + + // Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN + Regions []string `json:"regions"` + + // Cyberghost + Group string `json:"group"` + + Countries []string `json:"countries"` // Mullvad, PureVPN + Cities []string `json:"cities"` // Mullvad, PureVPN, Windscribe + Hostnames []string `json:"hostnames"` // Windscribe, Privado + + // Mullvad + ISPs []string `json:"isps"` + Owned bool `json:"owned"` + + // Mullvad, Windscribe, PIA + CustomPort uint16 `json:"custom_port"` + + // NordVPN + Numbers []uint16 `json:"numbers"` + + // PIA + EncryptionPreset string `json:"encryption_preset"` +} + +type ExtraConfigOptions struct { + ClientCertificate string `json:"-"` // Cyberghost + ClientKey string `json:"-"` // Cyberghost + EncryptionPreset string `json:"encryption_preset"` // PIA + OpenVPNIPv6 bool `json:"openvpn_ipv6"` // Mullvad +} + +// PortForwarding contains settings for port forwarding. +type PortForwarding struct { + Enabled bool `json:"enabled"` + Filepath models.Filepath `json:"filepath"` +} + +func (p *PortForwarding) lines() (lines []string) { + return []string{ + lastIndent + "File path: " + string(p.Filepath), + } +} diff --git a/internal/configuration/server.go b/internal/configuration/server.go new file mode 100644 index 00000000..b5464078 --- /dev/null +++ b/internal/configuration/server.go @@ -0,0 +1,49 @@ +package configuration + +import ( + "strconv" + "strings" + + "github.com/qdm12/golibs/params" +) + +// ControlServer contains settings to customize the control server operation. +type ControlServer struct { + Port uint16 + Log bool +} + +func (settings *ControlServer) String() string { + return strings.Join(settings.lines(), "\n") +} + +func (settings *ControlServer) lines() (lines []string) { + lines = append(lines, lastIndent+"HTTP control server:") + + lines = append(lines, indent+lastIndent+"Listening port: "+strconv.Itoa(int(settings.Port))) + + if settings.Log { + lines = append(lines, indent+lastIndent+"Logging: enabled") + } + + return lines +} + +func (settings *ControlServer) read(r reader) (err error) { + settings.Log, err = r.env.OnOff("HTTP_CONTROL_SERVER_LOG", params.Default("on")) + if err != nil { + return err + } + + var warning string + settings.Port, warning, err = r.env.ListeningPort( + "HTTP_CONTROL_SERVER_PORT", params.Default("8000")) + if len(warning) > 0 { + r.logger.Warn(warning) + } + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go new file mode 100644 index 00000000..e5a4f6e7 --- /dev/null +++ b/internal/configuration/settings.go @@ -0,0 +1,93 @@ +package configuration + +import ( + "strings" + + "github.com/qdm12/golibs/logging" + "github.com/qdm12/golibs/os" + "github.com/qdm12/golibs/params" +) + +// Settings contains all settings for the program to run. +type Settings struct { + OpenVPN OpenVPN + System System + DNS DNS + Firewall Firewall + HTTPProxy HTTPProxy + ShadowSocks ShadowSocks + Updater Updater + PublicIP PublicIP + VersionInformation bool + ControlServer ControlServer +} + +func (settings *Settings) String() string { + return strings.Join(settings.lines(), "\n") +} + +func (settings *Settings) lines() (lines []string) { + lines = append(lines, "Settings summary below:") + lines = append(lines, settings.OpenVPN.lines()...) + lines = append(lines, settings.DNS.lines()...) + lines = append(lines, settings.Firewall.lines()...) + lines = append(lines, settings.System.lines()...) + lines = append(lines, settings.HTTPProxy.lines()...) + lines = append(lines, settings.ShadowSocks.lines()...) + lines = append(lines, settings.ControlServer.lines()...) + lines = append(lines, settings.Updater.lines()...) + lines = append(lines, settings.PublicIP.lines()...) + if settings.VersionInformation { + lines = append(lines, lastIndent+"Github version information: enabled") + } + return lines +} + +// Read obtains all configuration options for the program and returns an error as soon +// as an error is encountered reading them. +func (settings *Settings) Read(env params.Env, os os.OS, logger logging.Logger) (err error) { + r := newReader(env, os, logger) + + settings.VersionInformation, err = r.env.OnOff("VERSION_INFORMATION", params.Default("on")) + if err != nil { + return err + } + + if err := settings.OpenVPN.read(r); err != nil { + return err + } + + if err := settings.System.read(r); err != nil { + return err + } + + if err := settings.DNS.read(r); err != nil { + return err + } + + if err := settings.Firewall.read(r); err != nil { + return err + } + + if err := settings.HTTPProxy.read(r); err != nil { + return err + } + + if err := settings.ShadowSocks.read(r); err != nil { + return err + } + + if err := settings.ControlServer.read(r); err != nil { + return err + } + + if err := settings.Updater.read(r); err != nil { + return err + } + + if err := settings.PublicIP.read(r); err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/settings_test.go b/internal/configuration/settings_test.go new file mode 100644 index 00000000..cb621e06 --- /dev/null +++ b/internal/configuration/settings_test.go @@ -0,0 +1,55 @@ +package configuration + +import ( + "testing" + + "github.com/qdm12/gluetun/internal/constants" + "github.com/stretchr/testify/assert" +) + +func Test_Settings_lines(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + settings Settings + lines []string + }{ + "default settings": { + settings: Settings{ + OpenVPN: OpenVPN{ + Provider: Provider{ + Name: constants.Mullvad, + }, + }, + }, + lines: []string{ + "Settings summary below:", + "|--OpenVPN:", + " |--Verbosity level: 0", + " |--Provider:", + " |--Mullvad settings:", + " |--Network protocol: ", + "|--DNS:", + "|--Firewall: disabled ⚠️", + "|--System:", + " |--Process user ID: 0", + " |--Process group ID: 0", + " |--Timezone: NOT SET ⚠️ - it can cause time related issues", + "|--HTTP control server:", + " |--Listening port: 0", + "|--Public IP getter: disabled", + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + lines := testCase.settings.lines() + + assert.Equal(t, testCase.lines, lines) + }) + } +} diff --git a/internal/configuration/shadowsocks.go b/internal/configuration/shadowsocks.go new file mode 100644 index 00000000..5f1b5359 --- /dev/null +++ b/internal/configuration/shadowsocks.go @@ -0,0 +1,72 @@ +package configuration + +import ( + "strconv" + "strings" + + "github.com/qdm12/golibs/params" +) + +// ShadowSocks contains settings to configure the Shadowsocks server. +type ShadowSocks struct { + Method string + Password string + Port uint16 + Enabled bool + Log bool +} + +func (settings *ShadowSocks) String() string { + return strings.Join(settings.lines(), "\n") +} + +func (settings *ShadowSocks) lines() (lines []string) { + if !settings.Enabled { + return nil + } + + lines = append(lines, lastIndent+"Shadowsocks server:") + + lines = append(lines, indent+lastIndent+"Listening port: "+strconv.Itoa(int(settings.Port))) + + lines = append(lines, indent+lastIndent+"Method: "+settings.Method) + + if settings.Log { + lines = append(lines, indent+lastIndent+"Logging: enabled") + } + + return lines +} + +func (settings *ShadowSocks) read(r reader) (err error) { + settings.Enabled, err = r.env.OnOff("SHADOWSOCKS", params.Default("off")) + if err != nil || !settings.Enabled { + return err + } + + settings.Password, err = r.getFromEnvOrSecretFile("SHADOWSOCKS_PASSWORD", false, nil) + if err != nil { + return err + } + + settings.Log, err = r.env.OnOff("SHADOWSOCKS_LOG", params.Default("off")) + if err != nil { + return err + } + + settings.Method, err = r.env.Get("SHADOWSOCKS_METHOD", params.Default("chacha20-ietf-poly1305")) + if err != nil { + return err + } + + var warning string + settings.Port, warning, err = r.env.ListeningPort("SHADOWSOCKS_PORT", params.Default("8388")) + if len(warning) > 0 { + r.logger.Warn(warning) + } + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/surfshark.go b/internal/configuration/surfshark.go new file mode 100644 index 00000000..6e6d51d5 --- /dev/null +++ b/internal/configuration/surfshark.go @@ -0,0 +1,34 @@ +package configuration + +import ( + "github.com/qdm12/gluetun/internal/constants" +) + +func (settings *Provider) surfsharkLines() (lines []string) { + if len(settings.ServerSelection.Regions) > 0 { + lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions)) + } + + return lines +} + +func (settings *Provider) readSurfshark(r reader) (err error) { + settings.Name = constants.Surfshark + + settings.ServerSelection.Protocol, err = readProtocol(r.env) + if err != nil { + return err + } + + settings.ServerSelection.TargetIP, err = readTargetIP(r.env) + if err != nil { + return err + } + + settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.SurfsharkRegionChoices()) + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/system.go b/internal/configuration/system.go new file mode 100644 index 00000000..c20bbc4e --- /dev/null +++ b/internal/configuration/system.go @@ -0,0 +1,53 @@ +package configuration + +import ( + "strconv" + "strings" + + "github.com/qdm12/golibs/params" +) + +// System contains settings to configure system related elements. +type System struct { + PUID int + PGID int + Timezone string +} + +func (settings *System) String() string { + return strings.Join(settings.lines(), "\n") +} + +func (settings *System) lines() (lines []string) { + lines = append(lines, lastIndent+"System:") + lines = append(lines, indent+lastIndent+"Process user ID: "+strconv.Itoa(settings.PUID)) + lines = append(lines, indent+lastIndent+"Process group ID: "+strconv.Itoa(settings.PGID)) + + if len(settings.Timezone) > 0 { + lines = append(lines, indent+lastIndent+"Timezone: "+settings.Timezone) + } else { + lines = append(lines, indent+lastIndent+"Timezone: NOT SET ⚠️ - it can cause time related issues") + } + return lines +} + +func (settings *System) read(r reader) (err error) { + settings.PUID, err = r.env.IntRange("PUID", 0, 65535, params.Default("1000"), + params.RetroKeys([]string{"UID"}, r.onRetroActive)) + if err != nil { + return err + } + + settings.PGID, err = r.env.IntRange("PGID", 0, 65535, params.Default("1000"), + params.RetroKeys([]string{"GID"}, r.onRetroActive)) + if err != nil { + return err + } + + settings.Timezone, err = r.env.Get("TZ") + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/unbound.go b/internal/configuration/unbound.go new file mode 100644 index 00000000..9fb6d99f --- /dev/null +++ b/internal/configuration/unbound.go @@ -0,0 +1,133 @@ +package configuration + +import ( + "errors" + "fmt" + "net" + "strings" + + unbound "github.com/qdm12/dns/pkg/unbound" + "github.com/qdm12/golibs/params" +) + +func (settings *DNS) readUnbound(r reader) (err error) { + if err := settings.readUnboundProviders(r.env); err != nil { + return err + } + + settings.Unbound.ListeningPort = 53 + + settings.Unbound.Caching, err = r.env.OnOff("DOT_CACHING", params.Default("on")) + if err != nil { + return err + } + + settings.Unbound.IPv4 = true + + settings.Unbound.IPv6, err = r.env.OnOff("DOT_IPV6", params.Default("off")) + if err != nil { + return err + } + + verbosityLevel, err := r.env.IntRange("DOT_VERBOSITY", 0, 5, params.Default("1")) + if err != nil { + return err + } + settings.Unbound.VerbosityLevel = uint8(verbosityLevel) + + verbosityDetailsLevel, err := r.env.IntRange("DOT_VERBOSITY_DETAILS", 0, 4, params.Default("0")) + if err != nil { + return err + } + settings.Unbound.VerbosityDetailsLevel = uint8(verbosityDetailsLevel) + + validationLogLevel, err := r.env.IntRange("DOT_VALIDATION_LOGLEVEL", 0, 2, params.Default("0")) + if err != nil { + return err + } + settings.Unbound.ValidationLogLevel = uint8(validationLogLevel) + + if err := settings.readUnboundPrivateAddresses(r.env); err != nil { + return err + } + + if err := settings.readUnboundUnblockedHostnames(r); err != nil { + return err + } + + settings.Unbound.AccessControl.Allowed = []net.IPNet{ + { + IP: net.IPv4zero, + Mask: net.IPv4Mask(0, 0, 0, 0), + }, + { + IP: net.IPv6zero, + Mask: net.IPMask{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + } + + return nil +} + +var ( + ErrInvalidDNSOverTLSProvider = errors.New("invalid DNS over TLS provider") +) + +func (settings *DNS) readUnboundProviders(env params.Env) (err error) { + s, err := env.Get("DOT_PROVIDERS", params.Default("cloudflare")) + if err != nil { + return err + } + for _, provider := range strings.Split(s, ",") { + _, ok := unbound.GetProviderData(provider) + if !ok { + return fmt.Errorf("%w: %s", ErrInvalidDNSOverTLSProvider, provider) + } + settings.Unbound.Providers = append(settings.Unbound.Providers, provider) + } + return nil +} + +var ( + ErrInvalidPrivateAddress = errors.New("private address is not a valid IP or CIDR range") +) + +func (settings *DNS) readUnboundPrivateAddresses(env params.Env) (err error) { + privateAddresses, err := env.CSV("DOT_PRIVATE_ADDRESS") + if err != nil { + return err + } else if len(privateAddresses) == 0 { + return nil + } + for _, address := range privateAddresses { + ip := net.ParseIP(address) + _, _, err := net.ParseCIDR(address) + if ip == nil && err != nil { + return fmt.Errorf("%w: %s", ErrInvalidPrivateAddress, address) + } + } + settings.Unbound.BlockedIPs = append( + settings.Unbound.BlockedIPs, privateAddresses...) + return nil +} + +var ( + ErrInvalidHostname = errors.New("invalid hostname") +) + +func (settings *DNS) readUnboundUnblockedHostnames(r reader) (err error) { + hostnames, err := r.env.CSV("UNBLOCK") + if err != nil { + return err + } else if len(hostnames) == 0 { + return nil + } + for _, hostname := range hostnames { + if !r.regex.MatchHostname(hostname) { + return fmt.Errorf("%w: %s", ErrInvalidHostname, hostname) + } + } + settings.Unbound.AllowedHostnames = append( + settings.Unbound.AllowedHostnames, hostnames...) + return nil +} diff --git a/internal/configuration/updater.go b/internal/configuration/updater.go new file mode 100644 index 00000000..574477f2 --- /dev/null +++ b/internal/configuration/updater.go @@ -0,0 +1,62 @@ +package configuration + +import ( + "strings" + "time" + + "github.com/qdm12/golibs/params" +) + +type Updater struct { + Period time.Duration `json:"period"` + DNSAddress string `json:"dns_address"` + Cyberghost bool `json:"cyberghost"` + Mullvad bool `json:"mullvad"` + Nordvpn bool `json:"nordvpn"` + PIA bool `json:"pia"` + Privado bool `json:"privado"` + Purevpn bool `json:"purevpn"` + Surfshark bool `json:"surfshark"` + Vyprvpn bool `json:"vyprvpn"` + Windscribe bool `json:"windscribe"` + // The two below should be used in CLI mode only + Stdout bool `json:"-"` // in order to update constants file (maintainer side) + CLI bool `json:"-"` +} + +func (settings *Updater) String() string { + return strings.Join(settings.lines(), "\n") +} + +func (settings *Updater) lines() (lines []string) { + if settings.Period == 0 { + return nil + } + + lines = append(lines, lastIndent+"Updater:") + + lines = append(lines, indent+lastIndent+"Period: every "+settings.Period.String()) + + return lines +} + +func (settings *Updater) read(r reader) (err error) { + settings.Cyberghost = true + settings.Mullvad = true + settings.Nordvpn = true + settings.PIA = true + settings.Purevpn = true + settings.Surfshark = true + settings.Vyprvpn = true + settings.Windscribe = true + settings.Stdout = false + settings.CLI = false + settings.DNSAddress = "127.0.0.1" + + settings.Period, err = r.env.Duration("UPDATER_PERIOD", params.Default("0")) + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/vyprvpn.go b/internal/configuration/vyprvpn.go new file mode 100644 index 00000000..6b09580b --- /dev/null +++ b/internal/configuration/vyprvpn.go @@ -0,0 +1,34 @@ +package configuration + +import ( + "github.com/qdm12/gluetun/internal/constants" +) + +func (settings *Provider) vyprvpnLines() (lines []string) { + if len(settings.ServerSelection.Regions) > 0 { + lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions)) + } + + return lines +} + +func (settings *Provider) readVyprvpn(r reader) (err error) { + settings.Name = constants.Vyprvpn + + settings.ServerSelection.Protocol, err = readProtocol(r.env) + if err != nil { + return err + } + + settings.ServerSelection.TargetIP, err = readTargetIP(r.env) + if err != nil { + return err + } + + settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.VyprvpnRegionChoices()) + if err != nil { + return err + } + + return nil +} diff --git a/internal/configuration/windscribe.go b/internal/configuration/windscribe.go new file mode 100644 index 00000000..cebeb343 --- /dev/null +++ b/internal/configuration/windscribe.go @@ -0,0 +1,65 @@ +package configuration + +import ( + "strconv" + + "github.com/qdm12/gluetun/internal/constants" + "github.com/qdm12/golibs/params" +) + +func (settings *Provider) windscribeLines() (lines []string) { + if len(settings.ServerSelection.Regions) > 0 { + lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions)) + } + + if len(settings.ServerSelection.Cities) > 0 { + lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities)) + } + + if len(settings.ServerSelection.Hostnames) > 0 { + lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames)) + } + + lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort))) + + return lines +} + +func (settings *Provider) readWindscribe(r reader) (err error) { + settings.Name = constants.Windscribe + + settings.ServerSelection.Protocol, err = readProtocol(r.env) + if err != nil { + return err + } + + settings.ServerSelection.TargetIP, err = readTargetIP(r.env) + if err != nil { + return err + } + + settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.WindscribeRegionChoices()) + if err != nil { + return err + } + + settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.WindscribeCityChoices()) + if err != nil { + return err + } + + settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", + constants.WindscribeHostnameChoices(), params.RetroKeys([]string{"HOSTNAME"}, r.onRetroActive)) + if err != nil { + return err + } + + settings.ServerSelection.CustomPort, err = readCustomPort(r.env, settings.ServerSelection.Protocol, + []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}, + []uint16{53, 80, 123, 443, 1194, 54783}) + if err != nil { + return err + } + + return nil +} diff --git a/internal/dns/loop.go b/internal/dns/loop.go index f45b462a..90158990 100644 --- a/internal/dns/loop.go +++ b/internal/dns/loop.go @@ -9,9 +9,9 @@ import ( "time" "github.com/qdm12/dns/pkg/unbound" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" ) @@ -20,8 +20,8 @@ type Looper interface { RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) GetStatus() (status models.LoopStatus) SetStatus(status models.LoopStatus) (outcome string, err error) - GetSettings() (settings settings.DNS) - SetSettings(settings settings.DNS) (outcome string) + GetSettings() (settings configuration.DNS) + SetSettings(settings configuration.DNS) (outcome string) } type looper struct { @@ -45,7 +45,7 @@ type looper struct { const defaultBackoffTime = 10 * time.Second -func NewLooper(conf unbound.Configurator, settings settings.DNS, client *http.Client, +func NewLooper(conf unbound.Configurator, settings configuration.DNS, client *http.Client, logger logging.Logger, username string, puid, pgid int) Looper { return &looper{ state: state{ diff --git a/internal/dns/state.go b/internal/dns/state.go index cea35e45..a578b6c8 100644 --- a/internal/dns/state.go +++ b/internal/dns/state.go @@ -5,14 +5,14 @@ import ( "reflect" "sync" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" ) type state struct { status models.LoopStatus - settings settings.DNS + settings configuration.DNS statusMu sync.RWMutex settingsMu sync.RWMutex } @@ -69,13 +69,13 @@ func (l *looper) SetStatus(status models.LoopStatus) (outcome string, err error) } } -func (l *looper) GetSettings() (settings settings.DNS) { +func (l *looper) GetSettings() (settings configuration.DNS) { l.state.settingsMu.RLock() defer l.state.settingsMu.RUnlock() return l.state.settings } -func (l *looper) SetSettings(settings settings.DNS) (outcome string) { +func (l *looper) SetSettings(settings configuration.DNS) (outcome string) { l.state.settingsMu.Lock() settingsUnchanged := reflect.DeepEqual(l.state.settings, settings) if settingsUnchanged { diff --git a/internal/httpproxy/loop.go b/internal/httpproxy/loop.go index ef0a0785..c4dee5cb 100644 --- a/internal/httpproxy/loop.go +++ b/internal/httpproxy/loop.go @@ -6,9 +6,9 @@ import ( "sync" "time" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" ) @@ -16,8 +16,8 @@ type Looper interface { Run(ctx context.Context, wg *sync.WaitGroup) SetStatus(status models.LoopStatus) (outcome string, err error) GetStatus() (status models.LoopStatus) - GetSettings() (settings settings.HTTPProxy) - SetSettings(settings settings.HTTPProxy) (outcome string) + GetSettings() (settings configuration.HTTPProxy) + SetSettings(settings configuration.HTTPProxy) (outcome string) } type looper struct { @@ -34,7 +34,7 @@ type looper struct { const defaultBackoffTime = 10 * time.Second -func NewLooper(logger logging.Logger, settings settings.HTTPProxy) Looper { +func NewLooper(logger logging.Logger, settings configuration.HTTPProxy) Looper { return &looper{ state: state{ status: constants.Stopped, diff --git a/internal/httpproxy/state.go b/internal/httpproxy/state.go index 567e4547..6f3bc3d4 100644 --- a/internal/httpproxy/state.go +++ b/internal/httpproxy/state.go @@ -5,14 +5,14 @@ import ( "reflect" "sync" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" ) type state struct { status models.LoopStatus - settings settings.HTTPProxy + settings configuration.HTTPProxy statusMu sync.RWMutex settingsMu sync.RWMutex } @@ -69,13 +69,13 @@ func (l *looper) SetStatus(status models.LoopStatus) (outcome string, err error) } } -func (l *looper) GetSettings() (settings settings.HTTPProxy) { +func (l *looper) GetSettings() (settings configuration.HTTPProxy) { l.state.settingsMu.RLock() defer l.state.settingsMu.RUnlock() return l.state.settings } -func (l *looper) SetSettings(settings settings.HTTPProxy) (outcome string) { +func (l *looper) SetSettings(settings configuration.HTTPProxy) (outcome string) { l.state.settingsMu.Lock() settingsUnchanged := reflect.DeepEqual(settings, l.state.settings) if settingsUnchanged { diff --git a/internal/models/selection.go b/internal/models/selection.go deleted file mode 100644 index 9b887f27..00000000 --- a/internal/models/selection.go +++ /dev/null @@ -1,155 +0,0 @@ -package models - -import ( - "fmt" - "net" - "strings" -) - -// ProviderSettings contains settings specific to a VPN provider. -type ProviderSettings struct { - Name VPNProvider `json:"name"` - ServerSelection ServerSelection `json:"server_selection"` - ExtraConfigOptions ExtraConfigOptions `json:"extra_config"` - PortForwarding PortForwarding `json:"port_forwarding"` -} - -type ServerSelection struct { - // Common - Protocol NetworkProtocol `json:"network_protocol"` - TargetIP net.IP `json:"target_ip,omitempty"` - - // Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN - Regions []string `json:"regions"` - - // Cyberghost - Group string `json:"group"` - - Countries []string `json:"countries"` // Mullvad, PureVPN - Cities []string `json:"cities"` // Mullvad, PureVPN, Windscribe - Hostnames []string `json:"hostnames"` // Windscribe, Privado - - // Mullvad - ISPs []string `json:"isps"` - Owned bool `json:"owned"` - - // Mullvad, Windscribe, PIA - CustomPort uint16 `json:"custom_port"` - - // NordVPN - Numbers []uint16 `json:"numbers"` - - // PIA - EncryptionPreset string `json:"encryption_preset"` -} - -type ExtraConfigOptions struct { - ClientCertificate string `json:"-"` // Cyberghost - ClientKey string `json:"-"` // Cyberghost - EncryptionPreset string `json:"encryption_preset"` // PIA - OpenVPNIPv6 bool `json:"openvpn_ipv6"` // Mullvad -} - -// PortForwarding contains settings for port forwarding. -type PortForwarding struct { - Enabled bool `json:"enabled"` - Filepath Filepath `json:"filepath"` -} - -func (p *PortForwarding) String() string { - if p.Enabled { - return fmt.Sprintf("on, saved in %s", p.Filepath) - } - return "off" -} - -func (p *ProviderSettings) String() string { - settingsList := []string{ - fmt.Sprintf("%s settings:", strings.Title(string(p.Name))), - "Network protocol: " + string(p.ServerSelection.Protocol), - } - customPort := "" - if p.ServerSelection.CustomPort > 0 { - customPort = fmt.Sprintf("%d", p.ServerSelection.CustomPort) - } - numbers := make([]string, len(p.ServerSelection.Numbers)) - for i, number := range p.ServerSelection.Numbers { - numbers[i] = fmt.Sprintf("%d", number) - } - ipv6 := "off" - if p.ExtraConfigOptions.OpenVPNIPv6 { - ipv6 = "on" - } - switch strings.ToLower(string(p.Name)) { - case "private internet access old": - settingsList = append(settingsList, - "Regions: "+commaJoin(p.ServerSelection.Regions), - "Encryption preset: "+p.ExtraConfigOptions.EncryptionPreset, - "Port forwarding: "+p.PortForwarding.String(), - ) - case "private internet access": - settingsList = append(settingsList, - "Regions: "+commaJoin(p.ServerSelection.Regions), - "Encryption preset: "+p.ExtraConfigOptions.EncryptionPreset, - "Port forwarding: "+p.PortForwarding.String(), - "Custom port: "+customPort, - ) - case "mullvad": - settingsList = append(settingsList, - "Countries: "+commaJoin(p.ServerSelection.Countries), - "Cities: "+commaJoin(p.ServerSelection.Cities), - "ISPs: "+commaJoin(p.ServerSelection.ISPs), - "Custom port: "+customPort, - "IPv6: "+ipv6, - ) - case "windscribe": - settingsList = append(settingsList, - "Regions: "+commaJoin(p.ServerSelection.Regions), - "Custom port: "+customPort, - ) - case "surfshark": - settingsList = append(settingsList, - "Regions: "+commaJoin(p.ServerSelection.Regions), - ) - case "cyberghost": - settingsList = append(settingsList, - "Client key: [redacted]", - "Client certificate: [redacted]", - "Group: "+p.ServerSelection.Group, - "Regions: "+commaJoin(p.ServerSelection.Regions), - ) - case "vyprvpn": - settingsList = append(settingsList, - "Regions: "+commaJoin(p.ServerSelection.Regions), - ) - case "nordvpn": - settingsList = append(settingsList, - "Regions: "+commaJoin(p.ServerSelection.Regions), - "Numbers: "+commaJoin(numbers), - ) - case "purevpn": - settingsList = append(settingsList, - "Regions: "+commaJoin(p.ServerSelection.Regions), - "Countries: "+commaJoin(p.ServerSelection.Countries), - "Cities: "+commaJoin(p.ServerSelection.Cities), - ) - case "privado": - settingsList = append(settingsList, - "Hostnames: "+commaJoin(p.ServerSelection.Hostnames), - ) - default: - settingsList = append(settingsList, - "", - ) - } - if p.ServerSelection.TargetIP != nil { - settingsList = append(settingsList, - "Target IP address: "+string(p.ServerSelection.TargetIP), - ) - } - return strings.Join(settingsList, "\n |--") -} - -func commaJoin(slice []string) string { - return strings.Join(slice, ", ") -} diff --git a/internal/openvpn/loop.go b/internal/openvpn/loop.go index abdbd44a..5e1b43cb 100644 --- a/internal/openvpn/loop.go +++ b/internal/openvpn/loop.go @@ -8,12 +8,12 @@ import ( "sync" "time" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/provider" "github.com/qdm12/gluetun/internal/routing" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" ) @@ -22,8 +22,8 @@ type Looper interface { Run(ctx context.Context, wg *sync.WaitGroup) GetStatus() (status models.LoopStatus) SetStatus(status models.LoopStatus) (outcome string, err error) - GetSettings() (settings settings.OpenVPN) - SetSettings(settings settings.OpenVPN) (outcome string) + GetSettings() (settings configuration.OpenVPN) + SetSettings(settings configuration.OpenVPN) (outcome string) GetServers() (servers models.AllServers) SetServers(servers models.AllServers) GetPortForwarded() (port uint16) @@ -58,7 +58,7 @@ type looper struct { const defaultBackoffTime = 15 * time.Second -func NewLooper(settings settings.OpenVPN, +func NewLooper(settings configuration.OpenVPN, username string, puid, pgid int, allServers models.AllServers, conf Configurator, fw firewall.Configurator, routing routing.Routing, logger logging.Logger, client *http.Client, openFile os.OpenFileFunc, diff --git a/internal/openvpn/state.go b/internal/openvpn/state.go index e0ca094a..ae85f1de 100644 --- a/internal/openvpn/state.go +++ b/internal/openvpn/state.go @@ -5,14 +5,14 @@ import ( "reflect" "sync" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" ) type state struct { status models.LoopStatus - settings settings.OpenVPN + settings configuration.OpenVPN allServers models.AllServers portForwarded uint16 statusMu sync.RWMutex @@ -27,7 +27,7 @@ func (s *state) setStatusWithLock(status models.LoopStatus) { s.status = status } -func (s *state) getSettingsAndServers() (settings settings.OpenVPN, allServers models.AllServers) { +func (s *state) getSettingsAndServers() (settings configuration.OpenVPN, allServers models.AllServers) { s.settingsMu.RLock() s.allServersMu.RLock() settings = s.settings @@ -83,13 +83,13 @@ func (l *looper) SetStatus(status models.LoopStatus) (outcome string, err error) } } -func (l *looper) GetSettings() (settings settings.OpenVPN) { +func (l *looper) GetSettings() (settings configuration.OpenVPN) { l.state.settingsMu.RLock() defer l.state.settingsMu.RUnlock() return l.state.settings } -func (l *looper) SetSettings(settings settings.OpenVPN) (outcome string) { +func (l *looper) SetSettings(settings configuration.OpenVPN) (outcome string) { l.state.settingsMu.Lock() settingsUnchanged := reflect.DeepEqual(l.state.settings, settings) if settingsUnchanged { diff --git a/internal/params/cyberghost.go b/internal/params/cyberghost.go deleted file mode 100644 index 9a2cba59..00000000 --- a/internal/params/cyberghost.go +++ /dev/null @@ -1,72 +0,0 @@ -package params - -import ( - "encoding/pem" - "fmt" - "strings" - - "github.com/qdm12/gluetun/internal/constants" - libparams "github.com/qdm12/golibs/params" -) - -// GetCyberghostGroup obtains the server group for the Cyberghost server from the -// environment variable CYBERGHOST_GROUP. -func (r *reader) GetCyberghostGroup() (group string, err error) { - s, err := r.env.Inside("CYBERGHOST_GROUP", - constants.CyberghostGroupChoices(), libparams.Default("Premium UDP Europe")) - return s, err -} - -// GetCyberghostRegions obtains the country names for the Cyberghost servers from the -// environment variable REGION. -func (r *reader) GetCyberghostRegions() (regions []string, err error) { - return r.env.CSVInside("REGION", constants.CyberghostRegionChoices()) -} - -// GetCyberghostClientKey obtains the client key to use for openvpn -// from the secret file /run/secrets/openvpn_clientkey or from the file -// /gluetun/client.key. -func (r *reader) GetCyberghostClientKey() (clientKey string, err error) { - b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTKEY", string(constants.ClientKey)) - if err != nil { - return "", err - } - return extractClientKey(b) -} - -func extractClientKey(b []byte) (key string, err error) { - pemBlock, _ := pem.Decode(b) - if pemBlock == nil { - return "", fmt.Errorf("cannot decode PEM block from client key") - } - parsedBytes := pem.EncodeToMemory(pemBlock) - s := string(parsedBytes) - s = strings.ReplaceAll(s, "\n", "") - s = strings.TrimPrefix(s, "-----BEGIN PRIVATE KEY-----") - s = strings.TrimSuffix(s, "-----END PRIVATE KEY-----") - return s, nil -} - -// GetCyberghostClientCertificate obtains the client certificate to use for openvpn -// from the secret file /run/secrets/openvpn_clientcrt or from the file -// /gluetun/client.crt. -func (r *reader) GetCyberghostClientCertificate() (clientCertificate string, err error) { - b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTCRT", string(constants.ClientCertificate)) - if err != nil { - return "", err - } - return extractClientCertificate(b) -} - -func extractClientCertificate(b []byte) (certificate string, err error) { - pemBlock, _ := pem.Decode(b) - if pemBlock == nil { - return "", fmt.Errorf("cannot decode PEM block from client certificate") - } - parsedBytes := pem.EncodeToMemory(pemBlock) - s := string(parsedBytes) - s = strings.ReplaceAll(s, "\n", "") - s = strings.TrimPrefix(s, "-----BEGIN CERTIFICATE-----") - s = strings.TrimSuffix(s, "-----END CERTIFICATE-----") - return s, nil -} diff --git a/internal/params/dns.go b/internal/params/dns.go deleted file mode 100644 index 5e040338..00000000 --- a/internal/params/dns.go +++ /dev/null @@ -1,162 +0,0 @@ -package params - -import ( - "fmt" - "net" - "strings" - "time" - - dns "github.com/qdm12/dns/pkg/unbound" - libparams "github.com/qdm12/golibs/params" -) - -// GetDNSOverTLS obtains if the DNS over TLS should be enabled -// from the environment variable DOT. -func (r *reader) GetDNSOverTLS() (DNSOverTLS bool, err error) { //nolint:gocritic - return r.env.OnOff("DOT", libparams.Default("on")) -} - -// GetDNSOverTLSProviders obtains the DNS over TLS providers to use -// from the environment variable DOT_PROVIDERS. -func (r *reader) GetDNSOverTLSProviders() (providers []string, err error) { - s, err := r.env.Get("DOT_PROVIDERS", libparams.Default("cloudflare")) - if err != nil { - return nil, err - } - for _, provider := range strings.Split(s, ",") { - _, ok := dns.GetProviderData(provider) - if !ok { - return nil, fmt.Errorf("DNS over TLS provider %q is not valid", provider) - } - providers = append(providers, provider) - } - return providers, nil -} - -// GetDNSOverTLSVerbosity obtains the verbosity level to use for Unbound -// from the environment variable DOT_VERBOSITY. -func (r *reader) GetDNSOverTLSVerbosity() (verbosityLevel uint8, err error) { - n, err := r.env.IntRange("DOT_VERBOSITY", 0, 5, libparams.Default("1")) - return uint8(n), err -} - -// GetDNSOverTLSVerbosityDetails obtains the log level to use for Unbound -// from the environment variable DOT_VERBOSITY_DETAILS. -func (r *reader) GetDNSOverTLSVerbosityDetails() (verbosityDetailsLevel uint8, err error) { - n, err := r.env.IntRange("DOT_VERBOSITY_DETAILS", 0, 4, libparams.Default("0")) - return uint8(n), err -} - -// GetDNSOverTLSValidationLogLevel obtains the log level to use for Unbound DOT validation -// from the environment variable DOT_VALIDATION_LOGLEVEL. -func (r *reader) GetDNSOverTLSValidationLogLevel() (validationLogLevel uint8, err error) { - n, err := r.env.IntRange("DOT_VALIDATION_LOGLEVEL", 0, 2, libparams.Default("0")) - return uint8(n), err -} - -// GetDNSMaliciousBlocking obtains if malicious hostnames/IPs should be blocked -// from being resolved by Unbound, using the environment variable BLOCK_MALICIOUS. -func (r *reader) GetDNSMaliciousBlocking() (blocking bool, err error) { - return r.env.OnOff("BLOCK_MALICIOUS", libparams.Default("on")) -} - -// GetDNSSurveillanceBlocking obtains if surveillance hostnames/IPs should be blocked -// from being resolved by Unbound, using the environment variable BLOCK_SURVEILLANCE -// and BLOCK_NSA for retrocompatibility. -func (r *reader) GetDNSSurveillanceBlocking() (blocking bool, err error) { - // Retro-compatibility - s, err := r.env.Get("BLOCK_NSA") - if err != nil { - return false, err - } else if len(s) != 0 { - r.logger.Warn("You are using the old environment variable BLOCK_NSA, please consider changing it to BLOCK_SURVEILLANCE") //nolint:lll - return r.env.OnOff("BLOCK_NSA", libparams.Compulsory()) - } - return r.env.OnOff("BLOCK_SURVEILLANCE", libparams.Default("off")) -} - -// GetDNSAdsBlocking obtains if ads hostnames/IPs should be blocked -// from being resolved by Unbound, using the environment variable BLOCK_ADS. -func (r *reader) GetDNSAdsBlocking() (blocking bool, err error) { - return r.env.OnOff("BLOCK_ADS", libparams.Default("off")) -} - -// GetDNSUnblockedHostnames obtains a list of hostnames to unblock from block lists -// from the comma separated list for the environment variable UNBLOCK. -func (r *reader) GetDNSUnblockedHostnames() (hostnames []string, err error) { - s, err := r.env.Get("UNBLOCK") - if err != nil { - return nil, err - } else if len(s) == 0 { - return nil, nil - } - hostnames = strings.Split(s, ",") - for _, hostname := range hostnames { - if !r.regex.MatchHostname(hostname) { - return nil, fmt.Errorf("hostname %q does not seem valid", hostname) - } - } - return hostnames, nil -} - -// GetDNSOverTLSCaching obtains if Unbound caching should be enable or not -// from the environment variable DOT_CACHING. -func (r *reader) GetDNSOverTLSCaching() (caching bool, err error) { - return r.env.OnOff("DOT_CACHING", libparams.Default("on")) -} - -// GetDNSOverTLSPrivateAddresses obtains if Unbound caching should be enable or not -// from the environment variable DOT_PRIVATE_ADDRESS. -func (r *reader) GetDNSOverTLSPrivateAddresses() (privateAddresses []string, err error) { - s, err := r.env.Get("DOT_PRIVATE_ADDRESS") - if err != nil { - return nil, err - } else if len(s) == 0 { - return nil, nil - } - privateAddresses = strings.Split(s, ",") - for _, address := range privateAddresses { - ip := net.ParseIP(address) - _, _, err := net.ParseCIDR(address) - if ip == nil && err != nil { - return nil, fmt.Errorf("private address %q is not a valid IP or CIDR range", address) - } - } - return privateAddresses, nil -} - -// GetDNSOverTLSIPv6 obtains if Unbound should resolve ipv6 addresses using -// ipv6 DNS over TLS from the environment variable DOT_IPV6. -func (r *reader) GetDNSOverTLSIPv6() (ipv6 bool, err error) { - return r.env.OnOff("DOT_IPV6", libparams.Default("off")) -} - -// GetDNSUpdatePeriod obtains the period to use to update the block lists and cryptographic files -// and restart Unbound from the environment variable DNS_UPDATE_PERIOD. -func (r *reader) GetDNSUpdatePeriod() (period time.Duration, err error) { - s, err := r.env.Get("DNS_UPDATE_PERIOD", libparams.Default("24h")) - if err != nil { - return period, err - } - return time.ParseDuration(s) -} - -// GetDNSPlaintext obtains the plaintext DNS address to use if DNS over TLS is disabled -// from the environment variable DNS_PLAINTEXT_ADDRESS. -func (r *reader) GetDNSPlaintext() (ip net.IP, err error) { - s, err := r.env.Get("DNS_PLAINTEXT_ADDRESS", libparams.Default("1.1.1.1")) - if err != nil { - return nil, err - } - ip = net.ParseIP(s) - if ip == nil { - return nil, fmt.Errorf("DNS plaintext address %q is not a valid IP address", s) - } - return ip, nil -} - -// GetDNSKeepNameserver obtains if the nameserver present in /etc/resolv.conf -// should be kept instead of overridden, from the environment variable DNS_KEEP_NAMESERVER. -func (r *reader) GetDNSKeepNameserver() (on bool, err error) { - return r.env.OnOff("DNS_KEEP_NAMESERVER", libparams.Default("off")) -} diff --git a/internal/params/firewall.go b/internal/params/firewall.go deleted file mode 100644 index ecf23e8e..00000000 --- a/internal/params/firewall.go +++ /dev/null @@ -1,68 +0,0 @@ -package params - -import ( - "fmt" - "strconv" - "strings" - - libparams "github.com/qdm12/golibs/params" -) - -// GetFirewall obtains if the firewall should be enabled from the environment variable FIREWALL. -func (r *reader) GetFirewall() (enabled bool, err error) { - return r.env.OnOff("FIREWALL", libparams.Default("on")) -} - -// GetAllowedVPNInputPorts obtains a list of input ports to allow from the -// VPN server side in the firewall, from the environment variable FIREWALL_VPN_INPUT_PORTS. -func (r *reader) GetVPNInputPorts() (ports []uint16, err error) { - s, err := r.env.Get("FIREWALL_VPN_INPUT_PORTS", libparams.Default("")) - if err != nil { - return nil, err - } - if len(s) == 0 { - return nil, nil - } - portsStr := strings.Split(s, ",") - ports = make([]uint16, len(portsStr)) - for i := range portsStr { - portInt, err := strconv.Atoi(portsStr[i]) - if err != nil { - return nil, fmt.Errorf("VPN input port %q is not valid (%s)", portInt, err) - } else if portInt <= 0 || portInt > 65535 { - return nil, fmt.Errorf("VPN input port %d must be between 1 and 65535", portInt) - } - ports[i] = uint16(portInt) - } - return ports, nil -} - -// GetInputPorts obtains a list of input ports to allow through the -// default interface in the firewall, from the environment variable FIREWALL_INPUT_PORTS. -func (r *reader) GetInputPorts() (ports []uint16, err error) { - s, err := r.env.Get("FIREWALL_INPUT_PORTS", libparams.Default("")) - if err != nil { - return nil, err - } - if len(s) == 0 { - return nil, nil - } - portsStr := strings.Split(s, ",") - ports = make([]uint16, len(portsStr)) - for i := range portsStr { - portInt, err := strconv.Atoi(portsStr[i]) - if err != nil { - return nil, fmt.Errorf("Input port %q is not valid (%s)", portInt, err) - } else if portInt <= 0 || portInt > 65535 { - return nil, fmt.Errorf("Input port %d must be between 1 and 65535", portInt) - } - ports[i] = uint16(portInt) - } - return ports, nil -} - -// GetFirewallDebug obtains if the firewall should run in debug verbose mode -// from the environment variable FIREWALL_DEBUG. -func (r *reader) GetFirewallDebug() (debug bool, err error) { - return r.env.OnOff("FIREWALL_DEBUG", libparams.Default("off")) -} diff --git a/internal/params/httpproxy.go b/internal/params/httpproxy.go deleted file mode 100644 index 8b2edb97..00000000 --- a/internal/params/httpproxy.go +++ /dev/null @@ -1,80 +0,0 @@ -package params - -import ( - "strings" - - libparams "github.com/qdm12/golibs/params" -) - -// GetHTTPProxy obtains if the HTTP proxy is on from the environment variable -// HTTPPROXY, and using PROXY and TINYPROXY as retro-compatibility names. -func (r *reader) GetHTTPProxy() (enabled bool, err error) { - retroKeysOption := libparams.RetroKeys( - []string{"TINYPROXY", "PROXY"}, - r.onRetroActive, - ) - return r.env.OnOff("HTTPPROXY", retroKeysOption, libparams.Default("off")) -} - -// GetHTTPProxyLog obtains the if http proxy requests should be logged from -// the environment variable HTTPPROXY_LOG, and using PROXY_LOG_LEVEL and -// TINYPROXY_LOG as retro-compatibility names. -func (r *reader) GetHTTPProxyLog() (log bool, err error) { - s, _ := r.env.Get("HTTPPROXY_LOG") - if len(s) == 0 { - s, _ = r.env.Get("PROXY_LOG_LEVEL") - if len(s) == 0 { - s, _ = r.env.Get("TINYPROXY_LOG") - if len(s) == 0 { - return false, nil // default log disabled - } - } - switch strings.ToLower(s) { - case "info", "connect", "notice": - return true, nil - default: - return false, nil - } - } - return r.env.OnOff("HTTPPROXY_LOG", libparams.Default("off")) -} - -// GetHTTPProxyPort obtains the HTTP proxy listening port from the environment variable -// HTTPPROXY_PORT, and using PROXY_PORT and TINYPROXY_PORT as retro-compatibility names. -func (r *reader) GetHTTPProxyPort() (port uint16, warning string, err error) { - retroKeysOption := libparams.RetroKeys( - []string{"TINYPROXY_PORT", "PROXY_PORT"}, - r.onRetroActive, - ) - return r.env.ListeningPort("HTTPPROXY_PORT", retroKeysOption, libparams.Default("8888")) -} - -// GetHTTPProxyUser obtains the HTTP proxy server user. -// It first tries to use the HTTPPROXY_USER environment variable (easier for the end user) -// and then tries to read from the secret file httpproxy_user if nothing was found. -func (r *reader) GetHTTPProxyUser() (user string, err error) { - const compulsory = false - return r.getFromEnvOrSecretFile( - "HTTPPROXY_USER", - compulsory, - []string{"TINYPROXY_USER", "PROXY_USER"}, - ) -} - -// GetHTTPProxyPassword obtains the HTTP proxy server password. -// It first tries to use the HTTPPROXY_PASSWORD environment variable (easier for the end user) -// and then tries to read from the secret file httpproxy_password if nothing was found. -func (r *reader) GetHTTPProxyPassword() (password string, err error) { - const compulsory = false - return r.getFromEnvOrSecretFile( - "HTTPPROXY_USER", - compulsory, - []string{"TINYPROXY_PASSWORD", "PROXY_PASSWORD"}, - ) -} - -// GetHTTPProxyStealth obtains the HTTP proxy server stealth mode -// from the environment variable HTTPPROXY_STEALTH. -func (r *reader) GetHTTPProxyStealth() (stealth bool, err error) { - return r.env.OnOff("HTTPPROXY_STEALTH", libparams.Default("off")) -} diff --git a/internal/params/mullvad.go b/internal/params/mullvad.go deleted file mode 100644 index 66228e67..00000000 --- a/internal/params/mullvad.go +++ /dev/null @@ -1,37 +0,0 @@ -package params - -import ( - "github.com/qdm12/gluetun/internal/constants" - libparams "github.com/qdm12/golibs/params" -) - -// GetMullvadCountries obtains the countries for the Mullvad servers from the -// environment variable COUNTRY. -func (r *reader) GetMullvadCountries() (countries []string, err error) { - return r.env.CSVInside("COUNTRY", constants.MullvadCountryChoices()) -} - -// GetMullvadCity obtains the cities for the Mullvad servers from the -// environment variable CITY. -func (r *reader) GetMullvadCities() (cities []string, err error) { - return r.env.CSVInside("CITY", constants.MullvadCityChoices()) -} - -// GetMullvadISPs obtains the ISPs for the Mullvad servers from the -// environment variable ISP. -func (r *reader) GetMullvadISPs() (isps []string, err error) { - return r.env.CSVInside("ISP", constants.MullvadISPChoices()) -} - -// GetMullvadPort obtains the port to reach the Mullvad server on from the -// environment variable PORT. -func (r *reader) GetMullvadPort() (port uint16, err error) { - n, err := r.env.IntRange("PORT", 0, 65535, libparams.Default("0")) - return uint16(n), err -} - -// GetMullvadOwned obtains if the server should be owned by Mullvad or not from the -// environment variable OWNED. -func (r *reader) GetMullvadOwned() (owned bool, err error) { - return r.env.YesNo("OWNED", libparams.Default("no")) -} diff --git a/internal/params/nordvpn.go b/internal/params/nordvpn.go deleted file mode 100644 index 3007a161..00000000 --- a/internal/params/nordvpn.go +++ /dev/null @@ -1,37 +0,0 @@ -package params - -import ( - "fmt" - "strconv" - - "github.com/qdm12/gluetun/internal/constants" -) - -// GetNordvpnRegions obtains the regions (countries) for the NordVPN server from the -// environment variable REGION. -func (r *reader) GetNordvpnRegions() (regions []string, err error) { - return r.env.CSVInside("REGION", constants.NordvpnRegionChoices()) -} - -// GetNordvpnRegion obtains the server numbers (optional) for the NordVPN servers from the -// environment variable SERVER_NUMBER. -func (r *reader) GetNordvpnNumbers() (numbers []uint16, err error) { - possibilities := make([]string, 65537) - for i := range possibilities { - possibilities[i] = fmt.Sprintf("%d", i) - } - possibilities[65536] = "" - values, err := r.env.CSVInside("SERVER_NUMBER", possibilities) - if err != nil { - return nil, err - } - numbers = make([]uint16, len(values)) - for i := range values { - n, err := strconv.Atoi(values[i]) - if err != nil { - return nil, err - } - numbers[i] = uint16(n) - } - return numbers, nil -} diff --git a/internal/params/openvpn.go b/internal/params/openvpn.go deleted file mode 100644 index cbb10979..00000000 --- a/internal/params/openvpn.go +++ /dev/null @@ -1,86 +0,0 @@ -package params - -import ( - "fmt" - "net" - - "github.com/qdm12/gluetun/internal/models" - libparams "github.com/qdm12/golibs/params" -) - -// GetUser obtains the user to use to connect to the VPN servers. -// It first tries to use the OPENVPN_USER environment variable (easier for the end user) -// and then tries to read from the secret file openvpn_user if nothing was found. -func (r *reader) GetUser() (user string, err error) { - const compulsory = true - return r.getFromEnvOrSecretFile("OPENVPN_USER", compulsory, []string{"USER"}) -} - -// GetPassword obtains the password to use to connect to the VPN servers. -// It first tries to use the OPENVPN_PASSWORD environment variable (easier for the end user) -// and then tries to read from the secret file openvpn_password if nothing was found. -func (r *reader) GetPassword() (s string, err error) { - const compulsory = true - return r.getFromEnvOrSecretFile("OPENVPN_PASSWORD", compulsory, []string{"PASSWORD"}) -} - -// GetNetworkProtocol obtains the network protocol to use to connect to the -// VPN servers from the environment variable PROTOCOL. -func (r *reader) GetNetworkProtocol() (protocol models.NetworkProtocol, err error) { - s, err := r.env.Inside("PROTOCOL", []string{"tcp", "udp"}, libparams.Default("udp")) - return models.NetworkProtocol(s), err -} - -// GetOpenVPNVerbosity obtains the verbosity level for verbosity between 0 and 6 -// from the environment variable OPENVPN_VERBOSITY. -func (r *reader) GetOpenVPNVerbosity() (verbosity int, err error) { - return r.env.IntRange("OPENVPN_VERBOSITY", 0, 6, libparams.Default("1")) -} - -// GetOpenVPNRoot obtains if openvpn should be run as root -// from the environment variable OPENVPN_ROOT. -func (r *reader) GetOpenVPNRoot() (root bool, err error) { - return r.env.YesNo("OPENVPN_ROOT", libparams.Default("no")) -} - -// GetTargetIP obtains the IP address to override over the list of IP addresses filtered -// from the environment variable OPENVPN_TARGET_IP. -func (r *reader) GetTargetIP() (ip net.IP, err error) { - s, err := r.env.Get("OPENVPN_TARGET_IP") - if len(s) == 0 { - return nil, nil - } else if err != nil { - return nil, err - } - ip = net.ParseIP(s) - if ip == nil { - return nil, fmt.Errorf("target IP address %q is not valid", s) - } - return ip, nil -} - -// GetOpenVPNCipher obtains a custom cipher to use with OpenVPN -// from the environment variable OPENVPN_CIPHER. -func (r *reader) GetOpenVPNCipher() (cipher string, err error) { - return r.env.Get("OPENVPN_CIPHER") -} - -// GetOpenVPNAuth obtains a custom auth algorithm to use with OpenVPN -// from the environment variable OPENVPN_AUTH. -func (r *reader) GetOpenVPNAuth() (auth string, err error) { - return r.env.Get("OPENVPN_AUTH") -} - -// GetOpenVPNIPv6 obtains if ipv6 should be tunneled through the -// openvpn tunnel from the environment variable OPENVPN_IPV6. -func (r *reader) GetOpenVPNIPv6() (ipv6 bool, err error) { - return r.env.OnOff("OPENVPN_IPV6", libparams.Default("off")) -} - -func (r *reader) GetOpenVPNMSSFix() (mssFix uint16, err error) { - n, err := r.env.IntRange("OPENVPN_MSSFIX", 0, 10000, libparams.Default("0")) - if err != nil { - return 0, err - } - return uint16(n), nil -} diff --git a/internal/params/params.go b/internal/params/params.go deleted file mode 100644 index 8a2b93d9..00000000 --- a/internal/params/params.go +++ /dev/null @@ -1,173 +0,0 @@ -package params - -import ( - "net" - "time" - - "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/golibs/logging" - "github.com/qdm12/golibs/os" - libparams "github.com/qdm12/golibs/params" - "github.com/qdm12/golibs/verification" -) - -// Reader contains methods to obtain parameters. -type Reader interface { - GetVPNSP() (vpnServiceProvider models.VPNProvider, err error) - - // DNS over TLS getters - GetDNSOverTLS() (DNSOverTLS bool, err error) - GetDNSOverTLSProviders() (providers []string, err error) - GetDNSOverTLSCaching() (caching bool, err error) - GetDNSOverTLSVerbosity() (verbosityLevel uint8, err error) - GetDNSOverTLSVerbosityDetails() (verbosityDetailsLevel uint8, err error) - GetDNSOverTLSValidationLogLevel() (validationLogLevel uint8, err error) - GetDNSMaliciousBlocking() (blocking bool, err error) - GetDNSSurveillanceBlocking() (blocking bool, err error) - GetDNSAdsBlocking() (blocking bool, err error) - GetDNSUnblockedHostnames() (hostnames []string, err error) - GetDNSOverTLSPrivateAddresses() (privateAddresses []string, err error) - GetDNSOverTLSIPv6() (ipv6 bool, err error) - GetDNSUpdatePeriod() (period time.Duration, err error) - GetDNSPlaintext() (ip net.IP, err error) - GetDNSKeepNameserver() (on bool, err error) - - // System - GetPUID() (puid int, err error) - GetPGID() (pgid int, err error) - GetTimezone() (timezone string, err error) - GetPublicIPFilepath() (filepath models.Filepath, err error) - - // Firewall getters - GetFirewall() (enabled bool, err error) - GetVPNInputPorts() (ports []uint16, err error) - GetInputPorts() (ports []uint16, err error) - GetOutboundSubnets() (outboundSubnets []net.IPNet, err error) - GetFirewallDebug() (debug bool, err error) - - // VPN getters - GetUser() (s string, err error) - GetPassword() (s string, err error) - GetNetworkProtocol() (protocol models.NetworkProtocol, err error) - GetOpenVPNVerbosity() (verbosity int, err error) - GetOpenVPNRoot() (root bool, err error) - GetTargetIP() (ip net.IP, err error) - GetOpenVPNCipher() (cipher string, err error) - GetOpenVPNAuth() (auth string, err error) - GetOpenVPNIPv6() (tunnel bool, err error) - GetOpenVPNMSSFix() (mssFix uint16, err error) - - // PIA getters - GetPortForwarding() (activated bool, err error) - GetPortForwardingStatusFilepath() (filepath models.Filepath, err error) - GetPIAEncryptionPreset() (preset string, err error) - GetPIARegions() (regions []string, err error) - GetPIAPort() (port uint16, err error) - - // Mullvad getters - GetMullvadCountries() (countries []string, err error) - GetMullvadCities() (cities []string, err error) - GetMullvadISPs() (ips []string, err error) - GetMullvadPort() (port uint16, err error) - GetMullvadOwned() (owned bool, err error) - - // Windscribe getters - GetWindscribeRegions() (countries []string, err error) - GetWindscribeCities() (cities []string, err error) - GetWindscribeHostnames() (hostnames []string, err error) - GetWindscribePort(protocol models.NetworkProtocol) (port uint16, err error) - - // Surfshark getters - GetSurfsharkRegions() (countries []string, err error) - - // Cyberghost getters - GetCyberghostGroup() (group string, err error) - GetCyberghostRegions() (regions []string, err error) - GetCyberghostClientKey() (clientKey string, err error) - GetCyberghostClientCertificate() (clientCertificate string, err error) - - // Vyprvpn getters - GetVyprvpnRegions() (regions []string, err error) - - // NordVPN getters - GetNordvpnRegions() (regions []string, err error) - GetNordvpnNumbers() (numbers []uint16, err error) - - // Privado getters - GetPrivadoHostnames() (hostnames []string, err error) - - // PureVPN getters - GetPurevpnRegions() (regions []string, err error) - GetPurevpnCountries() (countries []string, err error) - GetPurevpnCities() (cities []string, err error) - - // Shadowsocks getters - GetShadowSocks() (activated bool, err error) - GetShadowSocksLog() (activated bool, err error) - GetShadowSocksPort() (port uint16, warning string, err error) - GetShadowSocksPassword() (password string, err error) - GetShadowSocksMethod() (method string, err error) - - // HTTP proxy getters - GetHTTPProxy() (activated bool, err error) - GetHTTPProxyLog() (log bool, err error) - GetHTTPProxyPort() (port uint16, warning string, err error) - GetHTTPProxyUser() (user string, err error) - GetHTTPProxyPassword() (password string, err error) - GetHTTPProxyStealth() (stealth bool, err error) - - // Public IP getters - GetPublicIPPeriod() (period time.Duration, err error) - - // Control server - GetControlServerPort() (port uint16, warning string, err error) - GetControlServerLog() (enabled bool, err error) - - GetVersionInformation() (enabled bool, err error) - - GetUpdaterPeriod() (period time.Duration, err error) -} - -type reader struct { - env libparams.Env - logger logging.Logger - regex verification.Regex - os os.OS -} - -// Newreader returns a paramsReadeer object to read parameters from -// environment variables. -func NewReader(logger logging.Logger, os os.OS) Reader { - return &reader{ - env: libparams.NewEnv(), - logger: logger, - regex: verification.NewRegex(), - os: os, - } -} - -// GetVPNSP obtains the VPN service provider to use from the environment variable VPNSP. -func (r *reader) GetVPNSP() (vpnServiceProvider models.VPNProvider, err error) { - s, err := r.env.Inside( - "VPNSP", - []string{ - "pia", "private internet access", - "mullvad", "windscribe", "surfshark", "cyberghost", - "vyprvpn", "nordvpn", "purevpn", "privado", - }, libparams.Default("private internet access")) - if s == "pia" { - s = "private internet access" - } - return models.VPNProvider(s), err -} - -func (r *reader) GetVersionInformation() (enabled bool, err error) { - return r.env.OnOff("VERSION_INFORMATION", libparams.Default("on")) -} - -func (r *reader) onRetroActive(oldKey, newKey string) { - r.logger.Warn( - "You are using the old environment variable %s, please consider changing it to %s", - oldKey, newKey, - ) -} diff --git a/internal/params/pia.go b/internal/params/pia.go deleted file mode 100644 index 239ad9da..00000000 --- a/internal/params/pia.go +++ /dev/null @@ -1,72 +0,0 @@ -package params - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/constants" - "github.com/qdm12/gluetun/internal/models" - libparams "github.com/qdm12/golibs/params" -) - -// GetPortForwarding obtains if port forwarding on the VPN provider server -// side is enabled or not from the environment variable PORT_FORWARDING -// Only valid for older PIA servers for now. -func (r *reader) GetPortForwarding() (activated bool, err error) { - s, err := r.env.Get("PORT_FORWARDING", libparams.Default("off")) - if err != nil { - return false, err - } - // Custom for retro-compatibility - if s == "false" || s == "off" { - return false, nil - } else if s == "true" || s == "on" { - return true, nil - } - return false, fmt.Errorf("PORT_FORWARDING can only be \"on\" or \"off\"") -} - -// GetPortForwardingStatusFilepath obtains the port forwarding status file path -// from the environment variable PORT_FORWARDING_STATUS_FILE. -func (r *reader) GetPortForwardingStatusFilepath() (filepath models.Filepath, err error) { - filepathStr, err := r.env.Path( - "PORT_FORWARDING_STATUS_FILE", - libparams.Default("/tmp/gluetun/forwarded_port"), - libparams.CaseSensitiveValue()) - return models.Filepath(filepathStr), err -} - -// GetPIAEncryptionPreset obtains the encryption level for the PIA connection -// from the environment variable PIA_ENCRYPTION, and using ENCRYPTION for -// retro compatibility. -func (r *reader) GetPIAEncryptionPreset() (preset string, err error) { - // Retro-compatibility - s, err := r.env.Inside("ENCRYPTION", []string{ - constants.PIAEncryptionPresetNormal, - constants.PIAEncryptionPresetStrong}) - if err != nil { - return "", err - } else if len(s) != 0 { - r.logger.Warn("You are using the old environment variable ENCRYPTION, please consider changing it to PIA_ENCRYPTION") - return s, nil - } - return r.env.Inside( - "PIA_ENCRYPTION", - []string{ - constants.PIAEncryptionPresetNormal, - constants.PIAEncryptionPresetStrong, - }, - libparams.Default(constants.PIAEncryptionPresetStrong)) -} - -// GetPIARegions obtains the regions for the PIA servers from the -// environment variable REGION. -func (r *reader) GetPIARegions() (regions []string, err error) { - return r.env.CSVInside("REGION", constants.PIAGeoChoices()) -} - -// GetPIAPort obtains the port to reach the PIA server on from the -// environment variable PORT. -func (r *reader) GetPIAPort() (port uint16, err error) { - n, err := r.env.IntRange("PORT", 0, 65535, libparams.Default("0")) - return uint16(n), err -} diff --git a/internal/params/privado.go b/internal/params/privado.go deleted file mode 100644 index b84c1a53..00000000 --- a/internal/params/privado.go +++ /dev/null @@ -1,14 +0,0 @@ -package params - -import ( - "github.com/qdm12/gluetun/internal/constants" - libparams "github.com/qdm12/golibs/params" -) - -// GetPrivadoHostnames obtains the hostnames for the Privado server from the -// environment variable SERVER_HOSTNAME. -func (r *reader) GetPrivadoHostnames() (hosts []string, err error) { - return r.env.CSVInside("SERVER_HOSTNAME", - constants.PrivadoHostnameChoices(), - libparams.RetroKeys([]string{"HOSTNAME"}, r.onRetroActive)) -} diff --git a/internal/params/publicip.go b/internal/params/publicip.go deleted file mode 100644 index 19c3d1f5..00000000 --- a/internal/params/publicip.go +++ /dev/null @@ -1,28 +0,0 @@ -package params - -import ( - "time" - - "github.com/qdm12/gluetun/internal/models" - libparams "github.com/qdm12/golibs/params" -) - -// GetPublicIPPeriod obtains the period to fetch the IP address periodically. -// Set to 0 to disable. -func (r *reader) GetPublicIPPeriod() (period time.Duration, err error) { - s, err := r.env.Get("PUBLICIP_PERIOD", libparams.Default("12h")) - if err != nil { - return 0, err - } - return time.ParseDuration(s) -} - -// GetPublicIPFilepath obtains the public IP filepath -// from the environment variable PUBLICIP_FILE with retro-compatible -// environment variable IP_STATUS_FILE. -func (r *reader) GetPublicIPFilepath() (filepath models.Filepath, err error) { - filepathStr, err := r.env.Path("PUBLICIP_FILE", - libparams.RetroKeys([]string{"IP_STATUS_FILE"}, r.onRetroActive), - libparams.Default("/tmp/gluetun/ip"), libparams.CaseSensitiveValue()) - return models.Filepath(filepathStr), err -} diff --git a/internal/params/purevpn.go b/internal/params/purevpn.go deleted file mode 100644 index a2b93164..00000000 --- a/internal/params/purevpn.go +++ /dev/null @@ -1,23 +0,0 @@ -package params - -import ( - "github.com/qdm12/gluetun/internal/constants" -) - -// GetPurevpnRegions obtains the regions (continents) for the PureVPN servers from the -// environment variable REGION. -func (r *reader) GetPurevpnRegions() (regions []string, err error) { - return r.env.CSVInside("REGION", constants.PurevpnRegionChoices()) -} - -// GetPurevpnCountries obtains the countries for the PureVPN servers from the -// environment variable COUNTRY. -func (r *reader) GetPurevpnCountries() (countries []string, err error) { - return r.env.CSVInside("COUNTRY", constants.PurevpnCountryChoices()) -} - -// GetPurevpnCities obtains the cities for the PureVPN servers from the -// environment variable CITY. -func (r *reader) GetPurevpnCities() (cities []string, err error) { - return r.env.CSVInside("CITY", constants.PurevpnCityChoices()) -} diff --git a/internal/params/routing.go b/internal/params/routing.go deleted file mode 100644 index 33809491..00000000 --- a/internal/params/routing.go +++ /dev/null @@ -1,37 +0,0 @@ -package params - -import ( - "fmt" - "net" - "strings" - - libparams "github.com/qdm12/golibs/params" -) - -// GetOutboundSubnets obtains the CIDR subnets from the comma separated list of the -// environment variable FIREWALL_OUTBOUND_SUBNETS. -func (r *reader) GetOutboundSubnets() (outboundSubnets []net.IPNet, err error) { - const key = "FIREWALL_OUTBOUND_SUBNETS" - retroOption := libparams.RetroKeys( - []string{"EXTRA_SUBNETS"}, - r.onRetroActive, - ) - s, err := r.env.Get(key, retroOption) - if err != nil { - return nil, err - } else if s == "" { - return nil, nil - } - subnets := strings.Split(s, ",") - for _, subnet := range subnets { - _, cidr, err := net.ParseCIDR(subnet) - if err != nil { - return nil, fmt.Errorf("cannot parse outbound subnet %q from environment variable with key %s: %w", subnet, key, err) - } else if cidr == nil { - return nil, fmt.Errorf("cannot parse outbound subnet %q from environment variable with key %s: subnet is nil", - subnet, key) - } - outboundSubnets = append(outboundSubnets, *cidr) - } - return outboundSubnets, nil -} diff --git a/internal/params/server.go b/internal/params/server.go deleted file mode 100644 index cde5d4ad..00000000 --- a/internal/params/server.go +++ /dev/null @@ -1,13 +0,0 @@ -package params - -import ( - libparams "github.com/qdm12/golibs/params" -) - -func (r *reader) GetControlServerPort() (port uint16, warning string, err error) { - return r.env.ListeningPort("HTTP_CONTROL_SERVER_PORT", libparams.Default("8000")) -} - -func (r *reader) GetControlServerLog() (enabled bool, err error) { - return r.env.OnOff("HTTP_CONTROL_SERVER_LOG", libparams.Default("on")) -} diff --git a/internal/params/shadowsocks.go b/internal/params/shadowsocks.go deleted file mode 100644 index 8e71cb49..00000000 --- a/internal/params/shadowsocks.go +++ /dev/null @@ -1,37 +0,0 @@ -package params - -import ( - libparams "github.com/qdm12/golibs/params" -) - -// GetShadowSocks obtains if ShadowSocks is on from the environment variable -// SHADOWSOCKS. -func (r *reader) GetShadowSocks() (activated bool, err error) { - return r.env.OnOff("SHADOWSOCKS", libparams.Default("off")) -} - -// GetShadowSocksLog obtains the ShadowSocks log level from the environment variable -// SHADOWSOCKS_LOG. -func (r *reader) GetShadowSocksLog() (activated bool, err error) { - return r.env.OnOff("SHADOWSOCKS_LOG", libparams.Default("off")) -} - -// GetShadowSocksPort obtains the ShadowSocks listening port from the environment variable -// SHADOWSOCKS_PORT. -func (r *reader) GetShadowSocksPort() (port uint16, warning string, err error) { - return r.env.ListeningPort("SHADOWSOCKS_PORT", libparams.Default("8388")) -} - -// GetShadowSocksPassword obtains the ShadowSocks server password. -// It first tries to use the SHADOWSOCKS_PASSWORD environment variable (easier for the end user) -// and then tries to read from the secret file shadowsocks_password if nothing was found. -func (r *reader) GetShadowSocksPassword() (password string, err error) { - const compulsory = false - return r.getFromEnvOrSecretFile("SHADOWSOCKS_PASSWORD", compulsory, nil) -} - -// GetShadowSocksMethod obtains the ShadowSocks method to use from the environment variable -// SHADOWSOCKS_METHOD. -func (r *reader) GetShadowSocksMethod() (method string, err error) { - return r.env.Get("SHADOWSOCKS_METHOD", libparams.Default("chacha20-ietf-poly1305")) -} diff --git a/internal/params/surfshark.go b/internal/params/surfshark.go deleted file mode 100644 index ce2f6004..00000000 --- a/internal/params/surfshark.go +++ /dev/null @@ -1,11 +0,0 @@ -package params - -import ( - "github.com/qdm12/gluetun/internal/constants" -) - -// GetSurfsharkRegions obtains the regions for the Surfshark servers from the -// environment variable REGION. -func (r *reader) GetSurfsharkRegions() (regions []string, err error) { - return r.env.CSVInside("REGION", constants.SurfsharkRegionChoices()) -} diff --git a/internal/params/system.go b/internal/params/system.go deleted file mode 100644 index 91dcfdbc..00000000 --- a/internal/params/system.go +++ /dev/null @@ -1,26 +0,0 @@ -package params - -import ( - libparams "github.com/qdm12/golibs/params" -) - -// GetPUID obtains the user ID to use from the environment variable PUID -// with retro compatible variable UID. -func (r *reader) GetPUID() (ppuid int, err error) { - return r.env.IntRange("PUID", 0, 65535, - libparams.Default("1000"), - libparams.RetroKeys([]string{"UID"}, r.onRetroActive)) -} - -// GetGID obtains the group ID to use from the environment variable PGID -// with retro compatible variable PGID. -func (r *reader) GetPGID() (pgid int, err error) { - return r.env.IntRange("PGID", 0, 65535, - libparams.Default("1000"), - libparams.RetroKeys([]string{"GID"}, r.onRetroActive)) -} - -// GetTZ obtains the timezone from the environment variable TZ. -func (r *reader) GetTimezone() (timezone string, err error) { - return r.env.Get("TZ") -} diff --git a/internal/params/updater.go b/internal/params/updater.go deleted file mode 100644 index 8884b259..00000000 --- a/internal/params/updater.go +++ /dev/null @@ -1,17 +0,0 @@ -package params - -import ( - "time" - - libparams "github.com/qdm12/golibs/params" -) - -// GetUpdaterPeriod obtains the period to fetch the servers information when the tunnel is up. -// Set to 0 to disable. -func (r *reader) GetUpdaterPeriod() (period time.Duration, err error) { - s, err := r.env.Get("UPDATER_PERIOD", libparams.Default("0")) - if err != nil { - return 0, err - } - return time.ParseDuration(s) -} diff --git a/internal/params/vypervpn.go b/internal/params/vypervpn.go deleted file mode 100644 index ed162645..00000000 --- a/internal/params/vypervpn.go +++ /dev/null @@ -1,11 +0,0 @@ -package params - -import ( - "github.com/qdm12/gluetun/internal/constants" -) - -// GetVyprvpnRegions obtains the regions for the Vyprvpn servers from the -// environment variable REGION. -func (r *reader) GetVyprvpnRegions() (regions []string, err error) { - return r.env.CSVInside("REGION", constants.VyprvpnRegionChoices()) -} diff --git a/internal/params/windscribe.go b/internal/params/windscribe.go deleted file mode 100644 index 726a0b6a..00000000 --- a/internal/params/windscribe.go +++ /dev/null @@ -1,58 +0,0 @@ -package params - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/constants" - "github.com/qdm12/gluetun/internal/models" - libparams "github.com/qdm12/golibs/params" -) - -// GetWindscribeRegions obtains the regions for the Windscribe servers from the -// environment variable REGION. -func (r *reader) GetWindscribeRegions() (regions []string, err error) { - return r.env.CSVInside("REGION", constants.WindscribeRegionChoices()) -} - -// GetWindscribeCities obtains the cities for the Windscribe servers from the -// environment variable CITY. -func (r *reader) GetWindscribeCities() (cities []string, err error) { - return r.env.CSVInside("CITY", constants.WindscribeCityChoices()) -} - -// GetWindscribeHostnames obtains the hostnames for the Windscribe servers from the -// environment variable SERVER_HOSTNAME. -func (r *reader) GetWindscribeHostnames() (hostnames []string, err error) { - return r.env.CSVInside("SERVER_HOSTNAME", - constants.WindscribeHostnameChoices(), - libparams.RetroKeys([]string{"HOSTNAME"}, r.onRetroActive), - ) -} - -// GetWindscribePort obtains the port to reach the Windscribe server on from the -// environment variable PORT. -//nolint:gomnd -func (r *reader) GetWindscribePort(protocol models.NetworkProtocol) (port uint16, err error) { - n, err := r.env.IntRange("PORT", 0, 65535, libparams.Default("0")) - if err != nil { - return 0, err - } - if n == 0 { - return 0, nil - } - switch protocol { - case constants.TCP: - switch n { - case 21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783: - default: - return 0, fmt.Errorf("port %d is not valid for protocol %s", n, protocol) - } - case constants.UDP: - switch n { - case 53, 80, 123, 443, 1194, 54783: - default: - return 0, fmt.Errorf("port %d is not valid for protocol %s", n, protocol) - } - } - return uint16(n), nil -} diff --git a/internal/provider/cyberghost.go b/internal/provider/cyberghost.go index cdb49e4c..f7d935cf 100644 --- a/internal/provider/cyberghost.go +++ b/internal/provider/cyberghost.go @@ -9,10 +9,10 @@ import ( "strconv" "strings" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" ) @@ -41,7 +41,7 @@ func (c *cyberghost) filterServers(regions []string, group string) (servers []mo return servers } -func (c *cyberghost) GetOpenVPNConnection(selection models.ServerSelection) ( +func (c *cyberghost) GetOpenVPNConnection(selection configuration.ServerSelection) ( connection models.OpenVPNConnection, err error) { const httpsPort = 443 if selection.TargetIP != nil { @@ -65,7 +65,7 @@ func (c *cyberghost) GetOpenVPNConnection(selection models.ServerSelection) ( } func (c *cyberghost) BuildConf(connection models.OpenVPNConnection, - username string, settings settings.OpenVPN) (lines []string) { + username string, settings configuration.OpenVPN) (lines []string) { if len(settings.Cipher) == 0 { settings.Cipher = aes256cbc } diff --git a/internal/provider/mullvad.go b/internal/provider/mullvad.go index 31f2124a..c01d66a9 100644 --- a/internal/provider/mullvad.go +++ b/internal/provider/mullvad.go @@ -8,10 +8,10 @@ import ( "net/http" "strconv" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" ) @@ -43,7 +43,7 @@ func (m *mullvad) filterServers(countries, cities, isps []string, owned bool) (s return servers } -func (m *mullvad) GetOpenVPNConnection(selection models.ServerSelection) ( +func (m *mullvad) GetOpenVPNConnection(selection configuration.ServerSelection) ( connection models.OpenVPNConnection, err error) { var defaultPort uint16 = 1194 if selection.Protocol == constants.TCP { @@ -75,7 +75,7 @@ func (m *mullvad) GetOpenVPNConnection(selection models.ServerSelection) ( } func (m *mullvad) BuildConf(connection models.OpenVPNConnection, - username string, settings settings.OpenVPN) (lines []string) { + username string, settings configuration.OpenVPN) (lines []string) { if len(settings.Cipher) == 0 { settings.Cipher = aes256cbc } diff --git a/internal/provider/nordvpn.go b/internal/provider/nordvpn.go index 32b55887..e31496ed 100644 --- a/internal/provider/nordvpn.go +++ b/internal/provider/nordvpn.go @@ -8,10 +8,10 @@ import ( "net/http" "strconv" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" ) @@ -49,7 +49,7 @@ func (n *nordvpn) filterServers(regions []string, protocol models.NetworkProtoco return servers } -func (n *nordvpn) GetOpenVPNConnection(selection models.ServerSelection) ( +func (n *nordvpn) GetOpenVPNConnection(selection configuration.ServerSelection) ( connection models.OpenVPNConnection, err error) { var port uint16 switch { @@ -80,7 +80,7 @@ func (n *nordvpn) GetOpenVPNConnection(selection models.ServerSelection) ( } func (n *nordvpn) BuildConf(connection models.OpenVPNConnection, - username string, settings settings.OpenVPN) (lines []string) { + username string, settings configuration.OpenVPN) (lines []string) { if len(settings.Cipher) == 0 { settings.Cipher = aes256cbc } diff --git a/internal/provider/piav4.go b/internal/provider/piav4.go index 218d94af..0de15603 100644 --- a/internal/provider/piav4.go +++ b/internal/provider/piav4.go @@ -17,11 +17,11 @@ import ( "strings" "time" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" gluetunLog "github.com/qdm12/gluetun/internal/logging" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" ) @@ -45,7 +45,7 @@ var ( ErrInvalidPort = errors.New("invalid port number") ) -func (p *pia) getPort(selection models.ServerSelection) (port uint16, err error) { +func (p *pia) getPort(selection configuration.ServerSelection) (port uint16, err error) { if selection.CustomPort == 0 { switch selection.Protocol { case constants.TCP: @@ -93,7 +93,7 @@ func (p *pia) getPort(selection models.ServerSelection) (port uint16, err error) return port, nil } -func (p *pia) GetOpenVPNConnection(selection models.ServerSelection) ( +func (p *pia) GetOpenVPNConnection(selection configuration.ServerSelection) ( connection models.OpenVPNConnection, err error) { port, err := p.getPort(selection) if err != nil { @@ -131,7 +131,7 @@ func (p *pia) GetOpenVPNConnection(selection models.ServerSelection) ( } func (p *pia) BuildConf(connection models.OpenVPNConnection, - username string, settings settings.OpenVPN) (lines []string) { + username string, settings configuration.OpenVPN) (lines []string) { var X509CRL, certificate string var defaultCipher, defaultAuth string if settings.Provider.ExtraConfigOptions.EncryptionPreset == constants.PIAEncryptionPresetNormal { diff --git a/internal/provider/privado.go b/internal/provider/privado.go index 81d2f079..411e6115 100644 --- a/internal/provider/privado.go +++ b/internal/provider/privado.go @@ -8,10 +8,10 @@ import ( "net/http" "strconv" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" ) @@ -39,7 +39,7 @@ func (s *privado) filterServers(hostnames []string) (servers []models.PrivadoSer return servers } -func (s *privado) GetOpenVPNConnection(selection models.ServerSelection) ( +func (s *privado) GetOpenVPNConnection(selection configuration.ServerSelection) ( connection models.OpenVPNConnection, err error) { var port uint16 = 1194 switch selection.Protocol { @@ -73,7 +73,7 @@ func (s *privado) GetOpenVPNConnection(selection models.ServerSelection) ( } func (s *privado) BuildConf(connection models.OpenVPNConnection, - username string, settings settings.OpenVPN) (lines []string) { + username string, settings configuration.OpenVPN) (lines []string) { if len(settings.Cipher) == 0 { settings.Cipher = aes256cbc } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 229d6a70..f5b5f10f 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -5,18 +5,18 @@ import ( "net" "net/http" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" ) // Provider contains methods to read and modify the openvpn configuration to connect as a client. type Provider interface { - GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) - BuildConf(connection models.OpenVPNConnection, username string, settings settings.OpenVPN) (lines []string) + GetOpenVPNConnection(selection configuration.ServerSelection) (connection models.OpenVPNConnection, err error) + BuildConf(connection models.OpenVPNConnection, username string, settings configuration.OpenVPN) (lines []string) PortForward(ctx context.Context, client *http.Client, openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator, syncState func(port uint16) (pfFilepath models.Filepath)) diff --git a/internal/provider/purevpn.go b/internal/provider/purevpn.go index abc58c81..a4b1825c 100644 --- a/internal/provider/purevpn.go +++ b/internal/provider/purevpn.go @@ -8,10 +8,10 @@ import ( "net/http" "strconv" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" ) @@ -42,7 +42,7 @@ func (p *purevpn) filterServers(regions, countries, cities []string) (servers [] return servers } -func (p *purevpn) GetOpenVPNConnection(selection models.ServerSelection) ( +func (p *purevpn) GetOpenVPNConnection(selection configuration.ServerSelection) ( connection models.OpenVPNConnection, err error) { var port uint16 switch { @@ -75,7 +75,7 @@ func (p *purevpn) GetOpenVPNConnection(selection models.ServerSelection) ( } func (p *purevpn) BuildConf(connection models.OpenVPNConnection, - username string, settings settings.OpenVPN) (lines []string) { + username string, settings configuration.OpenVPN) (lines []string) { if len(settings.Cipher) == 0 { settings.Cipher = aes256cbc } diff --git a/internal/provider/surfshark.go b/internal/provider/surfshark.go index 17fabc7f..1ee29a0f 100644 --- a/internal/provider/surfshark.go +++ b/internal/provider/surfshark.go @@ -8,10 +8,10 @@ import ( "net/http" "strconv" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" ) @@ -40,7 +40,7 @@ func (s *surfshark) filterServers(regions []string) (servers []models.SurfsharkS return servers } -func (s *surfshark) GetOpenVPNConnection(selection models.ServerSelection) ( +func (s *surfshark) GetOpenVPNConnection(selection configuration.ServerSelection) ( connection models.OpenVPNConnection, err error) { var port uint16 switch { @@ -76,7 +76,7 @@ func (s *surfshark) GetOpenVPNConnection(selection models.ServerSelection) ( } func (s *surfshark) BuildConf(connection models.OpenVPNConnection, - username string, settings settings.OpenVPN) (lines []string) { + username string, settings configuration.OpenVPN) (lines []string) { if len(settings.Cipher) == 0 { settings.Cipher = aes256cbc } diff --git a/internal/provider/vyprvpn.go b/internal/provider/vyprvpn.go index 2b8814a6..3d3fe845 100644 --- a/internal/provider/vyprvpn.go +++ b/internal/provider/vyprvpn.go @@ -8,10 +8,10 @@ import ( "net/http" "strconv" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" ) @@ -40,7 +40,7 @@ func (v *vyprvpn) filterServers(regions []string) (servers []models.VyprvpnServe return servers } -func (v *vyprvpn) GetOpenVPNConnection(selection models.ServerSelection) ( +func (v *vyprvpn) GetOpenVPNConnection(selection configuration.ServerSelection) ( connection models.OpenVPNConnection, err error) { var port uint16 switch { @@ -72,7 +72,7 @@ func (v *vyprvpn) GetOpenVPNConnection(selection models.ServerSelection) ( } func (v *vyprvpn) BuildConf(connection models.OpenVPNConnection, - username string, settings settings.OpenVPN) (lines []string) { + username string, settings configuration.OpenVPN) (lines []string) { if len(settings.Cipher) == 0 { settings.Cipher = aes256cbc } diff --git a/internal/provider/windscribe.go b/internal/provider/windscribe.go index 572c97f0..8ea72862 100644 --- a/internal/provider/windscribe.go +++ b/internal/provider/windscribe.go @@ -9,10 +9,10 @@ import ( "strconv" "strings" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/firewall" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" ) @@ -44,7 +44,7 @@ func (w *windscribe) filterServers(regions, cities, hostnames []string) (servers } //nolint:lll -func (w *windscribe) GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error) { +func (w *windscribe) GetOpenVPNConnection(selection configuration.ServerSelection) (connection models.OpenVPNConnection, err error) { var port uint16 switch { case selection.CustomPort > 0: @@ -75,7 +75,7 @@ func (w *windscribe) GetOpenVPNConnection(selection models.ServerSelection) (con } func (w *windscribe) BuildConf(connection models.OpenVPNConnection, - username string, settings settings.OpenVPN) (lines []string) { + username string, settings configuration.OpenVPN) (lines []string) { if len(settings.Cipher) == 0 { settings.Cipher = aes256cbc } diff --git a/internal/publicip/loop.go b/internal/publicip/loop.go index 9b637a8b..b2e3a60a 100644 --- a/internal/publicip/loop.go +++ b/internal/publicip/loop.go @@ -7,9 +7,9 @@ import ( "sync" "time" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/os" ) @@ -19,8 +19,8 @@ type Looper interface { RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) GetStatus() (status models.LoopStatus) SetStatus(status models.LoopStatus) (outcome string, err error) - GetSettings() (settings settings.PublicIP) - SetSettings(settings settings.PublicIP) (outcome string) + GetSettings() (settings configuration.PublicIP) + SetSettings(settings configuration.PublicIP) (outcome string) GetPublicIP() (publicIP net.IP) } @@ -49,7 +49,7 @@ type looper struct { const defaultBackoffTime = 5 * time.Second func NewLooper(client *http.Client, logger logging.Logger, - settings settings.PublicIP, puid, pgid int, + settings configuration.PublicIP, puid, pgid int, os os.OS) Looper { return &looper{ state: state{ diff --git a/internal/publicip/state.go b/internal/publicip/state.go index 6f6a3cfe..cb781416 100644 --- a/internal/publicip/state.go +++ b/internal/publicip/state.go @@ -6,14 +6,14 @@ import ( "reflect" "sync" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" ) type state struct { status models.LoopStatus - settings settings.PublicIP + settings configuration.PublicIP ip net.IP statusMu sync.RWMutex settingsMu sync.RWMutex @@ -72,13 +72,13 @@ func (l *looper) SetStatus(status models.LoopStatus) (outcome string, err error) } } -func (l *looper) GetSettings() (settings settings.PublicIP) { +func (l *looper) GetSettings() (settings configuration.PublicIP) { l.state.settingsMu.RLock() defer l.state.settingsMu.RUnlock() return l.state.settings } -func (l *looper) SetSettings(settings settings.PublicIP) (outcome string) { +func (l *looper) SetSettings(settings configuration.PublicIP) (outcome string) { l.state.settingsMu.Lock() defer l.state.settingsMu.Unlock() settingsUnchanged := reflect.DeepEqual(settings, l.state.settings) diff --git a/internal/settings/dns.go b/internal/settings/dns.go deleted file mode 100644 index 3d5cc781..00000000 --- a/internal/settings/dns.go +++ /dev/null @@ -1,191 +0,0 @@ -package settings - -import ( - "fmt" - "net" - "strings" - "time" - - unboundmodels "github.com/qdm12/dns/pkg/models" - unbound "github.com/qdm12/dns/pkg/unbound" - "github.com/qdm12/gluetun/internal/params" -) - -// DNS contains settings to configure Unbound for DNS over TLS operation. -type DNS struct { //nolint:maligned - Enabled bool - PlaintextAddress net.IP - KeepNameserver bool - BlockMalicious bool - BlockAds bool - BlockSurveillance bool - UpdatePeriod time.Duration - Unbound unboundmodels.Settings -} - -func (d *DNS) String() string { - return strings.Join(d.lines(), "\n") -} - -const ( - subIndent = " |--" - indent = " " // used if lines already contain the subIndent -) - -func (d *DNS) lines() (lines []string) { - lines = append(lines, subIndent+"DNS:") - if d.PlaintextAddress != nil { - lines = append(lines, indent+subIndent+"Plaintext address: "+d.PlaintextAddress.String()) - } - keepNameserver := "no" - if d.KeepNameserver { - keepNameserver = "yes" - } - lines = append(lines, - indent+subIndent+"Keep nameserver (disabled blocking): "+keepNameserver) - if !d.Enabled { - lines = append(lines, indent+subIndent+"DNS over TLS: disabled") - return lines - } - lines = append(lines, indent+subIndent+"DNS over TLS:") - - lines = append(lines, indent+indent+subIndent+"Unbound:") - for _, line := range d.Unbound.Lines() { - lines = append(lines, indent+indent+indent+line) - } - - blockMalicious := disabled - if d.BlockMalicious { - blockMalicious = enabled - } - lines = append(lines, indent+indent+subIndent+"Block malicious: "+blockMalicious) - - blockAds := disabled - if d.BlockAds { - blockAds = enabled - } - lines = append(lines, indent+indent+subIndent+"Block ads: "+blockAds) - - blockSurveillance := disabled - if d.BlockSurveillance { - blockSurveillance = enabled - } - lines = append(lines, indent+indent+subIndent+"Block surveillance: "+blockSurveillance) - - update := "deactivated" - if d.UpdatePeriod > 0 { - update = "every " + d.UpdatePeriod.String() - } - lines = append(lines, indent+indent+subIndent+"Update: "+update) - - return lines -} - -// GetDNSSettings obtains DNS over TLS settings from environment variables using the params package. -func GetDNSSettings(paramsReader params.Reader) (settings DNS, err error) { - settings.Enabled, err = paramsReader.GetDNSOverTLS() - if err != nil { - return settings, err - } - - // Plain DNS settings - settings.PlaintextAddress, err = paramsReader.GetDNSPlaintext() - if err != nil { - return settings, err - } - settings.KeepNameserver, err = paramsReader.GetDNSKeepNameserver() - if err != nil { - return settings, err - } - - // DNS over TLS external settings - settings.BlockMalicious, err = paramsReader.GetDNSMaliciousBlocking() - if err != nil { - return settings, err - } - settings.BlockSurveillance, err = paramsReader.GetDNSSurveillanceBlocking() - if err != nil { - return settings, err - } - settings.BlockAds, err = paramsReader.GetDNSAdsBlocking() - if err != nil { - return settings, err - } - settings.UpdatePeriod, err = paramsReader.GetDNSUpdatePeriod() - if err != nil { - return settings, err - } - - // Unbound specific settings - settings.Unbound, err = getUnboundSettings(paramsReader) - if err != nil { - return settings, err - } - - // Consistency check - IPv6Support := false - for _, provider := range settings.Unbound.Providers { - providerData, ok := unbound.GetProviderData(provider) - switch { - case !ok: - return settings, fmt.Errorf("DNS provider %q does not have associated data", provider) - case !providerData.SupportsTLS: - return settings, fmt.Errorf("DNS provider %q does not support DNS over TLS", provider) - case providerData.SupportsIPv6: - IPv6Support = true - } - } - if settings.Unbound.IPv6 && !IPv6Support { - return settings, fmt.Errorf("None of the DNS over TLS provider(s) set support IPv6") - } - return settings, nil -} - -func getUnboundSettings(reader params.Reader) (settings unboundmodels.Settings, err error) { - settings.Providers, err = reader.GetDNSOverTLSProviders() - if err != nil { - return settings, err - } - settings.ListeningPort = 53 - settings.Caching, err = reader.GetDNSOverTLSCaching() - if err != nil { - return settings, err - } - settings.IPv4 = true - settings.IPv6, err = reader.GetDNSOverTLSIPv6() - if err != nil { - return settings, err - } - settings.VerbosityLevel, err = reader.GetDNSOverTLSVerbosity() - if err != nil { - return settings, err - } - settings.VerbosityDetailsLevel, err = reader.GetDNSOverTLSVerbosityDetails() - if err != nil { - return settings, err - } - settings.ValidationLogLevel, err = reader.GetDNSOverTLSValidationLogLevel() - if err != nil { - return settings, err - } - settings.BlockedHostnames = []string{} - settings.BlockedIPs, err = reader.GetDNSOverTLSPrivateAddresses() - if err != nil { - return settings, err - } - settings.AllowedHostnames, err = reader.GetDNSUnblockedHostnames() - if err != nil { - return settings, err - } - settings.AccessControl.Allowed = []net.IPNet{ - { - IP: net.IPv4zero, - Mask: net.IPv4Mask(0, 0, 0, 0), - }, - { - IP: net.IPv6zero, - Mask: net.IPMask{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - } - return settings, nil -} diff --git a/internal/settings/dns_test.go b/internal/settings/dns_test.go deleted file mode 100644 index 5f6384bc..00000000 --- a/internal/settings/dns_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package settings - -import ( - "net" - "testing" - "time" - - "github.com/qdm12/dns/pkg/models" - "github.com/stretchr/testify/assert" -) - -func Test_DNS_Lines(t *testing.T) { - t.Parallel() - testCases := map[string]struct { - settings DNS - lines []string - }{ - "disabled DOT": { - settings: DNS{ - PlaintextAddress: net.IP{1, 1, 1, 1}, - }, - lines: []string{ - " |--DNS:", - " |--Plaintext address: 1.1.1.1", - " |--Keep nameserver (disabled blocking): no", - " |--DNS over TLS: disabled", - }, - }, - "enabled DOT": { - settings: DNS{ - Enabled: true, - KeepNameserver: true, - Unbound: models.Settings{ - Providers: []string{"cloudflare"}, - }, - BlockMalicious: true, - BlockAds: true, - BlockSurveillance: true, - UpdatePeriod: time.Hour, - }, - lines: []string{ - " |--DNS:", - " |--Keep nameserver (disabled blocking): yes", - " |--DNS over TLS:", - " |--Unbound:", - " |--DNS over TLS providers:", - " |--cloudflare", - " |--Listening port: 0", - " |--Access control:", - " |--Allowed:", - " |--Caching: disabled", - " |--IPv4 resolution: disabled", - " |--IPv6 resolution: disabled", - " |--Verbosity level: 0/5", - " |--Verbosity details level: 0/4", - " |--Validation log level: 0/2", - " |--Blocked hostnames:", - " |--Blocked IP addresses:", - " |--Allowed hostnames:", - " |--Block malicious: enabled", - " |--Block ads: enabled", - " |--Block surveillance: enabled", - " |--Update: every 1h0m0s", - }, - }, - } - for name, testCase := range testCases { - testCase := testCase - t.Run(name, func(t *testing.T) { - t.Parallel() - lines := testCase.settings.lines() - assert.Equal(t, testCase.lines, lines) - }) - } -} diff --git a/internal/settings/firewall.go b/internal/settings/firewall.go deleted file mode 100644 index 1cfb77c6..00000000 --- a/internal/settings/firewall.go +++ /dev/null @@ -1,72 +0,0 @@ -package settings - -import ( - "fmt" - "net" - "strings" - - "github.com/qdm12/gluetun/internal/params" -) - -// Firewall contains settings to customize the firewall operation. -type Firewall struct { - VPNInputPorts []uint16 - InputPorts []uint16 - OutboundSubnets []net.IPNet - Enabled bool - Debug bool -} - -func (f *Firewall) String() string { - if !f.Enabled { - return "Firewall settings: disabled" - } - vpnInputPorts := make([]string, len(f.VPNInputPorts)) - for i, port := range f.VPNInputPorts { - vpnInputPorts[i] = fmt.Sprintf("%d", port) - } - inputPorts := make([]string, len(f.InputPorts)) - for i, port := range f.InputPorts { - inputPorts[i] = fmt.Sprintf("%d", port) - } - outboundSubnets := make([]string, len(f.OutboundSubnets)) - for i := range f.OutboundSubnets { - outboundSubnets[i] = f.OutboundSubnets[i].String() - } - - settingsList := []string{ - "Firewall settings:", - "VPN input ports: " + strings.Join(vpnInputPorts, ", "), - "Input ports: " + strings.Join(inputPorts, ", "), - "Outbound subnets: " + strings.Join(outboundSubnets, ", "), - } - if f.Debug { - settingsList = append(settingsList, "Debug: on") - } - return strings.Join(settingsList, "\n |--") -} - -// GetFirewallSettings obtains firewall settings from environment variables using the params package. -func GetFirewallSettings(paramsReader params.Reader) (settings Firewall, err error) { - settings.VPNInputPorts, err = paramsReader.GetVPNInputPorts() - if err != nil { - return settings, err - } - settings.InputPorts, err = paramsReader.GetInputPorts() - if err != nil { - return settings, err - } - settings.OutboundSubnets, err = paramsReader.GetOutboundSubnets() - if err != nil { - return settings, err - } - settings.Enabled, err = paramsReader.GetFirewall() - if err != nil { - return settings, err - } - settings.Debug, err = paramsReader.GetFirewallDebug() - if err != nil { - return settings, err - } - return settings, nil -} diff --git a/internal/settings/httpproxy.go b/internal/settings/httpproxy.go deleted file mode 100644 index f2bbc754..00000000 --- a/internal/settings/httpproxy.go +++ /dev/null @@ -1,71 +0,0 @@ -package settings - -import ( - "fmt" - "strings" - - "github.com/qdm12/gluetun/internal/params" -) - -// HTTPProxy contains settings to configure the HTTP proxy. -type HTTPProxy struct { - User string - Password string - Port uint16 - Enabled bool - Stealth bool - Log bool -} - -func (h *HTTPProxy) String() string { - if !h.Enabled { - return "HTTP Proxy settings: disabled" - } - auth, log, stealth := disabled, disabled, disabled - if h.User != "" { - auth = enabled - } - if h.Log { - log = enabled - } - if h.Stealth { - stealth = enabled - } - settingsList := []string{ - "HTTP proxy settings:", - fmt.Sprintf("Port: %d", h.Port), - "Authentication: " + auth, - "Stealth: " + stealth, - "Log: " + log, - } - return strings.Join(settingsList, "\n |--") -} - -// GetHTTPProxySettings obtains HTTPProxy settings from environment variables using the params package. -func GetHTTPProxySettings(paramsReader params.Reader) (settings HTTPProxy, warning string, err error) { - settings.Enabled, err = paramsReader.GetHTTPProxy() - if err != nil || !settings.Enabled { - return settings, "", err - } - settings.User, err = paramsReader.GetHTTPProxyUser() - if err != nil { - return settings, "", err - } - settings.Password, err = paramsReader.GetHTTPProxyPassword() - if err != nil { - return settings, "", err - } - settings.Stealth, err = paramsReader.GetHTTPProxyStealth() - if err != nil { - return settings, "", err - } - settings.Log, err = paramsReader.GetHTTPProxyLog() - if err != nil { - return settings, "", err - } - settings.Port, warning, err = paramsReader.GetHTTPProxyPort() - if err != nil { - return settings, warning, err - } - return settings, warning, nil -} diff --git a/internal/settings/openvpn.go b/internal/settings/openvpn.go deleted file mode 100644 index 0545c432..00000000 --- a/internal/settings/openvpn.go +++ /dev/null @@ -1,105 +0,0 @@ -package settings - -import ( - "fmt" - "strings" - - "github.com/qdm12/gluetun/internal/constants" - "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/params" -) - -// OpenVPN contains settings to configure the OpenVPN client. -type OpenVPN struct { - User string `json:"user"` - Password string `json:"password"` - Verbosity int `json:"verbosity"` - MSSFix uint16 `json:"mssfix"` - Root bool `json:"run_as_root"` - Cipher string `json:"cipher"` - Auth string `json:"auth"` - Provider models.ProviderSettings `json:"provider"` -} - -// GetOpenVPNSettings obtains the OpenVPN settings using the params functions. -func GetOpenVPNSettings(paramsReader params.Reader, vpnProvider models.VPNProvider) (settings OpenVPN, err error) { - settings.User, err = paramsReader.GetUser() - if err != nil { - return settings, err - } - // Remove spaces in user ID to simplify user's life, thanks @JeordyR - settings.User = strings.ReplaceAll(settings.User, " ", "") - if vpnProvider == constants.Mullvad { - settings.Password = "m" - } else { - settings.Password, err = paramsReader.GetPassword() - if err != nil { - return settings, err - } - } - settings.Verbosity, err = paramsReader.GetOpenVPNVerbosity() - if err != nil { - return settings, err - } - settings.Root, err = paramsReader.GetOpenVPNRoot() - if err != nil { - return settings, err - } - settings.Cipher, err = paramsReader.GetOpenVPNCipher() - if err != nil { - return settings, err - } - settings.Auth, err = paramsReader.GetOpenVPNAuth() - if err != nil { - return settings, err - } - settings.MSSFix, err = paramsReader.GetOpenVPNMSSFix() - if err != nil { - return settings, err - } - switch vpnProvider { - case constants.PrivateInternetAccess: - settings.Provider, err = GetPIASettings(paramsReader) - case constants.Mullvad: - settings.Provider, err = GetMullvadSettings(paramsReader) - case constants.Windscribe: - settings.Provider, err = GetWindscribeSettings(paramsReader) - case constants.Surfshark: - settings.Provider, err = GetSurfsharkSettings(paramsReader) - case constants.Cyberghost: - settings.Provider, err = GetCyberghostSettings(paramsReader) - case constants.Vyprvpn: - settings.Provider, err = GetVyprvpnSettings(paramsReader) - case constants.Nordvpn: - settings.Provider, err = GetNordvpnSettings(paramsReader) - case constants.Purevpn: - settings.Provider, err = GetPurevpnSettings(paramsReader) - case constants.Privado: - settings.Provider, err = GetPrivadoSettings(paramsReader) - default: - err = fmt.Errorf("VPN service provider %q is not valid", vpnProvider) - } - return settings, err -} - -func (o *OpenVPN) String() string { - runAsRoot := "no" - if o.Root { - runAsRoot = "yes" - } - settingsList := []string{ - "OpenVPN settings:", - "User: [redacted]", - "Password: [redacted]", - "Verbosity level: " + fmt.Sprintf("%d", o.Verbosity), - "Run as root: " + runAsRoot, - o.Provider.String(), - } - if len(o.Cipher) > 0 { - settingsList = append(settingsList, "Custom cipher: "+o.Cipher) - } - if len(o.Auth) > 0 { - settingsList = append(settingsList, "Custom auth algorithm: "+o.Auth) - } - return strings.Join(settingsList, "\n|--") -} diff --git a/internal/settings/providers.go b/internal/settings/providers.go deleted file mode 100644 index 11defb81..00000000 --- a/internal/settings/providers.go +++ /dev/null @@ -1,260 +0,0 @@ -package settings - -import ( - "fmt" - - "github.com/qdm12/gluetun/internal/constants" - "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/params" -) - -// GetPIASettings obtains PIA settings from environment variables using the params package. -func GetPIASettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) { - settings.Name = constants.PrivateInternetAccess - settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol() - if err != nil { - return settings, err - } - settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP() - if err != nil { - return settings, err - } - encryptionPreset, err := paramsReader.GetPIAEncryptionPreset() - if err != nil { - return settings, err - } - settings.ServerSelection.EncryptionPreset = encryptionPreset - settings.ExtraConfigOptions.EncryptionPreset = encryptionPreset - settings.ServerSelection.Regions, err = paramsReader.GetPIARegions() - if err != nil { - return settings, err - } - settings.ServerSelection.CustomPort, err = paramsReader.GetPIAPort() - if err != nil { - return settings, err - } - settings.PortForwarding.Enabled, err = paramsReader.GetPortForwarding() - if err != nil { - return settings, err - } - if settings.PortForwarding.Enabled { - settings.PortForwarding.Filepath, err = paramsReader.GetPortForwardingStatusFilepath() - if err != nil { - return settings, err - } - } - return settings, nil -} - -// GetMullvadSettings obtains Mullvad settings from environment variables using the params package. -func GetMullvadSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) { - settings.Name = constants.Mullvad - settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol() - if err != nil { - return settings, err - } - settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP() - if err != nil { - return settings, err - } - settings.ServerSelection.Countries, err = paramsReader.GetMullvadCountries() - if err != nil { - return settings, err - } - settings.ServerSelection.Cities, err = paramsReader.GetMullvadCities() - if err != nil { - return settings, err - } - settings.ServerSelection.ISPs, err = paramsReader.GetMullvadISPs() - if err != nil { - return settings, err - } - settings.ServerSelection.CustomPort, err = paramsReader.GetMullvadPort() - if err != nil { - return settings, err - } - if settings.ServerSelection.Protocol == constants.TCP { - switch settings.ServerSelection.CustomPort { - case 0, 80, 443, 1401: //nolint:gomnd - default: - return settings, fmt.Errorf("port %d is not valid for TCP protocol", settings.ServerSelection.CustomPort) - } - } else { - switch settings.ServerSelection.CustomPort { - case 0, 53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400: //nolint:gomnd - default: - return settings, fmt.Errorf("port %d is not valid for UDP protocol", settings.ServerSelection.CustomPort) - } - } - settings.ServerSelection.Owned, err = paramsReader.GetMullvadOwned() - if err != nil { - return settings, err - } - settings.ExtraConfigOptions.OpenVPNIPv6, err = paramsReader.GetOpenVPNIPv6() - if err != nil { - return settings, err - } - return settings, nil -} - -// GetWindscribeSettings obtains Windscribe settings from environment variables using the params package. -func GetWindscribeSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) { - settings.Name = constants.Windscribe - settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol() - if err != nil { - return settings, err - } - settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP() - if err != nil { - return settings, err - } - settings.ServerSelection.Regions, err = paramsReader.GetWindscribeRegions() - if err != nil { - return settings, err - } - settings.ServerSelection.Cities, err = paramsReader.GetWindscribeCities() - if err != nil { - return settings, err - } - settings.ServerSelection.Hostnames, err = paramsReader.GetWindscribeHostnames() - if err != nil { - return settings, err - } - settings.ServerSelection.CustomPort, err = paramsReader.GetWindscribePort(settings.ServerSelection.Protocol) - if err != nil { - return settings, err - } - return settings, nil -} - -// GetSurfsharkSettings obtains Surfshark settings from environment variables using the params package. -func GetSurfsharkSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) { - settings.Name = constants.Surfshark - settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol() - if err != nil { - return settings, err - } - settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP() - if err != nil { - return settings, err - } - settings.ServerSelection.Regions, err = paramsReader.GetSurfsharkRegions() - if err != nil { - return settings, err - } - return settings, nil -} - -// GetCyberghostSettings obtains Cyberghost settings from environment variables using the params package. -func GetCyberghostSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) { - settings.Name = constants.Cyberghost - settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol() - if err != nil { - return settings, err - } - settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP() - if err != nil { - return settings, err - } - settings.ExtraConfigOptions.ClientKey, err = paramsReader.GetCyberghostClientKey() - if err != nil { - return settings, err - } - settings.ExtraConfigOptions.ClientCertificate, err = paramsReader.GetCyberghostClientCertificate() - if err != nil { - return settings, err - } - settings.ServerSelection.Group, err = paramsReader.GetCyberghostGroup() - if err != nil { - return settings, err - } - settings.ServerSelection.Regions, err = paramsReader.GetCyberghostRegions() - if err != nil { - return settings, err - } - return settings, nil -} - -// GetVyprvpnSettings obtains Vyprvpn settings from environment variables using the params package. -func GetVyprvpnSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) { - settings.Name = constants.Vyprvpn - settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol() - if err != nil { - return settings, err - } - settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP() - if err != nil { - return settings, err - } - settings.ServerSelection.Regions, err = paramsReader.GetVyprvpnRegions() - if err != nil { - return settings, err - } - return settings, nil -} - -// GetNordvpnSettings obtains NordVPN settings from environment variables using the params package. -func GetNordvpnSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) { - settings.Name = constants.Nordvpn - settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol() - if err != nil { - return settings, err - } - settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP() - if err != nil { - return settings, err - } - settings.ServerSelection.Regions, err = paramsReader.GetNordvpnRegions() - if err != nil { - return settings, err - } - settings.ServerSelection.Numbers, err = paramsReader.GetNordvpnNumbers() - if err != nil { - return settings, err - } - return settings, nil -} - -// GetPurevpnSettings obtains Purevpn settings from environment variables using the params package. -func GetPurevpnSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) { - settings.Name = constants.Purevpn - settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol() - if err != nil { - return settings, err - } - settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP() - if err != nil { - return settings, err - } - settings.ServerSelection.Regions, err = paramsReader.GetPurevpnRegions() - if err != nil { - return settings, err - } - settings.ServerSelection.Countries, err = paramsReader.GetPurevpnCountries() - if err != nil { - return settings, err - } - settings.ServerSelection.Cities, err = paramsReader.GetPurevpnCities() - if err != nil { - return settings, err - } - return settings, nil -} - -// GetPrivadoSettings obtains Privado settings from environment variables using the params package. -func GetPrivadoSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) { - settings.Name = constants.Privado - settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol() - if err != nil { - return settings, err - } - settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP() - if err != nil { - return settings, err - } - settings.ServerSelection.Hostnames, err = paramsReader.GetPrivadoHostnames() - if err != nil { - return settings, err - } - return settings, nil -} diff --git a/internal/settings/publicip.go b/internal/settings/publicip.go deleted file mode 100644 index 93e8bbef..00000000 --- a/internal/settings/publicip.go +++ /dev/null @@ -1,39 +0,0 @@ -package settings - -import ( - "fmt" - "strings" - "time" - - "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/params" -) - -type PublicIP struct { - Period time.Duration `json:"period"` - IPFilepath models.Filepath `json:"ip_filepath"` -} - -func getPublicIPSettings(paramsReader params.Reader) (settings PublicIP, err error) { - settings.Period, err = paramsReader.GetPublicIPPeriod() - if err != nil { - return settings, err - } - settings.IPFilepath, err = paramsReader.GetPublicIPFilepath() - if err != nil { - return settings, err - } - return settings, nil -} - -func (s *PublicIP) String() string { - if s.Period == 0 { - return "Public IP getter settings: disabled" - } - settingsList := []string{ - "Public IP getter settings:", - fmt.Sprintf("Period: %s", s.Period), - fmt.Sprintf("IP file: %s", s.IPFilepath), - } - return strings.Join(settingsList, "\n|--") -} diff --git a/internal/settings/server.go b/internal/settings/server.go deleted file mode 100644 index 757efb34..00000000 --- a/internal/settings/server.go +++ /dev/null @@ -1,37 +0,0 @@ -package settings - -import ( - "fmt" - "strings" - - "github.com/qdm12/gluetun/internal/params" -) - -// ControlServer contains settings to customize the control server operation. -type ControlServer struct { - Port uint16 - Log bool -} - -func (c *ControlServer) String() string { - settingsList := []string{ - "HTTP Control server:", - fmt.Sprintf("Listening port: %d", c.Port), - fmt.Sprintf("Logging: %t", c.Log), - } - return strings.Join(settingsList, "\n |--") -} - -// GetControlServerSettings obtains the HTTP control server settings from -// environment variables using the params package. -func GetControlServerSettings(paramsReader params.Reader) (settings ControlServer, warning string, err error) { - settings.Log, err = paramsReader.GetControlServerLog() - if err != nil { - return settings, "", err - } - settings.Port, warning, err = paramsReader.GetControlServerPort() - if err != nil { - return settings, warning, err - } - return settings, warning, nil -} diff --git a/internal/settings/settings.go b/internal/settings/settings.go deleted file mode 100644 index 127540c7..00000000 --- a/internal/settings/settings.go +++ /dev/null @@ -1,113 +0,0 @@ -package settings - -import ( - "strings" - - "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/params" -) - -const ( - enabled = "enabled" - disabled = "disabled" -) - -// Settings contains all settings for the program to run. -type Settings struct { - VPNSP models.VPNProvider - OpenVPN OpenVPN - System System - DNS DNS - Firewall Firewall - HTTPProxy HTTPProxy - ShadowSocks ShadowSocks - Updater Updater - PublicIP PublicIP - VersionInformation bool - ControlServer ControlServer -} - -func (s *Settings) String() string { - versionInformation := disabled - if s.VersionInformation { - versionInformation = enabled - } - return strings.Join([]string{ - "Settings summary below:", - s.OpenVPN.String(), - s.System.String(), - s.DNS.String(), - s.Firewall.String(), - s.HTTPProxy.String(), - s.ShadowSocks.String(), - s.ControlServer.String(), - s.Updater.String(), - s.PublicIP.String(), - "Version information: " + versionInformation, - "", // new line at the end - }, "\n") -} - -// GetAllSettings obtains all settings for the program and returns an error as soon -// as an error is encountered reading them. -func GetAllSettings(paramsReader params.Reader) (settings Settings, warnings []string, err error) { - settings.VPNSP, err = paramsReader.GetVPNSP() - if err != nil { - return settings, nil, err - } - settings.OpenVPN, err = GetOpenVPNSettings(paramsReader, settings.VPNSP) - if err != nil { - return settings, nil, err - } - settings.DNS, err = GetDNSSettings(paramsReader) - if err != nil { - return settings, nil, err - } - settings.Firewall, err = GetFirewallSettings(paramsReader) - if err != nil { - return settings, nil, err - } - settings.System, err = GetSystemSettings(paramsReader) - if err != nil { - return settings, nil, err - } - settings.PublicIP, err = getPublicIPSettings(paramsReader) - if err != nil { - return settings, nil, err - } - settings.VersionInformation, err = paramsReader.GetVersionInformation() - if err != nil { - return settings, nil, err - } - settings.Updater, err = GetUpdaterSettings(paramsReader) - if err != nil { - return settings, nil, err - } - - var warning string - settings.HTTPProxy, warning, err = GetHTTPProxySettings(paramsReader) - if warning != "" { - warnings = append(warnings, warning) - } - if err != nil { - return settings, warnings, err - } - - settings.ShadowSocks, warning, err = GetShadowSocksSettings(paramsReader) - if warning != "" { - warnings = append(warnings, warning) - } - if err != nil { - return settings, warnings, err - } - - settings.ControlServer, warning, err = GetControlServerSettings(paramsReader) - if warning != "" { - warnings = append(warnings, warning) - } - if err != nil { - return settings, warnings, err - } - - return settings, warnings, nil -} diff --git a/internal/settings/shadowsocks.go b/internal/settings/shadowsocks.go deleted file mode 100644 index 8744422f..00000000 --- a/internal/settings/shadowsocks.go +++ /dev/null @@ -1,60 +0,0 @@ -package settings - -import ( - "fmt" - "strings" - - "github.com/qdm12/gluetun/internal/params" -) - -// ShadowSocks contains settings to configure the Shadowsocks server. -type ShadowSocks struct { - Method string - Password string - Port uint16 - Enabled bool - Log bool -} - -func (s *ShadowSocks) String() string { - if !s.Enabled { - return "ShadowSocks settings: disabled" - } - log := disabled - if s.Log { - log = enabled - } - settingsList := []string{ - "ShadowSocks settings:", - "Password: [redacted]", - "Log: " + log, - fmt.Sprintf("Port: %d", s.Port), - "Method: " + s.Method, - } - return strings.Join(settingsList, "\n |--") -} - -// GetShadowSocksSettings obtains ShadowSocks settings from environment variables using the params package. -func GetShadowSocksSettings(paramsReader params.Reader) (settings ShadowSocks, warning string, err error) { - settings.Enabled, err = paramsReader.GetShadowSocks() - if err != nil || !settings.Enabled { - return settings, "", err - } - settings.Password, err = paramsReader.GetShadowSocksPassword() - if err != nil { - return settings, "", err - } - settings.Log, err = paramsReader.GetShadowSocksLog() - if err != nil { - return settings, "", err - } - settings.Method, err = paramsReader.GetShadowSocksMethod() - if err != nil { - return settings, "", err - } - settings.Port, warning, err = paramsReader.GetShadowSocksPort() - if err != nil { - return settings, warning, err - } - return settings, warning, nil -} diff --git a/internal/settings/system.go b/internal/settings/system.go deleted file mode 100644 index b18a3ff5..00000000 --- a/internal/settings/system.go +++ /dev/null @@ -1,42 +0,0 @@ -package settings - -import ( - "fmt" - "strings" - - "github.com/qdm12/gluetun/internal/params" -) - -// System contains settings to configure system related elements. -type System struct { - PUID int - PGID int - Timezone string -} - -// GetSystemSettings obtains the System settings using the params functions. -func GetSystemSettings(paramsReader params.Reader) (settings System, err error) { - settings.PUID, err = paramsReader.GetPUID() - if err != nil { - return settings, err - } - settings.PGID, err = paramsReader.GetPGID() - if err != nil { - return settings, err - } - settings.Timezone, err = paramsReader.GetTimezone() - if err != nil { - return settings, err - } - return settings, nil -} - -func (s *System) String() string { - settingsList := []string{ - "System settings:", - fmt.Sprintf("Process user ID: %d", s.PUID), - fmt.Sprintf("Process group ID: %d", s.PGID), - fmt.Sprintf("Timezone: %s", s.Timezone), - } - return strings.Join(settingsList, "\n|--") -} diff --git a/internal/settings/updater.go b/internal/settings/updater.go deleted file mode 100644 index 19a967c8..00000000 --- a/internal/settings/updater.go +++ /dev/null @@ -1,59 +0,0 @@ -package settings - -import ( - "fmt" - "strings" - "time" - - "github.com/qdm12/gluetun/internal/params" -) - -type Updater struct { - Period time.Duration `json:"period"` - DNSAddress string `json:"dns_address"` - Cyberghost bool `json:"cyberghost"` - Mullvad bool `json:"mullvad"` - Nordvpn bool `json:"nordvpn"` - PIA bool `json:"pia"` - Privado bool `json:"privado"` - Purevpn bool `json:"purevpn"` - Surfshark bool `json:"surfshark"` - Vyprvpn bool `json:"vyprvpn"` - Windscribe bool `json:"windscribe"` - // The two below should be used in CLI mode only - Stdout bool `json:"-"` // in order to update constants file (maintainer side) - CLI bool `json:"-"` -} - -// GetUpdaterSettings obtains the server updater settings using the params functions. -func GetUpdaterSettings(paramsReader params.Reader) (settings Updater, err error) { - settings = Updater{ - Cyberghost: true, - Mullvad: true, - Nordvpn: true, - PIA: true, - Purevpn: true, - Surfshark: true, - Vyprvpn: true, - Windscribe: true, - Stdout: false, - CLI: false, - DNSAddress: "127.0.0.1", - } - settings.Period, err = paramsReader.GetUpdaterPeriod() - if err != nil { - return settings, err - } - return settings, nil -} - -func (s *Updater) String() string { - if s.Period == 0 { - return "Server updater settings: disabled" - } - settingsList := []string{ - "Server updater settings:", - fmt.Sprintf("Period: %s", s.Period), - } - return strings.Join(settingsList, "\n|--") -} diff --git a/internal/shadowsocks/loop.go b/internal/shadowsocks/loop.go index 14ec209a..e57ef57f 100644 --- a/internal/shadowsocks/loop.go +++ b/internal/shadowsocks/loop.go @@ -6,9 +6,9 @@ import ( "sync" "time" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" shadowsockslib "github.com/qdm12/ss-server/pkg" ) @@ -17,8 +17,8 @@ type Looper interface { Run(ctx context.Context, wg *sync.WaitGroup) SetStatus(status models.LoopStatus) (outcome string, err error) GetStatus() (status models.LoopStatus) - GetSettings() (settings settings.ShadowSocks) - SetSettings(settings settings.ShadowSocks) (outcome string) + GetSettings() (settings configuration.ShadowSocks) + SetSettings(settings configuration.ShadowSocks) (outcome string) } type looper struct { @@ -51,7 +51,7 @@ func (l *looper) logAndWait(ctx context.Context, err error) { const defaultBackoffTime = 10 * time.Second -func NewLooper(settings settings.ShadowSocks, logger logging.Logger) Looper { +func NewLooper(settings configuration.ShadowSocks, logger logging.Logger) Looper { return &looper{ state: state{ status: constants.Stopped, diff --git a/internal/shadowsocks/state.go b/internal/shadowsocks/state.go index 2c38c1c6..a91bc616 100644 --- a/internal/shadowsocks/state.go +++ b/internal/shadowsocks/state.go @@ -5,14 +5,14 @@ import ( "reflect" "sync" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" ) type state struct { status models.LoopStatus - settings settings.ShadowSocks + settings configuration.ShadowSocks statusMu sync.RWMutex settingsMu sync.RWMutex } @@ -69,13 +69,13 @@ func (l *looper) SetStatus(status models.LoopStatus) (outcome string, err error) } } -func (l *looper) GetSettings() (settings settings.ShadowSocks) { +func (l *looper) GetSettings() (settings configuration.ShadowSocks) { l.state.settingsMu.RLock() defer l.state.settingsMu.RUnlock() return l.state.settings } -func (l *looper) SetSettings(settings settings.ShadowSocks) (outcome string) { +func (l *looper) SetSettings(settings configuration.ShadowSocks) (outcome string) { l.state.settingsMu.Lock() settingsUnchanged := reflect.DeepEqual(settings, l.state.settings) if settingsUnchanged { diff --git a/internal/updater/loop.go b/internal/updater/loop.go index 5d57b65b..2be1f808 100644 --- a/internal/updater/loop.go +++ b/internal/updater/loop.go @@ -6,9 +6,9 @@ import ( "sync" "time" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/golibs/logging" ) @@ -18,8 +18,8 @@ type Looper interface { RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) GetStatus() (status models.LoopStatus) SetStatus(status models.LoopStatus) (outcome string, err error) - GetSettings() (settings settings.Updater) - SetSettings(settings settings.Updater) (outcome string) + GetSettings() (settings configuration.Updater) + SetSettings(settings configuration.Updater) (outcome string) } type looper struct { @@ -44,7 +44,7 @@ type looper struct { const defaultBackoffTime = 5 * time.Second -func NewLooper(settings settings.Updater, currentServers models.AllServers, +func NewLooper(settings configuration.Updater, currentServers models.AllServers, storage storage.Storage, setAllServers func(allServers models.AllServers), client *http.Client, logger logging.Logger) Looper { loggerWithPrefix := logger.WithPrefix("updater: ") diff --git a/internal/updater/state.go b/internal/updater/state.go index e55d6f44..79b5abc4 100644 --- a/internal/updater/state.go +++ b/internal/updater/state.go @@ -5,14 +5,14 @@ import ( "reflect" "sync" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" ) type state struct { status models.LoopStatus - settings settings.Updater + settings configuration.Updater statusMu sync.RWMutex periodMu sync.RWMutex } @@ -69,13 +69,13 @@ func (l *looper) SetStatus(status models.LoopStatus) (outcome string, err error) } } -func (l *looper) GetSettings() (settings settings.Updater) { +func (l *looper) GetSettings() (settings configuration.Updater) { l.state.periodMu.RLock() defer l.state.periodMu.RUnlock() return l.state.settings } -func (l *looper) SetSettings(settings settings.Updater) (outcome string) { +func (l *looper) SetSettings(settings configuration.Updater) (outcome string) { l.state.periodMu.Lock() defer l.state.periodMu.Unlock() settingsUnchanged := reflect.DeepEqual(settings, l.state.settings) diff --git a/internal/updater/updater.go b/internal/updater/updater.go index 05336944..fe8562f1 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -6,8 +6,8 @@ import ( "net/http" "time" + "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/settings" "github.com/qdm12/golibs/logging" "github.com/qdm12/golibs/network" ) @@ -18,7 +18,7 @@ type Updater interface { type updater struct { // configuration - options settings.Updater + options configuration.Updater // state servers models.AllServers @@ -31,7 +31,7 @@ type updater struct { client network.Client } -func New(settings settings.Updater, httpClient *http.Client, +func New(settings configuration.Updater, httpClient *http.Client, currentServers models.AllServers, logger logging.Logger) Updater { if len(settings.DNSAddress) == 0 { settings.DNSAddress = "1.1.1.1"