Merge pull request #33 from safing/fix/nameserver-unknown-process

Add VerdictFailed and check for unknown process in nameserver
This commit is contained in:
Daniel 2020-04-14 21:33:15 +02:00 committed by GitHub
commit 3d4c7311ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 79 additions and 15 deletions

View file

@ -31,6 +31,7 @@ var (
packetsAccepted *uint64
packetsBlocked *uint64
packetsDropped *uint64
packetsFailed *uint64
// localNet4 *net.IPNet
@ -92,12 +93,10 @@ func prep() (err error) {
// return fmt.Errorf("filter: failed to parse cidr fd17::/64: %s", err)
// }
var pA uint64
packetsAccepted = &pA
var pB uint64
packetsBlocked = &pB
var pD uint64
packetsDropped = &pD
packetsAccepted = new(uint64)
packetsBlocked = new(uint64)
packetsDropped = new(uint64)
packetsFailed = new(uint64)
return nil
}
@ -321,6 +320,9 @@ func issueVerdict(conn *network.Connection, pkt packet.Packet, verdict network.V
err = pkt.RerouteToNameserver()
case network.VerdictRerouteToTunnel:
err = pkt.RerouteToTunnel()
case network.VerdictFailed:
atomic.AddUint64(packetsFailed, 1)
fallthrough
default:
atomic.AddUint64(packetsDropped, 1)
err = pkt.Drop()
@ -361,10 +363,17 @@ func statLogger() {
case <-module.Stopping():
return
case <-time.After(10 * time.Second):
log.Tracef("filter: packets accepted %d, blocked %d, dropped %d", atomic.LoadUint64(packetsAccepted), atomic.LoadUint64(packetsBlocked), atomic.LoadUint64(packetsDropped))
log.Tracef(
"filter: packets accepted %d, blocked %d, dropped %d, failed %d",
atomic.LoadUint64(packetsAccepted),
atomic.LoadUint64(packetsBlocked),
atomic.LoadUint64(packetsDropped),
atomic.LoadUint64(packetsFailed),
)
atomic.StoreUint64(packetsAccepted, 0)
atomic.StoreUint64(packetsBlocked, 0)
atomic.StoreUint64(packetsDropped, 0)
atomic.StoreUint64(packetsFailed, 0)
}
}
}

View file

