diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index c91cd377..28f80c1d 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -31,6 +31,7 @@ import ( "github.com/qdm12/gluetun/internal/openvpn" "github.com/qdm12/gluetun/internal/portforward" "github.com/qdm12/gluetun/internal/pprof" + "github.com/qdm12/gluetun/internal/provider" "github.com/qdm12/gluetun/internal/publicip" "github.com/qdm12/gluetun/internal/routing" "github.com/qdm12/gluetun/internal/server" @@ -38,6 +39,7 @@ import ( "github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/gluetun/internal/tun" updater "github.com/qdm12/gluetun/internal/updater/loop" + "github.com/qdm12/gluetun/internal/updater/unzip" "github.com/qdm12/gluetun/internal/vpn" "github.com/qdm12/golibs/command" "github.com/qdm12/goshutdown" @@ -374,9 +376,14 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone) tickersGroupHandler.Add(pubIPTickerHandler) + updaterLogger := logger.New(log.SetComponent("updater")) + + unzipper := unzip.New(httpClient) + providers := provider.NewProviders(storage, time.Now, updaterLogger, httpClient, unzipper) + vpnLogger := logger.New(log.SetComponent("vpn")) vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts, - storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper, + providers, storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper, cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient, buildInfo, *allSettings.Version.Enabled) vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler( @@ -384,7 +391,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, go vpnLooper.Run(vpnCtx, vpnDone) updaterLooper := updater.NewLooper(allSettings.Updater, - storage, httpClient, logger.New(log.SetComponent("updater"))) + providers, storage, httpClient, updaterLogger) updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler( "updater", goroutine.OptionTimeout(defaultShutdownTimeout)) // wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker diff --git a/internal/cli/openvpnconfig.go b/internal/cli/openvpnconfig.go index b2723aff..e8f0298f 100644 --- a/internal/cli/openvpnconfig.go +++ b/internal/cli/openvpnconfig.go @@ -42,8 +42,8 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) e client := (*http.Client)(nil) warner := (Warner)(nil) - providerConf := provider.New(*allSettings.VPN.Provider.Name, storage, time.Now, - warner, client, unzipper) + providers := provider.NewProviders(storage, time.Now, warner, client, unzipper) + providerConf := providers.Get(*allSettings.VPN.Provider.Name) connection, err := providerConf.GetConnection(allSettings.VPN.Provider.ServerSelection) if err != nil { return err diff --git a/internal/cli/update.go b/internal/cli/update.go index d81825a5..d65b45b1 100644 --- a/internal/cli/update.go +++ b/internal/cli/update.go @@ -13,8 +13,10 @@ import ( "github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants/providers" + "github.com/qdm12/gluetun/internal/provider" "github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/gluetun/internal/updater" + "github.com/qdm12/gluetun/internal/updater/unzip" ) var ( @@ -81,7 +83,11 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e return fmt.Errorf("cannot create servers storage: %w", err) } - updater := updater.New(httpClient, storage, logger) + unzipper := unzip.New(httpClient) + + providers := provider.NewProviders(storage, time.Now, logger, httpClient, unzipper) + + updater := updater.New(httpClient, storage, providers, logger) err = updater.UpdateServers(ctx, options.Providers) if err != nil { return fmt.Errorf("cannot update server information: %w", err) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 51c0808c..52911839 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -3,37 +3,12 @@ package provider import ( "context" - "math/rand" "net" "net/http" - "time" "github.com/qdm12/gluetun/internal/configuration/settings" - "github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/gluetun/internal/provider/common" - "github.com/qdm12/gluetun/internal/provider/custom" - "github.com/qdm12/gluetun/internal/provider/cyberghost" - "github.com/qdm12/gluetun/internal/provider/expressvpn" - "github.com/qdm12/gluetun/internal/provider/fastestvpn" - "github.com/qdm12/gluetun/internal/provider/hidemyass" - "github.com/qdm12/gluetun/internal/provider/ipvanish" - "github.com/qdm12/gluetun/internal/provider/ivpn" - "github.com/qdm12/gluetun/internal/provider/mullvad" - "github.com/qdm12/gluetun/internal/provider/nordvpn" - "github.com/qdm12/gluetun/internal/provider/perfectprivacy" - "github.com/qdm12/gluetun/internal/provider/privado" - "github.com/qdm12/gluetun/internal/provider/privateinternetaccess" - "github.com/qdm12/gluetun/internal/provider/privatevpn" - "github.com/qdm12/gluetun/internal/provider/protonvpn" - "github.com/qdm12/gluetun/internal/provider/purevpn" - "github.com/qdm12/gluetun/internal/provider/surfshark" - "github.com/qdm12/gluetun/internal/provider/torguard" "github.com/qdm12/gluetun/internal/provider/utils" - "github.com/qdm12/gluetun/internal/provider/vpnunlimited" - "github.com/qdm12/gluetun/internal/provider/vyprvpn" - "github.com/qdm12/gluetun/internal/provider/wevpn" - "github.com/qdm12/gluetun/internal/provider/windscribe" ) // Provider contains methods to read and modify the openvpn configuration to connect as a client. @@ -53,60 +28,3 @@ type PortForwarder interface { KeepPortForward(ctx context.Context, client *http.Client, port uint16, gateway net.IP, serverName string) (err error) } - -type Storage interface { - FilterServers(provider string, selection settings.ServerSelection) ( - servers []models.Server, err error) - GetServerByName(provider, name string) (server models.Server, ok bool) -} - -func New(provider string, storage Storage, timeNow func() time.Time, - updaterWarner common.Warner, client *http.Client, unzipper common.Unzipper) Provider { - randSource := rand.NewSource(timeNow().UnixNano()) - switch provider { - case providers.Custom: - return custom.New() - case providers.Cyberghost: - return cyberghost.New(storage, randSource) - case providers.Expressvpn: - return expressvpn.New(storage, randSource, unzipper, updaterWarner) - case providers.Fastestvpn: - return fastestvpn.New(storage, randSource, unzipper, updaterWarner) - case providers.HideMyAss: - return hidemyass.New(storage, randSource, client, updaterWarner) - case providers.Ipvanish: - return ipvanish.New(storage, randSource, unzipper, updaterWarner) - case providers.Ivpn: - return ivpn.New(storage, randSource, client, updaterWarner) - case providers.Mullvad: - return mullvad.New(storage, randSource, client) - case providers.Nordvpn: - return nordvpn.New(storage, randSource, client, updaterWarner) - case providers.Perfectprivacy: - return perfectprivacy.New(storage, randSource, unzipper, updaterWarner) - case providers.Privado: - return privado.New(storage, randSource, client, unzipper, updaterWarner) - case providers.PrivateInternetAccess: - return privateinternetaccess.New(storage, randSource, timeNow, client) - case providers.Privatevpn: - return privatevpn.New(storage, randSource, unzipper, updaterWarner) - case providers.Protonvpn: - return protonvpn.New(storage, randSource, client, updaterWarner) - case providers.Purevpn: - return purevpn.New(storage, randSource, client, unzipper, updaterWarner) - case providers.Surfshark: - return surfshark.New(storage, randSource, client, unzipper, updaterWarner) - case providers.Torguard: - return torguard.New(storage, randSource, unzipper, updaterWarner) - case providers.VPNUnlimited: - return vpnunlimited.New(storage, randSource, unzipper, updaterWarner) - case providers.Vyprvpn: - return vyprvpn.New(storage, randSource, unzipper, updaterWarner) - case providers.Wevpn: - return wevpn.New(storage, randSource, updaterWarner) - case providers.Windscribe: - return windscribe.New(storage, randSource, client, updaterWarner) - default: - panic("provider " + provider + " is unknown") // should never occur - } -} diff --git a/internal/provider/providers.go b/internal/provider/providers.go new file mode 100644 index 00000000..18ff51b6 --- /dev/null +++ b/internal/provider/providers.go @@ -0,0 +1,91 @@ +package provider + +import ( + "fmt" + "math/rand" + "net/http" + "time" + + "github.com/qdm12/gluetun/internal/configuration/settings" + "github.com/qdm12/gluetun/internal/constants/providers" + "github.com/qdm12/gluetun/internal/models" + "github.com/qdm12/gluetun/internal/provider/common" + "github.com/qdm12/gluetun/internal/provider/custom" + "github.com/qdm12/gluetun/internal/provider/cyberghost" + "github.com/qdm12/gluetun/internal/provider/expressvpn" + "github.com/qdm12/gluetun/internal/provider/fastestvpn" + "github.com/qdm12/gluetun/internal/provider/hidemyass" + "github.com/qdm12/gluetun/internal/provider/ipvanish" + "github.com/qdm12/gluetun/internal/provider/ivpn" + "github.com/qdm12/gluetun/internal/provider/mullvad" + "github.com/qdm12/gluetun/internal/provider/nordvpn" + "github.com/qdm12/gluetun/internal/provider/perfectprivacy" + "github.com/qdm12/gluetun/internal/provider/privado" + "github.com/qdm12/gluetun/internal/provider/privateinternetaccess" + "github.com/qdm12/gluetun/internal/provider/privatevpn" + "github.com/qdm12/gluetun/internal/provider/protonvpn" + "github.com/qdm12/gluetun/internal/provider/purevpn" + "github.com/qdm12/gluetun/internal/provider/surfshark" + "github.com/qdm12/gluetun/internal/provider/torguard" + "github.com/qdm12/gluetun/internal/provider/vpnunlimited" + "github.com/qdm12/gluetun/internal/provider/vyprvpn" + "github.com/qdm12/gluetun/internal/provider/wevpn" + "github.com/qdm12/gluetun/internal/provider/windscribe" +) + +type Providers struct { + providerNameToProvider map[string]Provider +} + +type Storage interface { + FilterServers(provider string, selection settings.ServerSelection) ( + servers []models.Server, err error) + GetServerByName(provider, name string) (server models.Server, ok bool) +} + +func NewProviders(storage Storage, timeNow func() time.Time, + updaterWarner common.Warner, client *http.Client, unzipper common.Unzipper) *Providers { + randSource := rand.NewSource(timeNow().UnixNano()) + + targetLength := len(providers.AllWithCustom()) + providerNameToProvider := make(map[string]Provider, targetLength) + providerNameToProvider[providers.Custom] = custom.New() + providerNameToProvider[providers.Cyberghost] = cyberghost.New(storage, randSource) + providerNameToProvider[providers.Expressvpn] = expressvpn.New(storage, randSource, unzipper, updaterWarner) + providerNameToProvider[providers.Fastestvpn] = fastestvpn.New(storage, randSource, unzipper, updaterWarner) + providerNameToProvider[providers.HideMyAss] = hidemyass.New(storage, randSource, client, updaterWarner) + providerNameToProvider[providers.Ipvanish] = ipvanish.New(storage, randSource, unzipper, updaterWarner) + providerNameToProvider[providers.Ivpn] = ivpn.New(storage, randSource, client, updaterWarner) + providerNameToProvider[providers.Mullvad] = mullvad.New(storage, randSource, client) + providerNameToProvider[providers.Nordvpn] = nordvpn.New(storage, randSource, client, updaterWarner) + providerNameToProvider[providers.Perfectprivacy] = perfectprivacy.New(storage, randSource, unzipper, updaterWarner) + providerNameToProvider[providers.Privado] = privado.New(storage, randSource, client, unzipper, updaterWarner) + providerNameToProvider[providers.PrivateInternetAccess] = privateinternetaccess.New(storage, randSource, timeNow, client) //nolint:lll + providerNameToProvider[providers.Privatevpn] = privatevpn.New(storage, randSource, unzipper, updaterWarner) + providerNameToProvider[providers.Protonvpn] = protonvpn.New(storage, randSource, client, updaterWarner) + providerNameToProvider[providers.Purevpn] = purevpn.New(storage, randSource, client, unzipper, updaterWarner) + providerNameToProvider[providers.Surfshark] = surfshark.New(storage, randSource, client, unzipper, updaterWarner) + providerNameToProvider[providers.Torguard] = torguard.New(storage, randSource, unzipper, updaterWarner) + providerNameToProvider[providers.VPNUnlimited] = vpnunlimited.New(storage, randSource, unzipper, updaterWarner) + providerNameToProvider[providers.Vyprvpn] = vyprvpn.New(storage, randSource, unzipper, updaterWarner) + providerNameToProvider[providers.Wevpn] = wevpn.New(storage, randSource, updaterWarner) + providerNameToProvider[providers.Windscribe] = windscribe.New(storage, randSource, client, updaterWarner) + + if len(providerNameToProvider) != targetLength { + // Programming sanity check + panic(fmt.Sprintf("invalid number of providers, expected %d but got %d", + targetLength, len(providerNameToProvider))) + } + + return &Providers{ + providerNameToProvider: providerNameToProvider, + } +} + +func (p *Providers) Get(providerName string) (provider Provider) { + provider, ok := p.providerNameToProvider[providerName] + if !ok { + panic(fmt.Sprintf("provider %q not found", providerName)) + } + return provider +} diff --git a/internal/updater/loop/loop.go b/internal/updater/loop/loop.go index 7940c520..95a028bb 100644 --- a/internal/updater/loop/loop.go +++ b/internal/updater/loop/loop.go @@ -52,14 +52,14 @@ type Logger interface { Error(s string) } -func NewLooper(settings settings.Updater, storage updater.Storage, - client *http.Client, logger Logger) Looper { +func NewLooper(settings settings.Updater, providers updater.Providers, + storage updater.Storage, client *http.Client, logger Logger) Looper { return &looper{ state: state{ status: constants.Stopped, settings: settings, }, - updater: updater.New(client, storage, logger), + updater: updater.New(client, storage, providers, logger), logger: logger, start: make(chan struct{}), running: make(chan models.LoopStatus), diff --git a/internal/updater/updater.go b/internal/updater/updater.go index f2f5cbdd..22288e3e 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -15,6 +15,8 @@ import ( ) type Updater struct { + providers Providers + // state storage Storage @@ -25,6 +27,10 @@ type Updater struct { unzipper unzip.Unzipper } +type Providers interface { + Get(providerName string) provider.Provider +} + type Storage interface { SetServers(provider string, servers []models.Server) (err error) GetServersCount(provider string) (count int) @@ -40,15 +46,16 @@ type Logger interface { Error(s string) } -func New(httpClient *http.Client, - storage Storage, logger Logger) *Updater { +func New(httpClient *http.Client, storage Storage, + providers Providers, logger Logger) *Updater { unzipper := unzip.New(httpClient) return &Updater{ - storage: storage, - logger: logger, - timeNow: time.Now, - client: httpClient, - unzipper: unzipper, + providers: providers, + storage: storage, + logger: logger, + timeNow: time.Now, + client: httpClient, + unzipper: unzipper, } } @@ -57,9 +64,7 @@ func (u *Updater) UpdateServers(ctx context.Context, providers []string) (err er for _, providerName := range providers { u.logger.Info("updating " + caser.String(providerName) + " servers...") - fetcherStorage := (Storage)(nil) // unused - fetcher := provider.New(providerName, fetcherStorage, u.timeNow, - u.logger, u.client, u.unzipper) + fetcher := u.providers.Get(providerName) // TODO support servers offering only TCP or only UDP // for NordVPN and PureVPN err := u.updateProvider(ctx, fetcher) diff --git a/internal/vpn/loop.go b/internal/vpn/loop.go index 76ae2ffc..c36ce97b 100644 --- a/internal/vpn/loop.go +++ b/internal/vpn/loop.go @@ -13,6 +13,7 @@ import ( "github.com/qdm12/gluetun/internal/netlink" "github.com/qdm12/gluetun/internal/openvpn" "github.com/qdm12/gluetun/internal/portforward" + "github.com/qdm12/gluetun/internal/provider" "github.com/qdm12/gluetun/internal/publicip" "github.com/qdm12/gluetun/internal/routing" "github.com/qdm12/gluetun/internal/vpn/state" @@ -32,6 +33,7 @@ type Looper interface { type Loop struct { statusManager loopstate.Manager state state.Manager + providers Providers storage Storage // Fixed parameters buildInfo models.BuildInformation @@ -64,6 +66,10 @@ type firewallConfigurer interface { firewall.PortAllower } +type Providers interface { + Get(providerName string) provider.Provider +} + type Storage interface { FilterServers(provider string, selection settings.ServerSelection) (servers []models.Server, err error) GetServerByName(provider, name string) (server models.Server, ok bool) @@ -74,7 +80,7 @@ const ( ) func NewLoop(vpnSettings settings.VPN, vpnInputPorts []uint16, - storage Storage, openvpnConf openvpn.Interface, + providers Providers, storage Storage, openvpnConf openvpn.Interface, netLinker netlink.NetLinker, fw firewallConfigurer, routing routing.VPNGetter, portForward portforward.StartStopper, starter command.Starter, publicip publicip.Looper, dnsLooper dns.Looper, @@ -91,6 +97,7 @@ func NewLoop(vpnSettings settings.VPN, vpnInputPorts []uint16, return &Loop{ statusManager: statusManager, state: state, + providers: providers, storage: storage, buildInfo: buildInfo, versionInfo: versionInfo, diff --git a/internal/vpn/run.go b/internal/vpn/run.go index b1b9e161..6222943f 100644 --- a/internal/vpn/run.go +++ b/internal/vpn/run.go @@ -2,13 +2,9 @@ package vpn import ( "context" - "net/http" - "time" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants/vpn" - "github.com/qdm12/gluetun/internal/provider" - "github.com/qdm12/gluetun/internal/updater/unzip" "github.com/qdm12/log" ) @@ -29,16 +25,10 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) { return } - // Updater only objects which are unused in this loop - updaterWarner := (log.LoggerInterface)(nil) - updaterClient := (*http.Client)(nil) - updaterUnzipper := (unzip.Unzipper)(nil) - for ctx.Err() == nil { settings := l.state.GetSettings() - providerConf := provider.New(*settings.Provider.Name, l.storage, time.Now, - updaterWarner, updaterClient, updaterUnzipper) + providerConf := l.providers.Get(*settings.Provider.Name) portForwarding := *settings.Provider.PortForwarding.Enabled var vpnRunner vpnRunner