Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions scapy/arch/libpcap.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,15 @@ def load_winpcapy():
elif family == socket.AF_LINK:
# Special case: MAC
# (AF_LINK is mostly BSD specific)
val = ap.contents.sa_data
mac = str2mac(bytes(bytearray(val[:6])))
# https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/bsd/net/if_dl.h#L93-L112
# https://github.com/freebsd/freebsd-src/blob/ab7249c288a4d7d09c88f4de705b58a2afbf35f9/sys/net/if_dl.h#L55-L68
# https://github.com/illumos/illumos-gate/blob/6a2df4aa5381599179ab6afb3165db81960dee35/usr/src/uts/common/net/if_dl.h#L65-L76
# https://github.com/NetBSD/src/blob/bc2d21a8880597d1affe4ec581e0b65637258dc4/sys/net/if_dl.h#L78-L91
# https://github.com/openbsd/src/blob/ce063bbc9d6190c8ba255f11feb6910dc3541f29/sys/net/if_dl.h#L56-L70
sockaddr_dl = ccast(ap, POINTER(c_ubyte))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the PR. Wasn't it possible to keep the structure? This is a bit harder to read. I'm not sure I understand what happened wrong in the previous structure, since 1 and 2 bytes-long fields should be pretty much padded the same on all plateforms. Thanks !

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sockaddr_dl structure is slightly different on different platforms but the main difference is that the sdl_data array (where MAC addresses are kept) comes with different sizes and on all those platforms apart from illumos sdl_data is a minimum work area that can be larger so it's still necessary to cast it to the plain pointer and add the length anyway. I removed the structure when all the platform-specific if clauses started to grow and obscure what actually happens there. When I got to NetBSD (where dl_data is kept in a nested dl_addr structure) I decided to stop trying to describe what the sockaddr_dl structure looks like and switched to the pointer.

@gpotter2 gpotter2 Oct 10, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the header (not the data part) is different, I'm not sure I understand how your code works.

If the header is the same (except sdl_data), do you think we could make a
sockaddr_dl_hdr structure that doesn't include it? I'm not a fan of the "take offset 5, then offset 6", so if you could at least read the lengths using a structure I'd prefer that.

Sorry if it's annoying

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the header (not the data part) is different, I'm not sure I understand how your code works

It works because the parts scapy uses to get MAC addresses are the same. For example on illumos the first member of the structure is ushort_t sdl_family (2 bytes) while on *BSD those two bytes contain the first two members

	uint8_t	    sdl_len;	/* Total length of sockaddr */
	sa_family_t sdl_family;	/* AF_LINK */

but it doesn't matter because those members aren't used. The nested dl_addr structure on NetBSD is

struct dl_addr {
	uint8_t	    dl_type;	/* interface type */
	uint8_t	    dl_nlen;	/* interface name length, no trailing 0 reqd. */
	uint8_t	    dl_alen;	/* link level address length */
	uint8_t	    dl_slen;	/* link layer selector length */
	char	    dl_data[24]; /*
				  * minimum work area, can be larger; contains
				  * both if name and ll address; big enough for
				  * IFNAMSIZ plus 8byte ll addr.
				  */
};

and it's kept within sockaddr_dl like

struct sockaddr_dl {
	uint8_t	    sdl_len;	/* Total length of sockaddr */
	sa_family_t sdl_family;	/* AF_LINK */
	uint16_t    sdl_index;	/* if != 0, system given index for interface */
	struct dl_addr sdl_addr;
...

so the name length and the address length are effectively kept in the 5th and 6th bytes anyway and there is no need to access them like sdl_addr.dl_nlen.

I'm not a fan of the "take offset 5, then offset 6", so if you could at least read the lengths using a structure I'd prefer that

I'm not a fan of those things either but the all the platform-specific if clauses I used got really ugly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way I'll convert it to draft to see whether I can come up with something less ugly than raw offsets or a bunch of ifs describing those structures and a bunch of ifs using those structures.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, it's alright. Your reasoning is fine and is probably the easiest option

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to come up with something better anyway because there should probably be a less ugly way to do that. Until then I also double-checked the code on FreeBSD/NetBSD machines where Python and libpcap are built with ASan and didn't see any backtraces or anything like that. I also adjusted the GHAction a bit to run the tests on macOS in libpcap mode as root on all the macOS versions available on GitHub and apart from the flaky "L3bpfSocket - send and sniff on loopback" test it all works and the MAC addresses in both BPF and libpcap mode match what ifconfig shows.

nlen = sockaddr_dl[5]
alen = sockaddr_dl[6]
mac = str2mac(bytes(sockaddr_dl[8 + nlen:8 + nlen + alen]))
a = a.contents.next
continue
else:
Expand Down Expand Up @@ -491,7 +498,7 @@ def load(self):
for ifname, dat in conf.cache_pcapiflist.items():
description, ips, flags, mac, itype = dat
i += 1
if LINUX or BSD or SOLARIS and not mac:
if (LINUX or BSD or SOLARIS) and not mac:
from scapy.arch.unix import get_if_raw_hwaddr
try:
itype, _mac = get_if_raw_hwaddr(ifname)
Expand Down
10 changes: 0 additions & 10 deletions scapy/libs/winpcapy.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,6 @@ class sockaddr_in6(Structure):
("sin6_addr", 16 * c_ubyte),
("sin6_scope", c_uint32)]

class sockaddr_dl(Structure):
_fields_ = [("sdl_len", c_ubyte),
("sdl_family", c_ubyte),
("sdl_index", c_ushort),
("sdl_type", c_ubyte),
("sdl_nlen", c_ubyte),
("sdl_alen", c_ubyte),
("sdl_slen", c_ubyte),
("sdl_data", 46 * c_ubyte)]

else:
# https://github.com/torvalds/linux/blob/master/include/linux/socket.h
# https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2
Expand Down
Loading