From a69e067ecb588c23fa0c6f5af48bdf366b3c1673 Mon Sep 17 00:00:00 2001 From: Jay Hogg Date: Mon, 5 Jan 2026 00:27:36 -0600 Subject: [PATCH 1/2] DARWIN/MacOS patch to fix: (1) word alingmnent/endian-ness of netmask in routes (sockaddr) (2) remove host entries because the route read includes the entire ARP table. --- scapy/arch/bpf/pfroute.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scapy/arch/bpf/pfroute.py b/scapy/arch/bpf/pfroute.py index e81c2b504eb..77e51e6db0b 100644 --- a/scapy/arch/bpf/pfroute.py +++ b/scapy/arch/bpf/pfroute.py @@ -406,7 +406,10 @@ class sockaddr(Packet): XStrLenField( "sa_data", "", - length_from=lambda pkt: pkt.sa_len - 2 if pkt.sa_len >= 2 else 0, + # NOTE: Darwin right-justifies netmask on 4 byte word alignment + length_from=lambda pkt: + ((pkt.sa_len+3)//4*4 - 2 if pkt.sa_len >= 2 else 0) if DARWIN else + (pkt.sa_len - 2 if pkt.sa_len >= 2 else 0), ), lambda pkt: pkt.sa_family not in [ @@ -1023,6 +1026,9 @@ def read_routes(): if DARWIN and flags.RTF_WASCLONED and msg.rtm_parentflags.RTF_PRCLONING: # OSX needs filtering continue + if DARWIN and flags.RTF_HOST and not flags.RTF_BROADCAST: + # DARWIN includes the entire ARP table in the route response. Remove it. + continue addrs = msg.rtm_addrs net = 0 mask = 0xFFFFFFFF @@ -1046,7 +1052,10 @@ def read_routes(): if nm.sa_family == socket.AF_INET: mask = atol(nm.sin_addr) elif nm.sa_family in [0x00, 0xFF]: # NetBSD - mask = struct.unpack("I", nm.sa_data[-4:].rjust(4, b"\x00"))[0] + else: + mask = struct.unpack(" Date: Mon, 5 Jan 2026 23:28:27 -0600 Subject: [PATCH 2/2] Further review of DARWIN indicates the address structures (including netmask) are all sockaddr_in format for BSD systems, and not just sockaddr, which adds a ushort port address in before the sa_data buffer. The challenge is that the sa_family is not INETi for the netmask, otherwise it would just work. Updated per review notes: - Changed structure to add 2 ushort buffer before sa_data unconditionally. - Changed math to always align to 4 byte boundary. Observations on DARWIN show the sa_len is not always correct, so modulus math was used. - Simpified the unpack and swapped all BSD's to use BE format, since all low level network is expected as BE. - Simplified the DARWIN route filtering per suggestion. This may have an impact on other systems than the original PR. --- scapy/arch/bpf/pfroute.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/scapy/arch/bpf/pfroute.py b/scapy/arch/bpf/pfroute.py index 77e51e6db0b..863b27b28ec 100644 --- a/scapy/arch/bpf/pfroute.py +++ b/scapy/arch/bpf/pfroute.py @@ -401,15 +401,23 @@ class sockaddr(Packet): XStrLenField("sdl_pad", b"", length_from=lambda pkt: 16 - pkt.sa_len), lambda pkt: pkt.sa_len < 16 and pkt.sa_family == socket.AF_LINK, ), - # others + # others - only used for netmask, make alignment match sockaddr_in + ConditionalField( + StrFixedLenField("_sa_pad1", "", 2), + lambda pkt: pkt.sa_family + not in [ + socket.AF_INET, + socket.AF_INET6, + socket.AF_LINK, + ], + ), ConditionalField( XStrLenField( "sa_data", "", - # NOTE: Darwin right-justifies netmask on 4 byte word alignment + # NOTE: Adjust length to 4 byte alignment. Darwin sa_len is not always correct for simple math. length_from=lambda pkt: - ((pkt.sa_len+3)//4*4 - 2 if pkt.sa_len >= 2 else 0) if DARWIN else - (pkt.sa_len - 2 if pkt.sa_len >= 2 else 0), + ((pkt.sa_len+3)//4*4 - 4 if pkt.sa_len >= 2 else 0) ), lambda pkt: pkt.sa_family not in [ @@ -1023,12 +1031,13 @@ def read_routes(): flags = msg.rtm_flags if not flags.RTF_UP: continue - if DARWIN and flags.RTF_WASCLONED and msg.rtm_parentflags.RTF_PRCLONING: - # OSX needs filtering - continue - if DARWIN and flags.RTF_HOST and not flags.RTF_BROADCAST: - # DARWIN includes the entire ARP table in the route response. Remove it. - continue + if DARWIN: + if flags.RTF_WASCLONED and msg.rtm_parentflags.RTF_PRCLONING: + # OSX needs filtering + continue + if flags.RTF_HOST and not flags.RTF_BROADCAST: + # DARWIN includes the entire ARP table in the route response. Remove it. + continue addrs = msg.rtm_addrs net = 0 mask = 0xFFFFFFFF @@ -1052,10 +1061,7 @@ def read_routes(): if nm.sa_family == socket.AF_INET: mask = atol(nm.sin_addr) elif nm.sa_family in [0x00, 0xFF]: # NetBSD - if DARWIN: - mask = struct.unpack(">I", nm.sa_data[-4:].rjust(4, b"\x00"))[0] - else: - mask = struct.unpack("I", nm.sa_data[:4].rjust(4, b"\x00"))[0] else: mask = int.from_bytes(nm.sa_data[:4], "big") i += 1