diff --git a/firewall/dns.go b/firewall/dns.go index 24096ae6..14a5e3bf 100644 --- a/firewall/dns.go +++ b/firewall/dns.go @@ -17,13 +17,14 @@ import ( "github.com/safing/portmaster/resolver" ) -func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) ([]dns.RR, []string, int) { +func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) ([]dns.RR, []string, int, string) { goodEntries := make([]dns.RR, 0, len(entries)) filteredRecords := make([]string, 0, len(entries)) // keeps track of the number of valid and allowed // A and AAAA records. var allowedAddressRecords int + var interveningOptionKey string for _, rr := range entries { // get IP and classification @@ -45,10 +46,12 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) ( case classification == netutils.HostLocal: // No DNS should return localhost addresses filteredRecords = append(filteredRecords, rr.String()) + interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey continue case scope == netutils.Global && (classification == netutils.SiteLocal || classification == netutils.LinkLocal): // No global DNS should return LAN addresses filteredRecords = append(filteredRecords, rr.String()) + interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey continue } } @@ -58,12 +61,15 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) ( switch { case p.BlockScopeInternet() && classification == netutils.Global: filteredRecords = append(filteredRecords, rr.String()) + interveningOptionKey = profile.CfgOptionBlockScopeInternetKey continue case p.BlockScopeLAN() && (classification == netutils.SiteLocal || classification == netutils.LinkLocal): filteredRecords = append(filteredRecords, rr.String()) + interveningOptionKey = profile.CfgOptionBlockScopeLANKey continue case p.BlockScopeLocal() && classification == netutils.HostLocal: filteredRecords = append(filteredRecords, rr.String()) + interveningOptionKey = profile.CfgOptionBlockScopeLocalKey continue } @@ -75,7 +81,7 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) ( goodEntries = append(goodEntries, rr) } - return goodEntries, filteredRecords, allowedAddressRecords + return goodEntries, filteredRecords, allowedAddressRecords, interveningOptionKey } func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache) *resolver.RRCache { @@ -97,18 +103,19 @@ func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache) *res var filteredRecords []string var validIPs int + var interveningOptionKey string - rrCache.Answer, filteredRecords, validIPs = filterDNSSection(rrCache.Answer, p, rrCache.ServerScope) + rrCache.Answer, filteredRecords, validIPs, interveningOptionKey = filterDNSSection(rrCache.Answer, p, rrCache.ServerScope) rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...) // we don't count the valid IPs in the extra section - rrCache.Extra, filteredRecords, _ = filterDNSSection(rrCache.Extra, p, rrCache.ServerScope) + rrCache.Extra, filteredRecords, _, _ = filterDNSSection(rrCache.Extra, p, rrCache.ServerScope) rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...) if len(rrCache.FilteredEntries) > 0 { rrCache.Filtered = true if validIPs == 0 { - conn.Block("no addresses returned for this domain are permitted") + conn.Block("no addresses returned for this domain are permitted", interveningOptionKey) // If all entries are filtered, this could mean that these are broken/bogus resource records. if rrCache.Expired() { @@ -151,12 +158,6 @@ func DecideOnResolvedDNS( rrCache *resolver.RRCache, ) *resolver.RRCache { - // check profile - if checkProfileExists(ctx, conn, nil) { - // returns true if check triggered - return nil - } - // special grant for connectivity domains if checkConnectivityDomain(ctx, conn, nil) { // returns true if check triggered @@ -186,14 +187,14 @@ func mayBlockCNAMEs(ctx context.Context, conn *network.Connection) bool { result, reason := conn.Process().Profile().MatchEndpoint(ctx, conn.Entity) if result == endpoints.Denied { - conn.BlockWithContext(reason.String(), reason.Context()) + conn.BlockWithContext(reason.String(), profile.CfgOptionFilterCNAMEKey, reason.Context()) return true } if result == endpoints.NoMatch { result, reason = conn.Process().Profile().MatchFilterLists(ctx, conn.Entity) if result == endpoints.Denied { - conn.BlockWithContext(reason.String(), reason.Context()) + conn.BlockWithContext(reason.String(), profile.CfgOptionFilterCNAMEKey, reason.Context()) return true } } diff --git a/firewall/inspection/inspection.go b/firewall/inspection/inspection.go index 7dc59494..f42e4a30 100644 --- a/firewall/inspection/inspection.go +++ b/firewall/inspection/inspection.go @@ -85,11 +85,11 @@ func RunInspectors(conn *network.Connection, pkt packet.Packet) (network.Verdict verdict = network.VerdictDrop continueInspection = true case BLOCK_CONN: - conn.SetVerdict(network.VerdictBlock, "", nil) + conn.SetVerdict(network.VerdictBlock, "", "", nil) verdict = conn.Verdict activeInspectors[key] = true case DROP_CONN: - conn.SetVerdict(network.VerdictDrop, "", nil) + conn.SetVerdict(network.VerdictDrop, "", "", nil) verdict = conn.Verdict activeInspectors[key] = true case STOP_INSPECTING: diff --git a/firewall/interception.go b/firewall/interception.go index 04f7f4af..edc04d4b 100644 --- a/firewall/interception.go +++ b/firewall/interception.go @@ -171,7 +171,7 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) { ps := getPortStatusAndMarkUsed(pkt.Info().LocalPort()) if ps.isMe { // approve - conn.Accept("internally approved") + conn.Accept("connection by Portmaster", noReasonOptionKey) conn.Internal = true // finish conn.StopFirewallHandler() @@ -191,7 +191,7 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) { // check if filtering is enabled if !filterEnabled() { conn.Inspecting = false - conn.SetVerdict(network.VerdictAccept, "privacy filter disabled", nil) + conn.Accept("privacy filter disabled", noReasonOptionKey) conn.StopFirewallHandler() issueVerdict(conn, pkt, 0, true) return diff --git a/firewall/master.go b/firewall/master.go index a06f8fa3..ceed75c8 100644 --- a/firewall/master.go +++ b/firewall/master.go @@ -36,44 +36,81 @@ import ( // 3. DecideOnConnection // is called with the first packet of a network connection. +const noReasonOptionKey = "" + +var deciders = []func(context.Context, *network.Connection, packet.Packet) bool{ + checkPortmasterConnection, + checkSelfCommunication, + checkConnectionType, + checkConnectivityDomain, + checkConnectionScope, + checkEndpointLists, + checkBypassPrevention, + checkFilterLists, + dropInbound, + checkDomainHeuristics, + checkAutoPermitRelated, +} + // DecideOnConnection makes a decision about a connection. // When called, the connection and profile is already locked. func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) { - // update profiles and check if communication needs reevaluation - if conn.UpdateAndCheck() { + // Check if we have a process and profile. + layeredProfile := conn.Process().Profile() + if layeredProfile == nil { + conn.Deny("unknown process or profile", noReasonOptionKey) + return + } + + // Check if the layered profile needs updating. + if layeredProfile.NeedsUpdate() { + // Update revision counter in connection. + conn.ProfileRevisionCounter = layeredProfile.Update() + conn.SaveWhenFinished() + + // Reset verdict for connection. log.Tracer(ctx).Infof("filter: re-evaluating verdict on %s", conn) conn.Verdict = network.VerdictUndecided + // Reset entity if it exists. if conn.Entity != nil { conn.Entity.ResetLists() } } - var deciders = []func(context.Context, *network.Connection, packet.Packet) bool{ - checkPortmasterConnection, - checkSelfCommunication, - checkProfileExists, - checkConnectionType, - checkConnectivityDomain, - checkConnectionScope, - checkEndpointLists, - checkBypassPrevention, - checkFilterLists, - checkInbound, - checkDomainHeuristics, - checkDefaultPermit, - checkAutoPermitRelated, - checkDefaultAction, + // Run all deciders and return if they came to a conclusion. + done, defaultAction := runDeciders(ctx, conn, pkt) + if done { + return } + // Deciders did not conclude, use default action. + switch defaultAction { + case profile.DefaultActionPermit: + conn.Accept("default permit", profile.CfgOptionDefaultActionKey) + case profile.DefaultActionAsk: + prompt(ctx, conn, pkt) + default: + conn.Deny("default block", profile.CfgOptionDefaultActionKey) + } +} + +func runDeciders(ctx context.Context, conn *network.Connection, pkt packet.Packet) (done bool, defaultAction uint8) { + layeredProfile := conn.Process().Profile() + + // Read-lock the all the profiles. + layeredProfile.LockForUsage() + defer layeredProfile.UnlockForUsage() + + // Go though all deciders, return if one sets an action. for _, decider := range deciders { if decider(ctx, conn, pkt) { - return + return true, profile.DefaultActionNotSet } } - // DefaultAction == DefaultActionBlock - conn.Deny("endpoint is not allowed (default=block)") + // Return the default action. + return false, layeredProfile.DefaultAction() } // checkPortmasterConnection allows all connection that originate from @@ -82,7 +119,7 @@ func checkPortmasterConnection(ctx context.Context, conn *network.Connection, pk // grant self if conn.Process().Pid == os.Getpid() { log.Tracer(ctx).Infof("filter: granting own connection %s", conn) - conn.Verdict = network.VerdictAccept + conn.Accept("connection by Portmaster", noReasonOptionKey) conn.Internal = true return true } @@ -115,7 +152,7 @@ func checkSelfCommunication(ctx context.Context, conn *network.Connection, pkt p if err != nil { log.Tracer(ctx).Warningf("filter: failed to find load local peer process with PID %d: %s", otherPid, err) } else if otherProcess.Pid == conn.Process().Pid { - conn.Accept("connection to self") + conn.Accept("connection to self", noReasonOptionKey) conn.Internal = true return true } @@ -126,14 +163,6 @@ func checkSelfCommunication(ctx context.Context, conn *network.Connection, pkt p return false } -func checkProfileExists(_ context.Context, conn *network.Connection, _ packet.Packet) bool { - if conn.Process().Profile() == nil { - conn.Block("unknown process or profile") - return true - } - return false -} - func checkEndpointLists(ctx context.Context, conn *network.Connection, _ packet.Packet) bool { var result endpoints.EPResult var reason endpoints.Reason @@ -142,17 +171,20 @@ func checkEndpointLists(ctx context.Context, conn *network.Connection, _ packet. p := conn.Process().Profile() // check endpoints list + var optionKey string if conn.Inbound { result, reason = p.MatchServiceEndpoint(ctx, conn.Entity) + optionKey = profile.CfgOptionServiceEndpointsKey } else { result, reason = p.MatchEndpoint(ctx, conn.Entity) + optionKey = profile.CfgOptionEndpointsKey } switch result { case endpoints.Denied: - conn.DenyWithContext(reason.String(), reason.Context()) + conn.DenyWithContext(reason.String(), optionKey, reason.Context()) return true case endpoints.Permitted: - conn.AcceptWithContext(reason.String(), reason.Context()) + conn.AcceptWithContext(reason.String(), optionKey, reason.Context()) return true } @@ -167,16 +199,16 @@ func checkConnectionType(ctx context.Context, conn *network.Connection, _ packet case network.IncomingLAN, network.IncomingInternet, network.IncomingInvalid: if p.BlockInbound() { if conn.Scope == network.IncomingHost { - conn.Block("inbound connections blocked") + conn.Block("inbound connections blocked", profile.CfgOptionBlockInboundKey) } else { - conn.Drop("inbound connections blocked") + conn.Drop("inbound connections blocked", profile.CfgOptionBlockInboundKey) } return true } case network.PeerInternet: // BlockP2P only applies to connections to the Internet if p.BlockP2P() { - conn.Block("direct connections (P2P) blocked") + conn.Block("direct connections (P2P) blocked", profile.CfgOptionBlockP2PKey) return true } } @@ -202,7 +234,7 @@ func checkConnectivityDomain(_ context.Context, conn *network.Connection, _ pack case netenv.IsConnectivityDomain(conn.Entity.Domain): // Special grant! - conn.Accept("special grant for connectivity domain during network bootstrap") + conn.Accept("special grant for connectivity domain during network bootstrap", noReasonOptionKey) return true default: @@ -221,29 +253,29 @@ func checkConnectionScope(_ context.Context, conn *network.Connection, _ packet. switch classification { case netutils.Global, netutils.GlobalMulticast: if p.BlockScopeInternet() { - conn.Deny("Internet access blocked") // Block Outbound / Drop Inbound + conn.Deny("Internet access blocked", profile.CfgOptionBlockScopeInternetKey) // Block Outbound / Drop Inbound return true } case netutils.SiteLocal, netutils.LinkLocal, netutils.LocalMulticast: if p.BlockScopeLAN() { - conn.Block("LAN access blocked") // Block Outbound / Drop Inbound + conn.Block("LAN access blocked", profile.CfgOptionBlockScopeLANKey) // Block Outbound / Drop Inbound return true } case netutils.HostLocal: if p.BlockScopeLocal() { - conn.Block("Localhost access blocked") // Block Outbound / Drop Inbound + conn.Block("Localhost access blocked", profile.CfgOptionBlockScopeLocalKey) // Block Outbound / Drop Inbound return true } default: // netutils.Invalid - conn.Deny("invalid IP") // Block Outbound / Drop Inbound + conn.Deny("invalid IP", noReasonOptionKey) // Block Outbound / Drop Inbound return true } } else if conn.Entity.Domain != "" { - // DNS Query - // DNS is expected to resolve to LAN or Internet addresses - // TODO: handle domains mapped to localhost + // This is a DNS Request. + // DNS is expected to resolve to LAN or Internet addresses. + // Localhost queries are immediately responded to by the nameserver. if p.BlockScopeInternet() && p.BlockScopeLAN() { - conn.Block("Internet and LAN access blocked") + conn.Block("Internet and LAN access blocked", profile.CfgOptionBlockScopeInternetKey) return true } } @@ -256,10 +288,10 @@ func checkBypassPrevention(_ context.Context, conn *network.Connection, _ packet result, reason, reasonCtx := PreventBypassing(conn) switch result { case endpoints.Denied: - conn.BlockWithContext("bypass prevention: "+reason, reasonCtx) + conn.BlockWithContext("bypass prevention: "+reason, profile.CfgOptionPreventBypassingKey, reasonCtx) return true case endpoints.Permitted: - conn.AcceptWithContext("bypass prevention: "+reason, reasonCtx) + conn.AcceptWithContext("bypass prevention: "+reason, profile.CfgOptionPreventBypassingKey, reasonCtx) return true case endpoints.NoMatch: } @@ -274,7 +306,7 @@ func checkFilterLists(ctx context.Context, conn *network.Connection, pkt packet. result, reason := p.MatchFilterLists(ctx, conn.Entity) switch result { case endpoints.Denied: - conn.DenyWithContext(reason.String(), reason.Context()) + conn.DenyWithContext(reason.String(), profile.CfgOptionFilterListsKey, reason.Context()) return true case endpoints.NoMatch: // nothing to do @@ -315,7 +347,7 @@ func checkDomainHeuristics(ctx context.Context, conn *network.Connection, _ pack domainToCheck, score, ) - conn.Block("possible DGA domain commonly used by malware") + conn.Block("possible DGA domain commonly used by malware", profile.CfgOptionDomainHeuristicsKey) return true } log.Tracer(ctx).Tracef("filter: LMS score of eTLD+1 %s is %.2f", etld1, score) @@ -335,7 +367,7 @@ func checkDomainHeuristics(ctx context.Context, conn *network.Connection, _ pack domainToCheck, score, ) - conn.Block("possible data tunnel for covert communication and protection bypassing") + conn.Block("possible data tunnel for covert communication and protection bypassing", profile.CfgOptionDomainHeuristicsKey) return true } log.Tracer(ctx).Tracef("filter: LMS score of entire domain is %.2f", score) @@ -344,20 +376,10 @@ func checkDomainHeuristics(ctx context.Context, conn *network.Connection, _ pack return false } -func checkInbound(_ context.Context, conn *network.Connection, _ packet.Packet) bool { +func dropInbound(_ context.Context, conn *network.Connection, _ packet.Packet) bool { // implicit default=block for inbound if conn.Inbound { - conn.Drop("endpoint is not allowed (incoming is always default=block)") - return true - } - return false -} - -func checkDefaultPermit(_ context.Context, conn *network.Connection, _ packet.Packet) bool { - // check default action - p := conn.Process().Profile() - if p.DefaultAction() == profile.DefaultActionPermit { - conn.Accept("endpoint is not blocked (default=permit)") + conn.Drop("incoming connection blocked by default", profile.CfgOptionServiceEndpointsKey) return true } return false @@ -365,22 +387,24 @@ func checkDefaultPermit(_ context.Context, conn *network.Connection, _ packet.Pa func checkAutoPermitRelated(_ context.Context, conn *network.Connection, _ packet.Packet) bool { p := conn.Process().Profile() - if !p.DisableAutoPermit() { - related, reason := checkRelation(conn) - if related { - conn.Accept(reason) - return true - } - } - return false -} -func checkDefaultAction(_ context.Context, conn *network.Connection, pkt packet.Packet) bool { - p := conn.Process().Profile() - if p.DefaultAction() == profile.DefaultActionAsk { - prompt(conn, pkt) + // Auto permit is disabled for default action permit. + if p.DefaultAction() == profile.DefaultActionPermit { + return false + } + + // Check if auto permit is disabled. + if p.DisableAutoPermit() { + return false + } + + // Check for relation to auto permit. + related, reason := checkRelation(conn) + if related { + conn.Accept(reason, profile.CfgOptionDisableAutoPermitKey) return true } + return false } @@ -426,7 +450,7 @@ matchLoop: } if related { - reason = fmt.Sprintf("domain is related to process: %s is related to %s", domainElement, processElement) + reason = fmt.Sprintf("auto permitted: domain is related to process: %s is related to %s", domainElement, processElement) } return related, reason } diff --git a/nameserver/nameserver.go b/nameserver/nameserver.go index ac46c6d2..0a4fb434 100644 --- a/nameserver/nameserver.go +++ b/nameserver/nameserver.go @@ -197,11 +197,11 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg) // A reason for this might be that the request is sink-holed to a forced // IP address in which case we "accept" it, but let the firewall handle // the resolving as it wishes. - if responder, ok := conn.ReasonContext.(nsutil.Responder); ok { + if responder, ok := conn.Reason.Context.(nsutil.Responder); ok { // Save the request as open, as we don't know if there will be a connection or not. network.SaveOpenDNSRequest(conn) - tracer.Infof("nameserver: handing over request for %s to special filter responder: %s", q.ID(), conn.Reason) + tracer.Infof("nameserver: handing over request for %s to special filter responder: %s", q.ID(), conn.Reason.Msg) return reply(responder) } @@ -243,11 +243,11 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg) rrCache = firewall.DecideOnResolvedDNS(ctx, conn, q, rrCache) if rrCache == nil { // Check again if there is a responder from the firewall. - if responder, ok := conn.ReasonContext.(nsutil.Responder); ok { + if responder, ok := conn.Reason.Context.(nsutil.Responder); ok { // Save the request as open, as we don't know if there will be a connection or not. network.SaveOpenDNSRequest(conn) - tracer.Infof("nameserver: handing over request for %s to filter responder: %s", q.ID(), conn.Reason) + tracer.Infof("nameserver: handing over request for %s to filter responder: %s", q.ID(), conn.Reason.Msg) return reply(responder) } diff --git a/network/connection.go b/network/connection.go index 1a5d63c2..645f5cdc 100644 --- a/network/connection.go +++ b/network/connection.go @@ -83,12 +83,10 @@ type Connection struct { //nolint:maligned // TODO: fix alignment // The verdict may change so any access to it must be guarded by the // connection lock. Verdict Verdict - // Reason is a human readable description justifying the set verdict. + // Reason holds information justifying the verdict, as well as additional + // information about the reason. // Access to Reason must be guarded by the connection lock. - Reason string - // ReasonContext may holds additional reason-specific information and - // any access must be guarded by the connection lock. - ReasonContext interface{} + Reason Reason // Started holds the number of seconds in UNIX epoch time at which // the connection has been initated and first seen by the portmaster. // Staretd is only every set when creating a new connection object @@ -141,10 +139,26 @@ type Connection struct { //nolint:maligned // TODO: fix alignment // inspectorData holds additional meta data for the inspectors. // using the inspectors index as a map key. inspectorData map[uint8]interface{} - // profileRevisionCounter is used to track changes to the process + // ProfileRevisionCounter is used to track changes to the process // profile and required for correct re-evaluation of a connections // verdict. - profileRevisionCounter uint64 + ProfileRevisionCounter uint64 +} + +// Reason holds information justifying a verdict, as well as additional +// information about the reason. +type Reason struct { + // Msg is a human readable description of the reason. + Msg string + // OptionKey is the configuration option key of the setting that + // was responsible for the verdict. + OptionKey string + // Profile is the database key of the profile that held the setting + // that was responsible for the verdict. + Profile string + // ReasonContext may hold additional reason-specific information and + // any access must be guarded by the connection lock. + Context interface{} } func getProcessContext(proc *process.Process) ProcessContext { @@ -290,7 +304,7 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection { Entity: entity, // meta Started: time.Now().Unix(), - profileRevisionCounter: proc.Profile().RevisionCnt(), + ProfileRevisionCounter: proc.Profile().RevisionCnt(), } } @@ -300,73 +314,77 @@ func GetConnection(id string) (*Connection, bool) { } // AcceptWithContext accepts the connection. -func (conn *Connection) AcceptWithContext(reason string, ctx interface{}) { - if !conn.SetVerdict(VerdictAccept, reason, ctx) { +func (conn *Connection) AcceptWithContext(reason, reasonOptionKey string, ctx interface{}) { + if !conn.SetVerdict(VerdictAccept, reason, reasonOptionKey, ctx) { log.Warningf("filter: tried to accept %s, but current verdict is %s", conn, conn.Verdict) } } // Accept is like AcceptWithContext but only accepts a reason. -func (conn *Connection) Accept(reason string) { - conn.AcceptWithContext(reason, nil) +func (conn *Connection) Accept(reason, reasonOptionKey string) { + conn.AcceptWithContext(reason, reasonOptionKey, nil) } // BlockWithContext blocks the connection. -func (conn *Connection) BlockWithContext(reason string, ctx interface{}) { - if !conn.SetVerdict(VerdictBlock, reason, ctx) { +func (conn *Connection) BlockWithContext(reason, reasonOptionKey string, ctx interface{}) { + if !conn.SetVerdict(VerdictBlock, reason, reasonOptionKey, ctx) { log.Warningf("filter: tried to block %s, but current verdict is %s", conn, conn.Verdict) } } // Block is like BlockWithContext but does only accepts a reason. -func (conn *Connection) Block(reason string) { - conn.BlockWithContext(reason, nil) +func (conn *Connection) Block(reason, reasonOptionKey string) { + conn.BlockWithContext(reason, reasonOptionKey, nil) } // DropWithContext drops the connection. -func (conn *Connection) DropWithContext(reason string, ctx interface{}) { - if !conn.SetVerdict(VerdictDrop, reason, ctx) { +func (conn *Connection) DropWithContext(reason, reasonOptionKey string, ctx interface{}) { + if !conn.SetVerdict(VerdictDrop, reason, reasonOptionKey, ctx) { log.Warningf("filter: tried to drop %s, but current verdict is %s", conn, conn.Verdict) } } // Drop is like DropWithContext but does only accepts a reason. -func (conn *Connection) Drop(reason string) { - conn.DropWithContext(reason, nil) +func (conn *Connection) Drop(reason, reasonOptionKey string) { + conn.DropWithContext(reason, reasonOptionKey, nil) } // DenyWithContext blocks or drops the link depending on the connection direction. -func (conn *Connection) DenyWithContext(reason string, ctx interface{}) { +func (conn *Connection) DenyWithContext(reason, reasonOptionKey string, ctx interface{}) { if conn.Inbound { - conn.DropWithContext(reason, ctx) + conn.DropWithContext(reason, reasonOptionKey, ctx) } else { - conn.BlockWithContext(reason, ctx) + conn.BlockWithContext(reason, reasonOptionKey, ctx) } } // Deny is like DenyWithContext but only accepts a reason. -func (conn *Connection) Deny(reason string) { - conn.DenyWithContext(reason, nil) +func (conn *Connection) Deny(reason, reasonOptionKey string) { + conn.DenyWithContext(reason, reasonOptionKey, nil) } // FailedWithContext marks the connection with VerdictFailed and stores the reason. -func (conn *Connection) FailedWithContext(reason string, ctx interface{}) { - if !conn.SetVerdict(VerdictFailed, reason, ctx) { +func (conn *Connection) FailedWithContext(reason, reasonOptionKey string, ctx interface{}) { + if !conn.SetVerdict(VerdictFailed, reason, reasonOptionKey, ctx) { log.Warningf("filter: tried to drop %s due to error but current verdict is %s", conn, conn.Verdict) } } // Failed is like FailedWithContext but only accepts a string. -func (conn *Connection) Failed(reason string) { - conn.FailedWithContext(reason, nil) +func (conn *Connection) Failed(reason, reasonOptionKey string) { + conn.FailedWithContext(reason, reasonOptionKey, nil) } // SetVerdict sets a new verdict for the connection, making sure it does not interfere with previous verdicts. -func (conn *Connection) SetVerdict(newVerdict Verdict, reason string, reasonCtx interface{}) (ok bool) { +func (conn *Connection) SetVerdict(newVerdict Verdict, reason, reasonOptionKey string, reasonCtx interface{}) (ok bool) { if newVerdict >= conn.Verdict { conn.Verdict = newVerdict - conn.Reason = reason - conn.ReasonContext = reasonCtx + conn.Reason.Msg = reason + conn.Reason.Context = reasonCtx + if reasonOptionKey != "" && conn.Process() != nil { + conn.Reason.OptionKey = reasonOptionKey + conn.Reason.Profile = conn.Process().Profile().GetProfileSource(conn.Reason.OptionKey) + } return true } return false @@ -490,7 +508,7 @@ func (conn *Connection) packetHandler() { defaultFirewallHandler(conn, pkt) } // log verdict - log.Tracer(pkt.Ctx()).Infof("filter: connection %s %s: %s", conn, conn.Verdict.Verb(), conn.Reason) + log.Tracer(pkt.Ctx()).Infof("filter: connection %s %s: %s", conn, conn.Verdict.Verb(), conn.Reason.Msg) // save does not touch any changing data // must not be locked, will deadlock with cleaner functions diff --git a/network/dns.go b/network/dns.go index c4f9071a..bc03cab7 100644 --- a/network/dns.go +++ b/network/dns.go @@ -124,15 +124,15 @@ func (conn *Connection) GetExtraRRs(ctx context.Context, request *dns.Msg) []dns } // Create resource record with verdict and reason. - rr, err := nsutil.MakeMessageRecord(level, fmt.Sprintf("%s: %s", conn.Verdict.Verb(), conn.Reason)) + rr, err := nsutil.MakeMessageRecord(level, fmt.Sprintf("%s: %s", conn.Verdict.Verb(), conn.Reason.Msg)) if err != nil { log.Tracer(ctx).Warningf("filter: failed to add informational record to reply: %s", err) return nil } extra := []dns.RR{rr} - // Add additional records from ReasonContext. - if rrProvider, ok := conn.ReasonContext.(nsutil.RRProvider); ok { + // Add additional records from Reason.Context. + if rrProvider, ok := conn.Reason.Context.(nsutil.RRProvider); ok { rrs := rrProvider.GetExtraRRs(ctx, request) extra = append(extra, rrs...) } diff --git a/profile/profile-layered.go b/profile/profile-layered.go index e292edfb..f396d2eb 100644 --- a/profile/profile-layered.go +++ b/profile/profile-layered.go @@ -26,7 +26,7 @@ type LayeredProfile struct { localProfile *Profile layers []*Profile - revisionCounter uint64 + RevisionCounter uint64 validityFlag *abool.AtomicBool validityFlagLock sync.Mutex