Revamp intel and nameserver packages

This commit is contained in:
Daniel
2019-10-25 13:35:02 +02:00
parent 5799d2559b
commit 25b1d59663
18 changed files with 1675 additions and 1060 deletions

View File

@@ -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
}