Add option key responsible for the verdict

Also, expose the RevisionCounter
This commit is contained in:
Daniel 2020-10-29 16:24:17 +01:00
parent 263eb0578a
commit c09d32cf08
8 changed files with 176 additions and 133 deletions

View file

@ -17,13 +17,14 @@ import (
"github.com/safing/portmaster/resolver" "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)) goodEntries := make([]dns.RR, 0, len(entries))
filteredRecords := make([]string, 0, len(entries)) filteredRecords := make([]string, 0, len(entries))
// keeps track of the number of valid and allowed // keeps track of the number of valid and allowed
// A and AAAA records. // A and AAAA records.
var allowedAddressRecords int var allowedAddressRecords int
var interveningOptionKey string
for _, rr := range entries { for _, rr := range entries {
// get IP and classification // get IP and classification
@ -45,10 +46,12 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) (
case classification == netutils.HostLocal: case classification == netutils.HostLocal:
// No DNS should return localhost addresses // No DNS should return localhost addresses
filteredRecords = append(filteredRecords, rr.String()) filteredRecords = append(filteredRecords, rr.String())
interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey
continue continue
case scope == netutils.Global && (classification == netutils.SiteLocal || classification == netutils.LinkLocal): case scope == netutils.Global && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
// No global DNS should return LAN addresses // No global DNS should return LAN addresses
filteredRecords = append(filteredRecords, rr.String()) filteredRecords = append(filteredRecords, rr.String())
interveningOptionKey = profile.CfgOptionRemoveOutOfScopeDNSKey
continue continue
} }
} }
@ -58,12 +61,15 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) (
switch { switch {
case p.BlockScopeInternet() && classification == netutils.Global: case p.BlockScopeInternet() && classification == netutils.Global:
filteredRecords = append(filteredRecords, rr.String()) filteredRecords = append(filteredRecords, rr.String())
interveningOptionKey = profile.CfgOptionBlockScopeInternetKey
continue continue
case p.BlockScopeLAN() && (classification == netutils.SiteLocal || classification == netutils.LinkLocal): case p.BlockScopeLAN() && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
filteredRecords = append(filteredRecords, rr.String()) filteredRecords = append(filteredRecords, rr.String())
interveningOptionKey = profile.CfgOptionBlockScopeLANKey
continue continue
case p.BlockScopeLocal() && classification == netutils.HostLocal: case p.BlockScopeLocal() && classification == netutils.HostLocal:
filteredRecords = append(filteredRecords, rr.String()) filteredRecords = append(filteredRecords, rr.String())
interveningOptionKey = profile.CfgOptionBlockScopeLocalKey
continue continue
} }
@ -75,7 +81,7 @@ func filterDNSSection(entries []dns.RR, p *profile.LayeredProfile, scope int8) (
goodEntries = append(goodEntries, rr) goodEntries = append(goodEntries, rr)
} }
return goodEntries, filteredRecords, allowedAddressRecords return goodEntries, filteredRecords, allowedAddressRecords, interveningOptionKey
} }
func filterDNSResponse(conn *network.Connection, rrCache *resolver.RRCache) *resolver.RRCache { 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 filteredRecords []string
var validIPs int 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...) rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...)
// we don't count the valid IPs in the extra section // 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...) rrCache.FilteredEntries = append(rrCache.FilteredEntries, filteredRecords...)
if len(rrCache.FilteredEntries) > 0 { if len(rrCache.FilteredEntries) > 0 {
rrCache.Filtered = true rrCache.Filtered = true
if validIPs == 0 { 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 all entries are filtered, this could mean that these are broken/bogus resource records.
if rrCache.Expired() { if rrCache.Expired() {
@ -151,12 +158,6 @@ func DecideOnResolvedDNS(
rrCache *resolver.RRCache, rrCache *resolver.RRCache,
) *resolver.RRCache { ) *resolver.RRCache {
// check profile
if checkProfileExists(ctx, conn, nil) {
// returns true if check triggered
return nil
}
// special grant for connectivity domains // special grant for connectivity domains
if checkConnectivityDomain(ctx, conn, nil) { if checkConnectivityDomain(ctx, conn, nil) {
// returns true if check triggered // 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) result, reason := conn.Process().Profile().MatchEndpoint(ctx, conn.Entity)
if result == endpoints.Denied { if result == endpoints.Denied {
conn.BlockWithContext(reason.String(), reason.Context()) conn.BlockWithContext(reason.String(), profile.CfgOptionFilterCNAMEKey, reason.Context())
return true return true
} }
if result == endpoints.NoMatch { if result == endpoints.NoMatch {
result, reason = conn.Process().Profile().MatchFilterLists(ctx, conn.Entity) result, reason = conn.Process().Profile().MatchFilterLists(ctx, conn.Entity)
if result == endpoints.Denied { if result == endpoints.Denied {
conn.BlockWithContext(reason.String(), reason.Context()) conn.BlockWithContext(reason.String(), profile.CfgOptionFilterCNAMEKey, reason.Context())
return true return true
} }
} }

View file

@ -85,11 +85,11 @@ func RunInspectors(conn *network.Connection, pkt packet.Packet) (network.Verdict
verdict = network.VerdictDrop verdict = network.VerdictDrop
continueInspection = true continueInspection = true
case BLOCK_CONN: case BLOCK_CONN:
conn.SetVerdict(network.VerdictBlock, "", nil) conn.SetVerdict(network.VerdictBlock, "", "", nil)
verdict = conn.Verdict verdict = conn.Verdict
activeInspectors[key] = true activeInspectors[key] = true
case DROP_CONN: case DROP_CONN:
conn.SetVerdict(network.VerdictDrop, "", nil) conn.SetVerdict(network.VerdictDrop, "", "", nil)
verdict = conn.Verdict verdict = conn.Verdict
activeInspectors[key] = true activeInspectors[key] = true
case STOP_INSPECTING: case STOP_INSPECTING:

View file

@ -171,7 +171,7 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
ps := getPortStatusAndMarkUsed(pkt.Info().LocalPort()) ps := getPortStatusAndMarkUsed(pkt.Info().LocalPort())
if ps.isMe { if ps.isMe {
// approve // approve
conn.Accept("internally approved") conn.Accept("connection by Portmaster", noReasonOptionKey)
conn.Internal = true conn.Internal = true
// finish // finish
conn.StopFirewallHandler() conn.StopFirewallHandler()
@ -191,7 +191,7 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
// check if filtering is enabled // check if filtering is enabled
if !filterEnabled() { if !filterEnabled() {
conn.Inspecting = false conn.Inspecting = false
conn.SetVerdict(network.VerdictAccept, "privacy filter disabled", nil) conn.Accept("privacy filter disabled", noReasonOptionKey)
conn.StopFirewallHandler() conn.StopFirewallHandler()
issueVerdict(conn, pkt, 0, true) issueVerdict(conn, pkt, 0, true)
return return

View file

@ -36,44 +36,81 @@ import (
// 3. DecideOnConnection // 3. DecideOnConnection
// is called with the first packet of a network connection. // is called with the first packet of a network connection.
// DecideOnConnection makes a decision about a connection. const noReasonOptionKey = ""
// 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() {
log.Tracer(ctx).Infof("filter: re-evaluating verdict on %s", conn)
conn.Verdict = network.VerdictUndecided
if conn.Entity != nil { var deciders = []func(context.Context, *network.Connection, packet.Packet) bool{
conn.Entity.ResetLists()
}
}
var deciders = []func(context.Context, *network.Connection, packet.Packet) bool{
checkPortmasterConnection, checkPortmasterConnection,
checkSelfCommunication, checkSelfCommunication,
checkProfileExists,
checkConnectionType, checkConnectionType,
checkConnectivityDomain, checkConnectivityDomain,
checkConnectionScope, checkConnectionScope,
checkEndpointLists, checkEndpointLists,
checkBypassPrevention, checkBypassPrevention,
checkFilterLists, checkFilterLists,
checkInbound, dropInbound,
checkDomainHeuristics, checkDomainHeuristics,
checkDefaultPermit,
checkAutoPermitRelated, checkAutoPermitRelated,
checkDefaultAction, }
}
for _, decider := range deciders { // DecideOnConnection makes a decision about a connection.
if decider(ctx, conn, pkt) { // When called, the connection and profile is already locked.
func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) {
// Check if we have a process and profile.
layeredProfile := conn.Process().Profile()
if layeredProfile == nil {
conn.Deny("unknown process or profile", noReasonOptionKey)
return 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()
}
} }
// DefaultAction == DefaultActionBlock // Run all deciders and return if they came to a conclusion.
conn.Deny("endpoint is not allowed (default=block)") 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 true, profile.DefaultActionNotSet
}
}
// Return the default action.
return false, layeredProfile.DefaultAction()
} }
// checkPortmasterConnection allows all connection that originate from // checkPortmasterConnection allows all connection that originate from
@ -82,7 +119,7 @@ func checkPortmasterConnection(ctx context.Context, conn *network.Connection, pk
// grant self // grant self
if conn.Process().Pid == os.Getpid() { if conn.Process().Pid == os.Getpid() {
log.Tracer(ctx).Infof("filter: granting own connection %s", conn) log.Tracer(ctx).Infof("filter: granting own connection %s", conn)
conn.Verdict = network.VerdictAccept conn.Accept("connection by Portmaster", noReasonOptionKey)
conn.Internal = true conn.Internal = true
return true return true
} }
@ -115,7 +152,7 @@ func checkSelfCommunication(ctx context.Context, conn *network.Connection, pkt p
if err != nil { if err != nil {
log.Tracer(ctx).Warningf("filter: failed to find load local peer process with PID %d: %s", otherPid, err) 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 { } else if otherProcess.Pid == conn.Process().Pid {
conn.Accept("connection to self") conn.Accept("connection to self", noReasonOptionKey)
conn.Internal = true conn.Internal = true
return true return true
} }
@ -126,14 +163,6 @@ func checkSelfCommunication(ctx context.Context, conn *network.Connection, pkt p
return false 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 { func checkEndpointLists(ctx context.Context, conn *network.Connection, _ packet.Packet) bool {
var result endpoints.EPResult var result endpoints.EPResult
var reason endpoints.Reason var reason endpoints.Reason
@ -142,17 +171,20 @@ func checkEndpointLists(ctx context.Context, conn *network.Connection, _ packet.
p := conn.Process().Profile() p := conn.Process().Profile()
// check endpoints list // check endpoints list
var optionKey string
if conn.Inbound { if conn.Inbound {
result, reason = p.MatchServiceEndpoint(ctx, conn.Entity) result, reason = p.MatchServiceEndpoint(ctx, conn.Entity)
optionKey = profile.CfgOptionServiceEndpointsKey
} else { } else {
result, reason = p.MatchEndpoint(ctx, conn.Entity) result, reason = p.MatchEndpoint(ctx, conn.Entity)
optionKey = profile.CfgOptionEndpointsKey
} }
switch result { switch result {
case endpoints.Denied: case endpoints.Denied:
conn.DenyWithContext(reason.String(), reason.Context()) conn.DenyWithContext(reason.String(), optionKey, reason.Context())
return true return true
case endpoints.Permitted: case endpoints.Permitted:
conn.AcceptWithContext(reason.String(), reason.Context()) conn.AcceptWithContext(reason.String(), optionKey, reason.Context())
return true return true
} }
@ -167,16 +199,16 @@ func checkConnectionType(ctx context.Context, conn *network.Connection, _ packet
case network.IncomingLAN, network.IncomingInternet, network.IncomingInvalid: case network.IncomingLAN, network.IncomingInternet, network.IncomingInvalid:
if p.BlockInbound() { if p.BlockInbound() {
if conn.Scope == network.IncomingHost { if conn.Scope == network.IncomingHost {
conn.Block("inbound connections blocked") conn.Block("inbound connections blocked", profile.CfgOptionBlockInboundKey)
} else { } else {
conn.Drop("inbound connections blocked") conn.Drop("inbound connections blocked", profile.CfgOptionBlockInboundKey)
} }
return true return true
} }
case network.PeerInternet: case network.PeerInternet:
// BlockP2P only applies to connections to the Internet // BlockP2P only applies to connections to the Internet
if p.BlockP2P() { if p.BlockP2P() {
conn.Block("direct connections (P2P) blocked") conn.Block("direct connections (P2P) blocked", profile.CfgOptionBlockP2PKey)
return true return true
} }
} }
@ -202,7 +234,7 @@ func checkConnectivityDomain(_ context.Context, conn *network.Connection, _ pack
case netenv.IsConnectivityDomain(conn.Entity.Domain): case netenv.IsConnectivityDomain(conn.Entity.Domain):
// Special grant! // 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 return true
default: default:
@ -221,29 +253,29 @@ func checkConnectionScope(_ context.Context, conn *network.Connection, _ packet.
switch classification { switch classification {
case netutils.Global, netutils.GlobalMulticast: case netutils.Global, netutils.GlobalMulticast:
if p.BlockScopeInternet() { 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 return true
} }
case netutils.SiteLocal, netutils.LinkLocal, netutils.LocalMulticast: case netutils.SiteLocal, netutils.LinkLocal, netutils.LocalMulticast:
if p.BlockScopeLAN() { 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 return true
} }
case netutils.HostLocal: case netutils.HostLocal:
if p.BlockScopeLocal() { 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 return true
} }
default: // netutils.Invalid default: // netutils.Invalid
conn.Deny("invalid IP") // Block Outbound / Drop Inbound conn.Deny("invalid IP", noReasonOptionKey) // Block Outbound / Drop Inbound
return true return true
} }
} else if conn.Entity.Domain != "" { } else if conn.Entity.Domain != "" {
// DNS Query // This is a DNS Request.
// DNS is expected to resolve to LAN or Internet addresses // DNS is expected to resolve to LAN or Internet addresses.
// TODO: handle domains mapped to localhost // Localhost queries are immediately responded to by the nameserver.
if p.BlockScopeInternet() && p.BlockScopeLAN() { if p.BlockScopeInternet() && p.BlockScopeLAN() {
conn.Block("Internet and LAN access blocked") conn.Block("Internet and LAN access blocked", profile.CfgOptionBlockScopeInternetKey)
return true return true
} }
} }
@ -256,10 +288,10 @@ func checkBypassPrevention(_ context.Context, conn *network.Connection, _ packet
result, reason, reasonCtx := PreventBypassing(conn) result, reason, reasonCtx := PreventBypassing(conn)
switch result { switch result {
case endpoints.Denied: case endpoints.Denied:
conn.BlockWithContext("bypass prevention: "+reason, reasonCtx) conn.BlockWithContext("bypass prevention: "+reason, profile.CfgOptionPreventBypassingKey, reasonCtx)
return true return true
case endpoints.Permitted: case endpoints.Permitted:
conn.AcceptWithContext("bypass prevention: "+reason, reasonCtx) conn.AcceptWithContext("bypass prevention: "+reason, profile.CfgOptionPreventBypassingKey, reasonCtx)
return true return true
case endpoints.NoMatch: case endpoints.NoMatch:
} }
@ -274,7 +306,7 @@ func checkFilterLists(ctx context.Context, conn *network.Connection, pkt packet.
result, reason := p.MatchFilterLists(ctx, conn.Entity) result, reason := p.MatchFilterLists(ctx, conn.Entity)
switch result { switch result {
case endpoints.Denied: case endpoints.Denied:
conn.DenyWithContext(reason.String(), reason.Context()) conn.DenyWithContext(reason.String(), profile.CfgOptionFilterListsKey, reason.Context())
return true return true
case endpoints.NoMatch: case endpoints.NoMatch:
// nothing to do // nothing to do
@ -315,7 +347,7 @@ func checkDomainHeuristics(ctx context.Context, conn *network.Connection, _ pack
domainToCheck, domainToCheck,
score, score,
) )
conn.Block("possible DGA domain commonly used by malware") conn.Block("possible DGA domain commonly used by malware", profile.CfgOptionDomainHeuristicsKey)
return true return true
} }
log.Tracer(ctx).Tracef("filter: LMS score of eTLD+1 %s is %.2f", etld1, score) 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, domainToCheck,
score, 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 return true
} }
log.Tracer(ctx).Tracef("filter: LMS score of entire domain is %.2f", score) 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 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 // implicit default=block for inbound
if conn.Inbound { if conn.Inbound {
conn.Drop("endpoint is not allowed (incoming is always default=block)") conn.Drop("incoming connection blocked by default", profile.CfgOptionServiceEndpointsKey)
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)")
return true return true
} }
return false 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 { func checkAutoPermitRelated(_ context.Context, conn *network.Connection, _ packet.Packet) bool {
p := conn.Process().Profile() p := conn.Process().Profile()
if !p.DisableAutoPermit() {
// 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) related, reason := checkRelation(conn)
if related { if related {
conn.Accept(reason) conn.Accept(reason, profile.CfgOptionDisableAutoPermitKey)
return true 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)
return true
}
return false return false
} }
@ -426,7 +450,7 @@ matchLoop:
} }
if related { 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 return related, reason
} }

View file

@ -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 // 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 // IP address in which case we "accept" it, but let the firewall handle
// the resolving as it wishes. // 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. // Save the request as open, as we don't know if there will be a connection or not.
network.SaveOpenDNSRequest(conn) 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) 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) rrCache = firewall.DecideOnResolvedDNS(ctx, conn, q, rrCache)
if rrCache == nil { if rrCache == nil {
// Check again if there is a responder from the firewall. // 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. // Save the request as open, as we don't know if there will be a connection or not.
network.SaveOpenDNSRequest(conn) 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) return reply(responder)
} }

View file

@ -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 // The verdict may change so any access to it must be guarded by the
// connection lock. // connection lock.
Verdict Verdict 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. // Access to Reason must be guarded by the connection lock.
Reason string Reason Reason
// ReasonContext may holds additional reason-specific information and
// any access must be guarded by the connection lock.
ReasonContext interface{}
// Started holds the number of seconds in UNIX epoch time at which // Started holds the number of seconds in UNIX epoch time at which
// the connection has been initated and first seen by the portmaster. // the connection has been initated and first seen by the portmaster.
// Staretd is only every set when creating a new connection object // 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. // inspectorData holds additional meta data for the inspectors.
// using the inspectors index as a map key. // using the inspectors index as a map key.
inspectorData map[uint8]interface{} 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 // profile and required for correct re-evaluation of a connections
// verdict. // 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 { func getProcessContext(proc *process.Process) ProcessContext {
@ -290,7 +304,7 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
Entity: entity, Entity: entity,
// meta // meta
Started: time.Now().Unix(), 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. // AcceptWithContext accepts the connection.
func (conn *Connection) AcceptWithContext(reason string, ctx interface{}) { func (conn *Connection) AcceptWithContext(reason, reasonOptionKey string, ctx interface{}) {
if !conn.SetVerdict(VerdictAccept, reason, ctx) { if !conn.SetVerdict(VerdictAccept, reason, reasonOptionKey, ctx) {
log.Warningf("filter: tried to accept %s, but current verdict is %s", conn, conn.Verdict) log.Warningf("filter: tried to accept %s, but current verdict is %s", conn, conn.Verdict)
} }
} }
// Accept is like AcceptWithContext but only accepts a reason. // Accept is like AcceptWithContext but only accepts a reason.
func (conn *Connection) Accept(reason string) { func (conn *Connection) Accept(reason, reasonOptionKey string) {
conn.AcceptWithContext(reason, nil) conn.AcceptWithContext(reason, reasonOptionKey, nil)
} }
// BlockWithContext blocks the connection. // BlockWithContext blocks the connection.
func (conn *Connection) BlockWithContext(reason string, ctx interface{}) { func (conn *Connection) BlockWithContext(reason, reasonOptionKey string, ctx interface{}) {
if !conn.SetVerdict(VerdictBlock, reason, ctx) { if !conn.SetVerdict(VerdictBlock, reason, reasonOptionKey, ctx) {
log.Warningf("filter: tried to block %s, but current verdict is %s", conn, conn.Verdict) 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. // Block is like BlockWithContext but does only accepts a reason.
func (conn *Connection) Block(reason string) { func (conn *Connection) Block(reason, reasonOptionKey string) {
conn.BlockWithContext(reason, nil) conn.BlockWithContext(reason, reasonOptionKey, nil)
} }
// DropWithContext drops the connection. // DropWithContext drops the connection.
func (conn *Connection) DropWithContext(reason string, ctx interface{}) { func (conn *Connection) DropWithContext(reason, reasonOptionKey string, ctx interface{}) {
if !conn.SetVerdict(VerdictDrop, reason, ctx) { if !conn.SetVerdict(VerdictDrop, reason, reasonOptionKey, ctx) {
log.Warningf("filter: tried to drop %s, but current verdict is %s", conn, conn.Verdict) 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. // Drop is like DropWithContext but does only accepts a reason.
func (conn *Connection) Drop(reason string) { func (conn *Connection) Drop(reason, reasonOptionKey string) {
conn.DropWithContext(reason, nil) conn.DropWithContext(reason, reasonOptionKey, nil)
} }
// DenyWithContext blocks or drops the link depending on the connection direction. // 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 { if conn.Inbound {
conn.DropWithContext(reason, ctx) conn.DropWithContext(reason, reasonOptionKey, ctx)
} else { } else {
conn.BlockWithContext(reason, ctx) conn.BlockWithContext(reason, reasonOptionKey, ctx)
} }
} }
// Deny is like DenyWithContext but only accepts a reason. // Deny is like DenyWithContext but only accepts a reason.
func (conn *Connection) Deny(reason string) { func (conn *Connection) Deny(reason, reasonOptionKey string) {
conn.DenyWithContext(reason, nil) conn.DenyWithContext(reason, reasonOptionKey, nil)
} }
// FailedWithContext marks the connection with VerdictFailed and stores the reason. // FailedWithContext marks the connection with VerdictFailed and stores the reason.
func (conn *Connection) FailedWithContext(reason string, ctx interface{}) { func (conn *Connection) FailedWithContext(reason, reasonOptionKey string, ctx interface{}) {
if !conn.SetVerdict(VerdictFailed, reason, ctx) { 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) 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. // Failed is like FailedWithContext but only accepts a string.
func (conn *Connection) Failed(reason string) { func (conn *Connection) Failed(reason, reasonOptionKey string) {
conn.FailedWithContext(reason, nil) conn.FailedWithContext(reason, reasonOptionKey, nil)
} }
// SetVerdict sets a new verdict for the connection, making sure it does not interfere with previous verdicts. // 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 { if newVerdict >= conn.Verdict {
conn.Verdict = newVerdict conn.Verdict = newVerdict
conn.Reason = reason conn.Reason.Msg = reason
conn.ReasonContext = reasonCtx 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 true
} }
return false return false
@ -490,7 +508,7 @@ func (conn *Connection) packetHandler() {
defaultFirewallHandler(conn, pkt) defaultFirewallHandler(conn, pkt)
} }
// log verdict // 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 // save does not touch any changing data
// must not be locked, will deadlock with cleaner functions // must not be locked, will deadlock with cleaner functions

View file

@ -124,15 +124,15 @@ func (conn *Connection) GetExtraRRs(ctx context.Context, request *dns.Msg) []dns
} }
// Create resource record with verdict and reason. // 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 { if err != nil {
log.Tracer(ctx).Warningf("filter: failed to add informational record to reply: %s", err) log.Tracer(ctx).Warningf("filter: failed to add informational record to reply: %s", err)
return nil return nil
} }
extra := []dns.RR{rr} extra := []dns.RR{rr}
// Add additional records from ReasonContext. // Add additional records from Reason.Context.
if rrProvider, ok := conn.ReasonContext.(nsutil.RRProvider); ok { if rrProvider, ok := conn.Reason.Context.(nsutil.RRProvider); ok {
rrs := rrProvider.GetExtraRRs(ctx, request) rrs := rrProvider.GetExtraRRs(ctx, request)
extra = append(extra, rrs...) extra = append(extra, rrs...)
} }

View file

@ -26,7 +26,7 @@ type LayeredProfile struct {
localProfile *Profile localProfile *Profile
layers []*Profile layers []*Profile
revisionCounter uint64 RevisionCounter uint64
validityFlag *abool.AtomicBool validityFlag *abool.AtomicBool
validityFlagLock sync.Mutex validityFlagLock sync.Mutex