wip: migrate to mono-repo. SPN has already been moved to spn/
This commit is contained in:
290
service/nameserver/module.go
Normal file
290
service/nameserver/module.go
Normal file
@@ -0,0 +1,290 @@
|
||||
package nameserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/modules"
|
||||
"github.com/safing/portbase/modules/subsystems"
|
||||
"github.com/safing/portbase/notifications"
|
||||
"github.com/safing/portmaster/service/compat"
|
||||
"github.com/safing/portmaster/service/firewall"
|
||||
"github.com/safing/portmaster/service/netenv"
|
||||
)
|
||||
|
||||
var (
|
||||
module *modules.Module
|
||||
|
||||
stopListeners bool
|
||||
stopListener1 func() error
|
||||
stopListener2 func() error
|
||||
stopListenersLock sync.Mutex
|
||||
|
||||
eventIDConflictingService = "nameserver:conflicting-service"
|
||||
eventIDListenerFailed = "nameserver:listener-failed"
|
||||
)
|
||||
|
||||
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 {
|
||||
if err := registerMetrics(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get listen addresses.
|
||||
ip1, ip2, port, err := getListenAddresses(nameserverAddressConfig())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse nameserver listen address: %w", err)
|
||||
}
|
||||
|
||||
// Tell the compat module where we are listening.
|
||||
compat.SetNameserverListenIP(ip1)
|
||||
|
||||
// Get own hostname.
|
||||
hostname, err = os.Hostname()
|
||||
if err != nil {
|
||||
log.Warningf("nameserver: failed to get hostname: %s", err)
|
||||
}
|
||||
hostname += "."
|
||||
|
||||
// Start listener(s).
|
||||
if ip2 == nil {
|
||||
// Start a single listener.
|
||||
startListener(ip1, port, true)
|
||||
|
||||
// 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
|
||||
})
|
||||
}
|
||||
return firewall.SetNameserverIPMatcher(func(ip net.IP) bool {
|
||||
return ip.Equal(ip1)
|
||||
})
|
||||
}
|
||||
|
||||
// Dual listener.
|
||||
startListener(ip1, port, true)
|
||||
startListener(ip2, port, false)
|
||||
|
||||
// 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, first bool) {
|
||||
// Start DNS server as service worker.
|
||||
module.StartServiceWorker("dns resolver", 0, func(ctx context.Context) error {
|
||||
// Create DNS server.
|
||||
dnsServer := &dns.Server{
|
||||
Addr: net.JoinHostPort(
|
||||
ip.String(),
|
||||
strconv.Itoa(int(port)),
|
||||
),
|
||||
Net: "udp",
|
||||
Handler: dns.HandlerFunc(handleRequestAsWorker),
|
||||
}
|
||||
|
||||
// Register stop function.
|
||||
func() {
|
||||
stopListenersLock.Lock()
|
||||
defer stopListenersLock.Unlock()
|
||||
|
||||
// Check if we should stop
|
||||
if stopListeners {
|
||||
_ = dnsServer.Shutdown()
|
||||
dnsServer = nil
|
||||
return
|
||||
}
|
||||
|
||||
// Register stop function.
|
||||
if first {
|
||||
stopListener1 = dnsServer.Shutdown
|
||||
} else {
|
||||
stopListener2 = dnsServer.Shutdown
|
||||
}
|
||||
}()
|
||||
|
||||
// Check if we should stop.
|
||||
if dnsServer == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resolve generic listener error, if primary listener.
|
||||
if first {
|
||||
module.Resolve(eventIDListenerFailed)
|
||||
}
|
||||
|
||||
// Start listening.
|
||||
log.Infof("nameserver: starting to listen on %s", dnsServer.Addr)
|
||||
err := dnsServer.ListenAndServe()
|
||||
if err != nil {
|
||||
// Stop worker without error if we are shutting down.
|
||||
if module.IsStopping() {
|
||||
return nil
|
||||
}
|
||||
log.Warningf("nameserver: failed to listen on %s: %s", dnsServer.Addr, err)
|
||||
handleListenError(err, ip, port, first)
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func handleListenError(err error, ip net.IP, port uint16, primaryListener bool) {
|
||||
var n *notifications.Notification
|
||||
|
||||
// Create suffix for secondary listener
|
||||
var secondaryEventIDSuffix string
|
||||
if !primaryListener {
|
||||
secondaryEventIDSuffix = "-secondary"
|
||||
}
|
||||
|
||||
// Find a conflicting service.
|
||||
cfProcess := findConflictingProcess(ip, port)
|
||||
if cfProcess != nil {
|
||||
// Report the conflicting process.
|
||||
|
||||
// Build conflicting process description.
|
||||
var cfDescription string
|
||||
cfName, err := cfProcess.Name()
|
||||
if err == nil && cfName != "" {
|
||||
cfDescription = cfName
|
||||
}
|
||||
cfExe, err := cfProcess.Exe()
|
||||
if err == nil && cfDescription != "" {
|
||||
if cfDescription != "" {
|
||||
cfDescription += " (" + cfExe + ")"
|
||||
} else {
|
||||
cfDescription = cfName
|
||||
}
|
||||
}
|
||||
|
||||
// Notify user about conflicting service.
|
||||
n = notifications.Notify(¬ifications.Notification{
|
||||
EventID: eventIDConflictingService + secondaryEventIDSuffix,
|
||||
Type: notifications.Error,
|
||||
Title: "Conflicting DNS Software",
|
||||
Message: fmt.Sprintf(
|
||||
"Restart Portmaster after you have deactivated or properly configured the conflicting software: %s",
|
||||
cfDescription,
|
||||
),
|
||||
ShowOnSystem: true,
|
||||
AvailableActions: []*notifications.Action{
|
||||
{
|
||||
Text: "Open Docs",
|
||||
Type: notifications.ActionTypeOpenURL,
|
||||
Payload: "https://docs.safing.io/portmaster/install/status/software-compatibility",
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
// If no conflict is found, report the error directly.
|
||||
n = notifications.Notify(¬ifications.Notification{
|
||||
EventID: eventIDListenerFailed + secondaryEventIDSuffix,
|
||||
Type: notifications.Error,
|
||||
Title: "Secure DNS Error",
|
||||
Message: fmt.Sprintf(
|
||||
"The internal DNS server failed. Restart Portmaster to try again. Error: %s",
|
||||
err,
|
||||
),
|
||||
ShowOnSystem: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Attach error to module, if primary listener.
|
||||
if primaryListener {
|
||||
n.AttachToModule(module)
|
||||
}
|
||||
}
|
||||
|
||||
func stop() error {
|
||||
stopListenersLock.Lock()
|
||||
defer stopListenersLock.Unlock()
|
||||
|
||||
// Stop listeners.
|
||||
stopListeners = true
|
||||
if stopListener1 != nil {
|
||||
if err := stopListener1(); err != nil {
|
||||
log.Warningf("nameserver: failed to stop listener1: %s", err)
|
||||
}
|
||||
}
|
||||
if stopListener2 != nil {
|
||||
if err := stopListener2(); err != nil {
|
||||
log.Warningf("nameserver: failed to stop listener2: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if netenv.IPv6Enabled() {
|
||||
ip2 = net.IPv6loopback
|
||||
} else {
|
||||
log.Warningf("nameserver: no IPv6 stack detected, disabling IPv6 nameserver listener")
|
||||
}
|
||||
} 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
|
||||
}
|
||||
Reference in New Issue
Block a user