FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

macOS IPv6 SYN scan over utun selects link-local source, causing checksum and capture-filter mismatch · Issue #3528 · nmap/nmap · GitHub

/ nmap Public

macOS IPv6 SYN scan over utun selects link-local source, causing checksum and capture-filter mismatch #3528

Description

Describe the bug

With Homebrew Nmap 7.991 on macOS, an IPv6 SYN scan over a WireGuard utun interface fails unless I also specify its assigned IPv6 address using -S.

Nmap's debug output shows that it selects the interface's link-local IPv6 address internally and constructs its receive filter using that address. However, a simultaneous tcpdump capture on the same interface shows the matching probes transmitted with the interface's assigned ULA source address.

The captured TCP checksums are incorrect for the transmitted ULA source. Adjusting the checksum for the change from Nmap's selected link-local source to the captured ULA source accounts for the checksum difference exactly.

The scan reports the open port as filtered, with reason no-response. Explicitly supplying the assigned ULA with -S resolves the observed failure.

Environment

  • macOS 27.0.1, build 26A434; arm64
  • Runtime kernel: Darwin 27.0.0
  • Nmap installed using Homebrew
  • WireGuard interface: utun4
  • Target: an IPv6 host reached through the VPN and a router
  • Target service: TCP port 8920
  • The target is reachable using normal TCP connections and a TCP connect scan (-sT)

Current version output collected during the investigation:

Nmap version 7.991 ( https://nmap.org )
Platform: arm-apple-darwin25.6.0
Compiled with: nmap-liblua-5.4.8 openssl-3.6.5 libssh2-1.11.1 libz-1.2.12 libpcre2-10.49 nmap-libpcap-1.10.6 nmap-libdnet-1.18.0 ipv6
Compiled without:
Available nsock engines: kqueue poll select

Address notation

The following labels replace actual addresses throughout the commands and trace excerpts:

  • CLIENT_LINK_LOCAL: the fe80::… address assigned to utun4
  • CLIENT_ULA: the fd… VPN address assigned to utun4
  • SERVER_ULA: the target server's fd… address on another subnet

The relevant nmap --iflist entries list the link-local address before the ULA:

DEV     IP/MASK                  TYPE        UP  MTU
utun4   CLIENT_LINK_LOCAL/64      point2point up  1420
utun4   CLIENT_ULA/120            point2point up  1420

Addresses have been replaced with labels for privacy. Checksum values below are quoted from the original capture and apply to the original addresses.

Steps to reproduce

  1. Use a macOS WireGuard interface with both a link-local IPv6 address and a routable assigned IPv6 address.
  2. Select a reachable IPv6 target beyond the VPN peer/router, with a known open TCP port.
  3. Start a capture on the VPN interface:
sudo tcpdump -i utun4 -nn -tttt -s 0 -vv \
  'ip6 and host <SERVER_ULA> and tcp port 8920'
  1. Run an IPv6 SYN scan using an IPv6 literal, with DNS disabled:
sudo nmap -6 -sS -Pn -n -e utun4 \
  -v --reason --packet-trace -d2 \
  -p 8920 <SERVER_ULA>
  1. Repeat the same scan with the assigned VPN source address explicitly specified:
sudo nmap -6 -sS -Pn -n -e utun4 \
  -S <CLIENT_ULA> \
  -v --reason --packet-trace -d2 \
  -p 8920 <SERVER_ULA>

The first scan fails. Adding -S <CLIENT_ULA> makes it work.

Observed Nmap output without -S

Nmap installs this capture filter:

Packet capture filter (device utun4): dst host CLIENT_LINK_LOCAL and (icmp or icmp6 or ((tcp or udp or sctp) and (src host SERVER_ULA)))

Its packet trace reports the link-local source:

SENT (0.0220s) TCP [[CLIENT_LINK_LOCAL]:55804 > [SERVER_ULA]:8920 S seq=124925575 win=0 <mss 1460,wscale 5>] IP [hopl=255 tclass=0 flow=0 payloadlen=24]
SENT (1.0240s) TCP [[CLIENT_LINK_LOCAL]:55806 > [SERVER_ULA]:8920 S seq=124794501 win=0 <mss 1460,wscale 5>] IP [hopl=255 tclass=0 flow=0 payloadlen=24]

The result is:

PORT      STATE     SERVICE REASON
8920/tcp  filtered  unknown no-response

Raw packets sent: 2 (128B) | Rcvd: 0 (0B)

Simultaneous tcpdump output

The matching source ports and TCP sequence numbers identify these as the same two probes. However, their captured IPv6 source is CLIENT_ULA:

2026-10-04 23:00:28.284355 IP6 (flowlabel 0x50600, hlim 255, next-header TCP (6) payload length: 24) CLIENT_ULA.55804 > SERVER_ULA.8920: Flags [S], cksum 0xad54 (incorrect -> 0xe041), seq 124925575, win 1024, options [mss 1460], length 0

2026-10-04 23:00:29.286331 IP6 (flowlabel 0xe0c00, hlim 255, next-header TCP (6) payload length: 24) CLIENT_ULA.55806 > SERVER_ULA.8920: Flags [S], cksum 0xad56 (incorrect -> 0xe043), seq 124794501, win 1024, options [mss 1460], length 0

No replies appeared in this capture.

Using the original addresses, an Internet-checksum adjustment replacing only the IPv6 pseudoheader source address gives:

0xad54 -> 0xe041
0xad56 -> 0xe043

Both results exactly match tcpdump's expected checksums.

This establishes a source-address/checksum discrepancy in the utun capture. I have not included a remote-server capture of this exact failing run.

Expected behaviour

When an interface has multiple IPv6 addresses, Nmap should choose a source appropriate for the destination and ensure that its TCP checksum and receive filter agree with the source address actually transmitted.

For this routed ULA destination, selecting the interface's link-local source internally is inappropriate.

The scan should identify the reachable open port without requiring an explicit -S workaround.

Possible implementation cause

A review of current upstream source appears consistent with the observed behaviour:

  1. When -e is supplied without an explicit source address, Nmap calls devname2ipaddr():
    https://github.com/nmap/nmap/blob/master/nmap.cc#L1704-L1710

  2. devname2ipaddr() returns the first address matching the interface and address family. It does not consider the destination or IPv6 address scope:
    https://github.com/nmap/nmap/blob/master/libnetutil/netutil.cc#L1656-L1671

  3. The raw IPv6 socket send path used on platforms such as macOS skips the constructed IPv6 header and passes the transport payload to sendmsg(). In the inspected function, it does not bind the selected source or supply it through IPV6_PKTINFO:
    https://github.com/nmap/nmap/blob/master/libnetutil/netutil.cc#L2718-L2808

  4. The receive filter uses Nmap's internally selected source:
    https://github.com/nmap/nmap/blob/master/scan_engine_raw.cc#L854-L874

My interpretation is that macOS selects the appropriate ULA source for the emitted packet, while Nmap retains its link-local source assumption for the checksum and receive filter.

The source review is of current upstream code; I have not audited the installed binary against its exact build source.

Related reports

This reproduction uses -n, an IPv6 literal, and a raw SYN scan. I have not found an existing report documenting this exact link-local/ULA mismatch.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL