FreeBSD IPConfiguration — porting plan DEFERRED

A phased plan to eventually replace dhcpcd in this project with a port of Apple's IPConfiguration daemon-as-plugin. Sequenced after launchd, configd, and mDNSResponder are substantially complete. Recommendation: defer to Phase 7+ unless dhcpcd-with-netconfigd-shim shows real friction. This page exists to document the path so we don't relitigate the decision.

TL;DR

1. Goal & non-goals

1.1 Goal

Eventually replace dhcpcd in the freebsd-launchd project with a port of Apple's IPConfiguration. End state: netconfigd loads IPConfiguration.bundle at startup; the bundle owns DHCPv4/DHCPv6/IPv4LL/RA processing for every interface; state lands in netconfigd's dynamic store under the canonical Apple key namespace; client tools (scutil, ipconfig(8), GUI apps) reach the daemon via Distributed Objects over AF_UNIX.

1.2 Non-goals

2. Source overview

ComponentApple LOCActionPost-port LOC
IPConfiguration.bproj/ (the daemon plugin)39,722port + reshape~28,000
bootplib/ (shared protocol library, also used by sibling daemons)26,997cherry-pick: keep DHCP/DHCPv6 cores, drop NetBoot~16,000
IPConfiguration_framework/ (client API library)5,228rewrite Mach client → DO client~2,500
IPConfigurationHelper/ (XPC helper for PvD HTTPS fetch)1,250fold into main daemon or DO helper~800
ipconfig.tproj/ (CLI tool)1,630rewrite as DO client~700
Total port target~74,827~48,000
Out-of-scope siblings (bootpd, bsdpd, dhcp6d, rtadvd, BSDPClient, netbootdisk, tests)~24,000delete0

Largest single files:

3. Architecture (target)

+-----------------------------------+ | GUI / scutil / ipconfig(8) / VPN | +------------------+----------------+ | DO over AF_UNIX v +-------------------+ +-------+---------+ | /Local/Library/ | vnode src | netconfigd | | Preferences/ |<------------->| (loads | | Network.plist | Setup: keys | IPConfig.bundle) +-------------------+ | | | +-----------+ | +-------------------+ | |IPConfig | | | PF_ROUTE socket | KEM plugin | |.bundle | | | (RTM_NEWADDR/etc)|<------------->| |- dhcp.c | | +-------------------+ | |- DHCPv6 | | | |- arp_ses | | +-------------------+ | |- linklocal| | | wpa_supplicant | ctrl socket | |- rtadv | | | /var/run/wpa_* |<------------->| |- DHCPLease| | +-------------------+ | +-----------+ | +--------+--------+ | v +-------+-------+ | kernel | | (BPF, route, | | resolv.conf, | | ifconfig) | +---------------+

Same shape as Apple's: IPConfiguration is a plugin inside netconfigd's process, not a separate daemon. Shares the SCDynamicStore + DO IPC infrastructure netconfigd provides.

4. File-by-file plan

4.1 Delete on import

4.2 Rewrite (Mach → DO, Darwin → BSD)

FileApple LOCWhy rewriteTarget LOC
IPConfiguration.bproj/server.c654Mach RPC server stubs (_ipconfig_*) → NSConnection-rooted DO server. audit_token_t entitlement checks → getpeereid(2) uid checks.~700
IPConfiguration.bproj/ipconfigd.c10,312Plugin shell uses SCDynamicStore via Mach. Power event handlers (IORegisterForSystemPower) need stubbing or FreeBSD devd bridge. ~70% of file is portable IFState/state-machine logic that ports as-is.~7,500
IPConfiguration.bproj/wireless.c789Replace IO80211/Apple80211API.h with wpa_supplicant control-socket reader (STATUS, SIGNAL_POLL, attached event mode).~250
IPConfiguration.bproj/ifutil.c2,211~30% Darwin-specific ioctls: SIOCAUTOADDR, SIOCSIFL4S, SIOCCLAT46_*, SIOCPROTOATTACH, IFEF_AWDL, IFRTYPE_L4S_*. Ifdef out CLAT46/L4S; use FreeBSD ioctls for the rest.~1,200
IPConfiguration.bproj/HostUUID.c56gethostuuid(3)sysctlbyname("kern.hostuuid", ...).~30
IPConfiguration_framework/IPConfigurationService.c1,245Mach client → DO client. Public API surface (IPConfigurationServiceCreate, etc.) preserved.~600
IPConfiguration_framework/DHCPv6PDService.c565Same: Mach client → DO client.~300
ipconfig.tproj/client.c1,630CLI tool, Mach client → DO client. Subcommand parser stays.~700
IPConfigurationHelper/IPHXPCServer.m + IPHPvDInfoRequestServer.m1,167XPC helper for PvD HTTPS fetch. Either fold into main daemon or implement as small DO helper. Defer until PvD support actually matters.0 (Phase 4+)
bootplib/IPConfigurationLog.c~200os_log_t handle plumbing → syslog(3) wrapper, same shape as launchd's mylog.h.~150

4.3 Port verbatim (or near)

FileApple LOCNotes
IPConfiguration.bproj/dhcp.c4,660DHCPv4 state machine. One MobileGestalt call at line 447 to stub.
IPConfiguration.bproj/DHCPv6Client.c3,717DHCPv6 state machine. RFC 8415 retry constants in bootplib/DHCPv6.h:79-94 — no source changes.
IPConfiguration.bproj/arp_session.c2,505ARP probe/announce/conflict via BPF. bpflib is BSD-portable.
IPConfiguration.bproj/rtadv.c2,133RA consumer; PIO/RDNSS/DNSSL/PvD state.
IPConfiguration.bproj/bootp_session.c827Raw IP/UDP DHCPv4 socket on port 68.
IPConfiguration.bproj/RTADVSocket.c801ICMPv6 RS sender / RA receiver via <netinet6/nd6.h>.
IPConfiguration.bproj/CGA.c711RFC 3972 Cryptographically Generated Addresses.
IPConfiguration.bproj/DHCPv6Socket.c685UDP socket on port 546.
IPConfiguration.bproj/linklocal.c577IPv4LL (RFC 3927).
IPConfiguration.bproj/manual.c + manual_v6.c742Static IP config method.
IPConfiguration.bproj/DHCPDUIDIAID.c371DUID/IAID generation, persisted to DUID_IA.plist.
IPConfiguration.bproj/timer.c + FDSet.c494libdispatch-source timers + FD wrappers.
bootplib/RouterAdvertisement.c1,788RA parser/builder.
bootplib/dhcp_options.c1,716DHCPv4 option parse/build.
bootplib/DHCPv6Options.c1,637DHCPv6 option parse/build.
bootplib/DNSEncryptedServers.c + DNSNameList.c2,868RFC 1035 DNS-name compression + DDR.
bootplib/interfaces.c1,589getifaddrs + SIOCGIFMEDIA; few Darwin-only IFEF_/IFXNAMSIZ usages.
bootplib/cfutil.c1,068CFPropertyList helpers — corebase already provides.
bootplib/IPv4ClasslessRoute.c651Option 121 parser.
bootplib/NICache.c646DHCPv4 binding cache.
bootplib/arp.c633ARP packet build/parse.
bootplib/{ptrlist,dynarray,util}.c~700Utility types.
bootplib/DHCPDUID.c283DUID factory.
bootplib/inetroute.c263Route table reader.
bootplib/udp_transmit.c245Raw UDP send.
bootplib/bpflib.c204BPF socket open/filter.

5. Mach IPC → Distributed Objects

The single MIG IDL bootplib/ipconfig.defs (196 LOC, 21 routines) becomes an ObjC @protocol over NSConnection on a known AF_UNIX path. Same shape as the configd plan.

@protocol NCIPConfiguration

// Counts and lookups
- (NSNumber *)interfaceCount;
- (NSArray *)interfaceList;

// DHCPv4 state queries
- (NSData *)optionForInterface:(NSString *)ifname code:(int)code
                        status:(out NCStatus *)status;
- (NSString *)addressForInterface:(NSString *)ifname
                           status:(out NCStatus *)status;
- (NSData *)lastDHCPPacketForInterface:(NSString *)ifname
                                status:(out NCStatus *)status;
- (NSDictionary *)summaryForInterface:(NSString *)ifname;

// Service lifecycle
- (NSString *)addServiceOnInterface:(NSString *)ifname
                         methodPlist:(NSDictionary *)plist
                              status:(out NCStatus *)status;
- (NCStatus)removeServiceOnInterface:(NSString *)ifname
                            serviceID:(NSString *)sid;
- (NCStatus)refreshServiceOnInterface:(NSString *)ifname
                             serviceID:(NSString *)sid;
- (NCStatus)setMethodForInterface:(NSString *)ifname
                       methodPlist:(NSDictionary *)plist;

// IPv6 / DHCPv6
- (NSData *)lastDHCPv6PacketForInterface:(NSString *)ifname
                                  status:(out NCStatus *)status;