@ -34,11 +34,16 @@ import (
// is called with the first packet of a network connection.
// DecideOnConnection makes a decision about a connection.
// When called, the connection and profile is already locked.
func DecideOnConnection(conn *network.Connection, pkt packet.Packet) { //nolint:gocognit,gocyclo // TODO
// update profiles and check if communication needs reevaluation
if conn.UpdateAndCheck() {
log.Infof("filter: re-evaluating verdict on %s", conn)
conn.Verdict = network.VerdictUndecided
if conn.Entity != nil {
//conn.Entity.ResetLists()
}
}
// grant self
@ -158,7 +163,7 @@ func DecideOnConnection(conn *network.Connection, pkt packet.Packet) { //nolint:
result, reason = p.MatchFilterLists(conn.Entity)
switch result {
case endpoints.Denied:
conn.Deny("endpoint in filterlist: " + reason)
conn.Deny("endpoint in filterlists: " + reason)
return
case endpoints.NoMatch:
// nothing to do

View file

@ -125,6 +125,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
// check class
if question.Qclass != dns.ClassINET {
// we only serve IN records, return nxdomain
log.Warningf("nameserver: only IN record requests are supported but received Qclass %d, returning NXDOMAIN", question.Qclass)
returnNXDomain(w, query)
return nil
}
@ -134,7 +135,9 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
m := new(dns.Msg)
m.SetReply(query)
m.Answer = localhostRRs
_ = w.WriteMsg(m)
if err := w.WriteMsg(m); err != nil {
log.Warningf("nameserver: failed to handle request to %s: %s", q.FQDN, err)
}
return nil
}
@ -176,6 +179,35 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
// get connection
conn := network.NewConnectionFromDNSRequest(ctx, q.FQDN, remoteAddr.IP, uint16(remoteAddr.Port))
// once we decided on the connection we might need to save it to the database
// so we defer that check right now.
defer func() {
switch conn.Verdict {
// we immediately save blocked, dropped or failed verdicts so
// the pop up in the UI.
case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed:
conn.Save()
// for undecided or accepted connections we don't save them yet because
// that will happen later anyway.
case network.VerdictUndecided, network.VerdictAccept:
return
// FIXME(ppacher): how to handle undeterminable and the SPN re-routing here?
default:
log.Warningf("nameserver: unexpected verdict %s for connection %s, not saving", conn.Verdict, conn)
}
}()
if conn.Process().Profile() == nil {
tracer.Infof("nameserver: failed to find process for request %s, returning NXDOMAIN", conn)
returnNXDomain(w, query)
// FIXME(ppacher): if we save the connection (by marking it as failed)
// we might collect A LOT of connections for the UI.
//conn.Failed("Unknown process")
return nil
}
// save security level to query
q.SecurityLevel = conn.Process().Profile().SecurityLevel()
@ -186,20 +218,20 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
if lms < 10 {
tracer.Warningf("nameserver: possible data tunnel by %s: %s has lms score of %f, returning nxdomain", conn.Process(), q.FQDN, lms)
returnNXDomain(w, query)
conn.Block("Possible data tunnel")
return nil
}
// check profile before we even get intel and rr
firewall.DecideOnConnection(conn, nil)
switch conn.Verdict {
case network.VerdictBlock:
tracer.Infof("nameserver: %s blocked, returning nxdomain", conn)
returnNXDomain(w, query)
conn.Save() // save blocked request
return nil
case network.VerdictDrop:
case network.VerdictDrop, network.VerdictFailed:
tracer.Infof("nameserver: %s dropped, not replying", conn)
conn.Save() // save dropped request
return nil
}
@ -209,6 +241,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
// TODO: analyze nxdomain requests, malware could be trying DGA-domains
tracer.Warningf("nameserver: %s requested %s%s: %s", conn.Process(), q.FQDN, q.QType, err)
returnNXDomain(w, query)
conn.Failed("failed to resolve: " + err.Error())
return nil
}
@ -218,7 +251,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
if rrCache == nil {
tracer.Infof("nameserver: %s implicitly denied by filtering the dns response, returning nxdomain", conn)
returnNXDomain(w, query)
conn.Save() // save blocked request
conn.Block("DNS response filtered")
return nil
}
@ -262,8 +295,12 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
m.Answer = rrCache.Answer
m.Ns = rrCache.Ns
m.Extra = rrCache.Extra
_ = w.WriteMsg(m)
tracer.Debugf("nameserver: returning response %s%s to %s", q.FQDN, q.QType, conn.Process())
if err := w.WriteMsg(m); err != nil {
log.Warningf("nameserver: failed to return reponse %s%s to %s: %s", q.FQDN, q.QType, conn.Process(), err)
} else {
tracer.Debugf("nameserver: returning response %s%s to %s", q.FQDN, q.QType, conn.Process())
}
// save dns request as open
network.SaveOpenDNSRequest(conn)

View file

@ -198,6 +198,16 @@ func (conn *Connection) Deny(reason string) {
}
}
// Failed marks the connection with VerdictFailed and stores the reason.
func (conn *Connection) Failed(reason string) {
if conn.SetVerdict(VerdictFailed) {
conn.Reason = reason
log.Infof("filter: dropping connection %s because of an internal error: %s", conn, reason)
} else {
log.Warningf("filter: tried to drop %s due to error but current verdict is %s", conn, conn.Verdict)
}
}
// SetVerdict sets a new verdict for the connection, making sure it does not interfere with previous verdicts.
func (conn *Connection) SetVerdict(newVerdict Verdict) (ok bool) {
if newVerdict >= conn.Verdict {

View file

@ -13,6 +13,7 @@ const (
VerdictDrop Verdict = 4
VerdictRerouteToNameserver Verdict = 5
VerdictRerouteToTunnel Verdict = 6
VerdictFailed Verdict = 7
)
func (v Verdict) String() string {
@ -31,6 +32,8 @@ func (v Verdict) String() string {
return "RerouteToNameserver"
case VerdictRerouteToTunnel:
return "RerouteToTunnel"
case VerdictFailed:
return "Failed"
default:
return "<INVALID VERDICT>"
}