Unbound restart logic

- Update files and restart unbound every 24hours
- HTTP route to force update & restart of Unbound
- Fix #151
This commit is contained in:
Quentin McGaw
2020-05-02 17:08:15 +00:00
parent 0d7f6dab1a
commit 754bab9763
3 changed files with 101 additions and 40 deletions

View File

@@ -35,7 +35,7 @@
- Based on Alpine 3.11 for a small Docker image below 50MB - Based on Alpine 3.11 for a small Docker image below 50MB
- Supports **Private Internet Access**, **Mullvad** and **Windscribe** servers - Supports **Private Internet Access**, **Mullvad** and **Windscribe** servers
- DNS over TLS baked in with service provider(s) of your choice - DNS over TLS baked in with service provider(s) of your choice
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses - DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
- Choose the vpn network protocol, `udp` or `tcp` - Choose the vpn network protocol, `udp` or `tcp`
- Built in firewall kill switch to allow traffic only with needed PIA servers and LAN devices - Built in firewall kill switch to allow traffic only with needed PIA servers and LAN devices
- Built in SOCKS5 proxy (Shadowsocks, tunnels TCP+UDP) - Built in SOCKS5 proxy (Shadowsocks, tunnels TCP+UDP)
@@ -268,6 +268,7 @@ It can be useful to mount this file as a volume to read it from other containers
A built-in HTTP server listens on port `8000` to modify the state of the container. You have the following routes available: A built-in HTTP server listens on port `8000` to modify the state of the container. You have the following routes available:
- `http://<your-docker-host-ip>:8000/openvpn/actions/restart` restarts the openvpn process - `http://<your-docker-host-ip>:8000/openvpn/actions/restart` restarts the openvpn process
- `http://<your-docker-host-ip>:8000/unbound/actions/restart` re-downloads the DNS files (crypto and block lists) and restarts the unbound process
## FAQ ## FAQ

View File