- (NSData *)lastRAForInterface:(NSString *)ifname;
- (NSData *)dhcpDUID;
- (NSNumber *)iaidForInterface:(NSString *)ifname;

// Operational
- (NCStatus)setVerbose:(BOOL)verbose;
- (NCStatus)forgetNetworkForInterface:(NSString *)ifname;

@end

Per-call uid/gid check via getpeereid(2) on the AF_UNIX socket replaces Apple's BSM audit-token entitlement check (server.c:79-115). Same caller identity, much simpler than maintaining libbsm + an entitlement DB.

6. Kernel events: SCDynamicStore, not direct

Important finding: IPConfiguration does not subscribe to KEV_NETWORK_CLASS directly. grep -rln 'KEV_DL\|KEV_INET\|KEV_NETWORK_CLASS' IPConfiguration.bproj returns nothing. All kernel events arrive translated as SCDynamicStore key changes from the sibling KernelEventMonitor.bundle configd plugin. This is a clean win — our port can subscribe to the same key suffixes through netconfigd's KernelEventMonitor port (a Phase 2 item in the configd plan), and the PF_ROUTE knowledge stays in one place.

Keys IPConfiguration subscribes to (ipconfigd.c:9303-9333):

The PF_ROUTE → SC-key translation table goes in netconfigd's KernelEventMonitor port:

FreeBSD PF_ROUTE messageSC key suffix
RTM_IFINFO (link state from if_data.ifi_link_state)State:/Network/Interface/<bsd>/Link
RTM_NEWADDR (AF_INET)State:/Network/Interface/<bsd>/IPv4
RTM_NEWADDR (AF_INET6)State:/Network/Interface/<bsd>/IPv6
RTM_DELADDRsame keys, recomputed
RTM_IFANNOUNCE (IFAN_ARRIVAL/IFAN_DEPARTURE)recompute interface list

7. Apple-specific dependencies

DependencyWhereFreeBSD plan
IORegisterForSystemPower + IOPMConnectionCreateipconfigd.c:8469, 8530Drop in v1; FreeBSD has no pmset-equivalent. Sleep-coordinated lease renewal not supported. Future: bridge from devd ACPI events.
IOPMCopyScheduledPowerEvents / IOPMCancelScheduledPowerEventipconfigd.c:1515, 1531Drop. No scheduled-wake support.
gethostuuid(3)HostUUID.c:52Replace with sysctlbyname("kern.hostuuid", ...). Caveat: ISO first-boot needs to seed kern.hostuuid if blank.
MGCopyAnswer(kMGQProductType, ...)dhcp.c:447One-line stub: return "FreeBSD".
IO80211/Apple80211API.hwireless.c:178Replace with wpa_supplicant control-socket consumer (/var/run/wpa_supplicant/<ifname>, STATUS command).
SymptomReporter.frameworkreport_symptoms.c, DHCPv6Client.c:2692Delete entirely. Apple wireless-diag telemetry.
os_log familythroughoutMap to syslog(3) via opaque handle wrapper. Keep call-sites unchanged.
bsm/libbsm.h audit_token_to_au32()server.c:79-115Replace with getpeereid(2) on the DO socket. Drop the entitlement-DB concept.
IODeviceTree:/chosen NetBoot blobipconfigd.c:2933 via ioregpath.cDelete (NetBoot out of scope).
Darwin-only ioctls (SIOCAUTOADDR, SIOCSIFL4S, SIOCCLAT46_*, SIOCPROTOATTACH)ifutil.c (~12 ioctls)#ifdef __APPLE__ them out. CLAT46 (464XLAT) and L4S features unavailable.
SCPreferences watcherthroughout for tunables fileRequires netconfigd Phase 2+ SCPreferences-equivalent. Sequencing constraint.

