diff --git a/nameserver/only/nameserver.go b/nameserver/only/nameserver.go index a82a8016..2369086f 100644 --- a/nameserver/only/nameserver.go +++ b/nameserver/only/nameserver.go @@ -1,6 +1,7 @@ package only import ( + "context" "net" "time" @@ -12,10 +13,33 @@ import ( "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 ) func init() { - modules.Register("nameserver", nil, start, nil, "intel") + modules.Register("nameserver", prep, start, nil, "intel") +} + +func prep() error { + intel.SetLocalAddrFactory(func(network string) net.Addr { return nil }) + + localhostIPv4, err := dns.NewRR("localhost. 17 IN A 127.0.0.1") + if err != nil { + return err + } + + localhostIPv6, err := dns.NewRR("localhost. 17 IN AAAA ::1") + if err != nil { + return err + } + + localhostIPs = []dns.RR{localhostIPv4, localhostIPv6} + + return nil } func start() error { @@ -44,62 +68,110 @@ func nxDomain(w dns.ResponseWriter, query *dns.Msg) { func handleRequest(w dns.ResponseWriter, query *dns.Msg) { - // TODO: if there are 3 request for the same domain/type in a row, delete all caches of that domain - // TODO: handle securityLevelOff - // only process first question, that's how everyone does it. question := query.Question[0] fqdn := dns.Fqdn(question.Name) qtype := dns.Type(question.Qtype) - // debug log - rAddr, ok := w.RemoteAddr().(*net.UDPAddr) - if !ok { - log.Warningf("nameserver: could not get address of request, returning nxdomain") + // check class + if question.Qclass != dns.ClassINET { + // we only serve IN records, return nxdomain nxDomain(w, query) return } - // log.Tracef("nameserver: got request for %s%s from %s:%d", fqdn, qtype, rAddr.IP, uint16(rAddr.Port)) - // use this to time how long it takes process this request - // timed := time.Now() - // defer log.Tracef("nameserver: took %s to handle request for %s%s", time.Now().Sub(timed).String(), fqdn, qtype.String()) + // handle request for localhost + if fqdn == "localhost." { + m := new(dns.Msg) + m.SetReply(query) + m.Answer = localhostIPs + w.WriteMsg(m) + } + + // 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 + } + + // ignore external request + if !remoteAddr.IP.Equal(localAddr.IP) { + log.Warningf("nameserver: external request for %s%s, ignoring", fqdn, qtype) + return + } // check if valid domain name if !netutils.IsValidFqdn(fqdn) { - log.Tracef("nameserver: domain name %s is invalid, returning nxdomain", fqdn) + log.Debugf("nameserver: domain name %s is invalid, returning nxdomain", fqdn) nxDomain(w, query) return } + // 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) + + // 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) + // log.Tracef("nameserver: domain %s has lms score of %f", fqdn, lms) if lms < 10 { - log.Tracef("nameserver: possible data tunnel: %s has lms score of %f, returning nxdomain", fqdn, lms) - nxDomain(w, query) - return - } - - // check class - if question.Qclass != dns.ClassINET { - // we only serve IN records, send NXDOMAIN + 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(fqdn, qtype, 0) + _, rrCache := intel.GetIntelAndRRs(ctx, fqdn, qtype, status.SecurityLevelDynamic) // log.Tracef("nameserver: took %s to get intel and RRs", time.Since(start)) if rrCache == nil { // TODO: analyze nxdomain requests, malware could be trying DGA-domains - log.Infof("nameserver: %s%s is nxdomain", fqdn, qtype) + log.WarningTracef(ctx, "nameserver: %s:%d requested %s%s, is nxdomain", remoteAddr.IP, remoteAddr.Port, fqdn, qtype) nxDomain(w, query) return } + // save IP addresses to IPInfo + for _, rr := range append(rrCache.Answer, rrCache.Extra...) { + switch v := rr.(type) { + case *dns.A: + ipInfo, err := intel.GetIPInfo(v.A.String()) + if err != nil { + ipInfo = &intel.IPInfo{ + IP: v.A.String(), + Domains: []string{fqdn}, + } + ipInfo.Save() + } else { + if ipInfo.AddDomain(fqdn) { + ipInfo.Save() + } + } + case *dns.AAAA: + ipInfo, err := intel.GetIPInfo(v.AAAA.String()) + if err != nil { + ipInfo = &intel.IPInfo{ + IP: v.AAAA.String(), + Domains: []string{fqdn}, + } + ipInfo.Save() + } else { + if ipInfo.AddDomain(fqdn) { + ipInfo.Save() + } + } + } + } + // reply to query m := new(dns.Msg) m.SetReply(query) @@ -107,5 +179,5 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) { m.Ns = rrCache.Ns m.Extra = rrCache.Extra w.WriteMsg(m) - log.Tracef("nameserver: replied to request for %s%s from %s:%d", fqdn, qtype, rAddr.IP, uint16(rAddr.Port)) + log.DebugTracef(ctx, "nameserver: returning response %s%s to %s:%d", fqdn, qtype, remoteAddr.IP, remoteAddr.Port) }