Skip to content

Commit

Permalink
Merge pull request #1453 from safing/revamp/verdicts
Browse files Browse the repository at this point in the history
Revamped verdict handling
  • Loading branch information
ppacher authored Mar 20, 2024
2 parents d17f391 + 67d0d11 commit b30fd00
Show file tree
Hide file tree
Showing 13 changed files with 55 additions and 154 deletions.
11 changes: 1 addition & 10 deletions firewall/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,15 +177,6 @@ func FilterResolvedDNS(
return rrCache
}

// Finalize verdict.
defer func() {
// Reset from previous filtering.
conn.Verdict.Active = network.VerdictUndecided
conn.Verdict.Worst = network.VerdictUndecided
// Update all values again.
finalizeVerdict(conn)
}()

// special grant for connectivity domains
if checkConnectivityDomain(ctx, conn, layeredProfile, nil) {
// returns true if check triggered
Expand All @@ -197,7 +188,7 @@ func FilterResolvedDNS(

// Filter dns records and return if the query is blocked.
rrCache = filterDNSResponse(ctx, conn, layeredProfile, rrCache, sysResolver)
if conn.Verdict.Active == network.VerdictBlock {
if conn.Verdict == network.VerdictBlock {
return rrCache
}

Expand Down
6 changes: 3 additions & 3 deletions firewall/inspection/inspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func RunInspectors(conn *network.Connection, pkt packet.Packet) (network.Verdict
}

// check if the active verdict is already past the inspection criteria.
if conn.Verdict.Active > inspectVerdicts[key] {
if conn.Verdict > inspectVerdicts[key] {
activeInspectors[key] = true
continue
}
Expand All @@ -86,11 +86,11 @@ func RunInspectors(conn *network.Connection, pkt packet.Packet) (network.Verdict
continueInspection = true
case BLOCK_CONN:
conn.SetVerdict(network.VerdictBlock, "", "", nil)
verdict = conn.Verdict.Active
verdict = conn.Verdict
activeInspectors[key] = true
case DROP_CONN:
conn.SetVerdict(network.VerdictDrop, "", "", nil)
verdict = conn.Verdict.Active
verdict = conn.Verdict
activeInspectors[key] = true
case STOP_INSPECTING:
activeInspectors[key] = true
Expand Down
2 changes: 1 addition & 1 deletion firewall/interception/windowskext/kext.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ func UpdateVerdict(conn *network.Connection) error {
localPort: conn.LocalPort,
remoteIP: ipAddressToArray(conn.Entity.IP, isIpv6 == 1),
remotePort: conn.Entity.Port,
verdict: uint8(conn.Verdict.Active),
verdict: uint8(conn.Verdict),
}

// Make driver request
Expand Down
2 changes: 1 addition & 1 deletion firewall/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func decideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
case profile.DefaultActionAsk:
// Only prompt if there has not been a decision already.
// This prevents prompts from being created when re-evaluating connections.
if conn.Verdict.Firewall == network.VerdictUndecided {
if conn.Verdict == network.VerdictUndecided {
prompt(ctx, conn)
}
default:
Expand Down
81 changes: 18 additions & 63 deletions firewall/packet_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"github.com/safing/portmaster/network"
"github.com/safing/portmaster/network/netutils"
"github.com/safing/portmaster/network/packet"
"github.com/safing/portmaster/network/reference"
"github.com/safing/portmaster/process"
"github.com/safing/spn/access"
)
Expand Down Expand Up @@ -132,21 +131,25 @@ func resetConnectionVerdict(ctx context.Context, conn *network.Connection) (verd
}

tracer.Debugf("filter: re-evaluating verdict of %s", conn)
previousVerdict := conn.Verdict.Firewall
previousVerdict := conn.Verdict

// Apply privacy filter and check tunneling.
FilterConnection(ctx, conn, nil, true, true)

// Stop existing SPN tunnel if not needed anymore.
if conn.Verdict.Active != network.VerdictRerouteToTunnel && conn.TunnelContext != nil {
if conn.Verdict != network.VerdictRerouteToTunnel && conn.TunnelContext != nil {
err := conn.TunnelContext.StopTunnel()
if err != nil {
tracer.Debugf("filter: failed to stopped unneeded tunnel: %s", err)
}
}

// Save if verdict changed.
if conn.Verdict.Firewall != previousVerdict {
if conn.Verdict != previousVerdict {
err := interception.UpdateVerdictOfConnection(conn)
if err != nil {
log.Debugf("filter: failed to update connection verdict: %s", err)
}
conn.Save()
tracer.Infof("filter: verdict of connection %s changed from %s to %s", conn, previousVerdict.Verb(), conn.VerdictVerb())

Expand Down Expand Up @@ -368,16 +371,17 @@ func fastTrackHandler(conn *network.Connection, pkt packet.Packet) {
fastTrackedVerdict, permanent := fastTrackedPermit(conn, pkt)
if fastTrackedVerdict != network.VerdictUndecided {
// Set verdict on connection.
conn.Verdict.Active = fastTrackedVerdict
conn.Verdict.Firewall = fastTrackedVerdict
conn.Verdict = fastTrackedVerdict

// Apply verdict to (real) packet.
if !pkt.InfoOnly() {
issueVerdict(conn, pkt, fastTrackedVerdict, permanent)
}

// Stop handler if permanent.
if permanent {
conn.SetVerdict(fastTrackedVerdict, "fast-tracked", "", nil)
conn.Verdict.Worst = fastTrackedVerdict

// Do not finalize verdict, as we are missing necessary data.
conn.StopFirewallHandler()
}
Expand Down Expand Up @@ -447,7 +451,7 @@ func filterHandler(conn *network.Connection, pkt packet.Packet) {

// End directly, as no other processing is necessary.
conn.StopFirewallHandler()
finalizeVerdict(conn)

issueVerdict(conn, pkt, 0, true)
return
}
Expand Down Expand Up @@ -504,19 +508,17 @@ func FilterConnection(ctx context.Context, conn *network.Connection, pkt packet.
checkTunneling(ctx, conn)
}

// Handle verdict records and transitions.
finalizeVerdict(conn)

// Request tunneling if no tunnel is set and connection should be tunneled.
if conn.Verdict.Active == network.VerdictRerouteToTunnel &&
if conn.Verdict == network.VerdictRerouteToTunnel &&
conn.TunnelContext == nil {
err := requestTunneling(ctx, conn)
if err != nil {
if err == nil {
conn.ConnectionEstablished = true
} else {
// Set connection to failed, but keep tunneling data.
// The tunneling data makes connection easy to recognize as a failed SPN
// connection and the data will help with debugging and displaying in the UI.
conn.Failed(fmt.Sprintf("failed to request tunneling: %s", err), "")
finalizeVerdict(conn)
}
}
}
Expand Down Expand Up @@ -563,8 +565,8 @@ func issueVerdict(conn *network.Connection, pkt packet.Packet, verdict network.V
}

// do not allow to circumvent decision: e.g. to ACCEPT packets from a DROP-ed connection
if verdict < conn.Verdict.Active {
verdict = conn.Verdict.Active
if verdict < conn.Verdict {
verdict = conn.Verdict
}

var err error
Expand Down Expand Up @@ -622,53 +624,6 @@ var verdictRating = []network.Verdict{
network.VerdictUndecided,
}

func finalizeVerdict(conn *network.Connection) {
// Update worst verdict at the end.
defer func() {
for _, worstVerdict := range verdictRating {
if conn.Verdict.Firewall == worstVerdict {
conn.Verdict.Worst = worstVerdict
}
}
}()

// Check for non-applicable verdicts.
// The earlier and clearer we do this, the better.
switch conn.Verdict.Firewall { //nolint:exhaustive
case network.VerdictUndecided, network.VerdictUndeterminable, network.VerdictFailed:
if conn.Inbound {
conn.Verdict.Active = network.VerdictDrop
} else {
conn.Verdict.Active = network.VerdictBlock
}
return
}

// Apply firewall verdict to active verdict.
switch {
case conn.Verdict.Active == network.VerdictUndecided:
// Apply first verdict without change.
conn.Verdict.Active = conn.Verdict.Firewall

case conn.Verdict.Worst == network.VerdictBlock ||
conn.Verdict.Worst == network.VerdictDrop ||
conn.Verdict.Worst == network.VerdictFailed ||
conn.Verdict.Worst == network.VerdictUndeterminable:
// Always allow to change verdict from any real initial/worst non-allowed state.
// Note: This check needs to happen before updating the Worst verdict.
conn.Verdict.Active = conn.Verdict.Firewall

case reference.IsPacketProtocol(conn.Entity.Protocol):
// For known packet protocols, apply firewall verdict unchanged.
conn.Verdict.Active = conn.Verdict.Firewall

case conn.Verdict.Active != conn.Verdict.Firewall:
// For all other protocols (most notably, stream protocols), always block after the first change.
// Block in both directions, as there is a live connection, which we want to actively kill.
conn.Verdict.Active = network.VerdictBlock
}
}

// func tunnelHandler(pkt packet.Packet) {
// tunnelInfo := GetTunnelInfo(pkt.Info().Dst)
// if tunnelInfo == nil {
Expand Down
2 changes: 1 addition & 1 deletion firewall/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func checkTunneling(ctx context.Context, conn *network.Connection) {
case conn.Inbound:
// Can't tunnel incoming connections.
return
case conn.Verdict.Firewall != network.VerdictAccept:
case conn.Verdict != network.VerdictAccept:
// Connection will be blocked.
return
case conn.IPProtocol != packet.TCP && conn.IPProtocol != packet.UDP:
Expand Down
6 changes: 3 additions & 3 deletions nameserver/nameserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
}
}

switch conn.Verdict.Active {
switch conn.Verdict {
// We immediately save blocked, dropped or failed verdicts so
// they pop up in the UI.
case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed, network.VerdictRerouteToNameserver, network.VerdictRerouteToTunnel:
Expand Down Expand Up @@ -245,7 +245,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
}

// Check if there is a Verdict to act upon.
switch conn.Verdict.Active { //nolint:exhaustive // Only checking for specific values.
switch conn.Verdict { //nolint:exhaustive // Only checking for specific values.
case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed:
tracer.Infof(
"nameserver: returning %s response for %s to %s",
Expand Down Expand Up @@ -325,7 +325,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
}

// Check if there is a Verdict to act upon.
switch conn.Verdict.Active { //nolint:exhaustive // Only checking for specific values.
switch conn.Verdict { //nolint:exhaustive // Only checking for specific values.
case network.VerdictBlock, network.VerdictDrop, network.VerdictFailed:
tracer.Infof(
"nameserver: returning %s response for %s to %s",
Expand Down
15 changes: 2 additions & 13 deletions netquery/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,7 @@ func convertConnection(conn *network.Connection) (*Conn, error) {
IPProtocol: conn.IPProtocol,
LocalIP: conn.LocalIP.String(),
LocalPort: conn.LocalPort,
FirewallVerdict: conn.Verdict.Firewall,
ActiveVerdict: conn.Verdict.Active,
WorstVerdict: conn.Verdict.Worst,
ActiveVerdict: conn.Verdict,
Started: time.Unix(conn.Started, 0),
Tunneled: conn.Tunneled,
Encrypted: conn.Encrypted,
Expand All @@ -207,16 +205,7 @@ func convertConnection(conn *network.Connection) (*Conn, error) {
c.Type = ""
}

switch conn.Verdict.Firewall {
case network.VerdictAccept, network.VerdictRerouteToNameserver, network.VerdictRerouteToTunnel:
accepted := true
c.Allowed = &accepted
case network.VerdictBlock, network.VerdictDrop:
allowed := false
c.Allowed = &allowed
case network.VerdictUndecided, network.VerdictUndeterminable, network.VerdictFailed:
c.Allowed = nil
}
c.Allowed = &conn.ConnectionEstablished

if conn.Ended > 0 {
ended := time.Unix(conn.Ended, 0)
Expand Down
11 changes: 4 additions & 7 deletions network/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ func AddNetworkDebugData(di *debug.Info, profile, where string) {
debugConns []*Connection
accepted int
total int
transitioning int
)

for maybeConn := range it.Next {
// Switch to correct type.
conn, ok := maybeConn.(*Connection)
Expand Down Expand Up @@ -169,15 +169,13 @@ func AddNetworkDebugData(di *debug.Info, profile, where string) {

// Count.
total++
switch conn.Verdict.Firewall { //nolint:exhaustive
switch conn.Verdict { //nolint:exhaustive
case VerdictAccept,
VerdictRerouteToNameserver,
VerdictRerouteToTunnel:

accepted++
}
if conn.Verdict.Active != conn.Verdict.Firewall {
transitioning++
}

// Add to list.
debugConns = append(debugConns, conn)
Expand All @@ -186,10 +184,9 @@ func AddNetworkDebugData(di *debug.Info, profile, where string) {
// Add it all.
di.AddSection(
fmt.Sprintf(
"Network: %d/%d [~%d] Connections",
"Network: %d/%d Connections",
accepted,
total,
transitioning,
),
debug.UseCodeSection|debug.AddContentLineBreaks,
buildNetworkDebugInfoData(debugConns),
Expand Down
30 changes: 3 additions & 27 deletions network/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,7 @@ var connectionTestData = []*Connection{
Country: "",
ASN: 0,
},
Verdict: struct {
Worst Verdict
Active Verdict
Firewall Verdict
}{
Worst: 2,
Active: 2,
Firewall: 2,
},
Verdict: 2,
Reason: Reason{
Msg: "incoming connection blocked by default",
OptionKey: "filter/serviceEndpoints",
Expand Down Expand Up @@ -88,15 +80,7 @@ var connectionTestData = []*Connection{
Country: "DE",
ASN: 16509,
},
Verdict: struct {
Worst Verdict
Active Verdict
Firewall Verdict
}{
Worst: 2,
Active: 2,
Firewall: 2,
},
Verdict: 2,
Reason: Reason{
Msg: "default permit",
OptionKey: "filter/defaultAction",
Expand Down Expand Up @@ -139,15 +123,7 @@ var connectionTestData = []*Connection{
Country: "US",
ASN: 15169,
},
Verdict: struct {
Worst Verdict
Active Verdict
Firewall Verdict
}{
Worst: 2,
Active: 2,
Firewall: 2,
},
Verdict: 2,
Reason: Reason{
Msg: "default permit",
OptionKey: "filter/defaultAction",
Expand Down
Loading

0 comments on commit b30fd00

Please sign in to comment.