From 8669748289ec6ec12fadfc5077dad8a3a412ac94 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Wed, 8 Jul 2020 23:29:40 +0000 Subject: [PATCH] Shadowsocks loop --- cmd/gluetun/main.go | 35 +++-------- internal/shadowsocks/loop.go | 114 +++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 26 deletions(-) create mode 100644 internal/shadowsocks/loop.go diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index 7c4c8418..bc9752ec 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -159,40 +159,16 @@ func _main(background context.Context, args []string) int { err = firewallConf.RunUserPostRules(ctx, fileManager, "/iptables/post-rules.txt") fatalOnError(err) - if allSettings.ShadowSocks.Enabled { - nameserver := allSettings.DNS.PlaintextAddress.String() - if allSettings.DNS.Enabled { - nameserver = "127.0.0.1" - } - err = shadowsocksConf.MakeConf( - allSettings.ShadowSocks.Port, - allSettings.ShadowSocks.Password, - allSettings.ShadowSocks.Method, - nameserver, - uid, - gid) - fatalOnError(err) - err = firewallConf.AllowAnyIncomingOnPort(ctx, allSettings.ShadowSocks.Port) - fatalOnError(err) - stdout, stderr, waitFn, err := shadowsocksConf.Start(ctx, "0.0.0.0", allSettings.ShadowSocks.Port, allSettings.ShadowSocks.Password, allSettings.ShadowSocks.Log) - fatalOnError(err) - waiter.Add(func() error { - err := waitFn() - logger.Error("shadowsocks: %s", err) - return err - }) - go streamMerger.Merge(ctx, stdout, command.MergeName("shadowsocks"), command.MergeColor(constants.ColorShadowsocks())) - go streamMerger.Merge(ctx, stderr, command.MergeName("shadowsocks error"), command.MergeColor(constants.ColorShadowsocksError())) - } - restartOpenvpn := make(chan struct{}) restartUnbound := make(chan struct{}) restartPublicIP := make(chan struct{}) restartTinyproxy := make(chan struct{}) + restartShadowsocks := make(chan struct{}) openvpnDone := make(chan struct{}) unboundDone := make(chan struct{}) serverDone := make(chan struct{}) tinyproxyDone := make(chan struct{}) + shadowsocksDone := make(chan struct{}) openvpnLooper := openvpn.NewLooper(ovpnConf, allSettings.OpenVPN, logger, streamMerger, fatalOnError, uid, gid) // wait for restartOpenvpn @@ -209,9 +185,15 @@ func _main(background context.Context, args []string) int { tinyproxyLooper := tinyproxy.NewLooper(tinyProxyConf, firewallConf, allSettings.TinyProxy, logger, streamMerger, uid, gid) go tinyproxyLooper.Run(ctx, restartTinyproxy, tinyproxyDone) + shadowsocksLooper := shadowsocks.NewLooper(shadowsocksConf, firewallConf, allSettings.ShadowSocks, allSettings.DNS, logger, streamMerger, uid, gid) + go shadowsocksLooper.Run(ctx, restartShadowsocks, shadowsocksDone) + if allSettings.TinyProxy.Enabled { <-restartTinyproxy } + if allSettings.ShadowSocks.Enabled { + <-restartShadowsocks + } go func() { first := true @@ -278,6 +260,7 @@ func _main(background context.Context, args []string) int { <-unboundDone <-openvpnDone <-tinyproxyDone + <-shadowsocksDone return exitStatus } diff --git a/internal/shadowsocks/loop.go b/internal/shadowsocks/loop.go new file mode 100644 index 00000000..031878ba --- /dev/null +++ b/internal/shadowsocks/loop.go @@ -0,0 +1,114 @@ +package shadowsocks + +import ( + "context" + "time" + + "github.com/qdm12/golibs/command" + "github.com/qdm12/golibs/logging" + "github.com/qdm12/private-internet-access-docker/internal/constants" + "github.com/qdm12/private-internet-access-docker/internal/firewall" + "github.com/qdm12/private-internet-access-docker/internal/settings" +) + +type Looper interface { + Run(ctx context.Context, restart <-chan struct{}, done chan<- struct{}) +} + +type looper struct { + conf Configurator + firewallConf firewall.Configurator + settings settings.ShadowSocks + dnsSettings settings.DNS + logger logging.Logger + streamMerger command.StreamMerger + uid int + gid int +} + +func (l *looper) logAndWait(err error) { + l.logger.Error(err) + l.logger.Info("retrying in 1 minute") + time.Sleep(time.Minute) +} + +func NewLooper(conf Configurator, firewallConf firewall.Configurator, settings settings.ShadowSocks, dnsSettings settings.DNS, + logger logging.Logger, streamMerger command.StreamMerger, uid, gid int) Looper { + return &looper{ + conf: conf, + firewallConf: firewallConf, + settings: settings, + dnsSettings: dnsSettings, + logger: logger.WithPrefix("shadowsocks: "), + streamMerger: streamMerger, + uid: uid, + gid: gid, + } +} + +func (l *looper) Run(ctx context.Context, restart <-chan struct{}, done chan<- struct{}) { + select { + case <-restart: + case <-ctx.Done(): + close(done) + return + } + for { + nameserver := l.dnsSettings.PlaintextAddress.String() + if l.dnsSettings.Enabled { + nameserver = "127.0.0.1" + } + err := l.conf.MakeConf( + l.settings.Port, + l.settings.Password, + l.settings.Method, + nameserver, + l.uid, + l.gid) + if err != nil { + l.logAndWait(err) + continue + } + err = l.firewallConf.AllowAnyIncomingOnPort(ctx, l.settings.Port) + // TODO remove firewall rule on exit below + if err != nil { + l.logger.Error(err) + } + shadowsocksCtx, shadowsocksCancel := context.WithCancel(ctx) + stdout, stderr, waitFn, err := l.conf.Start(ctx, "0.0.0.0", l.settings.Port, l.settings.Password, l.settings.Log) + if err != nil { + shadowsocksCancel() + l.logAndWait(err) + continue + } + go l.streamMerger.Merge(shadowsocksCtx, stdout, + command.MergeName("shadowsocks"), command.MergeColor(constants.ColorShadowsocks())) + go l.streamMerger.Merge(shadowsocksCtx, stderr, + command.MergeName("shadowsocks error"), command.MergeColor(constants.ColorShadowsocksError())) + waitError := make(chan error) + go func() { + err := waitFn() // blocking + if shadowsocksCtx.Err() != context.Canceled { + waitError <- err + } + }() + select { + case <-ctx.Done(): + l.logger.Warn("context canceled: exiting loop") + shadowsocksCancel() + close(waitError) + close(done) + return + case <-restart: // triggered restart + l.logger.Info("restarting") + shadowsocksCancel() + close(waitError) + case err := <-waitError: // unexpected error + l.logger.Warn(err) + l.logger.Info("restarting") + shadowsocksCancel() + close(waitError) + time.Sleep(time.Second) + } + } +}