Skip to content

libpcap: get MAC addresses on Darwin, *BSD and illumos correctly - #5224

Merged
gpotter2 merged 1 commit into
secdev:masterfrom
evverx:mac-sockaddr-dl
Oct 10, 2026
Merged

gpotter2 merged 1 commit into
secdev:masterfrom
evverx:mac-sockaddr-dl

Conversation

@evverx

@evverx evverx commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

It fixes a bug where scapy ended up with "00:00:00:00:00:00" on Darwin and *BSD and with MAC addresses like "04:00:06:03:06:00" (where 4, 6, 3 and 6 were the ifindex, the type, the name length and the address length accordingly) on illumos. With this patch applied scapy extracts the lengths from the sockaddr_dl structure, skips the names and gets the MAC addresses by analogy with what it already did before 07dedfd was merged. The bug didn't affect Linux because AF_LINK isn't there so ioctl with SIOCGIFHWADDR is used instead.

It was tested on Darwin, FreeBSD, NetBSD, OpenBSD, illumos and Linux.

It fixes a bug where scapy ended up with "00:00:00:00:00:00" on Darwin
and *BSD and with MAC addresses like "04:00:06:03:06:00" (where 4, 6, 3
and 6 were the ifindex, the type, the name length and the address length
accordingly) on illumos. With this patch applied scapy extracts the
lengths from the sockaddr_dl structure, skips the names and gets the MAC
addresses by analogy with what it already did before
07dedfd was merged. The bug didn't
affect Linux because AF_LINK isn't there so ioctl with SIOCGIFHWADDR is
used instead.

It was tested on Darwin, FreeBSD, NetBSD, OpenBSD, illumos and Linux.

AI-Assisted: no
@codecov

codecov Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.14%. Comparing base (d1bbdaf) to head (053144e).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5224      +/-   ##
==========================================
+ Coverage   80.84%   81.14%   +0.30%     
==========================================
  Files         393      393              
  Lines       98325    98327       +2     
==========================================
+ Hits        79488    79791     +303     
+ Misses      18837    18536     -301     
Files with missing lines Coverage Δ
scapy/arch/libpcap.py 77.28% <100.00%> (+2.48%) ⬆️

... and 25 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread scapy/arch/libpcap.py
# 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

@evverx
evverx marked this pull request as draft October 10, 2026 14:32
@gpotter2
gpotter2 marked this pull request as ready for review October 10, 2026 15:20
@gpotter2
gpotter2 merged commit 9d945e5 into secdev:master Oct 10, 2026
23 checks passed
@gpotter2

Copy link
Copy Markdown
Member

Thanks a lot for the PR, again, and for taking the time to test this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants