From 49e79fe3fdd3b5f3a03a3a36e398d8604b9922bc Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 24 May 2022 11:18:23 +0200 Subject: [PATCH] Detect responses to multi/broadcast queries --- firewall/master.go | 43 ++++++++++++++++++++++++++++++++++++++++++ firewall/tunnel.go | 4 ++-- netenv/addresses.go | 14 ++++++-------- network/multicast.go | 43 ++++++++++++++++++++++++++++++++++++++++++ network/netutils/ip.go | 31 ++++++++++++++++++++++++++++++ process/process.go | 26 +++++++++++++++++++++++++ 6 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 network/multicast.go diff --git a/firewall/master.go b/firewall/master.go index 16dc2b5f..4be457aa 100644 --- a/firewall/master.go +++ b/firewall/master.go @@ -3,7 +3,9 @@ package firewall import ( "context" "fmt" + "net" "path/filepath" + "strconv" "strings" "github.com/agext/levenshtein" @@ -41,6 +43,7 @@ type deciderFn func(context.Context, *network.Connection, *profile.LayeredProfil var defaultDeciders = []deciderFn{ checkPortmasterConnection, checkSelfCommunication, + checkIfBroadcastReply, checkConnectionType, checkConnectionScope, checkEndpointLists, @@ -182,6 +185,46 @@ func checkSelfCommunication(ctx context.Context, conn *network.Connection, _ *pr return false } +func checkIfBroadcastReply(ctx context.Context, conn *network.Connection, _ *profile.LayeredProfile, _ packet.Packet) bool { + // Only check inbound connections. + if !conn.Inbound { + return false + } + // Only check if the process has been identified. + if !conn.Process().IsIdentified() { + return false + } + + // Check if the remote IP is part of a local network. + localNet, err := netenv.GetLocalNetwork(conn.Entity.IP) + if err != nil { + log.Tracer(ctx).Warningf("filter: failed to get local network: %s", err) + return false + } + if localNet == nil { + return false + } + + // Search for a matching requesting connection. + requestingConn := network.GetMulticastRequestConn(conn, localNet) + if requestingConn == nil { + return false + } + + conn.Accept( + fmt.Sprintf( + "response to multi/broadcast query to %s/%s", + packet.IPProtocol(requestingConn.Entity.Protocol), + net.JoinHostPort( + requestingConn.Entity.IP.String(), + strconv.Itoa(int(requestingConn.Entity.Port)), + ), + ), + "", + ) + return true +} + func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool { // DNS request from the system resolver require a special decision process, // because the original requesting process is not known. Here, we only check diff --git a/firewall/tunnel.go b/firewall/tunnel.go index 08adff33..28f0e37d 100644 --- a/firewall/tunnel.go +++ b/firewall/tunnel.go @@ -42,10 +42,10 @@ func checkTunneling(ctx context.Context, conn *network.Connection, pkt packet.Pa } // Check more extensively for Local/LAN connections. - myNet, err := netenv.IsMyNet(conn.Entity.IP) + localNet, err := netenv.GetLocalNetwork(conn.Entity.IP) if err != nil { log.Warningf("firewall: failed to check if %s is in my net: %s", conn.Entity.IP, err) - } else if myNet { + } else if localNet != nil { // With IPv6, just checking the IP scope is not enough, as the host very // likely has a public IPv6 address. // Don't tunnel LAN connections. diff --git a/netenv/addresses.go b/netenv/addresses.go index 14762c78..67c4c999 100644 --- a/netenv/addresses.go +++ b/netenv/addresses.go @@ -152,11 +152,9 @@ func IsMyIP(ip net.IP) (yes bool, err error) { return false, nil } -// IsMyNet returns whether the given IP is currently in the host's broadcast -// domain - ie. the networks that the host is directly attached to. -// Function is optimized with the assumption that is unlikely that the IP is -// in the broadcast domain. -func IsMyNet(ip net.IP) (yes bool, err error) { +// GetLocalNetwork uses the given IP to search for a network configured on the +// device and returns it. +func GetLocalNetwork(ip net.IP) (myNet *net.IPNet, err error) { myNetworksLock.Lock() defer myNetworksLock.Unlock() @@ -164,16 +162,16 @@ func IsMyNet(ip net.IP) (yes bool, err error) { if myNetworksNetworkChangedFlag.IsSet() { err := refreshMyNetworks() if err != nil { - return false, err + return nil, err } } // Check if the IP address is in my networks. for _, myNet := range myNetworks { if myNet.Contains(ip) { - return true, nil + return myNet, nil } } - return false, nil + return nil, nil } diff --git a/network/multicast.go b/network/multicast.go new file mode 100644 index 00000000..82cb2173 --- /dev/null +++ b/network/multicast.go @@ -0,0 +1,43 @@ +package network + +import ( + "net" + + "github.com/safing/portmaster/network/netutils" +) + +// GetMulticastRequestConn searches for and returns the requesting connnection +// of a possible multicast/broadcast response. +func GetMulticastRequestConn(responseConn *Connection, responseFromNet *net.IPNet) *Connection { + // Calculate the broadcast address the query would have gone to. + responseNetBroadcastIP := netutils.GetBroadcastAddress(responseFromNet.IP, responseFromNet.Mask) + + // Find requesting multicast/broadcast connection. + for _, conn := range conns.clone() { + switch { + case conn.Inbound: + // Ignore incoming connections. + //case conn.Ended != 0: + // Ignore ended connections. + case conn.Entity.Protocol != responseConn.Entity.Protocol: + // Ignore on protocol mismatch. + case conn.LocalPort != responseConn.LocalPort: + // Ignore on local port mismatch. + case !conn.LocalIP.Equal(responseConn.LocalIP): + // Ignore on local IP mismatch. + case !conn.Process().Equal(responseConn.Process()): + // Ignore if processes mismatch. + case conn.Entity.IPScope == netutils.LocalMulticast && + (responseConn.Entity.IPScope == netutils.LinkLocal || + responseConn.Entity.IPScope == netutils.SiteLocal): + // We found a (possibly routed) multicast request that matches the response! + return conn + case conn.Entity.IP.Equal(responseNetBroadcastIP) && + responseFromNet.Contains(conn.LocalIP): + // We found a (link local) broadcast request that matches the response! + return conn + } + } + + return nil +} diff --git a/network/netutils/ip.go b/network/netutils/ip.go index f6a34003..76bb0230 100644 --- a/network/netutils/ip.go +++ b/network/netutils/ip.go @@ -28,6 +28,9 @@ func GetIPScope(ip net.IP) IPScope { //nolint:gocognit if ip4 := ip.To4(); ip4 != nil { // IPv4 switch { + case ip4[0] == 0: + // 0.0.0.0/8 + return Invalid case ip4[0] == 127: // 127.0.0.0/8 (RFC1918) return HostLocal @@ -79,6 +82,8 @@ func GetIPScope(ip net.IP) IPScope { //nolint:gocognit } else if len(ip) == net.IPv6len { // IPv6 switch { + case ip.Equal(net.IPv6zero): + return Invalid case ip.Equal(net.IPv6loopback): return HostLocal case ip[0]&0xfe == 0xfc: @@ -124,3 +129,29 @@ func (scope IPScope) IsGlobal() bool { return false } } + +// GetBroadcastAddress returns the broadcast address of the given IP and network mask. +// If a mixed IPv4/IPv6 input is given, it returns nil. +func GetBroadcastAddress(ip net.IP, netMask net.IPMask) net.IP { + // Convert to standard v4. + if ip4 := ip.To4(); ip4 != nil { + ip = ip4 + } + mask := net.IP(netMask) + if ip4Mask := mask.To4(); ip4Mask != nil { + mask = ip4Mask + } + + // Check for mixed v4/v6 input. + if len(ip) != len(mask) { + return nil + } + + // Merge to broadcast address + n := len(ip) + broadcastAddress := make(net.IP, n) + for i := 0; i < n; i++ { + broadcastAddress[i] = ip[i] | ^mask[i] + } + return broadcastAddress +} diff --git a/process/process.go b/process/process.go index 8bae2772..ff04896e 100644 --- a/process/process.go +++ b/process/process.go @@ -67,6 +67,32 @@ func (p *Process) Profile() *profile.LayeredProfile { return p.profile } +// IsIdentified returns whether the process has been identified or if it +// represents some kind of unidentified process. +func (p *Process) IsIdentified() bool { + // Check if process exists. + if p == nil { + return false + } + + // Check for special PIDs. + switch p.Pid { + case UndefinedProcessID: + return false + case UnidentifiedProcessID: + return false + case UnsolicitedProcessID: + return false + default: + return true + } +} + +// Equal returns if the two processes are both identified and have the same PID. +func (p *Process) Equal(other *Process) bool { + return p.IsIdentified() && other.IsIdentified() && p.Pid == other.Pid +} + // IsSystemResolver is a shortcut to check if the process is or belongs to the // system resolver and needs special handling. func (p *Process) IsSystemResolver() bool {