mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
227 lines
5.7 KiB
Go
227 lines
5.7 KiB
Go
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
|
|
|
package nameserver
|
|
|
|
import (
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
|
|
"github.com/Safing/portbase/log"
|
|
"github.com/Safing/portbase/modules"
|
|
|
|
"github.com/Safing/portmaster/analytics/algs"
|
|
"github.com/Safing/portmaster/firewall"
|
|
"github.com/Safing/portmaster/intel"
|
|
"github.com/Safing/portmaster/network"
|
|
"github.com/Safing/portmaster/network/netutils"
|
|
)
|
|
|
|
var (
|
|
localhostIPs []dns.RR
|
|
)
|
|
|
|
func init() {
|
|
modules.Register("nameserver", prep, start, nil, "intel")
|
|
}
|
|
|
|
func prep() error {
|
|
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 {
|
|
server := &dns.Server{Addr: "0.0.0.0:53", Net: "udp"}
|
|
dns.HandleFunc(".", handleRequest)
|
|
go run(server)
|
|
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 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) {
|
|
|
|
// only process first question, that's how everyone does it.
|
|
question := query.Question[0]
|
|
fqdn := dns.Fqdn(question.Name)
|
|
qtype := dns.Type(question.Qtype)
|
|
|
|
// 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
|
|
}
|
|
|
|
log.Tracef("nameserver: handling 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 class
|
|
if question.Qclass != dns.ClassINET {
|
|
// we only serve IN records, return nxdomain
|
|
nxDomain(w, query)
|
|
return
|
|
}
|
|
|
|
// handle request for localhost
|
|
if fqdn == "localhost." {
|
|
m := new(dns.Msg)
|
|
m.SetReply(query)
|
|
m.Answer = localhostIPs
|
|
w.WriteMsg(m)
|
|
}
|
|
|
|
// check if valid domain name
|
|
if !netutils.IsValidFqdn(fqdn) {
|
|
log.Tracef("nameserver: domain name %s is invalid, returning nxdomain", fqdn)
|
|
nxDomain(w, query)
|
|
return
|
|
}
|
|
|
|
// 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.Tracef("nameserver: possible data tunnel: %s has lms score of %f, returning nxdomain", fqdn, lms)
|
|
nxDomain(w, query)
|
|
return
|
|
}
|
|
|
|
// [1/2] use this to time how long it takes to get process info
|
|
// timed := time.Now()
|
|
|
|
// get connection
|
|
// start = time.Now()
|
|
comm, err := network.GetCommunicationByDNSRequest(remoteAddr.IP, uint16(remoteAddr.Port), fqdn)
|
|
// log.Tracef("nameserver: took %s to get comms (and maybe process)", time.Since(start))
|
|
if err != nil {
|
|
log.Warningf("nameserver: someone is requesting %s, but could not identify process: %s, returning nxdomain", fqdn, err)
|
|
nxDomain(w, query)
|
|
return
|
|
}
|
|
|
|
// [2/2] use this to time how long it takes to get process info
|
|
// log.Tracef("nameserver: took %s to get connection/process of %s request", time.Now().Sub(timed).String(), fqdn)
|
|
|
|
// check profile before we even get intel and rr
|
|
// start = time.Now()
|
|
firewall.DecideOnCommunicationBeforeIntel(comm, fqdn)
|
|
// log.Tracef("nameserver: took %s to make decision", time.Since(start))
|
|
|
|
if comm.GetVerdict() == network.VerdictBlock || comm.GetVerdict() == network.VerdictDrop {
|
|
nxDomain(w, query)
|
|
return
|
|
}
|
|
|
|
// get intel and RRs
|
|
// start = time.Now()
|
|
domainIntel, rrCache := intel.GetIntelAndRRs(fqdn, qtype, comm.Process().ProfileSet().SecurityLevel())
|
|
// 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 tried to query %s, but is nxdomain", comm.Process().String(), fqdn)
|
|
nxDomain(w, query)
|
|
return
|
|
}
|
|
|
|
// set intel
|
|
comm.Lock()
|
|
comm.Intel = domainIntel
|
|
comm.Unlock()
|
|
comm.Save()
|
|
|
|
// check with intel
|
|
firewall.DecideOnCommunicationAfterIntel(comm, fqdn, rrCache)
|
|
switch comm.GetVerdict() {
|
|
case network.VerdictUndecided, network.VerdictBlock, network.VerdictDrop:
|
|
nxDomain(w, query)
|
|
return
|
|
}
|
|
|
|
// filter DNS response
|
|
rrCache = firewall.FilterDNSResponse(comm, fqdn, rrCache)
|
|
if rrCache == nil {
|
|
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)
|
|
m.Answer = rrCache.Answer
|
|
m.Ns = rrCache.Ns
|
|
m.Extra = rrCache.Extra
|
|
w.WriteMsg(m)
|
|
}
|