Detect responses to multi/broadcast queries
This commit is contained in:
@@ -3,7 +3,9 @@ package firewall
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/agext/levenshtein"
|
"github.com/agext/levenshtein"
|
||||||
@@ -41,6 +43,7 @@ type deciderFn func(context.Context, *network.Connection, *profile.LayeredProfil
|
|||||||
var defaultDeciders = []deciderFn{
|
var defaultDeciders = []deciderFn{
|
||||||
checkPortmasterConnection,
|
checkPortmasterConnection,
|
||||||
checkSelfCommunication,
|
checkSelfCommunication,
|
||||||
|
checkIfBroadcastReply,
|
||||||
checkConnectionType,
|
checkConnectionType,
|
||||||
checkConnectionScope,
|
checkConnectionScope,
|
||||||
checkEndpointLists,
|
checkEndpointLists,
|
||||||
@@ -182,6 +185,46 @@ func checkSelfCommunication(ctx context.Context, conn *network.Connection, _ *pr
|
|||||||
return false
|
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 {
|
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,
|
// DNS request from the system resolver require a special decision process,
|
||||||
// because the original requesting process is not known. Here, we only check
|
// 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.
|
// Check more extensively for Local/LAN connections.
|
||||||
myNet, err := netenv.IsMyNet(conn.Entity.IP)
|
localNet, err := netenv.GetLocalNetwork(conn.Entity.IP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("firewall: failed to check if %s is in my net: %s", conn.Entity.IP, err)
|
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
|
// With IPv6, just checking the IP scope is not enough, as the host very
|
||||||
// likely has a public IPv6 address.
|
// likely has a public IPv6 address.
|
||||||
// Don't tunnel LAN connections.
|
// Don't tunnel LAN connections.
|
||||||
|
|||||||
@@ -152,11 +152,9 @@ func IsMyIP(ip net.IP) (yes bool, err error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsMyNet returns whether the given IP is currently in the host's broadcast
|
// GetLocalNetwork uses the given IP to search for a network configured on the
|
||||||
// domain - ie. the networks that the host is directly attached to.
|
// device and returns it.
|
||||||
// Function is optimized with the assumption that is unlikely that the IP is
|
func GetLocalNetwork(ip net.IP) (myNet *net.IPNet, err error) {
|
||||||
// in the broadcast domain.
|
|
||||||
func IsMyNet(ip net.IP) (yes bool, err error) {
|
|
||||||
myNetworksLock.Lock()
|
myNetworksLock.Lock()
|
||||||
defer myNetworksLock.Unlock()
|
defer myNetworksLock.Unlock()
|
||||||
|
|
||||||
@@ -164,16 +162,16 @@ func IsMyNet(ip net.IP) (yes bool, err error) {
|
|||||||
if myNetworksNetworkChangedFlag.IsSet() {
|
if myNetworksNetworkChangedFlag.IsSet() {
|
||||||
err := refreshMyNetworks()
|
err := refreshMyNetworks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the IP address is in my networks.
|
// Check if the IP address is in my networks.
|
||||||
for _, myNet := range myNetworks {
|
for _, myNet := range myNetworks {
|
||||||
if myNet.Contains(ip) {
|
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 {
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
// IPv4
|
// IPv4
|
||||||
switch {
|
switch {
|
||||||
|
case ip4[0] == 0:
|
||||||
|
// 0.0.0.0/8
|
||||||
|
return Invalid
|
||||||
case ip4[0] == 127:
|
case ip4[0] == 127:
|
||||||
// 127.0.0.0/8 (RFC1918)
|
// 127.0.0.0/8 (RFC1918)
|
||||||
return HostLocal
|
return HostLocal
|
||||||
@@ -79,6 +82,8 @@ func GetIPScope(ip net.IP) IPScope { //nolint:gocognit
|
|||||||
} else if len(ip) == net.IPv6len {
|
} else if len(ip) == net.IPv6len {
|
||||||
// IPv6
|
// IPv6
|
||||||
switch {
|
switch {
|
||||||
|
case ip.Equal(net.IPv6zero):
|
||||||
|
return Invalid
|
||||||
case ip.Equal(net.IPv6loopback):
|
case ip.Equal(net.IPv6loopback):
|
||||||
return HostLocal
|
return HostLocal
|
||||||
case ip[0]&0xfe == 0xfc:
|
case ip[0]&0xfe == 0xfc:
|
||||||
@@ -124,3 +129,29 @@ func (scope IPScope) IsGlobal() bool {
|
|||||||
return false
|
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
|
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
|
// IsSystemResolver is a shortcut to check if the process is or belongs to the
|
||||||
// system resolver and needs special handling.
|
// system resolver and needs special handling.
|
||||||
func (p *Process) IsSystemResolver() bool {
|
func (p *Process) IsSystemResolver() bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user