8. The hard parts

  1. ipconfigd.c is 10,312 LOC of mixed concerns. Plugin shell + IFState + SCDynamicStore plumbing + power events + DHCP renew scheduling + wireless coordination + service-list ordering. Splitting into a portable core + Darwin-specific shell is the largest single sub-task. No clean cut at any function boundary above ~150 LOC.
  2. Mach IPC is concentrated server-side, but framework client uses it too. server.c (654 LOC) is the only server. But IPConfigurationService.c (1,245 LOC), DHCPv6PDService.c (565 LOC), and ipconfig.tproj/client.c (1,630 LOC) are all pure Mach clients. Our DO rewrite lands in four places.
  3. DUID UUID-mode requires deterministic kern.hostuuid. First-boot ISOs may have kern.hostuuid blank or all-zeros. Need a seed step (UUIDv4 from /dev/random, persisted) that runs before IPConfiguration first runs. Extend the launchd varrun one-shot to handle this.
  4. SymptomReporter + IOPMCopyScheduledPowerEvents sprinkled across files. Simple amputation but ~30 call sites in ipconfigd.c, DHCPv6Client.c:2692, dhcp.c retry paths, report_symptoms.c.
  5. os_log_t handle plumbing. Every translation unit takes os_log_t parameters in macros. Rewrite as opaque void * wrapping our logger, or replace the entire module with a syslog(3) wrapper.
  6. ifutil.c Darwin-specific ioctls. About 12 with no FreeBSD equivalent. #ifdef out; CLAT46 + L4S features unavailable.
  7. PvD HTTPS fetch helper is XPC. IPConfigurationHelper is a separate launchd-managed sandboxed helper that does NSURLSession TLS fetches. We don't have XPC. Either fold into main daemon (loses sandbox isolation) or implement DO-based helper. Defer until PvD support actually matters — RFC 8801 deployment is rare.
  8. BSM audit-token entitlement gating. Replace with getpeereid uid checks; the "entitlement" concept (code-signed plist key on caller binary) doesn't translate to FreeBSD.
  9. Two configd plugin dependencies (InterfaceNamer, KernelEventMonitor) must exist before IPConfiguration loads. netconfigd's responsibility — both are Phase 2 work in the configd plan. Sequencing constraint.
  10. SCPreferences watcher for the tunables plist. netconfigd Phase 2+ SCPreferences-equivalent.

9. The DHCPv6-spam-on-broken-networks problem

Confirmed: IPConfiguration has the same problem as dhcpcd. Both implement RFC 8415 verbatim. RFC 8415 says DHCPv6 Solicit has no max retransmission count (SOL_MAX_RC = 0 = infinite). Both clients retry Solicit forever on networks where the router advertises the M-flag but the DHCPv6 server returns "No Addresses Available." Both cap at one Solicit per hour with random jitter (SOL_MAX_RT = 3600).

Source citations:

The Apple-vs-FreeBSD difference is purely logging policy:

Knob to disable in IPConfiguration: DHCPv6Enabled boolean in Embedded-Info.plist (default true). Per-service: set kSCNetworkProtocolTypeIPv6 ConfigMethod to Manual or LinkLocal. Our port should default to DHCPv6Enabled=false, requiring per-service opt-in via the ConfigMethod Setup-key. This matches the nodhcp6 default we're shipping in dhcpcd today.

Conclusion: porting IPConfiguration does NOT solve the spam problem; it merely renames it. The fix in either world is the same: don't enable DHCPv6 by default on networks where it doesn't function. Not a reason to port; not a reason not to either.

10. Comparison with dhcpcd

FeatureIPConfigurationdhcpcd
DHCPv4 (RFC 2131)
DHCPv4 rapid commit (RFC 4039)✗ (yes for v6)
BOOTP (RFC 951)✓ (separate ConfigMethod)partial (frame layout shared)
IPv4LL (RFC 3927)✓ (linklocal.c)✓ (src/ipv4ll.c)
DHCPv6 stateful (RFC 8415)
DHCPv6 IA_PD prefix delegation
DHCPv6 Information-Request (RFC 3736)
DHCPv6 Reconfigure (RFC 8415 §18)partial (option codes, no full handler)
RS/RA (RFC 4861)
RDNSS / DNSSL (RFC 8106)
DHCP authentication (RFC 3118)
Dynamic SOL_MAX_RT learning✗ (fixed 3600)
Bonjour Sleep Proxy coordination✓ (Apple-only)
IOKit power-management awareness✓ (Apple-only)
CGA (RFC 3972)
RFC 7217 stable IIDs✓ (slaac private)
Captive portal URL option (RFC 8910)✓ (DHCPv6 code 103)partial
Hook-script framework✗ (publishes to SCDynamicStore instead)✓ (/lib/dhcpcd/dhcpcd-hooks/)
resolvconf(8) integrationwrites /etc/resolv.conf directly via configd plugin chain✓ via hook
WiFi association coordinationvia Apple80211 (wireless.c)via wpa_supplicant hook
Bridge / lagg / vlan layeringpartial (Apple's interface model)
Per-iface profiles✓ (each Service has its own UUID)
Privsep✗ (runs in configd as root)✓ (src/privsep*.c)
Cross-platformmacOS onlyNetBSD, FreeBSD, OpenBSD, DragonFly, Linux, Solaris, Haiku
SCDynamicStore publication✓ (canonical Apple keys)✗ (lease file + hooks)
Native ObjC client API (IPConfigurationServiceCreate)✗ (control socket protocol)

What IPConfiguration has that we'd gain: SCDynamicStore-keyed publication (cleaner integration with netconfigd), IPConfigurationServiceCreate framework API (Mac app symmetry), Bonjour Sleep Proxy + IOKit power coordination (irrelevant on FreeBSD without their kernel infra), CGA.

What dhcpcd has that we'd lose: RFC 3118 authentication (rare in real deployments), Reconfigure handler, dynamic SOL_MAX_RT learning, hook-script ecosystem, bridge/lagg/vlan layering recognition (the killer for our use case if we don't replace it), privilege separation, RFC 7217 stable IIDs, cross-platform maintenance burden shared with NetBSD/Linux/etc.

