Grounded in a source-level review of nextbsd-redux/{nextbsd-userland, nextbsd-kernel, nextbsd-kernel-modules, nextbsd-freebsd-compat, nextbsd, nextbsd-overlays} and gershwin-components at 2026-07-13. Background: WLAN on NextBSD — the Apple stack, whose integration half this replaces. That half was written against the pre-Mach freebsd-* / pkgdemon era and assumed dhcpcd and Distributed Objects — neither of which NextBSD uses.
wlan_xauth is absent from the kernel config, and NextBSD deletes /boot/kernel/*.ko, so net80211's autoload can't self-heal. Every WPA/WPA2/WPA3 association fails EINVAL at the first ioctl. Open networks work. Fix: device wlan_xauth in config/NEXTBSD. This is a bug, not a feature — it should land regardless of everything else here.wpa_supplicant 2.11 ships in base untouched; IntelWiFi.kext (if_iwlwifi) is built, firmware-bundled (267 MB), and boot-load-proven in CI; LinuxKPI's mac80211 shim is statically linked.wlan0 VAP (that's rc.conf's job on FreeBSD, and NextBSD has no /etc/rc.d), and ipconfigd fires DHCP at net80211 RUN state — before the 4-way handshake — then never retries.wpa_supplicant stock, from base. Do not vendor it into nextbsd-userland/src, do not teach it Mach, do not add the port. It never needs to know configd exists.wland is C + CoreFoundation, not Objective-C. There is no ObjC runtime and no Foundation in nextbsd-userland. The CoreWLAN-shaped ObjC API belongs above the Mach line, in Gershwin.Network.prefPane has a working WLAN tab and a NetworkBackend protocol that is already, in effect, the WLAN API spec.ell and D-Bus. It contributes its object model as a design reference, not code.wland properly — no shortcut. The cheap path was rejected because it does not actually work: without something publishing an association-complete signal, there is nothing for the DHCP gate to gate on, and the minimum honest shim is wland v0. See §5. Scope ends at System Preferences; a menubar item is Gershwin's call.wland.| Phase | What landed | Where |
|---|---|---|
| 0 — kernel | device wlan_xauth. WPA/WPA2/WPA3 can now associate at all; before this every association failed EINVAL at the first ioctl. | kernel#54 (closes #51) |
| 2a — ipconfigd | Multi-interface: per-interface lease table + a dedicated DHCP worker thread per NIC; teardown (SIOCDIFADDR + RTM_DELETE) on lease loss and link-down; primary elected by rank (wired > wireless) instead of last-binder-wins. Two further latent bugs fixed in passing — a publish failure used to silently skip the lease loop, and /etc/resolv.conf was written per-interface (so wlan0 stomped a wired em0's DNS) and non-atomically. | userland#43 (closes #37 #38 #39 #41) |
| 2b — configd | RTM_IFANNOUNCE in the link monitor, so a VAP cloned at runtime is visible and a destroyed one stops leaving a stale Link key. wland therefore needs no PF_ROUTE listener of its own. | userland#44 (closes #42) |
| 2c — the gate | DHCP now requires Link{Active} and AirPort{Authenticated}. Key-absent-means-ready, so every wired NIC is bit-for-bit unchanged. | userland#45 |
| 3 + 4 — wland | /usr/sbin/wland (job org.nextbsd.wland, service org.nextbsd.wlan) + the wlan CLI. VAP lifecycle, stock wpa_supplicant driven over wpa_ctrl with an empty config, known networks in SCPreferences, autojoin, and the AirPort publish. | userland#46 |
What CI proves: it all builds clean on amd64 and arm64, and the qemu boot-test passes with wland running under launchd (launchctl list shows 31 - org.nextbsd.wland, alive). Critically, the wired em0 DHCP path is untouched — which is the regression that mattered.
What CI cannot prove, and nobody should pretend otherwise: there is no 802.11 in qemu. Association, the 4-way handshake, the AirPort publish firing, and the DHCP gate actually releasing are reasoned, reviewed, and grounded in the kernel source — but they have never been executed. The first run on a real Intel radio is the real test, and it is the next thing that should happen.
Each is independent. All three must be fixed before WLAN works at all.
wlan_xauth is missing BLOCKER SHIP THIS NOW kernel#51Every WPA / WPA2 / WPA3 network fails to associate. Open networks work fine. There is no useful error surfaced to the user — wpa_supplicant simply never leaves SCANNING.
ioctl(SIOCS80211, IEEE80211_IOC_AUTHMODE) returns EINVAL, always. wpa_supplicant sets the authmode to IEEE80211_AUTH_WPA. ieee80211_ioctl.c:2858-2870 calls ieee80211_authenticator_get(), which (ieee80211_proto.c:471-478) looks up auth_modnames[IEEE80211_AUTH_WPA] = "wlan_xauth" and tries ieee80211_load_module("wlan_xauth") → kldload → no such module on disk → returns NULL → EINVAL. Association never begins.
Two independent facts combine to produce this, and neither is wrong on its own:
wlan_xauth is not in the kernel. NextBSD's config is include GENERIC plus additions (nextbsd-kernel/config/NEXTBSD:1) — nothing wireless is excluded, and device wlan + wlan_ccmp/wlan_tkip/wlan_wep/wlan_gcmp/wlan_amrr all arrive from GENERIC. But wlan_xauth is not in GENERIC either — upstream FreeBSD deliberately leaves it out, because net80211 autoloads it as a .ko on demand. On stock FreeBSD this is invisible and self-healing..ko tree to autoload from. nextbsd-kernel/ci/assemble-image.sh:168 does rm -f "$ROOTFS"/boot/kernel/*.ko — the entire module tree is deleted at image-assembly time; only .kext bundles survive. This is intentional and correct for the kext model.kldload path in the kernel is dead. Any subsystem that relies on autoloading a module on first use will fail silently and non-obviously, because the recovery mechanism upstream depends on has been removed wholesale. net80211 alone has four such call sites (ieee80211_proto.c:476, ieee80211_crypto.c:370, ieee80211_ratectl.c:125, ieee80211_scan.c:191). This is worth a separate audit — wlan_xauth is simply the first instance anyone tripped over. Tracked as kernel#52.
# nextbsd-kernel/config/NEXTBSD
device wlan_xauth # 802.11 802.1x/WPA authenticator.
# net80211 autoloads this as a .ko on stock FreeBSD;
# NextBSD ships no .ko tree (assemble-image.sh:168),
# so it must be compiled in.
Risk: none. It is a purely additive kernel-config line that statically links a small, in-tree net80211 module that upstream ships and every other FreeBSD system loads on demand. It cannot regress the wired path, it changes no userland, and it touches no other subsystem. If WLAN is not present on a machine, the code is simply never entered.
# Before: expect this to fail with "Invalid argument"
ifconfig wlan0 create wlandev iwlwifi0
wpa_supplicant -i wlan0 -c /tmp/wpa.conf -d # never leaves SCANNING
# After: expect association to complete
wpa_cli -i wlan0 status # wpa_state=COMPLETED
# Sanity-check the authenticator actually registered:
sysctl net.wlan.devices # radio is visible
ifconfig wlan0 list caps # WPA/RSN advertised
wlan_acl and wlan_rssadapt are the same latent class of failure and would be reasonable to add in the same commit, but neither is needed for a station — they only matter for hostap mode and alternate rate control, so leaving them out is defensible.
wlan0 VAP BLOCKER new work — wlandOn FreeBSD, wlans_iwlwifi0="wlan0" in rc.conf clones the 802.11 VAP from the physical device. NextBSD has neither: nextbsd-freebsd-compat/.github/workflows/build.yml:127 hard-strips /stage/etc, installworld (not make distribution) is used deliberately so /etc content is never generated, and /sbin/init is in the superseded list because launchd is PID 1. There is no rc.d and never will be.
So ifconfig wlan0 create wlandev iwlwifi0 has no owner. This is a responsibility Apple's wifid never had — IO80211 presents en0 directly — and it means the phy→VAP lifecycle must belong to something on NextBSD. That something is wland.
ipconfigd DHCPs too early, then gives up forever BLOCKER userland#37The trigger path has no interface-type check at all. The only filter anywhere is strncmp(ifname, "lo", 2).
Two aggravations. net80211 VAPs go through ether_ifattach, so they report IFT_ETHER — meaning even dhcp_pick_interface()'s sdl_type != 6 filter (dhcp_discover.c:111) will happily select wlan0. And the {4,8,16}s retransmit ladder means the user watches a 28-second hang before nothing happens.
AirPort keyMake ipconfigd's contract honest. Today it assumes Link{Active} means "L2 is usable for IP"; net80211's RUN state breaks that assumption for WPA. The repair is to require a second predicate — but only where a WLAN daemon has declared itself the authority.
Single file, src/IPConfiguration/sc_link_watch.c, ~70 LOC:
| Change | LOC |
|---|---|
Second subscription pattern State:/Network/Interface/[^/]+/AirPort alongside the existing …/Link; widen the CFArrayCreate at :251-253 and the initial SCDynamicStoreCopyKeyList sweep at :282. | ~8 |
Rename handle_link_key → handle_iface_key. Its ifname parse (:165-173) already works verbatim for both entities; route both into one evaluate_interface(). | ~15 |
evaluate_interface(store, ifname) — read /Link {Active}, call wlan_ready(), fire schedule_link_event(ifname, 1) only when both hold. The admin-up path (active=0) is unchanged. | ~25 |
wlan_ready(store, ifname) — SCDynamicStoreCopyValue on State:/Network/Interface/<if>/AirPort. Key absent ⇒ return 1. | ~25 |
em0, so em0 behaves bit-for-bit as it does today. Zero regression risk on the existing wired CI path — which matters, because tests/boot-test.sh gates on it.
And the re-trigger falls out for free. wland publishes AirPort {Authenticated: false} on association and {Authenticated: true} on CTRL-EVENT-CONNECTED. That second configset is itself a change on the new pattern, so configd wakes the existing linkwatch session, evaluate_interface("wlan0") re-runs, /Link is already Active, the gate is now open → DHCP. Event-driven, no polling, no debounce, no new IPC. It also incidentally fixes the "one failed attempt then silence" hole: a re-association generates a fresh trigger even though /Link never moved.
No configd change. No ipconfig.defs change. No MIG regeneration.
| Piece | Status | Evidence |
|---|---|---|
wpa_supplicant 2.11, wpa_cli, wpa_passphrase, hostapd, ifconfig, wlandebug | SHIPS | FreeBSD releng/15.1 base, untouched. Zero wireless paths in collisions or superseded; no WITHOUT_WIRELESS knob anywhere. |
net80211 + wlan_ccmp/tkip/wep/gcmp/amrr | IN KERNEL | Inherited from GENERIC. config/NEXTBSD:1 |
LinuxKPI mac80211 shim (linux_80211.c) | IN KERNEL | options COMPAT_LINUXKPI + optional compat_linuxkpi wlan |
IntelWiFi.kext (if_iwlwifi) + 267 MB firmware | SHIPS | Built by nextbsd-kernel-modules; firmware bundled in Contents/Resources/firmware, found via patch 0004's linker_file_foreach search. kext-smoke-test in CI. |
SCDynamicStore (create/get/set/copy-key-list/notify, dispatch + runloop sources) | WORKS | libSystemConfiguration/SCDynamicStore.c, SCNotify.c |
SCPreferences — atomic, locked, commit-notifying plist store | WORKS | SCPreferences.c: temp-file + rename(), lockfile, /Local/Library/Preferences/SystemConfiguration/preferences.plist |
wlanN typed as kSCNetworkInterfaceTypeIEEE80211 | WORKS | SCNetworkInterface.c:227-243 — name-prefix heuristic, because FreeBSD 802.11 clones report IFT_ETHER |
| CoreFoundation plist read/write (XML + binary) | WORKS | Vendored swift-corelibs CFPropertyList.c |
Gershwin Network.prefPane with WLAN tab (scan / connect-with-password / radio toggle) | EXISTS | gershwin-components/Network/ — NetworkBackend protocol + BSDBackend.m. Gershwin's to evolve, not this plan's — see §6 Phase 5. |
kSCEntNetAirPort + 802.11 property keys | ABSENT | SCSchemaDefinitions.h:71-78 stops at IPv4/IPv6/DHCP/DNS/Proxies/Interface/Link |
| Keychain / Security.framework / any secret store | ABSENT | Not in src/, not in PORTING.md, not planned |
Objective-C runtime / Foundation in nextbsd-userland | ABSENT | No libobjc2; zero .m files built; toolchains enable C/C++ only |
| Any Gershwin process speaking Mach | NONE, EVER | Zero mach/, bootstrap_look_up, xpc_, or SC references in the component tree |
Two independent fatal blockers. It is welded to nl80211/cfg80211 — there is no net80211 backend, and iwd deliberately leans on cfg80211 semantics rather than abstracting over them, so a FreeBSD port is a rewrite of the layer the daemon is built on, not a driver shim. And it is LGPL-2.1 (can't go in base), on top of dragging in ell and D-Bus.
What survives: iwd's object model — Station / Network / KnownNetwork / Agent — maps almost one-to-one onto CoreWLAN's CWInterface / CWNetwork / credential-prompt flow, and it already contains the autojoin brain wpa_supplicant lacks. iwd contributes ideas; wpa_supplicant contributes code.
There is no seed. eapolclient (eap8021x) does the EAP exchange only — it derives a PMK for Enterprise networks and hands it back. It does not scan, does not associate, and does not do the 4-way handshake. All of that lives in closed wifid and IO80211 firmware offload. And eap8021x is not in the NextBSD tree at all.
So "custom wland based on Apple's supplicant" means implementing SAE/Dragonfly, the 4-way handshake and key hierarchy, PMKSA caching, PMF/802.11w, and FT from scratch — the exact code that produced KRACK and Dragonblood — in a codebase where we are the only auditor. No.
wpa_supplicant into nextbsd-userland/src and teaching it Mach REJECTEDFour reasons, any one sufficient:
nextbsd-userland's README defines it as "Tiers 0–2 — the system layer that has no FreeBSD equivalent." wpa_supplicant is definitionally the FreeBSD thing; it already speaks net80211 through driver_bsd.c.wland exists, wland holds the Mach port and calls SCDynamicStoreSetValue(). A Mach-aware supplicant would duplicate a component we still have to write.mach.ko syscall-slot shim). A bug there should not be able to take down the supplicant.Also don't add the port. security/wpa_supplicant is the same 2.11, but installs to /usr/local/sbin and shadows base for no benefit. (FreeBSD's official pkg repo is configured and enabled — nextbsd/build.sh:169-187 writes pkg+https://pkg.FreeBSD.org/FreeBSD:15:${ABIARCH}/latest and rebrands ABI precisely so FreeBSD:15 packages install cleanly — so ports remain available if we ever need them. We don't, here.)
wpa_supplicant never needs Mach. It speaks net80211 ioctls downward and a UNIX control socket upward. It does not link CoreFoundation and does not know configd exists. All Mach knowledge lives in wland, which gets it for free by linking libSystemConfiguration — the same way ipconfigd does.
No Gershwin process has ever spoken Mach — zero mach/, bootstrap_look_up, xpc_connection, or SystemConfiguration references anywhere in gershwin-components. And this is deliberate, not accidental: gershwin-developer/Library/Scripts/Install-System-Domain.sh:74-95 force-disables HAVE_MACH when building Gershwin's libdispatch on NextBSD, so the desktop's dispatch is isolated from the base stack's Mach one. Gershwin apps link gnustep-base's Foundation, not libCoreFoundation.so.6. PORTING.md:838-846 states the policy: "three CF lanes, one CF per binary."
Linking libSystemConfiguration + libmach into a GNUstep bundle collides head-on with that split. The cheap path is the one already in the tree: ipconfigd is a Mach daemon and ipconfig is a pure-C CLI Mach client (bootstrap_look_up at ipconfig.c:150-154). Do the same. BSDBackend.m:262-268 already invokes a privileged helper via NSTask, so a /usr/sbin/wlan binary drops straight into the existing invocation path with no ObjC↔Mach linkage problem at all.
The CoreWLAN-shaped ObjC API stays above the Mach line, where ObjC actually exists. That is also where it belongs architecturally.
wland. The shortcut is rejected. The table below is retained as the rationale, not as an open question — it records what we are choosing against and why, so this doesn't get relitigated.
The two options were never competing architectures — the shortcut is a strict subset of the real thing's first phases. The only question was where to stop, and the answer is: we don't.
It doesn't actually work. That is the decisive argument, and it isn't an aesthetic one. Without wland, nothing publishes an AirPort key — so the DHCP gate in §1.4 has nothing to gate on, and every alternative is worse: hold the interface admin-down until wpa_supplicant reports CONNECTED (ugly, and racy against ipconfigd's own iface_bring_up), or let the 28-second DISCOVER ladder fail and hope for a manual retry (which the sticky-failure bug means never comes). The minimum honest shim — something that watches wpa_ctrl and publishes AirPort{Authenticated} — is wland v0. There is no cheaper thing that is also correct.
And the shortcut's end state is one we'd have to demolish. It leaves PSKs in /etc/wpa_supplicant.conf, autojoin as an integer, nothing in the dynamic store, and System Preferences shelling out through sudo -A to tools that don't exist on this system. Every one of those is later thrown away. The work isn't saved, it's deferred and then discarded.
| Shortcut REJECTED | What we're building CHOSEN | |
|---|---|---|
| Scope | wlan_xauth + a launchd job that creates the VAP and runs wpa_supplicant + a DHCP gate | All of that, plus wland + the wlan CLI + an SC-backed System Preferences backend |
| Effort | Weeks | Months |
| Credentials | Plaintext /etc/wpa_supplicant.conf, 0600, via wpa_cli save_config — what BSDBackend.m does today | Plaintext root-owned preferences.plist via SCPreferences. Still plaintext — there is no keychain either way |
| Autojoin | wpa_supplicant's priority integer | wland policy: RSSI ranking, last-used, per-network enable |
| Dynamic store | Nothing published. DHCP gate has to be hacked another way (see below) | State:/…/AirPort published; gate is clean and event-driven |
| System Preferences | sudo -A + NSTask shell-outs — and to what is unclear, since dhclient and sysrc don't exist here | Network.prefPane gets a real backend: wlan CLI → MIG → wland |
| Shape | GhostBSD | Apple |
wland is finished. Phases 0–2 are independently useful and independently shippable — §1.1's wlan_xauth fix in particular should go in as its own PR this week, and Phase 2's ipconfigd repairs are worth doing on their own merits (they fix two latent bugs that have nothing to do with WLAN). What it means is that we don't stop there, and we don't build a throwaway credential store or a throwaway DHCP hack on the way.
device wlan_xauth in nextbsd-kernel/config/NEXTBSD. This is not really a phase — it is a bug fix that happens to be a prerequisite. It has no dependency on Q1 (ship vs right), on wland, or on anything else in this document, and it should go in as its own PR rather than waiting for a WLAN decision. Full write-up, risk assessment, and standalone verification steps: §1.1. Ticket: nextbsd-kernel#51 (and the systemic audit, #52).
ifconfig wlan0 create wlandev iwlwifi0
ifconfig wlan0 up
wpa_supplicant -i wlan0 -c /tmp/wpa.conf -B
wpa_cli -i wlan0 status # expect wpa_state=COMPLETED
Expect DHCP to fail here — that is the §1.3 race, and observing it is the point. Confirms driver, firmware, net80211, wlan_xauth, and the supplicant all work before any new code is written.
ipconfigd: the gate + the latent bugsThe ~70-LOC sc_link_watch.c gate from §1.4, plus the pre-existing bugs in §8 — the gate does not work without them. Tickets: #37 (dead re-arm) and #38 (global single-flight) are hard blockers for the gate; #39, #40, #41 are the rest of the multi-interface debt.
wland v1 (C + CoreFoundation)/usr/sbin/wland, launchd job org.nextbsd.wland serving Mach service org.nextbsd.wlan. Responsibilities:
org.nextbsd.* and not com.apple.*. The shipped daemons (com.apple.configd, notifyd, IPConfiguration, …) keep Apple's exact labels, and that convention is documented in the Keychain plan — but it is the state of the shipped image, not an endpoint. The intent is to move to org.nextbsd.* across the board; what defers it is that those labels are baked into running jobs, plist filenames, and bootstrap_look_up() call sites, so the sweep is a real code-breaking migration that has to be scheduled.
wland doesn't have that problem, on two counts. Nothing Apple-derived looks its label up — we write the wlan CLI, we write SCWLANBackend, and the CoreWLAN-shaped ObjC API lives above the Mach line in Gershwin — so unlike securityd (whose label is a compatibility surface, because a clean-room Security.framework hardcodes bootstrap_look_up("com.apple.securityd")), the string is genuinely free. And Apple's daemon is wifid, not wland, so there is no Apple label to inherit anyway — com.apple.wland would be a fabricated Apple name for a daemon Apple never shipped. Nothing is built yet, so this costs nothing now and saves a migration later.
ifconfig wlanN create wlandev <phy>, destroy on removal.PF_ROUTE listener for RTM_IFANNOUNCE — unless userland#42 lands first, in which case configd does this and wland just subscribes. Today configd's link monitor handles RTM_IFINFO only, so a VAP cloned at runtime is invisible to it until the first link-state transition, and a destroyed one leaves a stale Link key forever. Since wland is the thing doing the cloning, it must track arrivals and departures itself.wpa_supplicant over wpa_ctrl — SCAN, SCAN_RESULTS, ADD_NETWORK, SET_NETWORK, SELECT_NETWORK; consume the async event stream (CTRL-EVENT-CONNECTED, -DISCONNECTED, -SSID-TEMP-DISABLED) off a dispatch_source on the control-socket fd. wpa_ctrl.c is BSD-licensed and small enough to compile in directly.wpa_supplicant.conf also carries SSIDs, two autojoin brains fight — the classic NetworkManager failure mode.State:/Network/Interface/<wlan>/AirPort.wlan CLI/usr/sbin/wlan, a pure-C MIG client modeled on src/IPConfiguration/ipconfig.c. This is both the debugging tool (there is no scutil — see §7) and the desktop's bridge across the Mach line.
A third NetworkBackend implementation (SCWLANBackend) in Network.prefPane, alongside NMBackend and BSDBackend, driving the wlan CLI from Phase 4.
This is needed rather than optional: the existing BSDBackend shells out to wpa_cli, dhclient and sysrc, none of which exist on NextBSD. So the NextBSD backend cannot be BSDBackend with tweaks — it needs something to talk to, and Phase 4 is what gives it one. The protocol itself doesn't change: scanForWLANs, connectToWLAN:withPassword:, setWLANEnabled:, savedConnections are already declared and already drive the WLAN tab.
wlan CLI and a stable State:/Network/Interface/<wlan>/AirPort schema are the contract, and anything Gershwin builds on top of them is out of scope here.
ipconfig.defs:19-22 says so explicitly — which is exactly why ipconfigd vendors only 2 of Apple's ~21 routines (Apple's ipconfig_set takes OOL xmlData). So wland cannot return a scan-results plist over MIG. Use the flat indexed pattern ipconfigd already established (ipconfig_if_count + ipconfig_if_addr): wlan_scan_count() then wlan_scan_entry(i).
CONFIG_DATA_MAX (config_types.h:74) rejects any key or value over 8192 bytes, and a busy scan blows straight past that. Only the compact connected-state summary belongs in State:/…/AirPort. Scan results go over the MIG surface to the CLI.
Three more, less severe:
kSCEntNetAirPort. The 802.11 entity and all its property keys must be added to SCSchemaDefinitions.h. We are inventing this schema; nothing consumes it today.scutil. The only mention in the tree is an .Xr scutil 8 cross-reference in configd's man page pointing at a binary that does not exist. There is no way to inspect the dynamic store ad hoc. The wlan CLI partly fills this; a general store dumper (CopyKeyList(".*") + CopyMultiple) is worth ~50 LOC on its own.StartAfter/Requires; the plist directory scan isn't even sorted. "Start after configd" is expressed as KeepAlive=true + open the SCDynamicStore lazily and tolerate NULL (sc_publish.c:136-143 is the model). Never assume configd is up in main().Binary to /usr/sbin/wland — /System/Library holds data only (plists, kexts), never executables. A bsd.prog.mk Makefile, not CMake (CMake is used by exactly two components and the cross-toolchain file says so explicitly). Plist in overlay/System/Library/LaunchDaemons/, which ships verbatim via cp -aR overlay/. /stage/. Build block in build-userland.sh after IPConfiguration, generating MIG stubs externally and passing them in via MIGOUT=.
The link line matters and is easy to get wrong after the #378 rename:
LDADD+= -llaunch -lsystem_kernel
LDADD+= -lSystemConfiguration -lCoreFoundation
LDADD+= -lsystem_dispatch -l:libsystem_blocks.so
CFLAGS+= -fblocks
WARNS= 0 # CF public headers are not -Werror clean
BINDIR= /usr/sbin
-ldispatch and -lBlocksRuntime no longer exist in base. Those names now belong to Gershwin's separate stock copies; linking them is what caused the rtld collision that crashed launchctl/dbus/sshd. Use -lsystem_dispatch and the exact-file -l:libsystem_blocks.so form.
Both bugs are pre-existing and independent of WLAN. Both will defeat the §1.4 gate if left alone. Neither has been noticed because NextBSD has only ever been tested on a single wired NIC.
bound_state_set(NULL, …) is never called anywhere — the only call site (ipconfigd.c:262) always passes a live lease. So after a lease expires, bound_state_any() stays true forever and the re-arm at ipconfigd.c:426 can never fire. Expire once ⇒ that ipconfigd never DHCPs again until restarted.
g_dhcp_started (ipconfigd.c:97-98) + bound_state (bound_state.c:12-17, one global struct, bound_state_count() returns 0 or 1) permit exactly one bound interface for the daemon's lifetime. On a laptop with em0 and wlan0, whichever links first wins and the other is ignored forever — so the WLAN gate will work correctly and still be defeated by em0 winning the race.
Related, and the next thing to break: State:/Network/Global/IPv4 is last-binder-wins. sc_publish.c:230-235 has ipconfigd declare whichever service just bound to be primary — the comment admits it. There is no IPMonitor, no service-order arbitration anywhere. mDNSResponder and hostnamed both read that key. Wired-only, nobody noticed. Add WLAN and "primary interface" is a race.
The honest read: ipconfigd is currently a single-interface wired-desktop daemon, and WLAN is what forces it to grow up. Also unhandled and worth knowing: no teardown on link-down (address and default route stay installed), no SIOCDIFADDR/RTM_DELETE/DHCPRELEASE anywhere, and /etc/resolv.conf is rewritten on bind but not on renew — so a renewal that changes DNS servers updates the store and not the resolver.
| Target | Status | What's needed |
|---|---|---|
| amd64 + Intel (iwlwifi) | WORKS after Phase 0 | Nothing. Kext + firmware already ship. |
amd64 + legacy Atheros (ath) | SHOULD WORK after Phase 0 | Compiled into the kernel from GENERIC; needs no firmware. Untested. |
Realtek (rtw88/rtw89), MediaTek (mt76), ath10k/11k | NO | Each needs a kext + firmware following the IntelWiFi pattern (personality generator + ko2kext + linux-firmware sparse-clone). No kernel work — LinuxKPI is already baked in. |
USB WLAN (run, urtwn, otus, rsu) | NO | Above, plus IOKit matcher work — the personality generator only emits IOProviderClass = IOPCIDevice. |
| arm64 — any WLAN at all | NO net80211 WHATSOEVER | arm64 GENERIC never sets device wlan. The entire 802.11 stack is absent from that kernel. Needs device wlan + the crypto set added explicitly to config/NEXTBSD. |
Filed 2026-07-13. Every one of these is a real bug independent of WLAN — they are all currently masked by the fact that NextBSD has only ever been exercised on a single wired NIC. WLAN is simply what makes them reachable. They are the foundation, and they go in before wland.
| Ticket | What | Blocks |
|---|---|---|
| kernel#51 blocker | wlan_xauth missing — every WPA/WPA2/WPA3 association fails EINVAL |
Everything. One additive config line; zero risk; ship independently. §1.1 |
| userland#37 blocker | ipconfigd: dead re-arm — bound_state_set(NULL,…) is never called, so after one lease expiry DHCP never runs again |
The DHCP gate. §8 |
| userland#38 blocker | ipconfigd: global single-flight — exactly one interface can ever bind, for the daemon's lifetime |
The DHCP gate. Without this, em0 wins the race and wlan0 is ignored forever even when the gate works correctly. §8 |
| userland#42 strongly wanted | configd: link monitor handles RTM_IFINFO only — no RTM_IFANNOUNCE, so runtime-created interfaces are invisible and destroyed ones leave stale Link keys |
Not a hard blocker, but if it lands, wland does not need to run a duplicate PF_ROUTE listener just to see the VAPs it creates. Worth doing first. Phase 3 |
| Ticket | What |
|---|---|
| userland#39 | ipconfigd: no deconfigure path — address and default route survive link-down, lease expiry, and interface removal. Every time you walk out of WLAN range, a stale IP and a stale default route stay installed. |
| userland#40 | ipconfigd: /etc/resolv.conf not rewritten on renew (DNS change updates the store but not the resolver), and the write is non-atomic (no temp file, no rename(), no fsync). |
| userland#41 | ipconfigd: State:/Network/Global/IPv4 is last-binder-wins — no service-order arbitration, no IPMonitor. mDNSResponder and hostnamed both consume this key, so a wrong answer propagates into Bonjour and hostname resolution. |
| Ticket | What |
|---|---|
| kernel#52 | Every lazy kldload path in the kernel is dead — assemble-image.sh:168 deletes the whole .ko tree, so any subsystem that autoloads a module on first use fails silently. wlan_xauth is just the first instance anyone tripped over; net80211 alone has four such call sites, and three of them work today only by the accident that GENERIC happens to compile them in. Deserves a real audit and a CI guard. |
| kernel#53 | arm64 has no net80211 whatsoever — arm64 GENERIC never sets device wlan. WLAN is amd64-only. Low priority; filed so the hardware matrix is honest. §9 |
wland. Only then start wland itself.
/etc/wpa_supplicant.conf (0600, root — what Gershwin's BSDBackend does today) or a root-owned preferences.plist under /Local/Library/Preferences/SystemConfiguration. The latter is Darwin-shaped and gives wland sole ownership of network blocks (which we want anyway, per §6 Phase 3); the former is what already works. Not a security difference — only an architecture one. A real keychain is greenfield and its own multi-month project.
wland own the VAP, or does a separate one-shot job create it? A trivial launchd job could run ifconfig wlan0 create wlandev iwlwifi0 at boot and be done. But it can't handle hotplug (USB dongles), can't handle multiple radios, and leaves nobody owning teardown. Since wland must run a RTM_IFANNOUNCE listener regardless (configd doesn't), giving it the VAP lifecycle is barely more work than not.
wpa_supplicant's built-in EAP stack, or not at all — eap8021x is absent from the tree and porting it buys nothing wpa_supplicant lacks. Defer until someone actually needs WPA-Enterprise.
ipconfigd multi-interface: how far? §8's two bugs are the minimum. But a laptop that roams between wired and WLAN really wants service-order arbitration (an IPMonitor), which is a genuinely larger piece. Is "both interfaces can bind, primary is whoever bound last" acceptable for v1?
Written 2026-07-13 against these trees at these commits:
| Repo | HEAD |
|---|---|
nextbsd-redux/nextbsd-userland | df01921 (2026-07-10) |
nextbsd-redux/nextbsd-kernel | 1b2eb00 (2026-07-10) |
nextbsd-redux/nextbsd-kernel-modules | f9f065c (2026-07-12) |
nextbsd-redux/nextbsd-freebsd-compat | afeb233 (2026-07-05) |
nextbsd-redux/nextbsd | f2c647e (2026-07-10) |
nextbsd-redux/nextbsd-overlays | 8fe48d0 (2026-07-03) |
gershwin-desktop/gershwin-on-nextbsd | 436d423 (2026-07-13) |
The companion research doc — now WLAN on NextBSD — the Apple stack, and why we use wpa_supplicant, renamed from freebsd-wifi-management.html → nextbsd-wifi-research.html → its current name — remains correct on Apple's stack, what's open source, the wpa_supplicant/iwd comparison, and the glossary. Its integration sections (old §4 "What this project ships today", §5 "Hypothetical wifid-fbsd", §7 "Open questions") have been deleted, not just flagged, because they asserted the following — all false on NextBSD:
wpa_supplicant from ports… already in pkglist.txt alongside dhcpcd" — NextBSD does not use dhcpcd. nextbsd/README.md:33 explicitly lists dhcpcd among the DHCP clients replaced by ipconfigd. And wpa_supplicant comes from base, not ports.wland, and it rested on a component NextBSD doesn't ship.com.apple.SystemConfiguration.configd. Distributed Objects was the abandoned sibling track.@protocol NCWLANClient sketch — there is no ObjC runtime in nextbsd-userland.wland is a cosmetic / API-symmetry investment, not a functional one" — false. Without something publishing an association-complete signal, DHCP on WLAN does not work at all.Companion plans: configd (note: its repo pointer at pkgdemon/freebsd-launchd-mach is stale — the code now lives at nextbsd-redux/nextbsd-userland) · IPConfiguration · launchd (Mach) · Keychain