Detect responses to multi/broadcast queries

This commit is contained in:
Daniel
2022-05-24 11:18:23 +02:00
parent e178b732bc
commit 49e79fe3fd
6 changed files with 151 additions and 10 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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
}

43
network/multicast.go Normal file
View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {