From 78d38a194d060f30f18000dff731d6b3bc6a5d31 Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Tue, 14 Apr 2020 16:54:15 +0200 Subject: [PATCH 1/3] Add check for unknown process --- nameserver/nameserver.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/nameserver/nameserver.go b/nameserver/nameserver.go index f4b10fa7..5732b8fb 100644 --- a/nameserver/nameserver.go +++ b/nameserver/nameserver.go @@ -176,6 +176,18 @@ 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)) + if conn.Process() == nil { + tracer.Infof("nameserver: failed to find process for request %s, returning NXDOMAIN", conn) + returnNXDomain(w, query) + return nil + } + + if conn.Process().Profile() == nil { + tracer.Infof("nameserver: process %s does not have a profile associated, returning NXDOMAIN", conn.Process()) + returnNXDomain(w, query) + return nil + } + // save security level to query q.SecurityLevel = conn.Process().Profile().SecurityLevel() From 4798f97cba4f393c03895b9f610d3ad2241c056a Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Tue, 14 Apr 2020 16:59:22 +0200 Subject: [PATCH 2/3] wip --- nameserver/nameserver.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/nameserver/nameserver.go b/nameserver/nameserver.go index 5732b8fb..44281163 100644 --- a/nameserver/nameserver.go +++ b/nameserver/nameserver.go @@ -176,15 +176,10 @@ 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)) - if conn.Process() == nil { + if conn.Process().Profile() == nil { tracer.Infof("nameserver: failed to find process for request %s, returning NXDOMAIN", conn) returnNXDomain(w, query) - return nil - } - - if conn.Process().Profile() == nil { - tracer.Infof("nameserver: process %s does not have a profile associated, returning NXDOMAIN", conn.Process()) - returnNXDomain(w, query) + conn.Save() // save blocked request return nil } From ef770638f84765547287b825a9ae31763c0de6bf Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Tue, 14 Apr 2020 17:34:54 +0200 Subject: [PATCH 3/3] Add VerdictFailed and update nameserver --- firewall/firewall.go | 23 ++++++++++++++------ firewall/master.go | 7 +++++- nameserver/nameserver.go | 46 +++++++++++++++++++++++++++++++++------- network/connection.go | 10 +++++++++ network/status.go | 3 +++ 5 files changed, 73 insertions(+), 16 deletions(-) diff --git a/firewall/firewall.go b/firewall/firewall.go index 51f06886..530f7437 100644 --- a/firewall/firewall.go +++ b/firewall/firewall.go @@ -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) } } } diff --git a/firewall/master.go b/firewall/master.go index a8899b92..ce1738b5 100644 --- a/firewall/master.go +++ b/firewall/master.go @@ -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 diff --git a/nameserver/nameserver.go b/nameserver/nameserver.go index 44281163..002001b2 100644 --- a/nameserver/nameserver.go +++ b/nameserver/nameserver.go @@ -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,10 +179,32 @@ 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) - conn.Save() // save blocked request + // 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 } @@ -193,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 } @@ -216,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 } @@ -225,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 } @@ -269,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) diff --git a/network/connection.go b/network/connection.go index 5a0626c3..d2ada322 100644 --- a/network/connection.go +++ b/network/connection.go @@ -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 { diff --git a/network/status.go b/network/status.go index 04781283..c0930acc 100644 --- a/network/status.go +++ b/network/status.go @@ -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 "" }