Files
gluetun/cmd/gluetun/main.go

491 lines
16 KiB
Go
Raw Normal View History

package main
import (
"context"
"errors"
"fmt"
"net"
2020-08-30 14:48:57 +00:00
"net/http"
nativeos "os"
2020-04-29 01:27:42 +00:00
"os/signal"
"strconv"
"strings"
2020-04-29 01:27:42 +00:00
"syscall"
"time"
"github.com/qdm12/dns/pkg/unbound"
2020-07-26 12:07:06 +00:00
"github.com/qdm12/gluetun/internal/alpine"
"github.com/qdm12/gluetun/internal/cli"
2021-02-06 11:05:50 -05:00
"github.com/qdm12/gluetun/internal/configuration"
2020-07-26 12:07:06 +00:00
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/dns"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/healthcheck"
"github.com/qdm12/gluetun/internal/httpproxy"
2020-07-26 12:07:06 +00:00
gluetunLogging "github.com/qdm12/gluetun/internal/logging"
2020-11-04 14:07:04 +00:00
"github.com/qdm12/gluetun/internal/models"
2020-07-26 12:07:06 +00:00
"github.com/qdm12/gluetun/internal/openvpn"
"github.com/qdm12/gluetun/internal/publicip"
"github.com/qdm12/gluetun/internal/routing"
"github.com/qdm12/gluetun/internal/server"
"github.com/qdm12/gluetun/internal/shadowsocks"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/unix"
"github.com/qdm12/gluetun/internal/updater"
2020-08-30 14:48:57 +00:00
versionpkg "github.com/qdm12/gluetun/internal/version"
"github.com/qdm12/golibs/logging"
2021-01-02 01:57:00 +00:00
"github.com/qdm12/golibs/os"
"github.com/qdm12/golibs/os/user"
2021-02-06 11:05:50 -05:00
"github.com/qdm12/golibs/params"
"github.com/qdm12/goshutdown"
"github.com/qdm12/updated/pkg/dnscrypto"
)
2020-08-29 19:14:52 +00:00
//nolint:gochecknoglobals
var (
2021-07-20 15:28:02 +00:00
version = "unknown"
commit = "unknown"
created = "an unknown date"
)
2020-08-29 19:14:52 +00:00
var (
errSetupRouting = errors.New("cannot setup routing")
errCreateUser = errors.New("cannot create user")
)
2020-05-02 14:48:18 +00:00
func main() {
buildInfo := models.BuildInformation{
2021-07-20 15:28:02 +00:00
Version: version,
Commit: commit,
Created: created,
}
2021-01-04 01:40:07 +00:00
ctx := context.Background()
2021-05-11 18:17:59 +00:00
ctx, stop := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM, nativeos.Interrupt)
2021-01-04 01:40:07 +00:00
ctx, cancel := context.WithCancel(ctx)
logger := logging.NewParent(logging.Settings{})
2021-01-04 01:40:07 +00:00
args := nativeos.Args
os := os.New()
osUser := user.New()
unix := unix.New()
cli := cli.New()
2021-01-04 01:40:07 +00:00
errorCh := make(chan error)
go func() {
errorCh <- _main(ctx, buildInfo, args, logger, os, osUser, unix, cli)
}()
select {
2021-05-11 18:17:59 +00:00
case <-ctx.Done():
stop()
logger.Warn("Caught OS signal, shutting down")
2021-01-04 01:40:07 +00:00
case err := <-errorCh:
2021-05-11 18:17:59 +00:00
stop()
2021-01-04 01:40:07 +00:00
close(errorCh)
if err == nil { // expected exit such as healthcheck
nativeos.Exit(0)
}
logger.Error(err)
2021-05-11 18:17:59 +00:00
cancel()
2021-01-04 01:40:07 +00:00
}
const shutdownGracePeriod = 5 * time.Second
timer := time.NewTimer(shutdownGracePeriod)
select {
case <-errorCh:
if !timer.Stop() {
<-timer.C
}
logger.Info("Shutdown successful")
case <-timer.C:
logger.Warn("Shutdown timed out")
}
nativeos.Exit(1)
}
2021-05-30 16:14:08 +00:00
var (
errCommandUnknown = errors.New("command is unknown")
)
//nolint:gocognit,gocyclo
2021-01-30 17:29:27 +00:00
func _main(ctx context.Context, buildInfo models.BuildInformation,
args []string, logger logging.ParentLogger, os os.OS,
osUser user.OSUser, unix unix.Unix, cli cli.CLI) error {
if len(args) > 1 { // cli operation
switch args[1] {
case "healthcheck":
2021-01-30 17:29:27 +00:00
return cli.HealthCheck(ctx)
case "clientkey":
2021-01-04 01:40:07 +00:00
return cli.ClientKey(args[2:], os.OpenFile)
2020-07-13 23:43:26 +00:00
case "openvpnconfig":
return cli.OpenvpnConfig(os, logger)
case "update":
return cli.Update(ctx, args[2:], os, logger)
default:
2021-05-30 16:14:08 +00:00
return fmt.Errorf("%w: %s", errCommandUnknown, args[1])
}
}
2020-07-23 02:15:37 +00:00
2020-10-20 02:45:28 +00:00
const clientTimeout = 15 * time.Second
httpClient := &http.Client{Timeout: clientTimeout}
// Create configurators
alpineConf := alpine.NewConfigurator(os.OpenFile, osUser)
ovpnConf := openvpn.NewConfigurator(
logger.NewChild(logging.Settings{Prefix: "openvpn configurator: "}),
os, unix)
dnsCrypto := dnscrypto.New(httpClient, "", "")
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
dnsConf := unbound.NewConfigurator(nil, os.OpenFile, dnsCrypto,
"/etc/unbound", "/usr/sbin/unbound", cacertsPath)
routingConf := routing.NewRouting(
logger.NewChild(logging.Settings{Prefix: "routing: "}))
firewallConf := firewall.NewConfigurator(
logger.NewChild(logging.Settings{Prefix: "firewall: "}),
routingConf, os.OpenFile)
2020-11-04 14:07:04 +00:00
fmt.Println(gluetunLogging.Splash(buildInfo))
if err := printVersions(ctx, logger, []printVersionElement{
{name: "Alpine", getVersion: alpineConf.Version},
{name: "OpenVPN 2.4", getVersion: ovpnConf.Version24},
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
{name: "Unbound", getVersion: dnsConf.Version},
{name: "IPtables", getVersion: firewallConf.Version},
}); err != nil {
return err
}
2021-02-06 11:05:50 -05:00
var allSettings configuration.Settings
err := allSettings.Read(params.NewEnv(), os,
logger.NewChild(logging.Settings{Prefix: "configuration: "}))
if err != nil {
2021-01-04 01:40:07 +00:00
return err
}
logger.Info(allSettings.String())
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
2021-01-04 01:40:07 +00:00
return err
}
if err := os.MkdirAll("/gluetun", 0644); err != nil {
2021-01-04 01:40:07 +00:00
return err
}
// TODO run this in a loop or in openvpn to reload from file without restarting
storage := storage.New(
logger.NewChild(logging.Settings{Prefix: "storage: "}),
os, constants.ServersData)
allServers, err := storage.SyncServers(constants.GetAllServers())
if err != nil {
2021-01-04 01:40:07 +00:00
return err
}
// Should never change
2020-12-29 16:44:35 +00:00
puid, pgid := allSettings.System.PUID, allSettings.System.PGID
2020-12-27 00:36:39 +00:00
const defaultUsername = "nonrootuser"
2020-12-29 16:44:35 +00:00
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
if err != nil {
return fmt.Errorf("%w: %s", errCreateUser, err)
}
2020-12-29 16:44:35 +00:00
if nonRootUsername != defaultUsername {
logger.Info("using existing username %s corresponding to user id %d", nonRootUsername, puid)
}
// set it for Unbound
// TODO remove this when migrating to qdm12/dns v2
allSettings.DNS.Unbound.Username = nonRootUsername
2020-12-29 16:44:35 +00:00
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
2021-01-04 01:40:07 +00:00
return err
}
if allSettings.Firewall.Debug {
firewallConf.SetDebug()
routingConf.SetDebug()
}
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
if err != nil {
2021-01-04 01:40:07 +00:00
return err
}
localNetworks, err := routingConf.LocalNetworks()
if err != nil {
2021-01-04 01:40:07 +00:00
return err
}
defaultIP, err := routingConf.DefaultIP()
if err != nil {
2021-01-04 01:40:07 +00:00
return err
}
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localNetworks, defaultIP)
if err := routingConf.Setup(); err != nil {
if strings.Contains(err.Error(), "operation not permitted") {
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
}
return fmt.Errorf("%w: %s", errSetupRouting, err)
}
defer func() {
routingConf.SetVerbose(false)
if err := routingConf.TearDown(); err != nil {
logger.Error("cannot teardown routing: " + err.Error())
}
}()
if err := firewallConf.SetOutboundSubnets(ctx, allSettings.Firewall.OutboundSubnets); err != nil {
2021-01-04 01:40:07 +00:00
return err
}
if err := routingConf.SetOutboundRoutes(allSettings.Firewall.OutboundSubnets); err != nil {
2021-01-04 01:40:07 +00:00
return err
}
if err := ovpnConf.CheckTUN(); err != nil {
logger.Warn(err)
err = ovpnConf.CreateTUN()
if err != nil {
2021-01-04 01:40:07 +00:00
return err
}
}
tunnelReadyCh := make(chan struct{})
2020-09-12 19:17:19 +00:00
defer close(tunnelReadyCh)
if allSettings.Firewall.Enabled {
err := firewallConf.SetEnabled(ctx, true) // disabled by default
if err != nil {
2021-01-04 01:40:07 +00:00
return err
}
}
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
err = firewallConf.SetAllowedPort(ctx, vpnPort, string(constants.TUN))
if err != nil {
2021-01-04 01:40:07 +00:00
return err
}
}
for _, port := range allSettings.Firewall.InputPorts {
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
if err != nil {
2021-01-04 01:40:07 +00:00
return err
}
} // TODO move inside firewall?
// Shutdown settings
const defaultShutdownTimeout = 400 * time.Millisecond
defaultShutdownOnSuccess := func(goRoutineName string) {
logger.Info(goRoutineName + ": terminated ✔️")
}
defaultShutdownOnFailure := func(goRoutineName string, err error) {
logger.Warn(goRoutineName + ": " + err.Error() + " ⚠️")
}
defaultGoRoutineSettings := goshutdown.GoRoutineSettings{Timeout: defaultShutdownTimeout}
defaultGroupSettings := goshutdown.GroupSettings{
Timeout: defaultShutdownTimeout,
OnSuccess: defaultShutdownOnSuccess,
}
controlGroupHandler := goshutdown.NewGroupHandler("control", defaultGroupSettings)
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupSettings)
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupSettings)
2020-07-23 02:15:37 +00:00
2020-12-29 16:44:35 +00:00
openvpnLooper := openvpn.NewLooper(allSettings.OpenVPN, nonRootUsername, puid, pgid, allServers,
ovpnConf, firewallConf, routingConf, logger, httpClient, os.OpenFile, tunnelReadyCh)
openvpnHandler, openvpnCtx, openvpnDone := goshutdown.NewGoRoutineHandler(
"openvpn", goshutdown.GoRoutineSettings{Timeout: time.Second})
// wait for restartOpenvpn
go openvpnLooper.Run(openvpnCtx, openvpnDone)
updaterLooper := updater.NewLooper(allSettings.Updater,
allServers, storage, openvpnLooper.SetServers, httpClient,
logger.NewChild(logging.Settings{Prefix: "updater: "}))
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
"updater", defaultGoRoutineSettings)
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
go updaterLooper.Run(updaterCtx, updaterDone)
tickersGroupHandler.Add(updaterHandler)
unboundLogger := logger.NewChild(logging.Settings{Prefix: "dns over tls: "})
unboundLooper := dns.NewLooper(dnsConf, allSettings.DNS, httpClient,
unboundLogger, os.OpenFile)
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
"unbound", defaultGoRoutineSettings)
2020-09-12 19:17:19 +00:00
// wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
go unboundLooper.Run(dnsCtx, dnsDone)
otherGroupHandler.Add(dnsHandler)
publicIPLooper := publicip.NewLooper(httpClient,
logger.NewChild(logging.Settings{Prefix: "ip getter: "}),
allSettings.PublicIP, puid, pgid, os)
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
"public IP", defaultGoRoutineSettings)
go publicIPLooper.Run(pubIPCtx, pubIPDone)
otherGroupHandler.Add(pubIPHandler)
pubIPTickerHandler, pubIPTickerCtx, pubIPTickerDone := goshutdown.NewGoRoutineHandler(
"public IP", defaultGoRoutineSettings)
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
tickersGroupHandler.Add(pubIPTickerHandler)
httpProxyLooper := httpproxy.NewLooper(
logger.NewChild(logging.Settings{Prefix: "http proxy: "}),
allSettings.HTTPProxy)
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
"http proxy", defaultGoRoutineSettings)
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
otherGroupHandler.Add(httpProxyHandler)
2020-07-08 23:20:33 +00:00
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks,
logger.NewChild(logging.Settings{Prefix: "shadowsocks: "}))
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
"shadowsocks proxy", defaultGoRoutineSettings)
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
otherGroupHandler.Add(shadowsocksHandler)
2020-07-08 23:29:40 +00:00
eventsRoutingHandler, eventsRoutingCtx, eventsRoutingDone := goshutdown.NewGoRoutineHandler(
"events routing", defaultGoRoutineSettings)
go routeReadyEvents(eventsRoutingCtx, eventsRoutingDone, buildInfo, tunnelReadyCh,
2020-09-12 19:17:19 +00:00
unboundLooper, updaterLooper, publicIPLooper, routingConf, logger, httpClient,
allSettings.VersionInformation, allSettings.OpenVPN.Provider.PortForwarding.Enabled, openvpnLooper.PortForward,
)
controlGroupHandler.Add(eventsRoutingHandler)
controlServerAddress := ":" + strconv.Itoa(int(allSettings.ControlServer.Port))
controlServerLogging := allSettings.ControlServer.Log
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
"http server", defaultGoRoutineSettings)
httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
logger.NewChild(logging.Settings{Prefix: "http server: "}),
buildInfo, openvpnLooper, unboundLooper, updaterLooper, publicIPLooper)
go httpServer.Run(httpServerCtx, httpServerDone)
controlGroupHandler.Add(httpServerHandler)
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
healthcheckServer := healthcheck.NewServer(constants.HealthcheckAddress,
allSettings.Health, healthLogger, openvpnLooper)
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
"HTTP health server", defaultGoRoutineSettings)
go healthcheckServer.Run(healthServerCtx, healthServerDone)
const orderShutdownTimeout = 3 * time.Second
orderSettings := goshutdown.OrderSettings{
Timeout: orderShutdownTimeout,
OnFailure: defaultShutdownOnFailure,
OnSuccess: defaultShutdownOnSuccess,
}
orderHandler := goshutdown.NewOrder("gluetun", orderSettings)
orderHandler.Append(controlGroupHandler, tickersGroupHandler, healthServerHandler,
openvpnHandler, otherGroupHandler)
// Start openvpn for the first time in a blocking call
// until openvpn is launched
2021-07-16 21:20:34 +00:00
_, _ = openvpnLooper.ApplyStatus(ctx, constants.Running) // TODO option to disable with variable
2021-01-04 01:40:07 +00:00
<-ctx.Done()
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
logger.Info("Clearing forwarded port status file %s", allSettings.OpenVPN.Provider.PortForwarding.Filepath)
2021-02-06 18:31:14 +00:00
if err := os.Remove(allSettings.OpenVPN.Provider.PortForwarding.Filepath); err != nil {
logger.Error(err)
}
2020-04-29 01:27:42 +00:00
}
2020-05-02 14:48:18 +00:00
return orderHandler.Shutdown(context.Background())
2020-05-02 14:48:18 +00:00
}
type printVersionElement struct {
name string
getVersion func(ctx context.Context) (version string, err error)
}
2020-10-20 02:45:28 +00:00
func printVersions(ctx context.Context, logger logging.Logger,
elements []printVersionElement) (err error) {
2020-10-20 02:45:28 +00:00
const timeout = 5 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
2020-05-02 14:48:18 +00:00
defer cancel()
for _, element := range elements {
version, err := element.getVersion(ctx)
2020-05-02 14:48:18 +00:00
if err != nil {
return err
2020-05-02 14:48:18 +00:00
}
logger.Info(element.name + " version: " + version)
2020-05-02 14:48:18 +00:00
}
return nil
2020-05-02 14:48:18 +00:00
}
func routeReadyEvents(ctx context.Context, done chan<- struct{}, buildInfo models.BuildInformation,
tunnelReadyCh <-chan struct{},
2020-09-12 19:17:19 +00:00
unboundLooper dns.Looper, updaterLooper updater.Looper, publicIPLooper publicip.Looper,
routing routing.Routing, logger logging.Logger, httpClient *http.Client,
versionInformation, portForwardingEnabled bool, startPortForward func(vpnGateway net.IP)) {
defer close(done)
2020-09-12 19:17:19 +00:00
// for linters only
var restartTickerContext context.Context
var restartTickerCancel context.CancelFunc = func() {}
unboundTickerDone := make(chan struct{})
close(unboundTickerDone)
updaterTickerDone := make(chan struct{})
close(updaterTickerDone)
first := true
2020-09-12 19:17:19 +00:00
for {
select {
case <-ctx.Done():
restartTickerCancel() // for linters only
<-unboundTickerDone
<-updaterTickerDone
2020-09-12 19:17:19 +00:00
return
case <-tunnelReadyCh: // blocks until openvpn is connected
vpnDestination, err := routing.VPNDestinationIP()
if err != nil {
logger.Warn(err)
} else {
logger.Info("VPN routing IP address: %s", vpnDestination)
}
if unboundLooper.GetSettings().Enabled {
_, _ = unboundLooper.ApplyStatus(ctx, constants.Running)
}
2020-09-12 19:17:19 +00:00
restartTickerCancel() // stop previous restart tickers
<-unboundTickerDone
<-updaterTickerDone
2020-09-12 19:17:19 +00:00
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
// Runs the Public IP getter job once
_, _ = publicIPLooper.SetStatus(ctx, constants.Running)
if versionInformation && first {
first = false
message, err := versionpkg.GetMessage(ctx, buildInfo, httpClient)
if err != nil {
2021-05-30 16:14:08 +00:00
logger.Error("cannot get version information: " + err.Error())
} else {
logger.Info(message)
}
}
unboundTickerDone = make(chan struct{})
updaterTickerDone = make(chan struct{})
go unboundLooper.RunRestartTicker(restartTickerContext, unboundTickerDone)
go updaterLooper.RunRestartTicker(restartTickerContext, updaterTickerDone)
if portForwardingEnabled {
2020-11-10 13:35:49 +00:00
// vpnGateway required only for PIA
2020-11-05 02:10:34 +00:00
vpnGateway, err := routing.VPNLocalGatewayIP()
if err != nil {
logger.Error(err)
2020-09-12 19:17:19 +00:00
}
2020-11-05 02:10:34 +00:00
logger.Info("VPN gateway IP address: %s", vpnGateway)
startPortForward(vpnGateway)
2020-09-12 19:17:19 +00:00
}
2020-05-02 14:48:18 +00:00
}
}
}