Revamp intel and nameserver packages
This commit is contained in:
@@ -3,7 +3,9 @@ package nameserver
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/safing/portmaster/network/environment"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
@@ -18,23 +20,20 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
localhostIPs []dns.RR
|
||||
)
|
||||
module *modules.Module
|
||||
dnsServer *dns.Server
|
||||
mtDNSRequest = "dns request"
|
||||
|
||||
var (
|
||||
listenAddress = "127.0.0.1:53"
|
||||
localhostIP = net.IPv4(127, 0, 0, 1)
|
||||
listenAddress = "0.0.0.0:53"
|
||||
IPv4Localhost = net.IPv4(127, 0, 0, 1)
|
||||
localhostRRs []dns.RR
|
||||
)
|
||||
|
||||
func init() {
|
||||
modules.Register("nameserver", prep, start, nil, "core", "intel")
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
listenAddress = "0.0.0.0:53"
|
||||
}
|
||||
module = modules.Register("nameserver", initLocalhostRRs, start, stop, "core", "intel", "network")
|
||||
}
|
||||
|
||||
func prep() error {
|
||||
func initLocalhostRRs() error {
|
||||
localhostIPv4, err := dns.NewRR("localhost. 17 IN A 127.0.0.1")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -45,153 +44,202 @@ func prep() error {
|
||||
return err
|
||||
}
|
||||
|
||||
localhostIPs = []dns.RR{localhostIPv4, localhostIPv6}
|
||||
|
||||
localhostRRs = []dns.RR{localhostIPv4, localhostIPv6}
|
||||
return nil
|
||||
}
|
||||
|
||||
func start() error {
|
||||
server := &dns.Server{Addr: listenAddress, Net: "udp"}
|
||||
dns.HandleFunc(".", handleRequest)
|
||||
go run(server)
|
||||
dnsServer = &dns.Server{Addr: listenAddress, Net: "udp"}
|
||||
dns.HandleFunc(".", handleRequestAsMicroTask)
|
||||
|
||||
module.StartServiceWorker("dns resolver", 0, func(ctx context.Context) error {
|
||||
err := dnsServer.ListenAndServe()
|
||||
if err != nil {
|
||||
// check if we are shutting down
|
||||
if module.ShutdownInProgress() {
|
||||
return nil
|
||||
}
|
||||
// is something blocking our port?
|
||||
checkErr := checkForConflictingService()
|
||||
if checkErr != nil {
|
||||
return checkErr
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func run(server *dns.Server) {
|
||||
for {
|
||||
err := server.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Errorf("nameserver: server failed: %s", err)
|
||||
checkForConflictingService(err)
|
||||
}
|
||||
func stop() error {
|
||||
if dnsServer != nil {
|
||||
return dnsServer.Shutdown()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func returnNXDomain(w dns.ResponseWriter, query *dns.Msg) {
|
||||
m := new(dns.Msg)
|
||||
m.SetRcode(query, dns.RcodeNameError)
|
||||
_ = w.WriteMsg(m)
|
||||
}
|
||||
|
||||
func returnServerFailure(w dns.ResponseWriter, query *dns.Msg) {
|
||||
m := new(dns.Msg)
|
||||
m.SetRcode(query, dns.RcodeServerFailure)
|
||||
_ = w.WriteMsg(m)
|
||||
}
|
||||
|
||||
func handleRequestAsMicroTask(w dns.ResponseWriter, query *dns.Msg) {
|
||||
err := module.RunMicroTask(&mtDNSRequest, func(ctx context.Context) error {
|
||||
return handleRequest(ctx, w, query)
|
||||
})
|
||||
if err != nil {
|
||||
log.Warningf("intel: failed to handle dns request: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func nxDomain(w dns.ResponseWriter, query *dns.Msg) {
|
||||
m := new(dns.Msg)
|
||||
m.SetRcode(query, dns.RcodeNameError)
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
|
||||
func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) error {
|
||||
// return with server failure if offline
|
||||
if environment.GetOnlineStatus() == environment.StatusOffline {
|
||||
returnServerFailure(w, query)
|
||||
return nil
|
||||
}
|
||||
|
||||
// only process first question, that's how everyone does it.
|
||||
question := query.Question[0]
|
||||
fqdn := dns.Fqdn(question.Name)
|
||||
qtype := dns.Type(question.Qtype)
|
||||
q := &intel.Query{
|
||||
FQDN: question.Name,
|
||||
QType: dns.Type(question.Qtype),
|
||||
}
|
||||
|
||||
// check class
|
||||
if question.Qclass != dns.ClassINET {
|
||||
// we only serve IN records, return nxdomain
|
||||
nxDomain(w, query)
|
||||
return
|
||||
returnNXDomain(w, query)
|
||||
return nil
|
||||
}
|
||||
|
||||
// handle request for localhost
|
||||
if fqdn == "localhost." {
|
||||
if strings.HasSuffix(q.FQDN, "localhost.") {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(query)
|
||||
m.Answer = localhostIPs
|
||||
w.WriteMsg(m)
|
||||
m.Answer = localhostRRs
|
||||
_ = w.WriteMsg(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
// get addresses
|
||||
remoteAddr, ok := w.RemoteAddr().(*net.UDPAddr)
|
||||
if !ok {
|
||||
log.Warningf("nameserver: could not get remote address of request for %s%s, ignoring", fqdn, qtype)
|
||||
return
|
||||
log.Warningf("nameserver: could not get remote address of request for %s%s, ignoring", q.FQDN, q.QType)
|
||||
return nil
|
||||
}
|
||||
if !remoteAddr.IP.Equal(localhostIP) {
|
||||
if !remoteAddr.IP.Equal(IPv4Localhost) {
|
||||
// if request is not coming from 127.0.0.1, check if it's really local
|
||||
|
||||
localAddr, ok := w.RemoteAddr().(*net.UDPAddr)
|
||||
if !ok {
|
||||
log.Warningf("nameserver: could not get local address of request for %s%s, ignoring", fqdn, qtype)
|
||||
return
|
||||
log.Warningf("nameserver: could not get local address of request for %s%s, ignoring", q.FQDN, q.QType)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ignore external request
|
||||
if !remoteAddr.IP.Equal(localAddr.IP) {
|
||||
log.Warningf("nameserver: external request for %s%s, ignoring", fqdn, qtype)
|
||||
return
|
||||
log.Warningf("nameserver: external request for %s%s, ignoring", q.FQDN, q.QType)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// check if valid domain name
|
||||
if !netutils.IsValidFqdn(fqdn) {
|
||||
log.Debugf("nameserver: domain name %s is invalid, returning nxdomain", fqdn)
|
||||
nxDomain(w, query)
|
||||
return
|
||||
if !netutils.IsValidFqdn(q.FQDN) {
|
||||
log.Debugf("nameserver: domain name %s is invalid, returning nxdomain", q.FQDN)
|
||||
returnNXDomain(w, query)
|
||||
return nil
|
||||
}
|
||||
|
||||
// start tracer
|
||||
ctx := log.AddTracer(context.Background())
|
||||
log.Tracer(ctx).Tracef("nameserver: handling new request for %s%s from %s:%d", fqdn, qtype, remoteAddr.IP, remoteAddr.Port)
|
||||
ctx, tracer := log.AddTracer(ctx)
|
||||
tracer.Tracef("nameserver: handling new request for %s%s from %s:%d", q.FQDN, q.QType, remoteAddr.IP, remoteAddr.Port)
|
||||
|
||||
// TODO: if there are 3 request for the same domain/type in a row, delete all caches of that domain
|
||||
|
||||
// get connection
|
||||
comm, err := network.GetCommunicationByDNSRequest(ctx, remoteAddr.IP, uint16(remoteAddr.Port), fqdn)
|
||||
comm, err := network.GetCommunicationByDNSRequest(ctx, remoteAddr.IP, uint16(remoteAddr.Port), q.FQDN)
|
||||
if err != nil {
|
||||
log.ErrorTracef(ctx, "nameserver: could not identify process of %s:%d, returning nxdomain: %s", remoteAddr.IP, remoteAddr.Port, err)
|
||||
nxDomain(w, query)
|
||||
return
|
||||
tracer.Errorf("nameserver: could not identify process of %s:%d, returning nxdomain: %s", remoteAddr.IP, remoteAddr.Port, err)
|
||||
returnNXDomain(w, query)
|
||||
return nil
|
||||
}
|
||||
defer func() {
|
||||
go comm.SaveIfNeeded()
|
||||
}()
|
||||
|
||||
// save security level to query
|
||||
q.SecurityLevel = comm.Process().ProfileSet().SecurityLevel()
|
||||
|
||||
// check for possible DNS tunneling / data transmission
|
||||
// TODO: improve this
|
||||
lms := algs.LmsScoreOfDomain(fqdn)
|
||||
lms := algs.LmsScoreOfDomain(q.FQDN)
|
||||
// log.Tracef("nameserver: domain %s has lms score of %f", fqdn, lms)
|
||||
if lms < 10 {
|
||||
log.WarningTracef(ctx, "nameserver: possible data tunnel by %s: %s has lms score of %f, returning nxdomain", comm.Process(), fqdn, lms)
|
||||
nxDomain(w, query)
|
||||
return
|
||||
tracer.Warningf("nameserver: possible data tunnel by %s: %s has lms score of %f, returning nxdomain", comm.Process(), q.FQDN, lms)
|
||||
returnNXDomain(w, query)
|
||||
return nil
|
||||
}
|
||||
|
||||
// check profile before we even get intel and rr
|
||||
firewall.DecideOnCommunicationBeforeIntel(comm, fqdn)
|
||||
firewall.DecideOnCommunicationBeforeIntel(comm, q.FQDN)
|
||||
comm.Lock()
|
||||
comm.SaveWhenFinished()
|
||||
comm.Unlock()
|
||||
|
||||
if comm.GetVerdict() == network.VerdictBlock || comm.GetVerdict() == network.VerdictDrop {
|
||||
log.InfoTracef(ctx, "nameserver: %s denied before intel, returning nxdomain", comm)
|
||||
nxDomain(w, query)
|
||||
return
|
||||
tracer.Infof("nameserver: %s denied before intel, returning nxdomain", comm)
|
||||
returnNXDomain(w, query)
|
||||
return nil
|
||||
}
|
||||
|
||||
// get intel and RRs
|
||||
domainIntel, rrCache := intel.GetIntelAndRRs(ctx, fqdn, qtype, comm.Process().ProfileSet().SecurityLevel())
|
||||
if rrCache == nil {
|
||||
rrCache, err := intel.Resolve(ctx, q)
|
||||
if err != nil {
|
||||
// TODO: analyze nxdomain requests, malware could be trying DGA-domains
|
||||
log.WarningTracef(ctx, "nameserver: %s requested %s%s, is nxdomain", comm.Process(), fqdn, qtype)
|
||||
nxDomain(w, query)
|
||||
return
|
||||
tracer.Warningf("nameserver: %s requested %s%s: %s", comm.Process(), q.FQDN, q.QType, err)
|
||||
returnNXDomain(w, query)
|
||||
return nil
|
||||
}
|
||||
|
||||
// set intel
|
||||
// get current intel
|
||||
comm.Lock()
|
||||
comm.Intel = domainIntel
|
||||
domainIntel := comm.Intel
|
||||
comm.Unlock()
|
||||
if domainIntel == nil {
|
||||
// fetch intel
|
||||
domainIntel, err = intel.GetIntel(ctx, q)
|
||||
if err != nil {
|
||||
tracer.Warningf("nameserver: failed to get intel for %s%s: %s", q.FQDN, q.QType, err)
|
||||
returnNXDomain(w, query)
|
||||
}
|
||||
comm.Lock()
|
||||
comm.Intel = domainIntel
|
||||
comm.Unlock()
|
||||
}
|
||||
|
||||
// check with intel
|
||||
firewall.DecideOnCommunicationAfterIntel(comm, fqdn, rrCache)
|
||||
firewall.DecideOnCommunicationAfterIntel(comm, q.FQDN, rrCache)
|
||||
switch comm.GetVerdict() {
|
||||
case network.VerdictUndecided, network.VerdictBlock, network.VerdictDrop:
|
||||
log.InfoTracef(ctx, "nameserver: %s denied after intel, returning nxdomain", comm)
|
||||
nxDomain(w, query)
|
||||
return
|
||||
tracer.Infof("nameserver: %s denied after intel, returning nxdomain", comm)
|
||||
returnNXDomain(w, query)
|
||||
return nil
|
||||
}
|
||||
|
||||
// filter DNS response
|
||||
rrCache = firewall.FilterDNSResponse(comm, fqdn, rrCache)
|
||||
rrCache = firewall.FilterDNSResponse(comm, q, rrCache)
|
||||
if rrCache == nil {
|
||||
log.InfoTracef(ctx, "nameserver: %s implicitly denied by filtering the dns response, returning nxdomain", comm)
|
||||
nxDomain(w, query)
|
||||
return
|
||||
tracer.Infof("nameserver: %s implicitly denied by filtering the dns response, returning nxdomain", comm)
|
||||
returnNXDomain(w, query)
|
||||
return nil
|
||||
}
|
||||
|
||||
// save IP addresses to IPInfo
|
||||
@@ -202,12 +250,13 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
|
||||
if err != nil {
|
||||
ipInfo = &intel.IPInfo{
|
||||
IP: v.A.String(),
|
||||
Domains: []string{fqdn},
|
||||
Domains: []string{q.FQDN},
|
||||
}
|
||||
ipInfo.Save()
|
||||
_ = ipInfo.Save()
|
||||
} else {
|
||||
if ipInfo.AddDomain(fqdn) {
|
||||
ipInfo.Save()
|
||||
added := ipInfo.AddDomain(q.FQDN)
|
||||
if added {
|
||||
_ = ipInfo.Save()
|
||||
}
|
||||
}
|
||||
case *dns.AAAA:
|
||||
@@ -215,12 +264,13 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
|
||||
if err != nil {
|
||||
ipInfo = &intel.IPInfo{
|
||||
IP: v.AAAA.String(),
|
||||
Domains: []string{fqdn},
|
||||
Domains: []string{q.FQDN},
|
||||
}
|
||||
ipInfo.Save()
|
||||
_ = ipInfo.Save()
|
||||
} else {
|
||||
if ipInfo.AddDomain(fqdn) {
|
||||
ipInfo.Save()
|
||||
added := ipInfo.AddDomain(q.FQDN)
|
||||
if added {
|
||||
_ = ipInfo.Save()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,6 +282,8 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
|
||||
m.Answer = rrCache.Answer
|
||||
m.Ns = rrCache.Ns
|
||||
m.Extra = rrCache.Extra
|
||||
w.WriteMsg(m)
|
||||
log.DebugTracef(ctx, "nameserver: returning response %s%s to %s", fqdn, qtype, comm.Process())
|
||||
_ = w.WriteMsg(m)
|
||||
tracer.Debugf("nameserver: returning response %s%s to %s", q.FQDN, q.QType, comm.Process())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,30 +3,34 @@ package only
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
"strings"
|
||||
|
||||
"github.com/safing/portmaster/network/environment"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/modules"
|
||||
|
||||
"github.com/safing/portmaster/analytics/algs"
|
||||
"github.com/safing/portmaster/intel"
|
||||
"github.com/safing/portmaster/network/netutils"
|
||||
"github.com/safing/portmaster/status"
|
||||
)
|
||||
|
||||
var (
|
||||
localhostIPs []dns.RR
|
||||
module *modules.Module
|
||||
dnsServer *dns.Server
|
||||
mtDNSRequest = "dns request"
|
||||
|
||||
listenAddress = "127.0.0.1:53"
|
||||
IPv4Localhost = net.IPv4(127, 0, 0, 1)
|
||||
localhostRRs []dns.RR
|
||||
)
|
||||
|
||||
func init() {
|
||||
modules.Register("nameserver", prep, start, nil, "core", "intel")
|
||||
module = modules.Register("nameserver", initLocalhostRRs, start, stop, "core", "intel", "network")
|
||||
}
|
||||
|
||||
func prep() error {
|
||||
intel.SetLocalAddrFactory(func(network string) net.Addr { return nil })
|
||||
|
||||
func initLocalhostRRs() error {
|
||||
localhostIPv4, err := dns.NewRR("localhost. 17 IN A 127.0.0.1")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -37,107 +41,128 @@ func prep() error {
|
||||
return err
|
||||
}
|
||||
|
||||
localhostIPs = []dns.RR{localhostIPv4, localhostIPv6}
|
||||
|
||||
localhostRRs = []dns.RR{localhostIPv4, localhostIPv6}
|
||||
return nil
|
||||
}
|
||||
|
||||
func start() error {
|
||||
server := &dns.Server{Addr: "0.0.0.0:53", Net: "udp"}
|
||||
dns.HandleFunc(".", handleRequest)
|
||||
go run(server)
|
||||
dnsServer = &dns.Server{Addr: listenAddress, Net: "udp"}
|
||||
dns.HandleFunc(".", handleRequestAsMicroTask)
|
||||
|
||||
module.StartServiceWorker("dns resolver", 0, func(ctx context.Context) error {
|
||||
err := dnsServer.ListenAndServe()
|
||||
if err != nil {
|
||||
// check if we are shutting down
|
||||
if module.ShutdownInProgress() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func run(server *dns.Server) {
|
||||
for {
|
||||
err := server.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Errorf("nameserver: server failed: %s", err)
|
||||
log.Info("nameserver: restarting server in 10 seconds")
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
func stop() error {
|
||||
if dnsServer != nil {
|
||||
return dnsServer.Shutdown()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func returnNXDomain(w dns.ResponseWriter, query *dns.Msg) {
|
||||
m := new(dns.Msg)
|
||||
m.SetRcode(query, dns.RcodeNameError)
|
||||
_ = w.WriteMsg(m)
|
||||
}
|
||||
|
||||
func returnServerFailure(w dns.ResponseWriter, query *dns.Msg) {
|
||||
m := new(dns.Msg)
|
||||
m.SetRcode(query, dns.RcodeServerFailure)
|
||||
_ = w.WriteMsg(m)
|
||||
}
|
||||
|
||||
func handleRequestAsMicroTask(w dns.ResponseWriter, query *dns.Msg) {
|
||||
err := module.RunMicroTask(&mtDNSRequest, func(ctx context.Context) error {
|
||||
return handleRequest(ctx, w, query)
|
||||
})
|
||||
if err != nil {
|
||||
log.Warningf("intel: failed to handle dns request: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func nxDomain(w dns.ResponseWriter, query *dns.Msg) {
|
||||
m := new(dns.Msg)
|
||||
m.SetRcode(query, dns.RcodeNameError)
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
|
||||
func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) error {
|
||||
// return with server failure if offline
|
||||
if environment.GetOnlineStatus() == environment.StatusOffline {
|
||||
returnServerFailure(w, query)
|
||||
return nil
|
||||
}
|
||||
|
||||
// only process first question, that's how everyone does it.
|
||||
question := query.Question[0]
|
||||
fqdn := dns.Fqdn(question.Name)
|
||||
qtype := dns.Type(question.Qtype)
|
||||
q := &intel.Query{
|
||||
FQDN: question.Name,
|
||||
QType: dns.Type(question.Qtype),
|
||||
}
|
||||
|
||||
// check class
|
||||
if question.Qclass != dns.ClassINET {
|
||||
// we only serve IN records, return nxdomain
|
||||
nxDomain(w, query)
|
||||
return
|
||||
returnNXDomain(w, query)
|
||||
return nil
|
||||
}
|
||||
|
||||
// handle request for localhost
|
||||
if fqdn == "localhost." {
|
||||
if strings.HasSuffix(q.FQDN, "localhost.") {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(query)
|
||||
m.Answer = localhostIPs
|
||||
w.WriteMsg(m)
|
||||
m.Answer = localhostRRs
|
||||
_ = w.WriteMsg(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
// get addresses
|
||||
remoteAddr, ok := w.RemoteAddr().(*net.UDPAddr)
|
||||
if !ok {
|
||||
log.Warningf("nameserver: could not get remote address of request for %s%s, ignoring", fqdn, qtype)
|
||||
return
|
||||
}
|
||||
localAddr, ok := w.RemoteAddr().(*net.UDPAddr)
|
||||
if !ok {
|
||||
log.Warningf("nameserver: could not get local address of request for %s%s, ignoring", fqdn, qtype)
|
||||
return
|
||||
log.Warningf("nameserver: could not get remote address of request for %s%s, ignoring", q.FQDN, q.QType)
|
||||
return nil
|
||||
}
|
||||
if !remoteAddr.IP.Equal(IPv4Localhost) {
|
||||
// if request is not coming from 127.0.0.1, check if it's really local
|
||||
|
||||
// ignore external request
|
||||
if !remoteAddr.IP.Equal(localAddr.IP) {
|
||||
log.Warningf("nameserver: external request for %s%s, ignoring", fqdn, qtype)
|
||||
return
|
||||
localAddr, ok := w.RemoteAddr().(*net.UDPAddr)
|
||||
if !ok {
|
||||
log.Warningf("nameserver: could not get local address of request for %s%s, ignoring", q.FQDN, q.QType)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ignore external request
|
||||
if !remoteAddr.IP.Equal(localAddr.IP) {
|
||||
log.Warningf("nameserver: external request for %s%s, ignoring", q.FQDN, q.QType)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// check if valid domain name
|
||||
if !netutils.IsValidFqdn(fqdn) {
|
||||
log.Debugf("nameserver: domain name %s is invalid, returning nxdomain", fqdn)
|
||||
nxDomain(w, query)
|
||||
return
|
||||
if !netutils.IsValidFqdn(q.FQDN) {
|
||||
log.Debugf("nameserver: domain name %s is invalid, returning nxdomain", q.FQDN)
|
||||
returnNXDomain(w, query)
|
||||
return nil
|
||||
}
|
||||
|
||||
// start tracer
|
||||
ctx := log.AddTracer(context.Background())
|
||||
log.Tracer(ctx).Tracef("nameserver: handling new request for %s%s from %s:%d", fqdn, qtype, remoteAddr.IP, remoteAddr.Port)
|
||||
ctx, tracer := log.AddTracer(ctx)
|
||||
tracer.Tracef("nameserver: handling new request for %s%s from %s:%d", q.FQDN, q.QType, remoteAddr.IP, remoteAddr.Port)
|
||||
|
||||
// TODO: if there are 3 request for the same domain/type in a row, delete all caches of that domain
|
||||
|
||||
// check for possible DNS tunneling / data transmission
|
||||
// TODO: improve this
|
||||
lms := algs.LmsScoreOfDomain(fqdn)
|
||||
// log.Tracef("nameserver: domain %s has lms score of %f", fqdn, lms)
|
||||
if lms < 10 {
|
||||
log.WarningTracef(ctx, "nameserver: possible data tunnel by %s:%d: %s has lms score of %f, returning nxdomain", remoteAddr.IP, remoteAddr.Port, fqdn, lms)
|
||||
nxDomain(w, query)
|
||||
return
|
||||
}
|
||||
|
||||
// get intel and RRs
|
||||
// start = time.Now()
|
||||
_, rrCache := intel.GetIntelAndRRs(ctx, fqdn, qtype, status.SecurityLevelDynamic)
|
||||
// log.Tracef("nameserver: took %s to get intel and RRs", time.Since(start))
|
||||
if rrCache == nil {
|
||||
rrCache, err := intel.Resolve(ctx, q)
|
||||
if err != nil {
|
||||
// TODO: analyze nxdomain requests, malware could be trying DGA-domains
|
||||
log.WarningTracef(ctx, "nameserver: %s:%d requested %s%s, is nxdomain", remoteAddr.IP, remoteAddr.Port, fqdn, qtype)
|
||||
nxDomain(w, query)
|
||||
return
|
||||
tracer.Warningf("nameserver: request for %s%s: %s", q.FQDN, q.QType, err)
|
||||
returnNXDomain(w, query)
|
||||
return nil
|
||||
}
|
||||
|
||||
// save IP addresses to IPInfo
|
||||
@@ -148,12 +173,13 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
|
||||
if err != nil {
|
||||
ipInfo = &intel.IPInfo{
|
||||
IP: v.A.String(),
|
||||
Domains: []string{fqdn},
|
||||
Domains: []string{q.FQDN},
|
||||
}
|
||||
ipInfo.Save()
|
||||
_ = ipInfo.Save()
|
||||
} else {
|
||||
if ipInfo.AddDomain(fqdn) {
|
||||
ipInfo.Save()
|
||||
added := ipInfo.AddDomain(q.FQDN)
|
||||
if added {
|
||||
_ = ipInfo.Save()
|
||||
}
|
||||
}
|
||||
case *dns.AAAA:
|
||||
@@ -161,12 +187,13 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
|
||||
if err != nil {
|
||||
ipInfo = &intel.IPInfo{
|
||||
IP: v.AAAA.String(),
|
||||
Domains: []string{fqdn},
|
||||
Domains: []string{q.FQDN},
|
||||
}
|
||||
ipInfo.Save()
|
||||
_ = ipInfo.Save()
|
||||
} else {
|
||||
if ipInfo.AddDomain(fqdn) {
|
||||
ipInfo.Save()
|
||||
added := ipInfo.AddDomain(q.FQDN)
|
||||
if added {
|
||||
_ = ipInfo.Save()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,6 +205,8 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
|
||||
m.Answer = rrCache.Answer
|
||||
m.Ns = rrCache.Ns
|
||||
m.Extra = rrCache.Extra
|
||||
w.WriteMsg(m)
|
||||
log.DebugTracef(ctx, "nameserver: returning response %s%s to %s:%d", fqdn, qtype, remoteAddr.IP, remoteAddr.Port)
|
||||
_ = w.WriteMsg(m)
|
||||
tracer.Debugf("nameserver: returning response %s%s", q.FQDN, q.QType)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,20 +7,45 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/modules"
|
||||
"github.com/safing/portbase/notifications"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
"github.com/safing/portmaster/process"
|
||||
)
|
||||
|
||||
func checkForConflictingService(err error) {
|
||||
pid, err := takeover()
|
||||
if err != nil || pid == 0 {
|
||||
log.Info("nameserver: restarting server in 10 seconds")
|
||||
time.Sleep(10 * time.Second)
|
||||
return
|
||||
var (
|
||||
otherResolverIPs = []net.IP{
|
||||
net.IPv4(127, 0, 0, 1), // default
|
||||
net.IPv4(127, 0, 0, 53), // some resolvers on Linux
|
||||
}
|
||||
)
|
||||
|
||||
func checkForConflictingService() error {
|
||||
var pid int
|
||||
var err error
|
||||
|
||||
// check multiple IPs for other resolvers
|
||||
for _, resolverIP := range otherResolverIPs {
|
||||
pid, err = takeover(resolverIP)
|
||||
if err == nil && pid != 0 {
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("nameserver: stopped conflicting name service with pid %d", pid)
|
||||
// we killed something!
|
||||
|
||||
// wait for a short duration for the other service to shut down
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// notify user
|
||||
(¬ifications.Notification{
|
||||
@@ -28,15 +53,14 @@ func checkForConflictingService(err error) {
|
||||
Message: fmt.Sprintf("Portmaster stopped a conflicting name service (pid %d) to gain required system integration.", pid),
|
||||
}).Save()
|
||||
|
||||
// wait for a short duration for the other service to shut down
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// restart via service-worker logic
|
||||
return fmt.Errorf("%w: stopped conflicting name service with pid %d", modules.ErrRestartNow, pid)
|
||||
}
|
||||
|
||||
func takeover() (int, error) {
|
||||
pid, _, err := process.GetPidByEndpoints(net.IPv4(127, 0, 0, 1), 53, net.IPv4(127, 0, 0, 1), 65535, packet.UDP)
|
||||
func takeover(resolverIP net.IP) (int, error) {
|
||||
pid, _, err := process.GetPidByEndpoints(resolverIP, 53, resolverIP, 65535, packet.UDP)
|
||||
if err != nil {
|
||||
// there may be nothing listening on :53
|
||||
log.Tracef("nameserver: expected conflicting name service, but could not find anything listenting on :53")
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user