11. Locked architectural decisions

DecisionChoice
Deployment shapeConfigd plugin (loaded into netconfigd's process), not standalone daemon. Mirrors Apple.
Source repoapple-oss-distributions/bootp at the latest tag (currently bootp-534.100.6). Strip everything outside IPConfiguration.bproj/, bootplib/, IPConfiguration_framework/, ipconfig.tproj/.
Mach IPCNone. Replace with GNUstep DO over AF_UNIX. bootplib/ipconfig.defs rewritten as NCIPConfiguration @protocol.
SCDynamicStoreUse netconfigd's port (Phase 2+ work). IPConfiguration subscribes to existing store keys; doesn't reach into the kernel directly.
Power managementDrop. No FreeBSD equivalent for IOPMConnection. Sleep-coordinated renew unsupported.
Wi-Fi coordinationwpa_supplicant control socket replaces Apple80211. Our dhcpcd switch already added wpa_supplicant from ports — same daemon.
EAPOL / 802.1XSkip in v1. Route through wpa_supplicant if needed later.
NetBoot / BSDPOut of scope.
Loggingsyslog(3) wrapper around the os_log macros.
PrivsepInherit launchd's UID model (configd plugins run as configd's UID — root in our case). No separate sandboxing in v1.
LicenseBSD-2-Clause for new code; preserve Apple OSReference 2.0 (Apache) headers on imported files.
DHCPv6 defaultDisabled. Per-service opt-in via ConfigMethod Setup-key. Matches our dhcpcd nodhcp6 posture.

12. Phased delivery

Phase 0 — Repo scaffold + Apple source import + amputation FUTURE

Phase 1 — Plugin loads, no DHCP work FUTURE

Phase 2 — DHCPv4 working on Ethernet FUTURE

Phase 3 — Dynamic store + DO IPC for clients FUTURE

Phase 4 — DHCPv6 + RA processing FUTURE

Phase 5 — IPv4LL fallback + integration tests FUTURE

Phase 6 — Wi-Fi coordination via wpa_supplicant FUTURE

Phase 7 — Cutover from dhcpcd FUTURE

13. Open questions

Q1. Should we do this at all? Defer. dhcpcd is the right tool today. Revisit when (a) Gershwin GUI hits walls with the dhcpcd-shim model, (b) we have netconfigd Phase 2-3 substantially shipped, or (c) someone genuinely wants the months-of-porting cost for architectural symmetry. None of these are true today.
Q2. Hybrid extraction option. Could we take just bootplib/ (DHCP option tables, DHCPv6 option parser, DHCPDUID/IAID, packet structs — pure C, no Mach) as static libraries inside a thin FreeBSD daemon, and skip the ipconfigd.c reshape? Tempting but the protocol cores are tightly coupled to ipconfigd_threads.h + timer.c + the IFEvent state machine. Extracting cleanly is itself a multi-month project. Not obviously cheaper than the full port.
Q3. PvD (RFC 8801) HTTPS helper. The XPC-based helper for fetching Provisioning Domain HTTPS metadata (IPConfigurationHelper) requires an XPC-equivalent we don't have. Defer until PvD deployment matters — currently rare.
Q4. Power-management integration. Apple's IORegisterForSystemPower + scheduled wake events let IPConfiguration coordinate lease renewal across sleep. FreeBSD has no pmset-equivalent. Build a bridge from devd ACPI events later, or live without sleep coordination on laptops.
Q5. Bonjour Sleep Proxy support. ipconfigd.c:978-1098 coordinates with mDNSResponder so a sleep proxy answers ARP/ND while the host sleeps. Useful on a Mac; less useful on a desktop. Requires freebsd-mdnsresponder shipped first. Punt to a Phase 8+ once mDNSResponder lands.

14. References