Files
gluetun/cmd/gluetun/main.go

372 lines
12 KiB
Go
Raw Normal View History

package main
import (
"context"
"fmt"
"os"
2020-04-29 01:27:42 +00:00
"os/signal"
"regexp"
"strings"
"sync"
2020-04-29 01:27:42 +00:00
"syscall"
"time"
"github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/network"
"github.com/qdm12/private-internet-access-docker/internal/alpine"
"github.com/qdm12/private-internet-access-docker/internal/cli"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/qdm12/private-internet-access-docker/internal/dns"
"github.com/qdm12/private-internet-access-docker/internal/firewall"
"github.com/qdm12/private-internet-access-docker/internal/models"
"github.com/qdm12/private-internet-access-docker/internal/openvpn"
"github.com/qdm12/private-internet-access-docker/internal/params"
"github.com/qdm12/private-internet-access-docker/internal/provider"
2020-06-05 19:32:12 -04:00
"github.com/qdm12/private-internet-access-docker/internal/publicip"
"github.com/qdm12/private-internet-access-docker/internal/routing"
"github.com/qdm12/private-internet-access-docker/internal/server"
"github.com/qdm12/private-internet-access-docker/internal/settings"
"github.com/qdm12/private-internet-access-docker/internal/shadowsocks"
"github.com/qdm12/private-internet-access-docker/internal/splash"
"github.com/qdm12/private-internet-access-docker/internal/tinyproxy"
)
2020-05-02 14:48:18 +00:00
func main() {
ctx := context.Background()
os.Exit(_main(ctx, os.Args))
}
func _main(background context.Context, args []string) int {
if len(args) > 1 { // cli operation
var err error
switch args[1] {
case "healthcheck":
err = cli.HealthCheck()
case "clientkey":
err = cli.ClientKey(args[2:])
default:
err = fmt.Errorf("command %q is unknown", args[1])
}
if err != nil {
fmt.Println(err)
return 1
}
return 0
}
ctx, cancel := context.WithCancel(background)
2020-05-02 13:05:09 +00:00
defer cancel()
2020-05-02 14:48:18 +00:00
logger := createLogger()
fatalOnError := makeFatalOnError(logger, cancel)
paramsReader := params.NewReader(logger)
fmt.Println(splash.Splash(
paramsReader.GetVersion(),
paramsReader.GetVcsRef(),
paramsReader.GetBuildDate()))
2020-05-02 13:05:09 +00:00
client := network.NewClient(15 * time.Second)
// Create configurators
fileManager := files.NewFileManager()
alpineConf := alpine.NewConfigurator(fileManager)
ovpnConf := openvpn.NewConfigurator(logger, fileManager)
dnsConf := dns.NewConfigurator(logger, client, fileManager)
firewallConf := firewall.NewConfigurator(logger)
routingConf := routing.NewRouting(logger, fileManager)
tinyProxyConf := tinyproxy.NewConfigurator(fileManager, logger)
shadowsocksConf := shadowsocks.NewConfigurator(fileManager, logger)
streamMerger := command.NewStreamMerger()
2020-05-02 14:48:18 +00:00
printVersions(ctx, logger, map[string]func(ctx context.Context) (string, error){
"OpenVPN": ovpnConf.Version,
"Unbound": dnsConf.Version,
"IPtables": firewallConf.Version,
"TinyProxy": tinyProxyConf.Version,
"ShadowSocks": shadowsocksConf.Version,
})
allSettings, err := settings.GetAllSettings(paramsReader)
2020-05-02 14:48:18 +00:00
fatalOnError(err)
logger.Info(allSettings.String())
// Should never change
uid, gid := allSettings.System.UID, allSettings.System.GID
providerConf := provider.New(allSettings.VPNSP, logger, client, fileManager, firewallConf)
2020-06-12 17:11:21 +00:00
if !allSettings.Firewall.Enabled {
firewallConf.Disable()
}
err = alpineConf.CreateUser("nonrootuser", uid)
2020-05-02 14:48:18 +00:00
fatalOnError(err)
err = fileManager.SetOwnership("/etc/unbound", uid, gid)
2020-05-02 14:48:18 +00:00
fatalOnError(err)
err = fileManager.SetOwnership("/etc/tinyproxy", uid, gid)
2020-05-02 14:48:18 +00:00
fatalOnError(err)
if err := ovpnConf.CheckTUN(); err != nil {
logger.Warn(err)
err = ovpnConf.CreateTUN()
2020-05-02 14:48:18 +00:00
fatalOnError(err)
}
defaultInterface, defaultGateway, defaultSubnet, err := routingConf.DefaultRoute()
2020-05-02 14:48:18 +00:00
fatalOnError(err)
// Temporarily reset chain policies allowing Kubernetes sidecar to
// successfully restart the container. Without this, the existing rules will
// pre-exist, preventing the nslookup of the PIA region address. These will
// simply be redundant at Docker runtime as they will already be set this way
// Thanks to @npawelek https://github.com/npawelek
2020-04-19 18:13:48 +00:00
err = firewallConf.AcceptAll(ctx)
2020-05-02 14:48:18 +00:00
fatalOnError(err)
2020-06-10 01:16:58 +00:00
connectedCh := make(chan struct{})
signalConnected := func() {
connectedCh <- struct{}{}
}
defer close(connectedCh)
2020-05-02 14:48:18 +00:00
go collectStreamLines(ctx, streamMerger, logger, signalConnected)
waiter := command.NewWaiter()
connections, err := providerConf.GetOpenVPNConnections(allSettings.OpenVPN.Provider.ServerSelection)
fatalOnError(err)
err = providerConf.BuildConf(
connections,
allSettings.OpenVPN.Verbosity,
uid,
gid,
allSettings.OpenVPN.Root,
allSettings.OpenVPN.Cipher,
allSettings.OpenVPN.Auth,
allSettings.OpenVPN.Provider.ExtraConfigOptions,
)
2020-05-02 14:48:18 +00:00
fatalOnError(err)
2020-04-19 18:13:48 +00:00
err = routingConf.AddRoutesVia(ctx, allSettings.Firewall.AllowedSubnets, defaultGateway, defaultInterface)
2020-05-02 14:48:18 +00:00
fatalOnError(err)
2020-04-19 18:13:48 +00:00
err = firewallConf.Clear(ctx)
2020-05-02 14:48:18 +00:00
fatalOnError(err)
2020-04-19 18:13:48 +00:00
err = firewallConf.BlockAll(ctx)
2020-05-02 14:48:18 +00:00
fatalOnError(err)
2020-04-19 18:13:48 +00:00
err = firewallConf.CreateGeneralRules(ctx)
2020-05-02 14:48:18 +00:00
fatalOnError(err)
2020-04-19 18:13:48 +00:00
err = firewallConf.CreateVPNRules(ctx, constants.TUN, defaultInterface, connections)
2020-05-02 14:48:18 +00:00
fatalOnError(err)
2020-04-19 18:13:48 +00:00
err = firewallConf.CreateLocalSubnetsRules(ctx, defaultSubnet, allSettings.Firewall.AllowedSubnets, defaultInterface)
2020-05-02 14:48:18 +00:00
fatalOnError(err)
2020-05-18 09:37:34 -04:00
err = firewallConf.RunUserPostRules(ctx, fileManager, "/iptables/post-rules.txt")
fatalOnError(err)
restartOpenvpn := make(chan struct{})
restartUnbound := make(chan struct{})
restartPublicIP := make(chan struct{})
2020-07-08 23:20:33 +00:00
restartTinyproxy := make(chan struct{})
2020-07-08 23:29:40 +00:00
restartShadowsocks := make(chan struct{})
wg := &sync.WaitGroup{}
openvpnLooper := openvpn.NewLooper(ovpnConf, allSettings.OpenVPN, logger, streamMerger, fatalOnError, uid, gid)
// wait for restartOpenvpn
go openvpnLooper.Run(ctx, restartOpenvpn, wg)
unboundLooper := dns.NewLooper(dnsConf, allSettings.DNS, logger, streamMerger, uid, gid)
// wait for restartUnbound
go unboundLooper.Run(ctx, restartUnbound, wg)
publicIPLooper := publicip.NewLooper(client, logger, fileManager, allSettings.System.IPStatusFilepath, uid, gid)
go publicIPLooper.Run(ctx, restartPublicIP)
2020-07-08 22:47:12 +00:00
go publicIPLooper.RunRestartTicker(ctx, restartPublicIP)
2020-07-08 23:20:33 +00:00
tinyproxyLooper := tinyproxy.NewLooper(tinyProxyConf, firewallConf, allSettings.TinyProxy, logger, streamMerger, uid, gid)
go tinyproxyLooper.Run(ctx, restartTinyproxy, wg)
2020-07-08 23:20:33 +00:00
2020-07-08 23:29:40 +00:00
shadowsocksLooper := shadowsocks.NewLooper(shadowsocksConf, firewallConf, allSettings.ShadowSocks, allSettings.DNS, logger, streamMerger, uid, gid)
go shadowsocksLooper.Run(ctx, restartShadowsocks, wg)
2020-07-08 23:29:40 +00:00
2020-07-08 23:20:33 +00:00
if allSettings.TinyProxy.Enabled {
<-restartTinyproxy
}
2020-07-08 23:29:40 +00:00
if allSettings.ShadowSocks.Enabled {
<-restartShadowsocks
}
2020-07-08 23:20:33 +00:00
2020-05-01 03:15:49 +00:00
go func() {
first := true
var restartTickerContext context.Context
var restartTickerCancel context.CancelFunc = func() {}
2020-06-10 01:16:58 +00:00
for {
select {
case <-ctx.Done():
restartTickerCancel()
2020-06-10 01:16:58 +00:00
return
case <-connectedCh: // blocks until openvpn is connected
if first {
first = false
restartUnbound <- struct{}{}
}
restartTickerCancel()
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
go unboundLooper.RunRestartTicker(restartTickerContext, restartUnbound)
onConnected(allSettings, logger, routingConf, defaultInterface, providerConf, restartPublicIP)
2020-06-10 01:16:58 +00:00
}
}
2020-05-01 03:15:49 +00:00
}()
httpServer := server.New("0.0.0.0:8000", logger, restartOpenvpn, restartUnbound)
go httpServer.Run(ctx, wg)
// Start openvpn for the first time
restartOpenvpn <- struct{}{}
2020-04-29 01:27:42 +00:00
signalsCh := make(chan os.Signal, 1)
signal.Notify(signalsCh,
syscall.SIGINT,
syscall.SIGTERM,
os.Interrupt,
)
exitStatus := 0
2020-04-29 01:27:42 +00:00
select {
case signal := <-signalsCh:
logger.Warn("Caught OS signal %s, shutting down", signal)
exitStatus = 1
2020-04-29 01:27:42 +00:00
cancel()
case <-ctx.Done():
logger.Warn("context canceled, shutting down")
}
2020-04-30 12:55:07 +00:00
logger.Info("Clearing ip status file %s", allSettings.System.IPStatusFilepath)
if err := fileManager.Remove(string(allSettings.System.IPStatusFilepath)); err != nil {
logger.Error(err)
exitStatus = 1
2020-04-30 12:55:07 +00:00
}
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
logger.Info("Clearing forwarded port status file %s", allSettings.OpenVPN.Provider.PortForwarding.Filepath)
if err := fileManager.Remove(string(allSettings.OpenVPN.Provider.PortForwarding.Filepath)); err != nil {
logger.Error(err)
exitStatus = 1
}
2020-04-29 01:27:42 +00:00
}
timeoutCtx, timeoutCancel := context.WithTimeout(background, time.Second)
defer timeoutCancel()
for _, err := range waiter.WaitForAll(timeoutCtx) {
2020-04-29 01:27:42 +00:00
logger.Error(err)
exitStatus = 1
2020-04-29 01:27:42 +00:00
}
wg.Wait()
return exitStatus
}
2020-05-02 14:48:18 +00:00
func makeFatalOnError(logger logging.Logger, cancel func()) func(err error) {
return func(err error) {
if err != nil {
logger.Error(err)
cancel()
time.Sleep(100 * time.Millisecond) // wait for operations to terminate
os.Exit(1)
}
}
}
func createLogger() logging.Logger {
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel, -1)
if err != nil {
panic(err)
}
return logger
}
func printVersions(ctx context.Context, logger logging.Logger, versionFunctions map[string]func(ctx context.Context) (string, error)) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
for name, f := range versionFunctions {
version, err := f(ctx)
if err != nil {
logger.Error(err)
} else {
logger.Info("%s version: %s", name, version)
}
}
}
func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger, logger logging.Logger, signalConnected func()) {
// Blocking line merging paramsReader for all programs: openvpn, tinyproxy, unbound and shadowsocks
logger.Info("Launching standard output merger")
streamMerger.CollectLines(ctx, func(line string) {
line = trimEventualProgramPrefix(line)
2020-05-02 14:48:18 +00:00
logger.Info(line)
if strings.Contains(line, "Initialization Sequence Completed") {
signalConnected()
}
}, func(err error) {
2020-05-09 00:43:09 +00:00
logger.Warn(err)
2020-05-02 14:48:18 +00:00
})
}
func trimEventualProgramPrefix(s string) string {
switch {
case strings.HasPrefix(s, "unbound: "):
prefixRegex := regexp.MustCompile(`unbound: \[[0-9]{10}\] unbound\[[0-9]+:0\] `)
prefix := prefixRegex.FindString(s)
return fmt.Sprintf("unbound: %s", s[len(prefix):])
case strings.HasPrefix(s, "shadowsocks: "):
prefixRegex := regexp.MustCompile(`shadowsocks:[ ]+2[0-9]{3}\-[0-1][0-9]\-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9] `)
prefix := prefixRegex.FindString(s)
return fmt.Sprintf("shadowsocks: %s", s[len(prefix):])
case strings.HasPrefix(s, "tinyproxy: "):
logLevelRegex := regexp.MustCompile(`INFO|CONNECT|NOTICE|WARNING|ERROR|CRITICAL`)
logLevel := logLevelRegex.FindString(s)
prefixRegex := regexp.MustCompile(`tinyproxy: (INFO|CONNECT|NOTICE|WARNING|ERROR|CRITICAL)[ ]+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9] \[[0-9]+\]: `)
prefix := prefixRegex.FindString(s)
return fmt.Sprintf("tinyproxy: %s %s", logLevel, s[len(prefix):])
default:
return s
}
}
func onConnected(allSettings settings.Settings,
logger logging.Logger, routingConf routing.Routing, defaultInterface string,
providerConf provider.Provider, restartPublicIP chan<- struct{},
2020-05-02 14:48:18 +00:00
) {
restartPublicIP <- struct{}{}
uid, gid := allSettings.System.UID, allSettings.System.GID
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
2020-05-02 14:48:18 +00:00
time.AfterFunc(5*time.Second, func() {
setupPortForwarding(logger, providerConf, allSettings.OpenVPN.Provider.PortForwarding.Filepath, uid, gid)
2020-05-02 14:48:18 +00:00
})
}
2020-06-05 19:32:12 -04:00
vpnGatewayIP, err := routingConf.VPNGatewayIP(defaultInterface)
if err != nil {
logger.Warn(err)
} else {
logger.Info("Gateway VPN IP address: %s", vpnGatewayIP)
}
2020-05-02 14:48:18 +00:00
}
func setupPortForwarding(logger logging.Logger, providerConf provider.Provider, filepath models.Filepath, uid, gid int) {
2020-05-02 14:48:18 +00:00
pfLogger := logger.WithPrefix("port forwarding: ")
var port uint16
var err error
for {
port, err = providerConf.GetPortForward()
2020-05-02 14:48:18 +00:00
if err != nil {
pfLogger.Error(err)
pfLogger.Info("retrying in 5 seconds...")
time.Sleep(5 * time.Second)
} else {
pfLogger.Info("port forwarded is %d", port)
break
}
}
pfLogger.Info("writing forwarded port to %s", filepath)
if err := providerConf.WritePortForward(filepath, port, uid, gid); err != nil {
2020-05-02 14:48:18 +00:00
pfLogger.Error(err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
if err := providerConf.AllowPortForwardFirewall(ctx, constants.TUN, port); err != nil {
2020-05-02 14:48:18 +00:00
pfLogger.Error(err)
}
}