@@ -249,7 +249,7 @@ func main() {
go func() { go func() {
<-connected.Done() // blocks until openvpn is connected <-connected.Done() // blocks until openvpn is connected
onConnected(ctx, allSettings, logger, dnsConf, fileManager, waiter, onConnected(ctx, allSettings, logger, dnsConf, fileManager, waiter,
streamMerger, routingConf, defaultInterface, piaConf) streamMerger, httpServer, routingConf, defaultInterface, piaConf)
}() }()
signalsCh := make(chan os.Signal, 1) signalsCh := make(chan os.Signal, 1)
@@ -354,7 +354,7 @@ func openvpnRunLoop(ctx context.Context, ovpnConf openvpn.Configurator, streamMe
func onConnected(ctx context.Context, allSettings settings.Settings, func onConnected(ctx context.Context, allSettings settings.Settings,
logger logging.Logger, dnsConf dns.Configurator, fileManager files.FileManager, logger logging.Logger, dnsConf dns.Configurator, fileManager files.FileManager,
waiter command.Waiter, streamMerger command.StreamMerger, waiter command.Waiter, streamMerger command.StreamMerger, httpServer server.Server,
routingConf routing.Routing, defaultInterface string, routingConf routing.Routing, defaultInterface string,
piaConf pia.Configurator, piaConf pia.Configurator,
) { ) {
@@ -365,12 +365,7 @@ func onConnected(ctx context.Context, allSettings settings.Settings,
} }
if allSettings.DNS.Enabled { if allSettings.DNS.Enabled {
err := setupUnbound(ctx, logger, dnsConf, allSettings.DNS, allSettings.System.UID, allSettings.System.GID, waiter, streamMerger) go unboundRunLoop(ctx, logger, dnsConf, allSettings.DNS, allSettings.System.UID, allSettings.System.GID, waiter, streamMerger, httpServer)
if err != nil {
logger.Error("unbound dns over tls setup: %s", err)
} else {
logger.Info("unbound dns over tls setup: completed")
}
} }
ip, err := routingConf.CurrentPublicIP(defaultInterface) ip, err := routingConf.CurrentPublicIP(defaultInterface)
@@ -389,53 +384,95 @@ func onConnected(ctx context.Context, allSettings settings.Settings,
} }
} }
func setupUnbound(ctx context.Context, logger logging.Logger, dnsConf dns.Configurator, func fallbackToUnencryptedDNS(dnsConf dns.Configurator, provider models.DNSProvider, ipv6 bool) error {
settings settings.DNS, uid, gid int, targetDNS := constants.DNSProviderMapping()[provider]
waiter command.Waiter, streamMerger command.StreamMerger, var targetIP net.IP
) (err error) { for _, targetIP = range targetDNS.IPs {
ctx, cancel := context.WithCancel(ctx) if ipv6 && targetIP.To4() == nil {
defer func() {
if err != nil {
cancel()
}
}()
initialDNSToUse := constants.DNSProviderMapping()[settings.Providers[0]]
var ipToUse net.IP
for _, ipToUse = range initialDNSToUse.IPs {
if settings.IPv6 && ipToUse.To4() == nil {
break break
} else if !settings.IPv6 && ipToUse.To4() != nil { } else if !ipv6 && targetIP.To4() != nil {
break break
} }
} }
dnsConf.UseDNSInternally(ipToUse) dnsConf.UseDNSInternally(targetIP)
return dnsConf.UseDNSSystemWide(targetIP)
}
func unboundRun(ctx, unboundCtx context.Context, unboundCancel context.CancelFunc, dnsConf dns.Configurator, settings settings.DNS, uid, gid int,
streamMerger command.StreamMerger, waiter command.Waiter, httpServer server.Server) (newCtx context.Context, newCancel context.CancelFunc, err error) {
if err := dnsConf.DownloadRootHints(uid, gid); err != nil { if err := dnsConf.DownloadRootHints(uid, gid); err != nil {
return err return unboundCtx, unboundCancel, err
} }
if err := dnsConf.DownloadRootKey(uid, gid); err != nil { if err := dnsConf.DownloadRootKey(uid, gid); err != nil {
return err return unboundCtx, unboundCancel, err
} }
if err := dnsConf.MakeUnboundConf(settings, uid, gid); err != nil { if err := dnsConf.MakeUnboundConf(settings, uid, gid); err != nil {
return err return unboundCtx, unboundCancel, err
} }
stream, waitFn, err := dnsConf.Start(ctx, settings.VerbosityDetailsLevel) unboundCancel()
newCtx, newCancel = context.WithTimeout(ctx, 24*time.Hour)
stream, waitFn, err := dnsConf.Start(newCtx, settings.VerbosityDetailsLevel)
if err != nil { if err != nil {
return err newCancel()
if fallbackErr := fallbackToUnencryptedDNS(dnsConf, settings.Providers[0], settings.IPv6); err != nil {
return newCtx, newCancel, fmt.Errorf("%s: %w", err, fallbackErr)
}
return newCtx, newCancel, err
} }
waiter.Add(func() error { //nolint:scopelint go streamMerger.Merge(newCtx, stream, command.MergeName("unbound"), command.MergeColor(constants.ColorUnbound()))
err := waitFn()
logger.Error("unbound: %s", err) //nolint:scopelint
return err
})
go streamMerger.Merge(ctx, stream, command.MergeName("unbound"), command.MergeColor(constants.ColorUnbound()))
dnsConf.UseDNSInternally(net.IP{127, 0, 0, 1}) // use Unbound dnsConf.UseDNSInternally(net.IP{127, 0, 0, 1}) // use Unbound
if err := dnsConf.UseDNSSystemWide(net.IP{127, 0, 0, 1}); err != nil { // use Unbound if err := dnsConf.UseDNSSystemWide(net.IP{127, 0, 0, 1}); err != nil { // use Unbound
return err newCancel()
if fallbackErr := fallbackToUnencryptedDNS(dnsConf, settings.Providers[0], settings.IPv6); err != nil {
return newCtx, newCancel, fmt.Errorf("%s: %w", err, fallbackErr)
}
return newCtx, newCancel, err
} }
if err := dnsConf.WaitForUnbound(); err != nil { if err := dnsConf.WaitForUnbound(); err != nil {
return err newCancel()
if fallbackErr := fallbackToUnencryptedDNS(dnsConf, settings.Providers[0], settings.IPv6); err != nil {
return newCtx, newCancel, fmt.Errorf("%s: %w", err, fallbackErr)
}
return newCtx, newCancel, err
}
// Unbound is up and running at this point
httpServer.SetUnboundRestart(newCancel)
waitErrors := make(chan error)
waiter.Add(func() error { //nolint:scopelint
return <-waitErrors
})
err = waitFn()
waitErrors <- err
if newCtx.Err() == context.Canceled || newCtx.Err() == context.DeadlineExceeded {
return newCtx, newCancel, nil
}
return newCtx, newCancel, err
}
func unboundRunLoop(ctx context.Context, logger logging.Logger, dnsConf dns.Configurator,
settings settings.DNS, uid, gid int,
waiter command.Waiter, streamMerger command.StreamMerger, httpServer server.Server,
) {
logger = logger.WithPrefix("unbound dns over tls setup: ")
if err := fallbackToUnencryptedDNS(dnsConf, settings.Providers[0], settings.IPv6); err != nil {
logger.Error(err)
}
unboundCtx, unboundCancel := context.WithCancel(ctx)
defer unboundCancel()
for {
if ctx.Err() == context.Canceled {
logger.Info("shutting down")
break
}
var err error
unboundCtx, unboundCancel, err = unboundRun(ctx, unboundCtx, unboundCancel, dnsConf, settings, uid, gid, streamMerger, waiter, httpServer)
if err != nil {
logger.Error(err)
time.Sleep(10 * time.Second)
continue
}
logger.Info("attempting restart")
} }
return nil
} }
func setupPortForwarding(logger logging.Logger, piaConf pia.Configurator, settings settings.PIA, uid, gid int) { func setupPortForwarding(logger logging.Logger, piaConf pia.Configurator, settings settings.PIA, uid, gid int) {

View File

@@ -12,6 +12,7 @@ import (
type Server interface { type Server interface {
SetOpenVPNRestart(f func()) SetOpenVPNRestart(f func())
SetUnboundRestart(f func())
Run(ctx context.Context) error Run(ctx context.Context) error
} }
@@ -21,16 +22,22 @@ type server struct {
restartOpenvpn func() restartOpenvpn func()
restartOpenvpnSet context.Context restartOpenvpnSet context.Context
restartOpenvpnSetSignal func() restartOpenvpnSetSignal func()
restartUnbound func()
restartUnboundSet context.Context
restartUnboundSetSignal func()
sync.RWMutex sync.RWMutex
} }
func New(address string, logger logging.Logger) Server { func New(address string, logger logging.Logger) Server {
restartOpenvpnSet, restartOpenvpnSetSignal := context.WithCancel(context.Background()) restartOpenvpnSet, restartOpenvpnSetSignal := context.WithCancel(context.Background())
restartUnboundSet, restartUnboundSetSignal := context.WithCancel(context.Background())
return &server{ return &server{
address: address, address: address,
logger: logger.WithPrefix("http server: "), logger: logger.WithPrefix("http server: "),
restartOpenvpnSet: restartOpenvpnSet, restartOpenvpnSet: restartOpenvpnSet,
restartOpenvpnSetSignal: restartOpenvpnSetSignal, restartOpenvpnSetSignal: restartOpenvpnSetSignal,
restartUnboundSet: restartUnboundSet,
restartUnboundSetSignal: restartUnboundSetSignal,
} }
} }
@@ -39,6 +46,10 @@ func (s *server) Run(ctx context.Context) error {
s.logger.Warn("restartOpenvpn function is not set, waiting...") s.logger.Warn("restartOpenvpn function is not set, waiting...")
<-s.restartOpenvpnSet.Done() <-s.restartOpenvpnSet.Done()
} }
if s.restartUnboundSet.Err() == nil {
s.logger.Warn("restartUnbound function is not set, waiting...")
<-s.restartUnboundSet.Done()
}
server := http.Server{Addr: s.address, Handler: s.makeHandler()} server := http.Server{Addr: s.address, Handler: s.makeHandler()}
go func() { go func() {
<-ctx.Done() <-ctx.Done()
@@ -61,6 +72,15 @@ func (s *server) SetOpenVPNRestart(f func()) {
} }
} }
func (s *server) SetUnboundRestart(f func()) {
s.Lock()
defer s.Unlock()
s.restartUnbound = f
if s.restartUnboundSet.Err() == nil {
s.restartUnboundSetSignal()
}
}
func (s *server) makeHandler() http.HandlerFunc { func (s *server) makeHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
s.logger.Info("HTTP %s %s", r.Method, r.RequestURI) s.logger.Info("HTTP %s %s", r.Method, r.RequestURI)
@@ -71,6 +91,10 @@ func (s *server) makeHandler() http.HandlerFunc {
s.RLock() s.RLock()
defer s.RUnlock() defer s.RUnlock()
s.restartOpenvpn() s.restartOpenvpn()
case "/unbound/actions/restart":
s.RLock()
defer s.RUnlock()
s.restartUnbound()
default: default:
routeDoesNotExist(s.logger, w, r) routeDoesNotExist(s.logger, w, r)
} }
@@ -87,4 +111,3 @@ func routeDoesNotExist(logger logging.Logger, w http.ResponseWriter, r *http.Req
logger.Error(err) logger.Error(err)
} }
} }