2020-04-12 20:05:28 +00:00
package main
import (
"context"
"fmt"
2020-10-12 10:55:08 -04:00
"net"
2020-08-30 14:48:57 +00:00
"net/http"
2020-04-12 20:05:28 +00:00
"os"
2020-04-29 01:27:42 +00:00
"os/signal"
2020-07-08 23:36:02 +00:00
"sync"
2020-04-29 01:27:42 +00:00
"syscall"
2020-04-12 20:05:28 +00:00
"time"
2020-07-26 12:07:06 +00:00
"github.com/qdm12/gluetun/internal/alpine"
"github.com/qdm12/gluetun/internal/cli"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/dns"
"github.com/qdm12/gluetun/internal/firewall"
gluetunLogging "github.com/qdm12/gluetun/internal/logging"
"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"
2020-08-25 19:38:50 -04:00
"github.com/qdm12/gluetun/internal/storage"
2020-07-26 12:07:06 +00:00
"github.com/qdm12/gluetun/internal/tinyproxy"
2020-09-12 14:04:54 -04:00
"github.com/qdm12/gluetun/internal/updater"
2020-08-30 14:48:57 +00:00
versionpkg "github.com/qdm12/gluetun/internal/version"
2020-04-12 20:05:28 +00:00
"github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/network"
)
2020-08-29 19:14:52 +00:00
//nolint:gochecknoglobals
var (
version = "unknown"
commit = "unknown"
buildDate = "an unknown date"
)
2020-05-02 14:48:18 +00:00
func main ( ) {
2020-06-02 23:03:18 +00:00
ctx := context . Background ( )
os . Exit ( _main ( ctx , os . Args ) )
}
2020-08-27 22:59:58 +00:00
func _main ( background context . Context , args [ ] string ) int { //nolint:gocognit,gocyclo
2020-06-02 23:03:18 +00:00
if len ( args ) > 1 { // cli operation
var err error
switch args [ 1 ] {
case "healthcheck" :
err = cli . HealthCheck ( )
2020-06-13 10:43:47 -04:00
case "clientkey" :
err = cli . ClientKey ( args [ 2 : ] )
2020-07-13 23:43:26 +00:00
case "openvpnconfig" :
err = cli . OpenvpnConfig ( )
2020-08-28 08:17:04 -04:00
case "update" :
err = cli . Update ( args [ 2 : ] )
2020-06-02 23:03:18 +00:00
default :
err = fmt . Errorf ( "command %q is unknown" , args [ 1 ] )
}
if err != nil {
2020-04-12 20:05:28 +00:00
fmt . Println ( err )
2020-06-02 23:03:18 +00:00
return 1
2020-04-12 20:05:28 +00:00
}
2020-06-02 23:03:18 +00:00
return 0
2020-04-12 20:05:28 +00:00
}
2020-06-02 23:03:18 +00:00
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 ( )
2020-07-23 02:15:37 +00:00
2020-09-12 14:04:54 -04:00
httpClient := & http . Client { Timeout : 15 * time . Second }
2020-04-12 20:05:28 +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 )
routingConf := routing . NewRouting ( logger , fileManager )
2020-07-11 21:03:55 +00:00
firewallConf := firewall . NewConfigurator ( logger , routingConf , fileManager )
2020-04-12 20:05:28 +00:00
tinyProxyConf := tinyproxy . NewConfigurator ( fileManager , logger )
2020-04-29 23:59:23 +00:00
streamMerger := command . NewStreamMerger ( )
2020-04-12 20:05:28 +00:00
2020-07-14 00:17:31 +00:00
paramsReader := params . NewReader ( logger , fileManager )
2020-08-29 19:14:52 +00:00
fmt . Println ( gluetunLogging . Splash ( version , commit , buildDate ) )
2020-07-14 00:17:31 +00:00
2020-05-02 14:48:18 +00:00
printVersions ( ctx , logger , map [ string ] func ( ctx context . Context ) ( string , error ) {
2020-08-20 19:19:54 -04:00
"OpenVPN" : ovpnConf . Version ,
"Unbound" : dnsConf . Version ,
"IPtables" : firewallConf . Version ,
"TinyProxy" : tinyProxyConf . Version ,
2020-05-02 14:48:18 +00:00
} )
2020-04-12 20:05:28 +00:00
allSettings , err := settings . GetAllSettings ( paramsReader )
2020-08-27 22:59:58 +00:00
if err != nil {
logger . Error ( err )
return 1
}
2020-04-12 20:05:28 +00:00
logger . Info ( allSettings . String ( ) )
2020-08-25 19:38:50 -04:00
// TODO run this in a loop or in openvpn to reload from file without restarting
storage := storage . New ( logger )
2020-08-28 08:17:04 -04:00
const updateServerFile = true
allServers , err := storage . SyncServers ( constants . GetAllServers ( ) , updateServerFile )
2020-08-25 19:38:50 -04:00
if err != nil {
logger . Error ( err )
return 1
}
2020-07-08 22:13:59 +00:00
// Should never change
uid , gid := allSettings . System . UID , allSettings . System . GID
err = alpineConf . CreateUser ( "nonrootuser" , uid )
2020-08-27 22:59:58 +00:00
if err != nil {
logger . Error ( err )
return 1
}
2020-07-08 22:13:59 +00:00
err = fileManager . SetOwnership ( "/etc/unbound" , uid , gid )
2020-08-27 22:59:58 +00:00
if err != nil {
logger . Error ( err )
return 1
}
2020-07-08 22:13:59 +00:00
err = fileManager . SetOwnership ( "/etc/tinyproxy" , uid , gid )
2020-08-27 22:59:58 +00:00
if err != nil {
logger . Error ( err )
return 1
}
2020-04-12 20:05:28 +00:00
2020-07-13 02:14:56 +00:00
if allSettings . Firewall . Debug {
firewallConf . SetDebug ( )
routingConf . SetDebug ( )
}
2020-07-20 00:35:53 +00:00
defaultInterface , defaultGateway , err := routingConf . DefaultRoute ( )
if err != nil {
2020-08-27 22:59:58 +00:00
logger . Error ( err )
return 1
2020-07-20 00:35:53 +00:00
}
localSubnet , err := routingConf . LocalSubnet ( )
if err != nil {
2020-08-27 22:59:58 +00:00
logger . Error ( err )
return 1
2020-07-20 00:35:53 +00:00
}
firewallConf . SetNetworkInformation ( defaultInterface , defaultGateway , localSubnet )
2020-04-12 20:05:28 +00:00
if err := ovpnConf . CheckTUN ( ) ; err != nil {
logger . Warn ( err )
err = ovpnConf . CreateTUN ( )
2020-08-27 22:59:58 +00:00
if err != nil {
logger . Error ( err )
return 1
}
2020-04-12 20:05:28 +00:00
}
2020-09-12 19:17:19 +00:00
tunnelReadyCh , dnsReadyCh := make ( chan struct { } ) , make ( chan struct { } )
signalTunnelReady := func ( ) { tunnelReadyCh <- struct { } { } }
2020-09-12 18:50:42 +00:00
signalDNSReady := func ( ) { dnsReadyCh <- struct { } { } }
2020-09-12 19:17:19 +00:00
defer close ( tunnelReadyCh )
2020-09-12 18:50:42 +00:00
defer close ( dnsReadyCh )
2020-04-12 20:05:28 +00:00
2020-07-11 21:03:55 +00:00
if allSettings . Firewall . Enabled {
err := firewallConf . SetEnabled ( ctx , true ) // disabled by default
2020-08-27 22:59:58 +00:00
if err != nil {
logger . Error ( err )
return 1
}
2020-07-11 21:03:55 +00:00
}
2020-07-12 21:21:41 +00:00
err = firewallConf . SetAllowedSubnets ( ctx , allSettings . Firewall . AllowedSubnets )
2020-08-27 22:59:58 +00:00
if err != nil {
logger . Error ( err )
return 1
}
2020-07-12 21:21:41 +00:00
2020-07-20 02:07:13 +00:00
for _ , vpnPort := range allSettings . Firewall . VPNInputPorts {
err = firewallConf . SetAllowedPort ( ctx , vpnPort , string ( constants . TUN ) )
2020-08-27 22:59:58 +00:00
if err != nil {
logger . Error ( err )
return 1
}
2020-07-20 02:07:13 +00:00
}
2020-07-23 02:15:37 +00:00
wg := & sync . WaitGroup { }
2020-09-12 19:17:19 +00:00
go collectStreamLines ( ctx , streamMerger , logger , signalTunnelReady )
2020-08-27 22:59:58 +00:00
2020-08-25 19:38:50 -04:00
openvpnLooper := openvpn . NewLooper ( allSettings . VPNSP , allSettings . OpenVPN , uid , gid , allServers ,
2020-10-12 10:55:08 -04:00
ovpnConf , firewallConf , routingConf , logger , httpClient , fileManager , streamMerger , cancel )
2020-09-12 14:34:15 -04:00
wg . Add ( 1 )
2020-07-08 13:14:39 +00:00
// wait for restartOpenvpn
2020-07-15 01:34:46 +00:00
go openvpnLooper . Run ( ctx , wg )
2020-04-30 23:41:57 +00:00
2020-09-12 14:04:54 -04:00
updaterOptions := updater . NewOptions ( "127.0.0.1" )
updaterLooper := updater . NewLooper ( updaterOptions , allSettings . UpdaterPeriod , allServers , storage , openvpnLooper . SetAllServers , httpClient , logger )
wg . Add ( 1 )
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
go updaterLooper . Run ( ctx , wg )
2020-07-08 22:13:59 +00:00
unboundLooper := dns . NewLooper ( dnsConf , allSettings . DNS , logger , streamMerger , uid , gid )
2020-09-12 14:34:15 -04:00
wg . Add ( 1 )
2020-09-12 19:17:19 +00:00
// wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
2020-09-12 18:50:42 +00:00
go unboundLooper . Run ( ctx , wg , signalDNSReady )
2020-06-16 00:11:22 +00:00
2020-07-16 01:12:54 +00:00
publicIPLooper := publicip . NewLooper ( client , logger , fileManager , allSettings . System . IPStatusFilepath , allSettings . PublicIPPeriod , uid , gid )
2020-09-12 14:34:15 -04:00
wg . Add ( 1 )
go publicIPLooper . Run ( ctx , wg )
wg . Add ( 1 )
go publicIPLooper . RunRestartTicker ( ctx , wg )
2020-09-12 19:17:19 +00:00
publicIPLooper . SetPeriod ( allSettings . PublicIPPeriod ) // call after RunRestartTicker
2020-07-08 22:33:28 +00:00
2020-07-20 00:39:59 +00:00
tinyproxyLooper := tinyproxy . NewLooper ( tinyProxyConf , firewallConf , allSettings . TinyProxy , logger , streamMerger , uid , gid , defaultInterface )
2020-07-15 01:34:46 +00:00
restartTinyproxy := tinyproxyLooper . Restart
2020-09-12 14:34:15 -04:00
wg . Add ( 1 )
2020-07-15 01:34:46 +00:00
go tinyproxyLooper . Run ( ctx , wg )
2020-07-08 23:20:33 +00:00
2020-08-20 19:19:54 -04:00
shadowsocksLooper := shadowsocks . NewLooper ( firewallConf , allSettings . ShadowSocks , logger , defaultInterface )
2020-07-15 01:34:46 +00:00
restartShadowsocks := shadowsocksLooper . Restart
2020-09-12 14:34:15 -04:00
wg . Add ( 1 )
2020-07-15 01:34:46 +00:00
go shadowsocksLooper . Run ( ctx , wg )
2020-07-08 23:29:40 +00:00
2020-07-08 23:20:33 +00:00
if allSettings . TinyProxy . Enabled {
2020-07-15 01:34:46 +00:00
restartTinyproxy ( )
2020-07-08 23:20:33 +00:00
}
2020-07-08 23:29:40 +00:00
if allSettings . ShadowSocks . Enabled {
2020-07-15 01:34:46 +00:00
restartShadowsocks ( )
2020-07-08 23:29:40 +00:00
}
2020-07-08 23:20:33 +00:00
2020-09-12 14:34:15 -04:00
wg . Add ( 1 )
2020-09-12 19:17:19 +00:00
go routeReadyEvents ( ctx , wg , tunnelReadyCh , dnsReadyCh ,
unboundLooper , updaterLooper , publicIPLooper , routingConf , logger , httpClient ,
allSettings . VersionInformation , allSettings . OpenVPN . Provider . PortForwarding . Enabled , openvpnLooper . PortForward ,
)
2020-05-01 03:15:49 +00:00
2020-09-12 19:17:19 +00:00
httpServer := server . New ( "0.0.0.0:8000" , logger , openvpnLooper , unboundLooper , updaterLooper )
2020-09-12 14:34:15 -04:00
wg . Add ( 1 )
2020-07-08 23:36:02 +00:00
go httpServer . Run ( ctx , wg )
2020-07-08 13:14:39 +00:00
// Start openvpn for the first time
2020-09-12 19:17:19 +00:00
openvpnLooper . Restart ( )
2020-07-08 13:14:39 +00:00
2020-04-29 01:27:42 +00:00
signalsCh := make ( chan os . Signal , 1 )
signal . Notify ( signalsCh ,
syscall . SIGINT ,
syscall . SIGTERM ,
os . Interrupt ,
)
2020-07-11 21:03:55 +00:00
shutdownErrorsCount := 0
2020-04-29 01:27:42 +00:00
select {
case signal := <- signalsCh :
2020-04-12 20:05:28 +00:00
logger . Warn ( "Caught OS signal %s, shutting down" , signal )
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 )
2020-07-11 21:03:55 +00:00
shutdownErrorsCount ++
2020-04-30 12:55:07 +00:00
}
2020-07-05 20:05:38 +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 {
2020-04-19 20:10:48 +00:00
logger . Error ( err )
2020-07-11 21:03:55 +00:00
shutdownErrorsCount ++
2020-04-19 20:10:48 +00:00
}
2020-04-29 01:27:42 +00:00
}
2020-09-12 14:04:54 -04:00
const shutdownGracePeriod = 5 * time . Second
waiting , waited := context . WithTimeout ( context . Background ( ) , shutdownGracePeriod )
2020-07-11 21:03:55 +00:00
go func ( ) {
defer waited ( )
wg . Wait ( )
} ( )
<- waiting . Done ( )
if waiting . Err ( ) == context . DeadlineExceeded {
if shutdownErrorsCount > 0 {
logger . Warn ( "Shutdown had %d errors" , shutdownErrorsCount )
}
logger . Warn ( "Shutdown timed out" )
return 1
}
if shutdownErrorsCount > 0 {
logger . Warn ( "Shutdown had %d errors" )
return 1
}
logger . Info ( "Shutdown successful" )
return 0
2020-04-12 20:05:28 +00:00
}
2020-05-02 14:48:18 +00:00
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 )
}
}
}
2020-09-12 19:17:19 +00:00
func collectStreamLines ( ctx context . Context , streamMerger command . StreamMerger , logger logging . Logger , signalTunnelReady func ( ) ) {
2020-05-02 14:48:18 +00:00
// Blocking line merging paramsReader for all programs: openvpn, tinyproxy, unbound and shadowsocks
logger . Info ( "Launching standard output merger" )
streamMerger . CollectLines ( ctx , func ( line string ) {
2020-07-12 21:19:44 -04:00
line , level := gluetunLogging . PostProcessLine ( line )
if line == "" {
return
}
switch level {
case logging . InfoLevel :
logger . Info ( line )
case logging . WarnLevel :
logger . Warn ( line )
case logging . ErrorLevel :
logger . Error ( line )
}
2020-09-18 14:28:14 +00:00
switch {
case line == "openvpn: Initialization Sequence Completed" :
2020-09-12 19:17:19 +00:00
signalTunnelReady ( )
2020-09-18 14:28:14 +00:00
case line == "openvpn: TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)" :
logger . Warn ( "This means that either..." )
logger . Warn ( "1. The VPN server IP address you are trying to connect to is no longer valid, see https://github.com/qdm12/gluetun/wiki/Update-servers-information" )
logger . Warn ( "2. The VPN server crashed, try changing region" )
logger . Warn ( "3. Your Internet connection is not working, ensure it works" )
logger . Warn ( "Feel free to create an issue at https://github.com/qdm12/gluetun/issues/new/choose" )
2020-05-02 14:48:18 +00:00
}
} , func ( err error ) {
2020-05-09 00:43:09 +00:00
logger . Warn ( err )
2020-05-02 14:48:18 +00:00
} )
}
2020-10-12 10:55:08 -04:00
//nolint:gocognit
2020-09-12 19:17:19 +00:00
func routeReadyEvents ( ctx context . Context , wg * sync . WaitGroup , tunnelReadyCh , dnsReadyCh <- chan struct { } ,
unboundLooper dns . Looper , updaterLooper updater . Looper , publicIPLooper publicip . Looper ,
routing routing . Routing , logger logging . Logger , httpClient * http . Client ,
2020-10-12 10:55:08 -04:00
versionInformation , portForwardingEnabled bool , startPortForward func ( vpnGateway net . IP ) ) {
2020-09-12 19:17:19 +00:00
defer wg . Done ( )
tickerWg := & sync . WaitGroup { }
// for linters only
var restartTickerContext context . Context
var restartTickerCancel context . CancelFunc = func ( ) { }
for {
select {
case <- ctx . Done ( ) :
restartTickerCancel ( ) // for linters only
tickerWg . Wait ( )
return
case <- tunnelReadyCh : // blocks until openvpn is connected
unboundLooper . Restart ( )
restartTickerCancel ( ) // stop previous restart tickers
tickerWg . Wait ( )
restartTickerContext , restartTickerCancel = context . WithCancel ( ctx )
tickerWg . Add ( 2 )
go unboundLooper . RunRestartTicker ( restartTickerContext , tickerWg )
go updaterLooper . RunRestartTicker ( restartTickerContext , tickerWg )
defaultInterface , _ , err := routing . DefaultRoute ( )
if err != nil {
logger . Warn ( err )
} else {
2020-10-12 10:55:08 -04:00
vpnDestination , err := routing . VPNDestinationIP ( defaultInterface )
2020-09-12 19:17:19 +00:00
if err != nil {
logger . Warn ( err )
} else {
2020-10-12 10:55:08 -04:00
logger . Info ( "VPN routing IP address: %s" , vpnDestination )
}
}
if portForwardingEnabled {
// TODO make instantaneous once v3 go out of service
const waitDuration = 5 * time . Second
timer := time . NewTimer ( waitDuration )
select {
case <- ctx . Done ( ) :
if ! timer . Stop ( ) {
<- timer . C
}
continue
case <- timer . C :
// vpnGateway required only for PIA v4
vpnGateway , err := routing . VPNLocalGatewayIP ( )
if err != nil {
logger . Error ( err )
}
logger . Info ( "VPN gateway IP address: %s" , vpnGateway )
startPortForward ( vpnGateway )
2020-09-12 19:17:19 +00:00
}
}
case <- dnsReadyCh :
publicIPLooper . Restart ( ) // TODO do not restart if disabled
if ! versionInformation {
break
}
message , err := versionpkg . GetMessage ( version , commit , httpClient )
if err != nil {
logger . Error ( err )
break
}
logger . Info ( message )
2020-05-02 14:48:18 +00:00
}
}
}