mirror of
https://github.com/safing/portmaster
synced 2025-09-02 02:29:12 +00:00
Add option key responsible for the verdict
Also, expose the RevisionCounter
This commit is contained in:
parent
263eb0578a
commit
c09d32cf08
8 changed files with 176 additions and 133 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
|
||||||
conn.Entity.ResetLists()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var deciders = []func(context.Context, *network.Connection, packet.Packet) bool{
|
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() {
|
|
||||||
related, reason := checkRelation(conn)
|
// Auto permit is disabled for default action permit.
|
||||||
if related {
|
if p.DefaultAction() == profile.DefaultActionPermit {
|
||||||
conn.Accept(reason)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkDefaultAction(_ context.Context, conn *network.Connection, pkt packet.Packet) bool {
|
// Check if auto permit is disabled.
|
||||||
p := conn.Process().Profile()
|
if p.DisableAutoPermit() {
|
||||||
if p.DefaultAction() == profile.DefaultActionAsk {
|
return false
|
||||||
prompt(conn, pkt)
|
}
|
||||||
|
|
||||||
|
// Check for relation to auto permit.
|
||||||
|
related, reason := checkRelation(conn)
|
||||||
|
if related {
|
||||||
|
conn.Accept(reason, profile.CfgOptionDisableAutoPermitKey)
|
||||||
return true
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue