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:
@@ -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
|
||||||
|
|
||||||
|
|||||||
113
cmd/main.go
113
cmd/main.go
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user