Detect responses to multi/broadcast queries
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
43
network/multicast.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user