diff --git a/firewall/interception.go b/firewall/interception.go index dc430fa5..a30d28be 100644 --- a/firewall/interception.go +++ b/firewall/interception.go @@ -44,20 +44,22 @@ var ( ownPID = os.Getpid() ) -const configChangeEvent = "config change" -const profileConfigChangeEvent = "profile config change" +const ( + configChangeEvent = "config change" + profileConfigChangeEvent = "profile config change" + onSPNConnectEvent = "spn connect" +) func init() { // TODO: Move interception module to own package (dir). interceptionModule = modules.Register("interception", interceptionPrep, interceptionStart, interceptionStop, "base", "updates", "network", "notifications", "profiles") network.SetDefaultFirewallHandler(defaultHandler) - - // setup event callback when spn has connected - captain.ResetConnections = resetAllConnectionsVerdict } func interceptionPrep() error { + // Reset connections every time configuration changes + // this will be triggered on spn enable/disable err := interceptionModule.RegisterEventHook( "config", configChangeEvent, @@ -71,10 +73,26 @@ func interceptionPrep() error { _ = fmt.Errorf("failed registering event hook: %w", err) } + // Reset connections every time profile changes err = interceptionModule.RegisterEventHook( "profiles", profileConfigChangeEvent, - "firewall config change event", + "firewall profile change event", + func(ctx context.Context, _ interface{}) error { + resetAllConnections() + return nil + }, + ) + if err != nil { + _ = fmt.Errorf("failed registering event hook: %w", err) + } + + // Reset connections when spn is connected + // disconnecting is triggered on config change event because disconnection happens instantly + err = interceptionModule.RegisterEventHook( + "captain", + onSPNConnectEvent, + "firewall spn connect event", func(ctx context.Context, _ interface{}) error { resetAllConnections() return nil @@ -92,11 +110,12 @@ func interceptionPrep() error { } func resetAllConnections() { - log.Critical("Reseting all connections") + // Resetting will force all the connection to be evaluated by the firewall again + // this will set new verdicts if configuration was update or spn has been disabled or enabled log.Info("interception: resetting all connections") - err := interception.DeleteAllConnections() + err := interception.ResetAllConnections() if err != nil { - log.Criticalf("failed to run ResetAllExternalConnections: %q", err) + log.Errorf("failed to reset all connections: %q", err) } for _, id := range network.GetAllIDs() { conn, err := getConnectionByID(id) @@ -106,6 +125,8 @@ func resetAllConnections() { if !captain.IsExcepted(conn.Entity.IP) { conn.SetFirewallHandler(initialHandler) + // Don't keep the previous tunneled value + conn.Tunneled = false // Reset entity if it exists. if conn.Entity != nil { conn.Entity.ResetLists() @@ -172,8 +193,6 @@ func handlePacket(ctx context.Context, pkt packet.Packet) { return } - //log.Errorf("%s -> %s", pkt, conn.Verdict) - // handle packet conn.HandlePacket(pkt) } @@ -240,37 +259,6 @@ func getConnectionByID(id string) (*network.Connection, error) { return connection, nil } -func resetAllConnectionsVerdict(ctx context.Context) { - resetAllConnections() - // interception.CloseAllConnections() - // network.ClearConnections() - // log.Critical("Clearing connections") - // interception.CloseAllConnections() - // ids := network.GetAllIDs() - // for _, id := range ids { - // connI, err, _ := getConnectionSingleInflight.Do(id, func() (interface{}, error) { - // // First, check for an existing connection. - // conn, ok := network.GetConnection(id) - // if ok { - // return conn, nil - // } - - // return nil, nil - // }) - - // if err != nil || connI == nil { - // log.Errorf("Null connection with id %s", id) - // continue - // } - - // conn := connI.(*network.Connection) //nolint:forcetypeassert // Can only be a *network.Connection. - // log.Errorf("Resetting connection for %s:%d", conn.LocalIP, conn.Entity.Port) - // // conn.Reset("Initialling SPN", "") - // checkTunneling(ctx, conn, nil) - // DecideOnConnection(ctx, conn, nil) - // } -} - // fastTrackedPermit quickly permits certain network critical or internal connections. func fastTrackedPermit(pkt packet.Packet) (handled bool) { meta := pkt.Info() diff --git a/firewall/interception/connection_manager_linux.go b/firewall/interception/connection_manager_linux.go deleted file mode 100644 index 8f3b46fd..00000000 --- a/firewall/interception/connection_manager_linux.go +++ /dev/null @@ -1,95 +0,0 @@ -package interception - -import ( - "encoding/binary" - "fmt" - "net" - - ct "github.com/florianl/go-conntrack" - - "github.com/safing/portbase/log" - "github.com/safing/portmaster/firewall/interception/nfq" -) - -// CloseAllConnections closes all active connection on conntrack. -func CloseAllConnections() error { - nfct, err := ct.Open(&ct.Config{}) - if err != nil { - return err - } - defer func() { _ = nfct.Close() }() - - connections, err := nfct.Dump(ct.Conntrack, ct.IPv4) - if err != nil { - return err - } - log.Criticalf("Number of connections: %d", len(connections)) - for _, connection := range connections { - fmt.Printf("[%2d] %s - %s\n", connection.Origin.Proto.Number, connection.Origin.Src, connection.Origin.Dst) - err := nfct.Delete(ct.Conntrack, ct.IPv4, connection) - log.Errorf("Error deleting connection %q", err) - } - - return nil -} - -// DeleteAllConnections deletes all entries from conntrack table. -func DeleteAllConnections() error { - nfct, err := ct.Open(&ct.Config{}) - if err != nil { - return err - } - defer func() { _ = nfct.Close() }() - - connections, err := getAllPermanentConnections(nfct) - - for _, connection := range connections { - _ = nfct.Delete(ct.Conntrack, ct.IPv4, connection) - } - - return err -} - -// DeleteConnection deletes a specific connection. -func DeleteConnection(sourceIP net.IP, sourcePort uint16, destinationIP net.IP, destinationPort uint16) error { - nfct, err := ct.Open(&ct.Config{}) - if err != nil { - return err - } - defer func() { _ = nfct.Close() }() - - filter := &ct.IPTuple{Src: &sourceIP, Dst: &destinationIP, Proto: &ct.ProtoTuple{SrcPort: &sourcePort, DstPort: &destinationPort}} - connectionFilter := ct.Con{ - Origin: filter, - } - - connections, _ := nfct.Get(ct.Conntrack, ct.IPv4, connectionFilter) - for _, connection := range connections { - _ = nfct.Delete(ct.Conntrack, ct.IPv4, connection) - } - - connectionFilter.Origin = nil - connectionFilter.Reply = filter - connections, err = nfct.Get(ct.Conntrack, ct.IPv4, connectionFilter) - for _, connection := range connections { - _ = nfct.Delete(ct.Conntrack, ct.IPv4, connection) - } - return err -} - -func getAllPermanentConnections(nfct *ct.Nfct) ([]ct.Con, error) { - permanentFlags := []uint32{nfq.MarkAccept, nfq.MarkBlock, nfq.MarkDrop, nfq.MarkAcceptAlways, nfq.MarkBlockAlways, nfq.MarkDropAlways, nfq.MarkRerouteSPN} - filter := ct.FilterAttr{} - filter.MarkMask = []byte{0xFF, 0xFF, 0xFF, 0xFF} - filter.Mark = []byte{0x00, 0x00, 0x00, 0x00} // 4 zeros starting value - connections := make([]ct.Con, 0) - for _, mark := range permanentFlags { - binary.BigEndian.PutUint32(filter.Mark, mark) // Little endian is in reverse not sure why. BigEndian makes it in correct order. - currentConnections, err := nfct.Query(ct.Conntrack, ct.IPv4, filter) - if err != nil { - return nil, err - } - connections = append(connections, currentConnections...) - } - return connections, nil -} diff --git a/firewall/interception/nfq/conntrack.go b/firewall/interception/nfq/conntrack.go new file mode 100644 index 00000000..9066eef4 --- /dev/null +++ b/firewall/interception/nfq/conntrack.go @@ -0,0 +1,53 @@ +//go:build linux + +package nfq + +import ( + "encoding/binary" + + ct "github.com/florianl/go-conntrack" +) + +// DeleteAllMarkedConnection deletes all marked entries from the conntrack table. +func DeleteAllMarkedConnection() error { + nfct, err := ct.Open(&ct.Config{}) + if err != nil { + return err + } + defer func() { _ = nfct.Close() }() + + // Delete all ipv4 marked connections + connections := getAllMarkedConnections(nfct, ct.IPv4) + for _, connection := range connections { + _ = nfct.Delete(ct.Conntrack, ct.IPv4, connection) + } + + // Delete all ipv6 marked connections + connections = getAllMarkedConnections(nfct, ct.IPv6) + for _, connection := range connections { + _ = nfct.Delete(ct.Conntrack, ct.IPv6, connection) + } + + return nil +} + +func getAllMarkedConnections(nfct *ct.Nfct, f ct.Family) []ct.Con { + // initialize variables + permanentFlags := [...]uint32{MarkAccept, MarkBlock, MarkDrop, MarkAcceptAlways, MarkBlockAlways, MarkDropAlways, MarkRerouteNS, MarkRerouteSPN} + filter := ct.FilterAttr{} + filter.MarkMask = []byte{0xFF, 0xFF, 0xFF, 0xFF} + filter.Mark = []byte{0x00, 0x00, 0x00, 0x00} // 4 zeros starting value + connections := make([]ct.Con, 0) + + // get all connections from the specified family (ipv4 or ipv6) + for _, mark := range permanentFlags { + binary.BigEndian.PutUint32(filter.Mark, mark) // Little endian is in reverse not sure why. BigEndian makes it in correct order. + currentConnections, err := nfct.Query(ct.Conntrack, f, filter) + if err != nil { + continue + } + connections = append(connections, currentConnections...) + } + + return connections +} diff --git a/firewall/interception/nfqueue_linux.go b/firewall/interception/nfqueue_linux.go index 488cc7a4..c72af96b 100644 --- a/firewall/interception/nfqueue_linux.go +++ b/firewall/interception/nfqueue_linux.go @@ -341,3 +341,8 @@ func (dnfq *disabledNfQueue) PacketChannel() <-chan packet.Packet { } func (dnfq *disabledNfQueue) Destroy() {} + +// ResetAllConnections resets all connections so they are forced to go thought the firewall again +func ResetAllConnections() error { + return nfq.DeleteAllMarkedConnection() +} diff --git a/network/connection.go b/network/connection.go index c774e52a..3dd2e1ac 100644 --- a/network/connection.go +++ b/network/connection.go @@ -451,12 +451,6 @@ func GetAllIDs() []string { return append(conns.keys(), dnsConns.keys()...) } -// ClearConnections Clear all connections. -func ClearConnections() { - dnsConns = newConnectionStore() - conns = newConnectionStore() -} - // SetLocalIP sets the local IP address together with its network scope. The // connection is not locked for this. func (conn *Connection) SetLocalIP(ip net.IP) { @@ -526,22 +520,6 @@ func (conn *Connection) Failed(reason, reasonOptionKey string) { conn.FailedWithContext(reason, reasonOptionKey, nil) } -// Reset resets all values of the connection. -func (conn *Connection) Reset(reason, reasonOptionKey string) { - conn.Verdict.Current = VerdictUndecided - conn.Verdict.Previous = VerdictUndecided - conn.Verdict.User = VerdictUndecided - conn.Reason.Msg = reason - conn.Reason.Context = nil - - conn.Reason.OptionKey = "" - conn.Reason.Profile = "" - if reasonOptionKey != "" && conn.Process() != nil { - conn.Reason.OptionKey = reasonOptionKey - conn.Reason.Profile = conn.Process().Profile().GetProfileSource(conn.Reason.OptionKey) - } -} - // SetVerdict sets a new verdict for the connection, making sure it does not interfere with previous verdicts. func (conn *Connection) SetVerdict(newVerdict Verdict, reason, reasonOptionKey string, reasonCtx interface{}) (ok bool) { // if newVerdict >= conn.Verdict.Current {