diff --git a/firewall/dns.go b/firewall/dns.go index 498d3a52d..b0ac071a9 100644 --- a/firewall/dns.go +++ b/firewall/dns.go @@ -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 @@ -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 } diff --git a/firewall/inspection/inspection.go b/firewall/inspection/inspection.go index 9481932c0..92de03453 100644 --- a/firewall/inspection/inspection.go +++ b/firewall/inspection/inspection.go @@ -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 } @@ -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 diff --git a/firewall/interception/windowskext/kext.go b/firewall/interception/windowskext/kext.go index 351d1b747..a7e6a1c36 100644 --- a/firewall/interception/windowskext/kext.go +++ b/firewall/interception/windowskext/kext.go @@ -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 diff --git a/firewall/master.go b/firewall/master.go index 4183d5610..8c4b1e596 100644 --- a/firewall/master.go +++ b/firewall/master.go @@ -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: diff --git a/firewall/packet_handler.go b/firewall/packet_handler.go index 093df3191..65105e156 100644 --- a/firewall/packet_handler.go +++ b/firewall/packet_handler.go @@ -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" ) @@ -132,13 +131,13 @@ 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) @@ -146,7 +145,11 @@ func resetConnectionVerdict(ctx context.Context, conn *network.Connection) (verd } // 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()) @@ -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() } @@ -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 } @@ -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) } } } @@ -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 @@ -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 { diff --git a/firewall/tunnel.go b/firewall/tunnel.go index cadab2eaf..013bec7bb 100644 --- a/firewall/tunnel.go +++ b/firewall/tunnel.go @@ -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: diff --git a/nameserver/nameserver.go b/nameserver/nameserver.go index 5243f8c4b..464db7825 100644 --- a/nameserver/nameserver.go +++ b/nameserver/nameserver.go @@ -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: @@ -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", @@ -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", diff --git a/netquery/manager.go b/netquery/manager.go index 810782a8b..34b779b5c 100644 --- a/netquery/manager.go +++ b/netquery/manager.go @@ -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, @@ -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) diff --git a/network/api.go b/network/api.go index 4635b49aa..c59b5aaf6 100644 --- a/network/api.go +++ b/network/api.go @@ -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) @@ -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) @@ -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), diff --git a/network/api_test.go b/network/api_test.go index 0e908c8ca..626475271 100644 --- a/network/api_test.go +++ b/network/api_test.go @@ -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", @@ -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", @@ -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", diff --git a/network/connection.go b/network/connection.go index 6954b0f1d..b1ed96fe1 100644 --- a/network/connection.go +++ b/network/connection.go @@ -121,17 +121,9 @@ type Connection struct { //nolint:maligned // TODO: fix alignment // Verdict holds the decisions that are made for a connection // The verdict may change so any access to it must be guarded by the // connection lock. - Verdict struct { - // Worst verdict holds the worst verdict that was assigned to this - // connection from a privacy/security perspective. - Worst Verdict - // Active verdict holds the verdict that Portmaster will respond with. - // This is different from the Firewall verdict in order to guarantee proper - // transition between verdicts that need the connection to be re-established. - Active Verdict - // Firewall holds the last (most recent) decision by the firewall. - Firewall Verdict - } + Verdict Verdict + // Whether or not the connection has been established at least once. + ConnectionEstablished bool // Reason holds information justifying the verdict, as well as additional // information about the reason. // Access to Reason must be guarded by the connection lock. @@ -722,22 +714,15 @@ func (conn *Connection) SetVerdict(newVerdict Verdict, reason, reasonOptionKey s return true // TODO: remove } -// SetVerdictDirectly sets the firewall verdict. +// SetVerdictDirectly sets the verdict. func (conn *Connection) SetVerdictDirectly(newVerdict Verdict) { - conn.Verdict.Firewall = newVerdict + conn.Verdict = newVerdict } // VerdictVerb returns the verdict as a verb, while taking any special states // into account. func (conn *Connection) VerdictVerb() string { - if conn.Verdict.Firewall == conn.Verdict.Active { - return conn.Verdict.Firewall.Verb() - } - return fmt.Sprintf( - "%s (transitioning to %s)", - conn.Verdict.Active.Verb(), - conn.Verdict.Firewall.Verb(), - ) + return conn.Verdict.Verb() } // DataIsComplete returns whether all information about the connection is @@ -766,6 +751,14 @@ func (conn *Connection) SaveWhenFinished() { func (conn *Connection) Save() { conn.UpdateMeta() + switch conn.Verdict { + case VerdictAccept, VerdictRerouteToNameserver: + conn.ConnectionEstablished = true + case VerdictRerouteToTunnel: + // this is already handled when the connection tunnel has been + // established. + } + // Do not save/update until data is complete. if !conn.DataIsComplete() { return @@ -1003,7 +996,7 @@ func packetHandlerHandleConn(ctx context.Context, conn *Connection, pkt packet.P switch { case conn.DataIsComplete(): tracer.Infof("filter: connection %s %s: %s", conn, conn.VerdictVerb(), conn.Reason.Msg) - case conn.Verdict.Firewall != VerdictUndecided: + case conn.Verdict != VerdictUndecided: tracer.Debugf("filter: connection %s fast-tracked", pkt) default: tracer.Debugf("filter: gathered data on connection %s", conn) diff --git a/network/dns.go b/network/dns.go index 4f481b320..a0bef466f 100644 --- a/network/dns.go +++ b/network/dns.go @@ -208,7 +208,7 @@ func writeOpenDNSRequestsToDB() { // ReplyWithDNS creates a new reply to the given request with the data from the RRCache, and additional informational records. func (conn *Connection) ReplyWithDNS(ctx context.Context, request *dns.Msg) *dns.Msg { // Select request responder. - switch conn.Verdict.Active { + switch conn.Verdict { case VerdictBlock: return nsutil.BlockIP().ReplyWithDNS(ctx, request) case VerdictDrop: @@ -229,7 +229,7 @@ func (conn *Connection) ReplyWithDNS(ctx context.Context, request *dns.Msg) *dns func (conn *Connection) GetExtraRRs(ctx context.Context, request *dns.Msg) []dns.RR { // Select level to add the verdict record with. var level log.Severity - switch conn.Verdict.Active { + switch conn.Verdict { case VerdictFailed: level = log.ErrorLevel case VerdictUndecided, VerdictUndeterminable, diff --git a/network/metrics.go b/network/metrics.go index 7101e9218..66f19e1b1 100644 --- a/network/metrics.go +++ b/network/metrics.go @@ -140,7 +140,7 @@ func (conn *Connection) addToMetrics() { } // Check the verdict. - switch conn.Verdict.Firewall { //nolint:exhaustive // Not critical. + switch conn.Verdict { //nolint:exhaustive // Not critical. case VerdictBlock, VerdictDrop: blockedOutConnCounter.Inc() conn.addedToMetrics = true