Switch nameserver to listen on localhost

This commit is contained in:
Daniel
2021-01-08 16:36:36 +01:00
parent a087a8b9ef
commit 564928a97f
6 changed files with 289 additions and 114 deletions

View File

@@ -2,6 +2,7 @@ package nameserver
import (
"flag"
"runtime"
"github.com/safing/portbase/config"
"github.com/safing/portbase/log"
@@ -15,9 +16,16 @@ const (
var (
nameserverAddressFlag string
nameserverAddressConfig config.StringOption
defaultNameserverAddress = "localhost:53"
)
func init() {
// On Windows, packets are redirected to the same interface.
if runtime.GOOS == "windows" {
defaultNameserverAddress = "0.0.0.0:53"
}
flag.StringVar(&nameserverAddressFlag, "nameserver-address", "", "override nameserver listen address")
}
@@ -45,7 +53,7 @@ func registerConfig() error {
ExpertiseLevel: config.ExpertiseLevelDeveloper,
ReleaseLevel: config.ReleaseLevelStable,
DefaultValue: getDefaultNameserverAddress(),
ValidationRegex: "^([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}:[0-9]{1,5}|\\[[:0-9A-Fa-f]+\\]:[0-9]{1,5})$",
ValidationRegex: "^(localhost|[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}|\\[[:0-9A-Fa-f]+\\]):[0-9]{1,5}$",
RequiresRestart: true,
Annotations: config.Annotations{
config.DisplayOrderAnnotation: 514,

169
nameserver/module.go Normal file
View File

@@ -0,0 +1,169 @@
package nameserver
import (
"context"
"fmt"
"net"
"strconv"
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
"github.com/safing/portbase/modules/subsystems"
"github.com/safing/portmaster/firewall"
"github.com/safing/portmaster/netenv"
"github.com/miekg/dns"
)
var (
module *modules.Module
stopListener func() error
)
func init() {
module = modules.Register("nameserver", prep, start, stop, "core", "resolver")
subsystems.Register(
"dns",
"Secure DNS",
"DNS resolver with scoping and DNS-over-TLS",
module,
"config:dns/",
nil,
)
}
func prep() error {
return registerConfig()
}
func start() error {
logFlagOverrides()
ip1, ip2, port, err := getListenAddresses(nameserverAddressConfig())
if err != nil {
return fmt.Errorf("failed to parse nameserver listen address: %w", err)
}
// Start listener(s).
if ip2 == nil {
// Start a single listener.
dnsServer := startListener(ip1, port)
stopListener = dnsServer.Shutdown
// Set nameserver matcher in firewall to fast-track dns queries.
if ip1.Equal(net.IPv4zero) || ip1.Equal(net.IPv6zero) {
// Fast track dns queries destined for any of the local IPs.
return firewall.SetNameserverIPMatcher(func(ip net.IP) bool {
dstIsMe, err := netenv.IsMyIP(ip)
if err != nil {
log.Warningf("nameserver: failed to check if IP %s is local: %s", ip, err)
}
return dstIsMe
})
} else {
return firewall.SetNameserverIPMatcher(func(ip net.IP) bool {
return ip.Equal(ip1)
})
}
} else {
// Dual listener.
dnsServer1 := startListener(ip1, port)
dnsServer2 := startListener(ip2, port)
stopListener = func() error {
// Shutdown both listeners.
err1 := dnsServer1.Shutdown()
err2 := dnsServer2.Shutdown()
// Return first error.
if err1 != nil {
return err1
}
return err2
}
// Fast track dns queries destined for one of the listener IPs.
return firewall.SetNameserverIPMatcher(func(ip net.IP) bool {
return ip.Equal(ip1) || ip.Equal(ip2)
})
}
}
func startListener(ip net.IP, port uint16) *dns.Server {
// Create DNS server.
dnsServer := &dns.Server{
Addr: net.JoinHostPort(
ip.String(),
strconv.Itoa(int(port)),
),
Net: "udp",
}
dns.HandleFunc(".", handleRequestAsWorker)
// Start DNS server as service worker.
log.Infof("nameserver: starting to listen on %s", dnsServer.Addr)
module.StartServiceWorker("dns resolver", 0, func(ctx context.Context) error {
err := dnsServer.ListenAndServe()
if err != nil {
// check if we are shutting down
if module.IsStopping() {
return nil
}
// is something blocking our port?
checkErr := checkForConflictingService(ip, port)
if checkErr != nil {
return checkErr
}
}
return err
})
return dnsServer
}
func stop() error {
if stopListener != nil {
return stopListener()
}
return nil
}
func getListenAddresses(listenAddress string) (ip1, ip2 net.IP, port uint16, err error) {
// Split host and port.
ipString, portString, err := net.SplitHostPort(listenAddress)
if err != nil {
return nil, nil, 0, fmt.Errorf(
"failed to parse address %s: %w",
listenAddress,
err,
)
}
// Parse the IP address. If the want to listen on localhost, we need to
// listen separately for IPv4 and IPv6.
if ipString == "localhost" {
ip1 = net.IPv4(127, 0, 0, 17)
ip2 = net.IPv6loopback
} else {
ip1 = net.ParseIP(ipString)
if ip1 == nil {
return nil, nil, 0, fmt.Errorf(
"failed to parse IP %s from %s",
ipString,
listenAddress,
)
}
}
// Parse the port.
port64, err := strconv.ParseUint(portString, 10, 16)
if err != nil {
return nil, nil, 0, fmt.Errorf(
"failed to parse port %s from %s: %w",
portString,
listenAddress,
err,
)
}
return ip1, ip2, uint16(port64), nil
}

View File

@@ -6,76 +6,18 @@ import (
"net"
"strings"
"github.com/safing/portmaster/network/packet"
"github.com/safing/portbase/modules/subsystems"
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
"github.com/safing/portmaster/firewall"
"github.com/safing/portmaster/nameserver/nsutil"
"github.com/safing/portmaster/netenv"
"github.com/safing/portmaster/network"
"github.com/safing/portmaster/network/netutils"
"github.com/safing/portmaster/network/packet"
"github.com/safing/portmaster/resolver"
"github.com/miekg/dns"
)
var (
module *modules.Module
dnsServer *dns.Server
defaultNameserverAddress = "0.0.0.0:53"
)
func init() {
module = modules.Register("nameserver", prep, start, stop, "core", "resolver")
subsystems.Register(
"dns",
"Secure DNS",
"DNS resolver with scoping and DNS-over-TLS",
module,
"config:dns/",
nil,
)
}
func prep() error {
return registerConfig()
}
func start() error {
logFlagOverrides()
dnsServer = &dns.Server{Addr: nameserverAddressConfig(), Net: "udp"}
dns.HandleFunc(".", handleRequestAsWorker)
module.StartServiceWorker("dns resolver", 0, func(ctx context.Context) error {
err := dnsServer.ListenAndServe()
if err != nil {
// check if we are shutting down
if module.IsStopping() {
return nil
}
// is something blocking our port?
checkErr := checkForConflictingService()
if checkErr != nil {
return checkErr
}
}
return err
})
return nil
}
func stop() error {
if dnsServer != nil {
return dnsServer.Shutdown()
}
return nil
}
func handleRequestAsWorker(w dns.ResponseWriter, query *dns.Msg) {
err := module.RunWorker("dns request", func(ctx context.Context) error {
return handleRequest(ctx, w, query)

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"net"
"os"
"time"
"strconv"
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
@@ -14,39 +14,46 @@ import (
)
var (
otherResolverIPs = []net.IP{
commonResolverIPs = []net.IP{
net.IPv4zero,
net.IPv4(127, 0, 0, 1), // default
net.IPv4(127, 0, 0, 53), // some resolvers on Linux
net.IPv6zero,
net.IPv6loopback,
}
)
func checkForConflictingService() error {
var pid int
var err error
func checkForConflictingService(ip net.IP, port uint16) error {
// Evaluate which IPs to check.
var ipsToCheck []net.IP
if ip.Equal(net.IPv4zero) || ip.Equal(net.IPv6zero) {
ipsToCheck = commonResolverIPs
} else {
ipsToCheck = []net.IP{ip}
}
// check multiple IPs for other resolvers
for _, resolverIP := range otherResolverIPs {
pid, err = takeover(resolverIP)
if err == nil && pid != 0 {
// Check if there is another resolver when need to take over.
var killed int
for _, resolverIP := range ipsToCheck {
pid, err := takeover(resolverIP, port)
switch {
case err != nil:
// Log the error and let the worker try again.
log.Infof("nameserver: could not stop conflicting service: %s", err)
return nil
case pid != 0:
// Conflicting service identified and killed!
killed = pid
break
}
}
// handle returns
if err != nil {
log.Infof("nameserver: could not stop conflicting service: %s", err)
// leave original service-worker error intact
return nil
}
if pid == 0 {
// no conflicting service identified
// Check if something was killed.
if killed == 0 {
return nil
}
// we killed something!
// wait for a short duration for the other service to shut down
time.Sleep(10 * time.Millisecond)
// Notify the user that we killed something.
notifications.Notify(&notifications.Notification{
EventID: "namserver:stopped-conflicting-service",
Type: notifications.Info,
@@ -54,15 +61,15 @@ func checkForConflictingService() error {
Category: "Secure DNS",
Message: fmt.Sprintf(
"The Portmaster stopped a conflicting name service (pid %d) to gain required system integration.",
pid,
killed,
),
})
// restart via service-worker logic
return fmt.Errorf("%w: stopped conflicting name service with pid %d", modules.ErrRestartNow, pid)
// Restart nameserver via service-worker logic.
return fmt.Errorf("%w: stopped conflicting name service with pid %d", modules.ErrRestartNow, killed)
}
func takeover(resolverIP net.IP) (int, error) {
func takeover(resolverIP net.IP, resolverPort uint16) (int, error) {
pid, _, err := state.Lookup(&packet.Info{
Inbound: true,
Version: 0, // auto-detect
@@ -70,13 +77,18 @@ func takeover(resolverIP net.IP) (int, error) {
Src: nil, // do not record direction
SrcPort: 0, // do not record direction
Dst: resolverIP,
DstPort: 53,
DstPort: resolverPort,
})
if err != nil {
// there may be nothing listening on :53
return 0, nil
}
// Just don't, uh, kill ourselves...
if pid == os.Getpid() {
return 0, nil
}
proc, err := os.FindProcess(pid)
if err != nil {
// huh. gone already? I guess we'll wait then...
@@ -92,5 +104,14 @@ func takeover(resolverIP net.IP) (int, error) {
}
}
log.Warningf(
"nameserver: killed conflicting service with PID %d over %s",
pid,
net.JoinHostPort(
resolverIP.String(),
strconv.Itoa(int(resolverPort)),
),
)
return pid, nil
}