P6 — Config & DNS — progress log#
Per-cluster entries for P6 (s_conf.c+config_read.c, chkconf.c, the resolver
res*.c). The command-handler clusters inside these P6 TUs follow the same
command-cluster-port skill
seam used in P5 (the msgtab fn-pointer table + a <tu>_link.o partial port).
-
2026-06-04 — P6a (
s_conf.cTKLINE cluster) merged. The first P6 sub-phase. Ported the TKLINE command cluster —m_tkline/m_untkline(the oper handlers),prep_kline(param validation),do_kline(add/update a t/kline + reap matching local clients),wdhms2sec(the0w1d2h3m4sduration parser),tkline_expire(the periodic ring-down called from the ircd.c event loop), and thetkconflist global — toircd-common/s_conf.rs.- Cluster choice / why now. P5's command-handler clusters are complete (only
s_auth.cremains in P5, nom_*handlers, deferred to P7/P-IAUTH). The onlym_*handlers still in C are the P6 TUs:m_dns(res.c, couples to the open resolver-swap decision) and the TKLINE family. TKLINE is the cleanest remaining real command cluster — no socket I/O, no unmade design decision, a self-contained connected component overtkconf. - Guard / partial port.
config_read.cis#include-d intos_conf.c(one big P6 TU) →s_conf.ocan't be dropped outright.#if defined(TKLINE) && !defined(PORT_TKLINE_P6)guards wrap the seven symbol definitions; ans_conf_link.osecond-compile (-DPORT_TKLINE_P6, carrying the same machine-path defines as the natives_conf.orecipe —IRCDMOTD_PATH/IRCDM4_PATH/IRCDCONF_PATH/KLINE_PATH/IRCDCONF_DIR/M4_PATH) is substituted into the link set ("s_conf.o" => "s_conf_link.o"inlink_objs()). The cref oracle keeps the full unguardeds_conf.o. Gotcha:tkconfis referenced by surviving C (find_kill, s_conf.c:2163) and itsexterndecl in s_conf_ext.h is gated#ifndef S_CONF_C(suppressed inside the owning TU) — so the guard does not delete the definition; it swaps it for anextern aConfItem *tkconf;so the C side still compiles while Rust owns the definition. (The function prototypes useEXTERN→empty, so they stay visible and need no such treatment.) - Config-resolved body.
KLINEoff →m_kline+ the#ifdef KLINEkline-config-file-write block inprep_klineare not compiled/ported;do_kline/prep_klineonly ever run withtkline=1, but thetkline?tkconf:kconfternary keepskconfreferenced → it stays a C extern.TKLINEon;TKLINE_MAXTIME= 99999999 (the prep_kline clamp applies);SCH_TKILL= SCH_NOTICE (the notices go to&NOTICES, not a dedicated channel).NO_DNS_LOOKUPon (clients register numeric-host → do_kline's resolved/unresolved branch is faithful either way). Callees:make_conf/free_confare the Rust ports in list.rs,find_classin class.rs,match/exit_client/is_allowed/m_noprivare Rust;kconf/match_ipmask/ipv6_convert/get_client_name/nexttkexpirestay C externs (via bindings). Faithful gotchas:wdhms2sec'stmppersists across loop iterations (set in the digit branch, consumed in the unit branch) and the intmulti*tmpuseswrapping_mul(C signed overflow vs Rust debug panic);(%u)formats atime_texactly as the C does (low 32 bits). - Classification. Handler cluster — msgtab
TKLINE/UNTKLINE = {m_nop, m_nopriv, m_tkline, m_tkline, m_unreg}: reachable by an oper (col 2) or service (col 3); a server prefix hitsm_nop(col 0). NoIsServer(cptr)branch, no remote-user field formatting, nosendto_servpropagation. → L1 + L2, NO S2S (no server-reachable / remote-field path at all). - L1 (
ircd-testkit/tests/s_conf_tkline_diff.rs):wdhms2seccorpus (unit math, bare-digitTKLINE_MULTIPLIERpath, empty/NULL, bad-unit → -1) + thetkconflifecycle withhighest_fdpinned -1 on both sides (reap loop inert):do_klinehead-insert, dedup re-add (bumpshold, no new node — inverse),tkline_expire(0)partial removal with head+middle relink + next-min return (inverse),tkline_expire(1)empties (inverse), re-add post-teardown is a clean single node. Zero-diff vscref_. - L2 (
ircd-golden/tests/golden_s_conf_tkline.rs, fixturetkline_oper.conf, O-line flagT= ACL_TKLINE):oper_tkline_notices(OPER → JOIN &NOTICES → TKLINE insert notice → UNTKLINE unlink notice → bad-format error to self);oper_tkline_reap(two clients — the oper TKLINEs a connected victim's exact~victim@127.0.0.1; victim gets 465 + ERROR Closing Link, the oper — different ident, unmatched — sees the TKLINE + "TKill line active for" notices; the only layer exercising the reap loop);nonoper_tkline_denied(the dispatch-gate inverse — a non-oper TKLINE → 481 m_nopriv, never do_kline). All byte-identical vs reference-C. - No S2S — TKLINE has no
IsServerbranch, formats no remote-user fields, and does not propagate to servers (local&NOTICESnotices + local-client reaping only), so the S2S path is genuinely unreachable under the locked config.
- Cluster choice / why now. P5's command-handler clusters are complete (only
-
2026-06-04 — P6b (
res.cm_dns) merged. The DNS resolver/cache debug command — the last command handler still in C — is ported toircd-common/src/res.rs; everymsgtabhandler is now Rust.- Cluster choice —
m_dnsonly: it walks the DNS cache (cachetop) and prints thecainfo/reinfostats structs. The resolver proper (cache management, queries, parsing) stays C for the full P6 DNS port. - Guard / partial port —
res_link.osecond-compile (-DPORT_DNS_M_DNS, mirrorings_conf_link.o):#ifndef PORT_DNS_M_DNSbracketsm_dns, and aRES_DNS_STATICmacro de-static's the three file-statics the handler reads (cachetop,cainfo,reinfo) so the Rust port resolves them. The plainres.ooracle keeps everythingstatic.res.o → res_link.osubstitution added tolink_objs();res.ostays inCREF_OBJSfor the archive (not inPORTED— the resolver is still C). - Config-resolved body —
cainfo/reinfoare res.c-private struct tags (cacheinfo/resinfo), not in any header, so mirrored as#[repr(C)]int-only structs inres.rs.is_allowed/m_nopriv/inetntopare the Rust ports (linker-resolved);sendto_oneis the still-C variadic trampoline. AFINET=AF_INET6 →inetntop(AF_INET6=10, …). - Classification — handler cluster, msgtab
DNS 0 MPAR { m_nop, m_nopriv, m_dns, m_nop, m_nop }: col 2 (STAT_OPER) is the only reach; the handler-internalis_allowed(sptr, ACL_DNS)needs O-line flagd. - L2 —
golden_s_dns(booted withdns_oper.conf):oper_dns(OPER → 381, thenDNS= the threeCa/Re/Rustats NOTICE lines +DNS l= empty cache dump) andnonoper_dns_denied(the dispatch-gate inverse — non-operDNS→ 481 m_nopriv, never the stats). Both byte-identical vs reference-C. - No S2S —
m_dnshas noIsServerbranch and formats no remote-user fields (it reads only the local cache + stats). The cache-dump line formatting (Ex/= … (CN)/= … (IP)) has no L2 path under the project-wideNO_DNS_LOOKUP(the cache is always empty); covered by the faithful port + review.
- Cluster choice —
-
2026-06-04 — P6c (
s_conf.cflags cluster) merged. The second P6 sub-phase and the first bite into the config grammar TU. Ported the six pure config-flag converters —iline_flags_parse/iline_flags_to_string(I-lineCFLAG_*),pline_flags_parse/pline_flags_to_string(P-line/portPFLAG_*),oline_flags_parse/oline_flags_to_string(O-lineACL_*) — toircd-common/src/s_conf.rs.- Cluster choice / why now. The user picked the config grammar over the
resolver (the resolver swap — faithful vs hickory-dns — is still an open
design decision;
NO_DNS_LOOKUPmeans res*.c isn't exercised at runtime, so that work risks being throwaway). The flags converters are the cleanest self-contained connected component ofs_conf.c: six purechar*<->longfunctions, no shared statics with the rest of the TU, no conf-list dependency — a P1-style leaf port. - Guard / partial port. Extended the existing P6a
s_conf_link.osecond-compile with-DPORT_SCONF_FLAGS_P6(alongside-DPORT_TKLINE_P6); a single#if !defined(PORT_SCONF_FLAGS_P6) … #endifbrackets the contiguouss_conf.c:91-376block. No newlink_objs()entry (thes_conf.o -> s_conf_link.osubstitution already exists); the cref oracle keeps the full unguardeds_conf.ofor the L1 diff. - Config-resolved body.
XLINEoff → noe/CFLAG_XEXEMPT(iline);CLIENTS_CHANNELoff → no&/ACL_CLIENTS(oline);ENABLE_SIDTRACEoff → nov/ACL_SIDTRACEletter (oline).TKLINEon → the#ifndef TKLINE → #undef OPER_TKLINE(config.h:911) is skipped, so allOPER_*are on ⇒oline_flags_parse's only effective post-mask isif (tmp & ACL_LOCOP) tmp &= ~ACL_ALL_REMOTE(every#ifndef OPER_x → tmp &= ~ACL_xcompiled out).KLINEoff butOPER_KLINEon → theq/ACL_KLINEbit is kept. Flag-bit values come fromircd_sys::bindings(CFLAG_*/PFLAG_*/ACL_*, u32 consts bindgen evaluates from struct_def.h) — no hardcoded magic. The'A'arm is the exact C expressionACL_ALL & ~(ACL_LOCOP|ACL_CLIENTS|ACL_NOPENALTY|ACL_CANFLOOD)(not a hand-enumerated bit list — it carriesACL_TKLINE/ACL_SIDTRACEsinceACL_ALLdoes, independent of the gated letters). The three_to_stringstatic output buffers are module-privatestatic mut [c_char; BUFSIZE](not ABI), the NUL written without bumping the index (0-warning). - Classification. Utility/callee cluster — none of the six are in
msgtab.*_parserun frominitconf(config load, stays C → resolves to Rust);iline/oline_to_stringrender inm_stats(s_serv.c, already Rust) STATS i/o.pline_flags_to_stringis only on the volatile STATS l/L link-info path (sendQ +timeofday-firsttime) → no non-volatile L2 path, L1-only. → L1 + L2, NO S2S (pure converters, noIsServerbranch, no remote-user fields). - L1 (
ircd-testkit/tests/s_conf_flags_diff.rs): all six fns vscref_over a per-fn corpus — every flag letter, the full set, dup/mixed/unknown chars, empty, iline NULL→0, the gatede/&/v(ignored), the LOCOP-masked combo (exercises the K/k, S/s, C/c either-branches into_string), plus a parse→to_string round-trip. Zero-diff (7 tests). - L2 (
ircd-golden/tests/golden_s_conf_flags.rs, fixtureflags_oper.conf): the sharedoper.confhas empty flags (only the-branch), so a new fixture carries rich flags — O-line…|10|LKSCdr→ STATS o renders the LOCOP-masked local variantsLkscdr(243); I-line…|10|DIRE|→ STATS i rendersDIRE(215). Exercises Rust*_parseat config load and Rust*_to_stringat STATS end-to-end; both byte-identical vs reference-C. Regression: the existinggolden_s_serv_statsSTATS i/o (-branch onoper.conf) stays green. - No S2S — the flag converters have no
IsServerbranch and format no remote-user fields; genuinely unreachable from a server link.
- Cluster choice / why now. The user picked the config grammar over the
resolver (the resolver swap — faithful vs hickory-dns — is still an open
design decision;
-
2026-06-04 — P6d (
s_conf.cmatch_ipmask) merged. Ported the CIDR/#IP-bitmask matchermatch_ipmask(char *mask, aClient *cptr, int maskwithusername)(s_conf.c:400-467) intoircd-common/s_conf.rs. A pure, side-effect-free predicate overcptr->ip(anin6_addrunder AFINET6) +cptr->username; returns -1 on error, 0 on match, 1 when NO match. The next bite intos_conf.cafter P6c — a leaf IP helper, not config grammar.- Cluster choice / why now. The cleanest remaining self-contained
s_conf.cfunction: no socket, no conf-list dependency, no unmade design decision. High leverage — it's the CIDR matcher behind Rustchannel.rs:971ban-matching and the already-Rust TKLINEdo_kline/prep_kline+ surviving-C conf lookups.ipv6_convert(the sibling IP-normalization helper) is left for a later bite: config-load-only, no clean L2 numeric path, shares no statics. - Guard / partial port.
#if !defined(PORT_SCONF_IPMASK_P6)wraps the def;s_conf_link.o's make-eval recipe gains-DPORT_SCONF_IPMASK_P6(alongside the P6a/P6c guards). Thes_conf_ext.hprototype usesEXTERN→empty, stays visible. The cref oracle keeps the unguardeds_conf.o(→cref_match_ipmask). RED was the undefined-symbol link error:match_ipmaskreferenced by Rustchannel.rs(ban), Rusts_conf.rs(do_kline/prep_kline), and surviving C s_conf.c — exactly one symbol to port, confirming the checklist. - Config-resolved body.
AFINET=AF_INET6(=10) →struct IN_ADDR==in6_addr; an IPv4 joiner is V4-mapped (::ffff:7f00:0001).NO_DNS_LOOKUPon, but the matcher reads only the binarycptr->ip, so faithful regardless. Removedmatch_ipmaskfroms_conf.rs's bindings import (now a local#[no_mangle]def; same-module callers bind it directly); channel.rs keeps importing the bindgen extern decl (resolves to the Rust symbol at link). - Faithfulness.
strncpyzt(dummy,mask,128)=strncpy+ forcedummy[127]=0, operate on the 128-byte copy, keepomask=original for the error message;sscanf(p+1,"%d",&m)via libc;IN6_IS_ADDR_V4MAPPED=addr32[0..2]==0,0,htonl(0xffff)(0xffffu32.to_be());lmask = htonl(0xffffffffL<<(32-j))→(0xffff_ffffu32<<(32-j)).to_be()(the Cu_longshift truncated by htonl == a u32 shift;jis 1..31 in that block, so the shift amount is in range —j==0skips it).s6_addraccessed via the bindgen union__in6_u.__u6_addr8/__u6_addr32;addr32[m]reaches at most index 3 whenj!=0(m 97..127 → m>>5==3), index 4 only when m==128 → j==0 (not indexed). Thebadmask:sendto_flag(SCH_ERROR,…)notice only fires whenmaskwithusername. - Classification. Utility/callee — not in
msgtab. → L1 + L2, NO S2S (pure local-IP predicate, noIsServerbranch, no remote-user field formatting, nosendto_serv). - L1 (
ircd-testkit/tests/s_conf_match_ipmask_diff.rs): Rustmatch_ipmaskvscref_over a corpus on parallelmake_client'd clients (ip set via the projectinetpton, exactly how the I/O layer fills it): v4-mapped /32 //24 //0 with one-off inverses; the sub-32-bitj-boundary (/28, /20) with below/above inverses;user@host/cidr(maskwithusername=1) user-match-then-IP + user-mismatch inverse; badmask (maskwithusername=0so nosendto_flag): no-slash, m<0, m>128, v4-mapped m>96-after-+96, inetpton fail, non-numeric prefix → all -1; native v6 /32 //48 //0 (the m+=96 branch NOT taken). Zero-diff (5 tests). - L2 (
ircd-golden/tests/golden_s_conf_match_ipmask.rs): the end-to-end gate that the Rust matcher fires on the real JOIN path. alice (chanop)+b *!*@127.0.0.0/24on#banc→ a second client from 127.0.0.1 JOIN is banned474(the literal glob-match of127.0.0.0/24vs the string-IP misses →is_bannedfalls tomatch_ipmask, which matches via CIDR); the inverse+b *!*@128.0.0.0/24on#okclets the JOIN through (echo +366). The 127-vs-128 contrast isolatesmatch_ipmaskas the sole discriminator. Byte-identical ref-C vs Rust. - No S2S —
match_ipmaskhas noIsServerbranch and formats no remote-user fields; genuinely unreachable from a server link.
- Cluster choice / why now. The cleanest remaining self-contained
-
2026-06-04 — P6e (
s_conf.cfind_admin/find_me) merged. Ported the two leaf conf-list scansfind_admin(void)/find_me(void)(s_conf.c:899-918) intoircd-common/s_conf.rs. Each is a read-only walk of the globalconflist returning the firstaConfItemwhosestatuscarries a single bit (CONF_ADMIN=1024 /CONF_ME=256), else NULL. The next bite intos_conf.cafter P6d — still read-only accessors over the conf list, not the config grammar.- Cluster choice / why now. The cleanest remaining self-contained
s_conf.cfunctions: trivial list scans, no socket, no unmade design decision. They share only theconfglobal — already a bindgen extern (pub static mut conf), not a private static, so no de-static'ing needed. High leverage:find_adminis the accessor behind the already-Rustm_admin(P5ii, theADMINcommand);find_megates boot. - Guard / partial port.
#if !defined(PORT_SCONF_FINDADMIN_P6)wraps both C defs;s_conf_link.o's make-eval recipe gains-DPORT_SCONF_FINDADMIN_P6(alongside the P6a/P6c/P6d guards). Thes_conf_ext.hprototypes (EXTERN→empty) stay visible. The cref oracle keeps the unguardeds_conf.o(→cref_find_admin/cref_find_me). RED was the undefined-symbol link error:find_adminreferenced by Rusts_serv.rs(m_admin),find_meby surviving Circd.c:1047+s_bsd.c:2932— exactly two symbols, confirming the checklist. - Config-resolved body. Both functions are config-independent; the only build fact
is the bit values (
CONF_ME=256,CONF_ADMIN=1024, bindgen consts). Faithful to the Cfor (aconf=conf; aconf; aconf=aconf->next) if (aconf->status & BIT) break; return aconf;— the loop variable is NULL at the end, so a no-match returns NULL. Walk the raw intrusive list via(*aconf).next, read(*aconf).status(u_int). - Classification. Utility/callee — neither is in
msgtab. → L1 + L2, NO S2S (noIsServerbranch, no remote-user field formatting, nosendto_serv; m_admin'shunt_serverforward is already-Rust and separately covered). - L1 (
ircd-testkit/tests/s_conf_find_admin_me_diff.rs): build ONE hand-chained conf list (viamake_conf) and point both the liveconfglobal and the renamed oraclecref_confat the same head, so the Rust port and thecref_oracle walk identical memory and must return the identical pointer + null verdict. Cases: admin-not-at-head; the no-match inverse for each finder (→ NULL); ADMIN/ME on distinct entries (no aliasing); both bits on one entry (same entry); the empty list (both NULL). A process-wideMutexserializes the tests (cargo's parallel#[test]threads otherwise race the shared global — an empty-list test settingconf=NULLraced a non-empty test mid-scan; caught in RED→GREEN). Zero-diff (6 tests). - L2 (
ircd-golden/tests/golden_s_conf_find_admin.rs): driveADMINfrom a registered local client againstminimal.conf's standard A-line (A|Admin|admin@test|info||TestNet|); assert RPL_ADMINME (256) / ADMINLOC1 (257,host="Admin") / ADMINLOC2 (258,passwd="admin@test") / ADMINEMAIL (259,name="info") byte-identical ref-C vs Rust. Rustm_admincalls the now-Rustfind_admin.find_me's coverage is the boot itself — both servers refuse to start without their M-line, so the successful boot exercises its scan. - No S2S — neither scan has an
IsServerbranch or formats remote-user fields; genuinely unreachable from a server link.
- Cluster choice / why now. The cleanest remaining self-contained
-
2026-06-04 — P6f (
s_conf.copenconf/initconf— the config-file parser) merged. The config-grammar bite of P6: portedopenconf()(s_conf.c:1340, plainopen(O_RDONLY)under M4_PREPROC-off) andinitconf()(s_conf.c:1495, the wholeircd.confline reader + semantic switch) into Rust, building byte-identicalaConfItemlists + side effects. This is the first bite into the config grammar proper (P6c/d/e were leaf converters/scans);rehash, the conf lookups, and theres.cresolver stay C.- Rust-native parsing library. Per the user directive, the line lexer is a new
nom-based moduleircd-common/src/config_parse.rs(nom 7, added toircd-common/Cargo.toml) — a faithful two-stage port of initconf's inline lexing:process_escapes(s_conf.c:1551-1574 escape/comment pass) →split_fields(parse.cgetfield|-splitter). It reproduces the historical dead-quotes[]quirk (the in-place shift overwrites the translated byte, so every\X→literalX; trailing\truncates;#is a comment) and getfield's trailing-\|edge (*(end+1)==0leaves the backslash literal,|a final separator). The semantic switch lives ins_conf.rs. - Config-resolved body.
CONFIG_DIRECTIVE_INCLUDE/M4_PREPROCoff → thedgets/getfieldreader compiles (NOT theconfig_read.c#includepath),openconfis the plain-open#elsebranch;XLINEoff (no name3/CONF_XLINE);ENABLE_CIDR_LIMITSon (tmp5 9th field → add_class cidr arg);FASTER_ILINE_REHASHon (find_conf_entry only for CONF_LISTEN_PORT);SPLIT_SERVERS=10/SPLIT_USERS=5000 pinned (bindgen drops the object-macros). Callees: Rustmake_conf/free_conf/delist_conf/find_class/add_class/collapse/{iline,pline,oline}_flags_parse/match_ipmask; Cipv6_convert/add_listener/setup_ping/find_conf_entry/check_split/check_class/lookup_confhost/dgets. - Guard / partial port.
#if !defined(PORT_INITCONF_P6)wrapsopenconf+initconf;lookup_confhost(a file-static, the only initconf-private callee that stays C — DNS/socket) is un-static'd under the guard so Rust can call it.s_conf_link.o's eval recipe gains-DPORT_INITCONF_P6(alongside the P6a/c/d/e guards). The cref oracle keeps the unguardeds_conf.o→cref_initconf/cref_openconf/cref_getfield. RED was the undefined-symbol link error forinitconf(ircd.c:1016 boot) /openconf; GREEN confirmed vianm target/debug/ircd(initconf/openconf from Rust, lookup_confhost from s_conf_link.o). - Faithful simplification. The C
tmp2heap copy of the port field (DupString'd for CONNECT/ZCONNECT, read at ping setup, carrying a latent cross-line double-free on theif(tmp2) MyFree(tmp2)leftover path) is elided — its only observable effect,ping->port, is computed identically from the still-live port field. No structural difference. Theme.serv->sidfixed 5-bytetoupperloop is bounds-read to the field NUL (the realistic 4-char SID "000A" reads exactly field+NUL; only a malformed <4-char SID would diverge, where C reads adjacent line-buffer bytes). - Classification. Neither in
msgtab→ L1 + L2, NO S2S (noIsServerbranch, no remote-user fields). - L1a (
ircd-testkit/tests/config_parse_diff.rs): nomsplit_fieldsvscref_getfieldover plain/empty/escaped-delimiter/realistic lines — byte-identical field sequences (4 tests). - L1b (
ircd-testkit/tests/s_conf_initconf_diff.rs): Rustinitconfvscref_initconfparse the same on-disk config into separate conf/kconf lists (both seeded viainitclass/cref_initclass,BOOT_QUICKto skip DNS); walk both in lockstep asserting every field (status/port/flags/host/passwd/name/name2/ source_ip/class#/ping.port) + networkname byte-identical, over A/I/i/O/o/Y/C/c/N/D/H/L/V/Q/B/K/k + skipped comment/indented/no-delimiter lines. M (me.serv) + P (listen socket) lines excluded here → covered by L2 boot. - L2 (
ircd-golden/tests/golden_s_conf_initconf.rs): reference-C vs Rust parse the sameflags_oper.conf; STATS y (218, the Y-line→add_class path), i (215), o (243) byte-identical. The boot itself exercises the M-line (me.serv name/sid) + P-line (add_listener). Regression: registration banner, S2S link burst (C/N lines →lookup_confhost), STATS i/o flags, channel/debug goldens all stay green. - No S2S —
initconfhas noIsServerbranch and formats no remote-user fields.
- Rust-native parsing library. Per the user directive, the line lexer is a new
-
2026-06-04 — P6g (
s_conf.cfind_Oline) merged. Ported the O-line lookupm_operuses to resolveOPER <name> <pass>to an O-line; the cleanest remaining self-containeds_conf.cleaf, same shape as the P6efind_admin/find_mebite.- Cluster choice / why now. Of the remaining
s_conf.csymbols (find_conf*,attach_conf/detach_conf,rehash,lookup_confhost),find_Olineis the only read-only leaf with a client L2 path —m_oper(already Rust,s_user.rs:1996) calls it directly. Thefind_conf*family (find_conf/find_conf_host/find_conf_host_sid/find_conf_ip/find_conf_exact/find_conf_name/find_conf_entry) is reached only from still-Cs_bsd.c/s_serv.cserver-link/registration handling → a separate later bite (P6h) with an S2S gate; theattach/detach/rehashmutating cluster is later still. - Guard / partial port.
#if !defined(PORT_SCONF_FINDOLINE_P6)wraps the C def (s_conf.c:1033-1061), mirroring the P6ePORT_SCONF_FINDADMIN_P6guard;-DPORT_SCONF_FINDOLINE_P6added to thes_conf_link.ocompile inbuild.rs. The plains_conf.o(cref oracle) keeps the C def →cref_find_Olinestays available for L1. - Config-resolved body.
CONF_OPS == CONF_OPERATOR == 128(bindgen const). Faithful translation:match/match_ipmaskkeep the IRC convention (0 == matched, nonzero == no match), thematch(host,userhost) && match(host,userip) && (!strchr('/') || match_ipmask)triple-continue is preserved verbatim, and theclients < MaxLinks(Class(tmp))early-return vs thetmp2over-limit fallback is byte-for-byte. Depends only on already-ported leaves (match_,match_ipmask[P6d],mycmp,max_links). - Classification. Utility/callee — not in
msgtab. → L1 + L2, NO S2S:find_Olinehas noIsServer(cptr)branch and formats no remote-user fields; it reads only the local client (m_operis a local-client command). - L1 (
ircd-testkit/tests/s_conf_find_oline_diff.rs): hand-chainedconflist of O-line entries (name/host strings, amake_class()with setmaxLinks, aclientscounter) + amake_client/make_userclient; bothconfandcref_confpointed at the same head;c_find_Oline==cref_find_Olineacross name match + the inverse name mismatch, host glob mismatch, CIDR in-range hit vs out-of-range (/24adjacent) miss, theclients >= MaxLinksover-limittmp2fallback (and an in-limit entry preferred over an earlier over-limit one), wrong-status skip, and empty list. Zero diff. - L2 (
ircd-golden/tests/golden_s_conf_find_oline.rs, fixturefind_oline.conf): a registeredalicerunsOPER carol secret(name matches theO|*@10.0.0.0/8|secret|carol|…O-line but its host CIDR misses the 127.0.0.1 client →find_OlineNULL → 491 ERR_NOOPERHOST) thenOPER alice secret(*@*→ 381 RPL_YOUREOPER); byte-identical vs reference-C. This adds the host/CIDR-miss branch the existinggolden_s_user_oper.rs*@*O-line cannot reach (it only exercises the hit + name-miss). No canonicalizer masking needed.
- Cluster choice / why now. Of the remaining
-
2026-06-04 — P6h (
s_conf.cfind_conf*family) merged. Ported the seven read-only conf/Link-list lookups — the bite the P6g note named ("a separate later bite (P6h) with an S2S gate"). These are the read-only half ofs_conf.c's remaining surface; the mutatingattach/detachfamily, the K-line lookups, andrehashare still C.- Cluster choice.
find_conf_exact(globalconf, user@host match + CONF_OPERATOR over-limit skip),find_conf_name(globalconf, NULL-name/match),find_conf/find_conf_host/find_conf_host_sid/find_conf_ip(a passedLink*chain),find_conf_entry(globalconf, full port+host+passwd+name match with CONF_ILLEGAL masked off). Every one is read-only;find_conf_ipdoes an in-place@-split oftmp->hostit restores before continuing/returning — preserved byte-for-byte. - Guard / partial port. One shared
#if !defined(PORT_SCONF_FINDCONF_P6)wraps s_conf.c:994-1212 (nesting the existing P6gfind_Olineguard inside);-DPORT_SCONF_FINDCONF_P6added to thes_conf_link.ocompile. The plains_conf.o(cref oracle) keeps the C defs for L1. - Config-resolved body. Config-independent loops; only the bit values (
CONF_OPERATOR=128,CONF_SERVER_MASK=56,CONF_HUB,CONF_ILLEGAL— bindgen consts) + struct layout matter. Reused the existingbad_ptr()/max_links()/match_/mycmphelpers;index→strchr,bcmp→memcmp,sizeof(IN_ADDR)→size_of::<in6_addr>()(AFINET=AF_INET6). - Classification. Utility/callee — not in
msgtab. Callers (all still C, resolve to the Rust defs via the link seam):s_serv.cm_server/m_server_estab(the C/N-linefind_conf,find_conf_hostCONF_LEAF,find_conf_host_sidCONF_HUB,find_conf_nameCONF_QUARANTINED_SERVER),s_bsd.c(server connect + registration:find_conf,find_conf_ip,find_conf_host,find_conf_exact),s_misc.c(find_conf_name). → L1 + S2S, no client-only L2. - L1 (
ircd-testkit/tests/s_conf_find_conf_diff.rs): hand-built parallel conf lists /Linkchains, bothconfandcref_confpointed at the same head;c_find_conf*==cref_find_conf*pointer-for-pointer across each fn's positive + inverse — name/host/status/port/passwd-BadPtr-asymmetry misses, the CONF_OPERATOR over-limit skip (later in-limit op preferred), the server-maskmycmp-exact vs non-servermatch-glob split, the>HOSTLEN/BadPtr NUL early-outs, the empty-passwd-vs-sid-mask gate, and thefind_conf_iphost-string-restored assertion. The differential caught a real bug in the first draft: I had translated C's!match(tmp->passwd, sid)sid gate asmatch_(...) != 0(inverted) — C's!matchis true when the passwd-mask matches the sid (entry eligible), so it is== 0. Fixed; zero diff. - S2S (
ircd-golden/tests/golden_s2s_s_conf_find_conf.rs): the s2s.conf fixture'sC|…peer.test/N|…peer.test+H|*||peer.testlines meanPeer::link()drivesfind_conf(C/N accept),find_conf_host(no L line → not leaf), andfind_conf_host_sid(empty-passwd hub gate) duringm_server_estab. A faithful port → byte-identical burst (EOB + UNICK alice) + post-link routing (remote-user introduce → local WHOIS 311). The existing 37-testgolden_s2s_*suite + registration goldens are the broader regression gate for the same establishment path.
- Cluster choice.
-
2026-06-05 — P6i (
s_conf.cK-line lookup family) merged. Ported the four read-only lookups named in the PLAN P6 row —find_kill,find_two_masks,find_conf_flags,find_denied— the read-only remainder ofs_conf.cafter the P6hfind_conf*bite.- Cluster choice. Of the remaining
s_conf.csymbols (find_kill/find_two_masks/find_conf_flags/find_denied, the mutatingattach/detach/attach_confs*family,rehash,find_bounce), the K-line lookups are the read-only leaves — the established P6 bite shape. They share no private statics (each walks the globalkconf/tkconf/conflists orsvrtop);find_bounce(the RPL_BOUNCE sender) sits betweenfind_conf_flagsandfind_deniedand stays C (separate concern, its own future bite). The mutatingattach/detach/rehashcluster is later still. - Guard / partial port. One macro
PORT_SCONF_FINDKILL_P6, three#if !defined(...)regions (find_kill at s_conf.c:2143, find_two_masks+find_conf_flags at :2302, find_denied at :2484 — find_bounce between them left C).-DPORT_SCONF_FINDKILL_P6added to thes_conf_link.oeval recipe inbuild.rs. The plains_conf.ocref oracle keeps the C defs →cref_find_killetc. for L1. RED was the undefined-symbol link error for find_kill/find_two_masks/find_conf_flags/find_denied (callers: s_serv.rscheck_version, s_user.rsregister_user, ircd.c:289/471); GREEN confirmed vianm target/debug/ircd(all fourTfrom Rust). - Config-resolved body. TKLINE on → find_kill's
tklines ? tkconf : kconftwo-passfindkline:goto (translated to a labeled'findkline: loop) compiles. KLINE off (kconf seeded from config K/k lines, not the runtime /KLINE). TIMEDKLINES off → thereply/now/check_time_intervalblock is not compiled, sotimedklinesis an inert_arg. Bit values (CONF_TKILL/TOTHERKILL/KILL/OTHERKILL/DENY/NOCONNECT_SERVER/VER) +ConfClass(x)=x->class->classare the only config inputs. - Faithfulness notes. find_kill: the unresolved (
ip==NULL) vs resolved host branches, the=-prefixed numeric-only skip, thematch/match_ipmask/-split, thestatus==CONF_(T)KILL ? username : identcheckselection, the!port || port==acpt->portgate, and the ERR_YOUREBANNEDCREEPsendto_one(with theBadPtr→"*"/": "/""comment formatting) are byte-for-byte. find_denied: theisdigit(passwd)→atoi→CONF_NOCONNECT_SERVERfind_clientcross-check and the!-reversedsvrtopbcptr->namescan (returningconfhead for a satisfied reversed mask). Reusedmatch_/match_ipmask/mycmp/bad_ptr/reply/me_name/max_links; added inetntop/ipv6string/find_client/svrtop/aServer imports + strncasecmp/strpbrk. - Classification. Utility/callees — none in
msgtab. → L1 + L2 (find_kill) + S2S (find_two_masks); find_denied L1-only. - L1 (
ircd-testkit/tests/s_conf_find_kill_diff.rs): hand-built parallel kconf/tkconf/conf/svrtop state, both live globals +cref_*globals pointed at the same heads;c_find_*==cref_find_*over each fn's positive + inverse (9 tests, zero diff). find_kill: tkconf hit, expired-TK→kconf fallthrough, OTHERKILL→ident vs KILL→username, port match/mismatch, CIDR in/out, '='-numeric-only skip on a resolved client, resolved-hostname match, IsKlineExempt(user->flags) short-circuit, no-user guard (the HOSTLEN guard is unreachable — sockhost/username are fixed 64/11-byte buffers — so it is documented, not tested). find_two_masks/find_conf_flags: status/host/name/no-slash/prefix/strpbrk-empty inverses. find_denied: host-scan hit, reversed present/absent, name+port miss, digit-passwd empty-inner-loop, no-D-line. A test bug (setcptr->flagsinstead ofuser->flags) was caught by the differential agreeing both returned -1 — confirming the port reads user->flags correctly. - L2 (
ircd-golden/tests/golden_s_conf_find_kill.rs,kline.conf): a client from 127.0.0.1 hitsK|127.0.0.1|Banned from this server|*|0|during register_user → killed mid-registration with465 ERR_YOUREBANNEDCREEP+ERROR :Closing Link … (K-lined: …), byte-identical. Exercises the unresolved host match (sockhost == ip string → ip=NULL →match(host,sockhost)), the*user-mask, the zero-port gate, the*comment=passwdpath. The miss path is covered by every other registration golden. - S2S (
ircd-golden/tests/golden_s2s_s_conf_find_two_masks.rs,vline_reject.conf= s2s.conf +V|*||peer.test|): the peer's SERVER line reaches m_server_estabcheck_version→find_two_masks(name,"id/info",CONF_VER)matches the*version mask +peer.testname mask → "Bad version" exit before the burst (no EOB), byte-identical. The no-match path (no V-line → 0 → link proceeds) runs on every othergolden_s2s_*link. find_conf_flags's match path needs a 3-tokenid|miscversion (itsmiscbranch) → covered at L1; its no-match path runs on every link. find_denied is reachable only fromtry_connections(server auto-connect timer + outbound connect) — not reachable in the golden harness → L1 only, documented.
- Cluster choice. Of the remaining
-
2026-06-05 — P6j (
s_conf.cfind_bounce) merged. Ported the RPL_BOUNCE sender named in the P6i note ("find_bounce(RPL_BOUNCE sender — separate concern), … its own future bite"). It is the read-only remainder ofs_conf.cafter the P6i K-line lookups; what stays C is now only the mutating cluster (attach_conf/detach_conf/attach_confs/attach_confs_host/det_confs_butmask/attach_Iline/count_cnlines),rehash, and the initconf-privatelookup_confhost/ipv6_convert.- Cluster choice.
find_bounce(s_conf.c:2407) is a self-contained read-only walk of the globalconflist forCONF_BOUNCE(B-line) entries; it shares no private statics with the mutating family. The P6i bite deliberately left it C ("find_bounce sits between find_conf_flags and find_denied and stays C") because it is a sender, not a lookup — its observable is emitted bytes, not a return value, so it needs a different L1 shape. - Guard / partial port. New macro
PORT_SCONF_FINDBOUNCE_P6, one#if !defined(...)region around s_conf.c:2407–2480.-DPORT_SCONF_FINDBOUNCE_P6added to thes_conf_link.oeval recipe inbuild.rs. The plains_conf.ocref oracle keeps the C def →cref_find_bouncefor L1. RED was the undefined-symbol link error forfind_bounce(callers: Rusts_user.rsm_nick/m_user, Cs_bsd.c:1809, Cs_conf.c:631/637in still-Ccount_cnlines); GREEN confirmed vianm target/debug/ircd(T find_bouncefrom Rust). - Config-resolved body.
CONF_BOUNCE=262144,RPL_BOUNCE=10 (bindgen consts). Threefd-keyed dispatch modes preserved byte-for-byte:fd>=0early rejection (cptr==NULL, class+host unknown → for an empty-host B-line,sprintf(rpl, replies[RPL_BOUNCE], ME, "unknown", name, port)+strcat("\r\n")+ a rawsendto(fd,…)syscall);fd==-1("too many", class known → a bare-number B-line host!strchr('.')&&(isdigit||'-')is matched againstclassviaatoi);fd==-2(host known, class not a number → the class-number branch is skipped, host glob/match_ipmaskonly). On a match the reply goes via the variadicsendto_one(cptr, …, BadTo(cptr->name), name, port). Theif/else ifladder faithfully reproduces C's outer-if (class-number) / outer-else (strchr('/')→match_ipmask elsematch) nesting. Reusedreply/me_name/bad_to/match_/match_ipmask; addedstrcat/sendtoto the module's libc extern block (sprintf/strlen/strchr/isdigit/atoiwere already there). - Classification. Utility/callee — not in
msgtab. → L1 + L2, NO S2S (noIsServerbranch, no remote-user fields; reads only the globalconf+ the localcptr). - L1 (
ircd-testkit/tests/s_conf_find_bounce_diff.rs):find_bouncereturnsvoid, so the differential captures sent bytes, not a return value. Bothconfandcref_confpoint at the same hand-built B-line head;me.name/cref_me.nameset identically. fd<0 cases diffcptr->sendQ(dbuf_map) aftersendto_one; fd>=0 cases pass asocketpairwrite end asfdand non-blockingrecvthe peer. 9 tests, byte-identical: host-glob hit (:irc.test 010 * bounce.elsewhere 7000 :…\r\n) vs miss (continue→empty), CIDR/8in-range vs out-of-range, numeric host onfd==-1(class==atoi hit, class≠atoi miss) vs thefd==-2skip (numeric host falls to host-match → miss), non-CONF_BOUNCE-entry skip + first-matching-B-line-wins (later B-line never reached), thefd<0 && cptr==NULLup-front guard (no-op, no UB), empty conf list, and the fd>=0 empty-hostunknownwrite vs non-empty-host no-op. (Fixed mid-port:person()must setacptnon-NULL —send_messagedoes(*to).acpt->sendM += 1.) - L2 (
ircd-golden/tests/golden_s_conf_find_bounce.rs, fixturebounce.conf): the only reachable client path under the locked config is the server-only P-line rejection inm_nick(Rust).bounce.confmakes the listener server-only (P||||16667|0|S|, theS=PFLAG_SERVERONLYflag in P-line fieldtmp3) and addsB|127.0.0.1||bounce.elsewhere|7000|. A client connecting from 127.0.0.1 sends NICK →IsConfServeronly(cptr->acpt->confs->value.aconf)→find_bounce(cptr,-1,-1)→010bounce (host-match branch:match("127.0.0.1","127.0.0.1")==0,BadTo("*")) →exit_client(… "Server only port"). Byte-identical reference-C vs Rust. - No S2S —
find_bouncehas noIsServerbranch. Thefd>=0raw-sendtoearly-rejection branch (callers_bsd.call-connections-in-use) needs MAXCLIENTS live connections → unreachable in the golden harness, covered at L1 only.
- Cluster choice.
-
2026-06-05 — P6k (
s_conf.cattach/detach mutating cluster) merged. Ported the connected component of conf-attachment mutators named in the P6h–P6j "still C: the mutating family" notes — the bulk ofs_conf.c's remaining surface.- Cluster choice.
attach_conf(s_conf.c:800),detach_conf(722),det_confs_butmask(389),attach_confs(938),attach_confs_host(969), + the private staticsis_attached(781),add_cidr_limit/remove_cidr_limit(667/698). These form one connected component over shared statics + thecptr->confs/global-conf/class-refcount/CIDR-patricia mutation:det_confs_butmask→detach_conf→remove_cidr_limit/free_class/free_conf;attach_confs/attach_confs_host→attach_conf→is_attached/add_cidr_limit.attach_Iline+count_cnlines(callers of this core) stay C — a later bite (P6l) — and resolve to the Rust defs at link. - Guard / partial port. New macro
PORT_SCONF_ATTACH_P6, three#if !defined(...)regions (det_confs_butmask at :389; the CIDR statics + detach_conf + is_attached + attach_conf at :666–905; attach_confs + attach_confs_host at :932–996 — the existing P6e find_admin + P6h find_conf guards sit between them).-DPORT_SCONF_ATTACH_P6added to thes_conf_link.oeval recipe inbuild.rs. The plains_conf.ocref oracle keeps the C defs →cref_attach_confetc. for L1. RED was the undefined-symbol link error for the five exported symbols (callers: s_serv.rs m_server_estab, s_user.rs m_oper/m_umode, s_bsd.c register/connect, s_conf.c:631 count_cnlines); GREEN confirmed vianm target/debug/ircd(all fiveTfrom Rust). - Config-resolved body. ENABLE_CIDR_LIMITS on →
add_cidr_limit/remove_cidr_limitcompile and are called from attach/detach.pnode->datais a*mut c_voidused as an integer counter (C void-ptr arith = +1 byte; cast to long for the cap compare) →((*pnode).data as usize).wrapping_add(1) as *mut c_void+(*pnode).data as usize as c_long >= cidr_amount. YLINE_LIMITS_IPHASH on → attach_conf's per-host limit loop walkshash_find_ip(cptr->user->sip)/iphnext(ported faithfully, only entered when a ConfMax*Local/Global > 0). YLINE_LIMITS_OLD_BEHAVIOUR off → the max-links gate isConfLinks(aconf) >= ConfMaxLinks(aconf) && ConfMaxLinks > 0→ -3. - Faithfulness notes. detach_conf's global-conf-removal loop (
for (aconf2=&conf; ...)) doesn't break after free — it readsaconf3->nextinto*aconf2, NULsaconf3->next, frees, and re-reads*aconf2(the successor) next iteration — preserved verbatim via raw*mut *mut aConfItemwalking. The--aconf->clients <= 0 && IsIllegalshort-circuit, theConfMaxLinks==-1 && ConfLinks==0free_class+NULL, and attach_conf's get_client_ping/ConfLinks++ tail are byte-for-byte. attach_confs/attach_confs_host keep the non-servermatch/ server-maskmycmp-exact split and always call attach_conf when the outer condition holds (thefirstcheck is the inner guard, not a gate on the attach). Reused match_/mycmp/bad_ptr; added free_class/free_link/make_link/get_client_ping/hash_find_ip/patricia_match_ip/patricia_make_and_lookup_ip/patricia_remove imports + the conf_is_illegal/my_connect inline macro helpers. - Classification. Utility/callees — none in
msgtab. → L1 + L2, NO S2S (no IsServer branch, no remote-user fields). - L1 (
ircd-testkit/tests/s_conf_attach_diff.rs): unlike the read-only P6e–P6j lookups, these MUTATE shared state, so the two impls can't share it. Each scenario runs identically on two fully-isolated worlds via anApiof fn-pointers — LIVE (Rust port) overconf/istat/classes, CREF overcref_conf/cref_istat/cref_classes, each built with its own allocators — returning aVec<i64>of observations (return codes, refcounts, chain shape, is_conflink delta, freed-ness) asserted equal. 8 tests, zero diff: attach happy-path + is_attached idempotency + detach round-trip (re-attach clean, counts back to baseline); IsIllegal -1 + Y-line-max -3 gates + the under-cap inverse; ENABLE_CIDR_LIMITS amount=2 admits 2 same-CIDR clients, rejects the 3rd with -4, freed slot reusable after detach; free-class on maxLinks==-1; illegal-conf removal from the global conf list (pointer gone, survivor head); det_confs_butmask keeping only the masked conf; attach_confs glob-vs-mycmp + attach_confs_host host match, each with its miss.mk_classwipes the struct (make_class is a bare MyMalloc — uninitialized maxH* would trip the IPHASH loop and differ between worlds; caught as the first SIGSEGV). - L2 (
ircd-golden/tests/golden_s_conf_attach.rs,ylinemax.conf): class 10 maxlinks 1 + an I-line binding 127.0.0.0/8. alice registers and takes the only slot (ConfLinks 0→1); bob hits the same I-line and is rejected mid-registration via attach_conf -3 EXITC_YLINEMAX → "Too many connections" exit, byte-identical. Adds the one client-reachable attach branch the existing all-success registration goldens never hit; the success + detach paths are covered pervasively by the registration + golden_s2s_* suite (confirmed green: golden_registration, golden_s2s_link/server/squit/quit/find_conf). - No S2S — attach_conf/detach_conf have no IsServer branch and format no remote-user fields. The server-link wrappers (attach_confs C/N in m_server_estab, det_confs_butmask on SQUIT) are exercised by the 37-test golden_s2s_* link-establishment + teardown suite.
- Cluster choice.
-
2026-06-05 — P6l (
s_conf.cattach_Iline+count_cnlines) merged. Ported the two callers of the P6k attach/detach core that the P6k note explicitly deferred ("attach_Iline/count_cnlines(callers of this core) stay C — a later bite (P6l)"). With these gone, the onlys_conf.cthat stays C isrehash+ the initconf-privatelookup_confhost/ipv6_convert.- Cluster choice.
attach_Iline(s_conf.c:485) — the I-line lookup used during local client registration: scan the globalconffor the bestCONF_CLIENT(I) line whose port/name/host/password matchcptr, set the +r / kline-exempt conf flags ontocptr->user->flags, copy the resolved host intocptr->sockhostif the conf permits, thenattach_conf(cptr, aconf).count_cnlines(s_conf.c:648) — read-only walk of a passedLinkchain returning the singleCONF_NOCONNECT_SERVER(N) line (NULL if 0 or >1 of each C/N). Both are direct callers of the P6k core (attach_Iline→attach_conf/find_bounce); grouping them is the natural finals_conf.cbite beforerehash. - Guard / partial port. New macro
PORT_SCONF_ILINE_P6, one#if !defined(...)region wrapping s_conf.c:483–664 (theUHConfMatchmacro — used only byattach_Iline— through the end ofcount_cnlines).-DPORT_SCONF_ILINE_P6added to thes_conf_link.oeval recipe inbuild.rs. The plains_conf.ocref oracle keeps the C defs →cref_attach_Iline/cref_count_cnlinesfor L1. RED was the undefined-symbol link error forattach_Iline(s_bsd.c:929 check_client) +count_cnlines(s_bsd.c:1025 check_server); GREEN confirmed vianm target/debug/ircd(bothTfrom Rust). - Config-resolved body. UNIXPORT off → the
IsUnixSocket(cptr)/aconf->host[0]=='/'unix-path branch and the#ifdef UNIXPORTblock drop out; only thestrchr('/')→match_ipmask(CIDR) vsUHConfMatch(glob) host split compiles. XLINE off → theIsConfXlineExempt→ClearXlinedblock drops out. NO_DNS_LOOKUP on → clients register numeric, soattach_Ilineis normally called withhp == NULL(theadd_local_domain/uhostname-match path is the rare branch, ported faithfully + exercised at L1 with a constructedhostent).UHConfMatch(x,y,z)=match(x, index(x,'@') ? y : y+z).ConfClass(aconf)=(*(*aconf).class).class. - Faithfulness notes. The
for-loop with manycontinues is rendered as aloopthat advancesaconf = (*aconf).nextat the top so eachcontinuemirrors the for-increment (thebreakpaths — password-no-fall + the final attach — still exit cleanly). The password check is!BadPtr(passwd) && strcmp(cptr->passwd, aconf->passwd) != 0(C's!StrEq=strcmp != 0);SetRestricted/SetKlineExemptwritecptr->user->flags;get_sockhost(cptr, uhost+ulen)copies the host tail.count_cnlineskeeps the deadclinetracking verbatim (C assigns it but returns onlynline). Reusedattach_conf/find_bounce/match_/match_ipmask/bad_ptr/bad_to/reply/me_name; addedadd_local_domain/get_sockhost/hostent/ERR_PASSWDMISMATCH/FLAGS_GOTIDimports + sixis_conf_*flag helpers +conf_class/uh_conf_match. - Classification. Utility/callees — neither in
msgtab. → L1 + L2 (attach_Iline), L1 (count_cnlines), NO S2S. - L1 (
ircd-testkit/tests/s_conf_iline_diff.rs):attach_IlineMUTATES (attach_conf) → two fully-isolated worlds via anApiof fn-pointers (the P6ks_conf_attach_diff.rspattern); each scenario returns aVec<i64>(return code,aconf->clients, classlinks,cptr->confschain length,cptr->user->flags,cptr->sockhostbytes,sendQbytes) asserted equal LIVE vs CREF. 8 tests, zero diff: happy glob match + the no-matching-I-line-2inverse; the port gate (skip vs attach); CIDR*@10.0.0.0/8in-range hit / out-of-range miss; empty host/name match-any; password fall-through (Fflag → skip to the next I-line vs no-F→464sent +-8, second never tried) + correct-passwd attach;CFLAG_RESTRICTED/CFLAG_KEXEMPT→FLAGS_RESTRICT/FLAGS_EXEMPTonuser->flags+ the no-flag inverse; hp!=NULL name match onuser@fullname+get_sockhostcopy + the non-matching-name-2inverse.count_cnlines: exactly-one-N → that N, zero-N → NULL, two-N → NULL, non-server entries skipped, empty chain → NULL. - L2 (
ircd-golden/tests/golden_s_conf_iline.rs,iline_pass.conf): the only I-line —I|*@127.0.0.0/8|secret|||10||— carries a password and noFflag, so a client from 127.0.0.1 sending noPASSmismatches and, with no fall-through, is rejected mid-registration →attach_Ilineemits464 ERR_PASSWDMISMATCH+ returns-8(EXITC_BADPASS) →register_userexits withERROR :Closing Link … (Bad password), byte-identical reference-C vs Rust. Exercises the!BadPtr && !StrEqmismatch + the no-fall-throughsendto_onepath the all-success registration goldens (passwordless I-lines) never hit. The success path is covered pervasively by every other registration golden. - No S2S — neither function has an
IsServerbranch or formats remote-user fields.count_cnlines's server-link reachable path (check_serveron every inbound SERVER) is exercised by the existing 37-testgolden_s2s_*link-establishment suite (golden_s2s_link/server/squit/quit confirmed green).
- Cluster choice.
-
2026-06-05 — P6m (
s_conf.cfinalize:rehash+ipv6_convert+lookup_confhost+ the data globals) merged. Ported the last threes_conf.cfunctions and moved its last three data globals —s_conf.cis now FULLY Rust,s_conf.odropped outright (the P6a..P6ls_conf_link.opartial port is gone). This closes thes_conf.cstrand the P6l note named ("the onlys_conf.cthat stays C isrehash+ the initconf‑privatelookup_confhost/ipv6_convert").- Cluster choice. The complete remaining
nm --defined-only s_conf_link.oset wasrehash/ipv6_convert/lookup_confhost(T) +conf/kconf/networkname(B) + the file-staticrcsid(r, unreferenced → vanishes for free). Porting all six drops the.o.rehashreachesinitconf/lookup_confhost/ipv6_convert, so the three functions are one natural final bite; the globals must move with them (they were the last s_conf.c-owned data). - Drop mechanism. Added
s_conf.otoPORTED, removed the"s_conf.o" => "s_conf_link.o"entry inlink_objs()+ the wholes_conf_link.omake --evalrecipe in build.rs. The cref oracle keeps the full unguardeds_conf.o(CREF_OBJS) so thecref_*symbols survive for every existing s_conf L1 diff. RED was the undefined-symbol link error for rehash/ipv6_convert/lookup_confhost/conf/kconf/networkname (callers: list.rsdelist_conf, s_serv.rsreport_configured_links/m_connect/m_rehash, s_conf.rs's own already-ported lookups, ircd.c SIGHUP); GREEN confirmed vianm target/debug/ircd(all sixT/Bfrom Rust). - Data-global move.
conf/kconf/networknameflip fromuse ircd_sys::bindings::{…}to#[no_mangle] pub static mutdefs in s_conf.rs (thetkconf/classes/msgtabpattern). list.rs + s_serv.rs keep importing them from bindings — those externs resolve to the new Rust defs at link. Only s_conf.rs had to stop importing them (can't import + define the same name); also removed its now-conflictingipv6_convertimport + the P6fextern { fn lookup_confhost }decl + the P6fnetworknameimport. - Config-resolved bodies.
rehash(s_conf.c:1231): ULTRIX off (no fork/pidfile), USE_SYSLOG off, TKLINE on (thesig=='t'→tkline_expire(1)block compiles). The twoaConfItem**walk loops (the in-use/ILLEGAL marking pass + the post-initconf flush) are rendered as raw*mut *mut aConfItemloops preserving C's*tmp = tmp2->nextvstmp = &tmp2->nextbranch exactly;IsMe(acptr)=status == STAT_MEinline;NextClass(FirstClass())=(*classes).next,MaxLinks(x)=-1=(*x).maxLinks=-1;mysrand(timeofday)casts tou32; theread_motd(IRCDMOTD_PATH)path comes fromircd_sys::MOTD_PATH.ipv6_convert(s_conf.c:1444): faithful pointer arithmetic — theuser@copy-with-'@', the/cidrNUL-off beforeinetpton, thej=len-1/buf+jsplice, the*(s-1)='/'restore (C's post-incrementeds= myslash.add(1)), non-IP passthrough (t=orig).lookup_confhost(s_conf.c:2101):BadPtrguards,@-tail,isalpha||isdigitfirst-char gate,inetpton(nonzero=stored) elsegethost_byname(still-C)h_addr_list[0]copy elseminus_onefill + badlookup;AND16(ipnum)==255(all 0xff) →bzero16 bytes. - IRCDMOTD_PATH plumbing.
read_motdis already Rust (s_misc.rs) butIRCDMOTD_PATHis a recursively-expanded Makefile path var, not a header#define(absent from bindgen). build.rs expands it viamake --eval=__ircd_print_motdand emitscargo:rustc-env=IRCD_MOTD_PATH=…; ircd-sys/src/lib.rs re-exports it aspub const MOTD_PATH = env!("IRCD_MOTD_PATH")(rustc-env applies only to the emitting crate, so the const bridges it to ircd-common). The Rustrehashbuilds a temporaryCStringfrom it —read_motdonly opens the path, never retains it. - Classification. Utility/callees — none in
msgtab.rehashis reached from Rustm_rehash(oper REHASH) + still-Circd.c(SIGHUP);ipv6_convert/lookup_confhostfrom the Rustinitconfat boot/rehash. → L1 (ipv6_convert) + L2 (existing REHASH + initconf goldens), NO S2S — none has anIsServerbranch or formats remote-user fields (REHASH server propagation ism_rehash's concern, ported in P5rr). - L1 (
ircd-testkit/tests/s_conf_rehash_diff.rs):c_ipv6_convertvscref_ipv6_convertover bare-v6 (::1),2001:db8::1, v4-mapped (::ffff:127.0.0.1),user@v6,v6/cidr,user@v6/cidr, and non-IP passthrough (*@*,notanip,notanip/24,bob@notanip) on independent input copies, asserting byte-identical output and that the in-place@//poke was restored.lookup_confhostisstaticin the unguarded oracle (the symbol-rename only renames globals → nocref_lookup_confhost) so it has no L1 shape → covered by the initconf/boot golden (initconfcalls it on every conf host).rehashreopens listeners / forks iauth / resets the resolver fd → no isolated-L1 shape → covered by the L2 REHASH transcript. - L2 (
golden_s_serv_maintREHASH:rehash_success_matches_reference382 +rehash_reject_matches_reference481 now drive the Rustrehash, byte-identical;golden_s_conf_initconfSTATS y/i/o over the boot-parsed config exerciseslookup_confhost/ipv6_convert). Regression:golden_registration,golden_s2s_link,golden_s2s_s_conf_find_conf,golden_s_conf_iline/attach+ all s_conf L1 diffs stay green (the conf/kconf data-global move touches every conf consumer). - No S2S — none of the three has an
IsServerbranch or remote-field formatting. The REHASH command's server-to-server propagation lives inm_rehash(P5rr);rehashitself only reloads local config + the resolver/iauth fds.
- Cluster choice. The complete remaining
-
2026-06-05 — P6n (
res_comp.c— BIND DNS wire-format primitives) merged. The pure wire-format leaf of the resolver, ported outright toircd-common/res_comp.rs;res_comp.oadded toPORTEDand dropped (no partial_link.o— every symbol is portable).- Cluster choice —
res_comp.cis the foundation the rest of the resolver builds on:res_mkquery.c(ircd_dn_comp/ircd__putshort/ircd__putlong) andres.cproper (ircd_dn_expand/ircd_getshort/ircd_getlong/__ircd_dn_skipname) both call into it. It is the natural first DNS bite — fully self-contained, pure, no event-loop entanglement. The remaining resolver TUs (res_mkquery.c,res_init.c,res.cproper) stay C for a later P6 cluster. - Config — no gates touch this TU. The
#if 0block (res_hnok/res_ownok/res_mailok/res_dnok) is dead code (not compiled); theNEXT-gatedres_getshortis absent.nm --undefined-only cbuild/res_comp.o= only libc (memcpy/strchr/__errno_location) → zero ircd-internal deps. - Classification — utility/leaf TU: none of its 7 public symbols appear in
msgtab. The seven are global (T) so each has acref_oracle → fully L1-diffable. The 7 BINDstatichelpers (ns_name_ntop/_pton/_unpack/_pack/_uncompress/_compress/_skip,special/printable/mklower/dn_find) have no oracle → ported as private Rust fns, covered transitively through the public entry points. - Faithfulness — faithful raw-pointer/
unsafeport mirroring the C pointer arithmetic exactly (label-length bytes, the0xc0compression-pointer encode/decode, thednptrs/lastdnptrtable walk + mutation indn_find/ns_name_pack,dn_comp's post-increment*dnptrs++sodn_findseesdnptrs[1..]).errnoset via*libc::__errno_location()toEMSGSIZE/ENOENTon every error path so callers observe the identical side effect. Thegoto nextindn_findbecame a labelled'outerloop. ConstantsNS_CMPRSFLGS=0xc0/NS_MAXCDNAME=255/MAXCDNAME=255fromnameser_def.h. - L1 (
ircd-testkit/tests/res_comp_diff.rs, 9 tests, zero diff) — get/put value match + big-endian byte order + put→get round-trip;dn_comp→dn_expandpack/expand round-trip (incl. root.); the compression-pointer reuse (two suffix-sharing names) diffed as buffer-relativednptrsoffsets + the emitted wire bytes againstcref_; a hand-built compressed-pointer message (0xc0indirection) → identical decoded string + consumed-octet count;dn_skipnameover plain/compressed/truncated names; and the inverse error paths — dstsiz-too-small (expand + comp), malformed out-of-range pointer — each returning-1from both with matchingerrno. - L2 / S2S — none. Pure wire-format leaf with no client- or server-reachable command path under the locked config (the resolver proper that uses these is still C;
m_dnsdebug output does not surface compression internals). End-to-end DNS coverage arrives when the resolver proper is ported under a fixture nameserver. - Plan —
docs/superpowers/plans/2026-06-05-p6n-res_comp.md.
- Cluster choice —
-
2026-06-05 — P6o (
res_mkquery.c→res_mkquery.rs) merged. The BIND-derived DNS query-packet builderircd_res_mkquery, the second resolver leaf after P6n.res_mkquery.odropped outright (added toPORTED; no_link.o).- Cluster choice —
nm cbuild/res_mkquery.o: one defined symbol (ircd_res_mkquery); undefined deps areircd_dn_comp/ircd__putshort/ircd__putlong(Rust, P6n),ircd_res/ircd_res_init(stay C, res_init.o),memmove/__h_errno_location/libc. Dropping it leaves every dep defined, so it's a clean single-function bite. - Layout hazard / HEADER bitfields — the function writes the 12-byte DNS header through a
HEADER *bitfield struct (nameser_def.h:249:id:16/opcode:4/rd:1/rcode:4/qdcount:16/ancount:16/arcount:16). Rather than hand-reproduce gcc's little-endian bitfield packing, the port uses the bindgenHEADERtype (ircd_sys::bindings, asserted size 12) and itsset_*accessors, which are byte-identical to the C layout by construction.htonsreproduced asu16::to_be;h_errno = NETDB_INTERNALvia anextern "C" { fn __h_errno_location() }.bzero→ptr::write_bytes,bcopy→ptr::copy. - Config-resolved body —
DEBUGoff → theRES_DEBUGprintfblock is not compiled (not ported). Three op cases: QUERY/NS_NOTIFY_OP (shared question-section path + the optional additional-record branch whendata != NULL), IQUERY (answer section), default →-1. - Classification — utility/leaf, not in
msgtab; callers are the still-Cres.cresolver (query_name/do_query_name/do_query_number). No client/server command path under NO_DNS_LOOKUP ⇒ L1 only, like P6n. - L1 (
ircd-testkit/tests/res_mkquery_diff.rs) —c_res_mkquery(#[link_name]→ Rust) vscref_ircd_res_mkquery, byte-identical packets (return length + emitted bytes) over: QUERY with/without RES_RECURSE (therdbit), NS_NOTIFY_OP withdata==NULL(matches QUERY) anddata!=NULL(the additional-recorddn_comp+T_NULL+ttl+rdlength+arcount path), IQUERY withdatalen>0and==0; plus the-1inverses — unknown op, NULL buf,buflen<HFIXEDSZ, and the question-sectionbuflen -= QFIXEDSZ < 0/dn_comp < 0overflow at several small buflens. Both resolver-state globals (ircd_res,cref_ircd_res) are pinned identically withRES_INITset (skips the nondeterministicircd_res_init/res_randomidgettimeofday+getpid path) and the shared++idside effect is diffed after every call. A process-globalRES_LOCKserializes the parallel#[test]threads (the globals are shared across threads in the one test process — without the lock theidraces and the header bytes diverge). Zero diff over 7 tests. - No S2S/L2 — pure wire-format leaf, no
IsServer/remote-field/command path. End-to-end DNS coverage lands with the resolver proper (res.c+res_init.c) under a fixture nameserver.
- Cluster choice —
-
2026-06-05 — P6p (
res_init.cresolver-state bootstrap) merged. Portedircd/res_init.coutright toircd-common/res_init.rs→res_init.odropped; onlyres.c(the resolver proper) is still C in P6.- Cluster choice. The smaller, self-contained of the two remaining DNS files.
nm cbuild/res_init.odefinesircd_res(B),ircd_res_init/ircd_res_randomid(T), and the file-staticres_setoptions(.isra.0);nm --undefined-onlyshows zero ircd-internal deps — only libc +inetpton(the P1support.rssymbol). Same self-contained-leaf shape as P6n/P6o → drop the.o(add toPORTED), no_link.o. - Config-resolved body. Verified
DEBUG/NEXT/RESOLVSORT/RFC1535/HAVE_GETIPNODEBYNAME/USELOOPBACK/__BIND_RES_TEXTall OFF incbuild/{setup,config}.h+build.rsdefines. So:ircd_resis a plain zero-init BSS global (no= {RES_TIMEOUT,});ircd_res_initis the simple path only (defaults →LOCALDOMAINblock →/etc/resolv.confparse loop with theMATCHmacro +inetpton(AF_INET6,…)nameservers →gethostnamefallback → the#ifndef RFC1535default-dnsrchderivationLOCALDOMAINPARTS=2/MAXDFLSRCH=3→RES_OPTIONSenv →options |= RES_INIT), no NetInfo/ircd_netinfo_res_init, no sortlist;res_setoptionshandlesndots:(clamp toRES_MAXNDOTS=15),inet6(→RES_USE_INET6=0x2000),debug(no-op, DEBUG off);res_randomid=0xffff & (tv_sec ^ tv_usec ^ getpid()). - Faithfulness. Raw-pointer port mirroring the C:
strncpyzt→strncpy+NUL; the two near-identical search-list tokenizers inlined faithfully (theLOCALDOMAINone breaks on\n, the filesearchone doesn't);dnsrch[]pointers walk intodefdname; nsaddrsin6_addr=in6addr_any (16 zero bytes) /sin6_family=AF_INET6 /sin6_port=htons(53);ndotswritten via the bindgenset_ndotsbitfield accessor.ircd_resdefined as#[no_mangle] static mutviaMaybeUninit::zeroed().assume_init()(thes_misc.rs::ircstidiom);res_mkquery.rs/res.c/s_bsd.ckeep importingircd_res/ircd_res_initfrom bindgen and resolve to the Rust defs at link (the conf/kconf-globals mechanism). - Classification. Utility/leaf (none in
msgtab); noIsServer/remote-field path. - L1.
ircd-testkit/tests/res_init_diff.rs: zero diff vscref_ircd_res_init. Bothircd_res/cref_ircd_reszeroed +.idpinned0x4321per test (skip the nondeterministicres_randomid); env-set + both inits + env-restore under a process-globalRES_LOCK(parallel#[test]threads share the env + globals). The result structs diffed field-by-field (scalars/bitfields, 3×nsaddr_listsockaddr_in6bytes,defdnamebytes,dnsrch[]as offsets intodefdname). The differential makes the/etc/resolv.conf+inetptonpath host-robust (both read the same file → identical, or both skip it); the host-independent branches run on every CI host via env —plain(default-dnsrch),LOCALDOMAIN(search tokenizer),RES_OPTIONSndots:3 inet6andndots:99(theRES_MAXNDOTSclamp).res_randomidnondeterministic → only& 0xffffsmoke-tested (the one symbol with no L1 diff). - L2/S2S. None — pure leaf, no client/server command path under
NO_DNS_LOOKUP. Boot+registration (golden_registration) and the resolver-adjacentgolden_s_dnsgoldens stay byte-identical, confirming the Rustircd_res_init/ircd_res(called froms_bsd.cat startup) boot the server identically to reference-C. End-to-end DNS coverage (L2/L4) arrives when the resolver properres.cis ported under a fixture nameserver.
- Cluster choice. The smaller, self-contained of the two remaining DNS files.
-
2026-06-05 — P6q (
res.ccache-hash leaves) merged. First bite of theres.cport — the last C file.res.cis one large connected cluster (resolver socket + request queue + hostent cache) over file-statics with only 11_ext.h-exported entry points; the dependency floor is the hostent cache, whose foundation is the two pure hash leaveshash_number/hash_name. Ported bottom-up so the cache core (make_cache/update_list/find_cache_*/rem_cache, P6r+) can build on Rust hashes.- Cluster choice — only
hash_number/hash_name(pure: read solely their argument, touch no module state → zero data-global exposure), keeping this bite about establishing the newres.cstatic seam on a trivially verifiable pair before the gnarly cache core. - Guard / seam — new three-way
RES_CACHE_LINKAGEmacro inres.c:externunder-DPORT_RES_CACHE_P6q(res_link.o LINK build: bodies#ifndef'd out → Rust defines them, still-C walkers resolve to Rust at link), empty under-DRES_CACHE_EXPOSE(cref oracle res.o: GLOBAL →cref_hash_*for L1), elsestatic(plain).build.rs: res_link.o gains-DPORT_RES_CACHE_P6q; a new step recompiles the crefres.owith-DRES_CACHE_EXPOSE(overwrites the plain one — res.o is consumed only bylibcref.a, the link set uses res_link.o, so the real binary is untouched). RED confirmed: dropping the C bodies left 8 undefinedhash_name/hash_numberrefs from the still-C walkers (res.c:1084/1088/1282/1316/1337/1382/1583/1600) — the exact port checklist. - Config-resolved body — neither function is config-gated;
ARES_CACSIZE=1009. - Faithfulness —
hash_number:hashv = ip[0]; for i in 1..16 { hashv = 2*hashv + ip[i] }over a wrappingu_int(the doubling overflows 32 bits for large addresses —% ARES_CACSIZEapplied only at the end), reads exactly 16 bytes (sizeof struct in6_addr; AFINET=AF_INET6) →wrapping_add/wrapping double.hash_name: sum bytes until the first./NUL,% 1009;charis signed on x86-64, so a high-bit byte promotes to a negativeintbefore the unsigned+=— replicated via*p as i32 as u32+wrapping_addfor 8-bit-clean parity. Mutable*mutargs (Cu_char*/char*). Returnsc_intin[0,1009). - Classification — utility/leaf, not in
msgtab, no client/server command path (the resolver is inert underNO_DNS_LOOKUP; the cache never populates at runtime). - L1 —
ircd-testkit/tests/res_hash_diff.rs: Rusthash_number/hash_name(#[link_name]→ the only def once res_link.o drops the C bodies) vscref_over crafted corpora — 16-byte v6 addrs (zero, all-0xff overflow, single-bit, ::ffff: v4-mapped, every-byte-high-bit, random), names (empty, dot-first, no-dot, embedded-dot, case-sensitive, UTF-8/0xff high-bit, long label); each result range-checked[0,1009). Zero diff. Both pure → no global state / noRES_LOCK. - L2 / S2S — none. No client/server command reaches these under the locked config;
golden_s_dns+ the registration/boot goldens (which boot the full ircd + still-C resolver, exercising the C cache walkers that now call the Rust hashes) confirm no regression. End-to-end DNS coverage arrives when the resolver proper is ported under a fixture nameserver.
- Cluster choice — only
-
2026-06-05 — P6r (
res.ccache removal/expiry core) merged. Second bite of theres.cport (after P6q'shash_number/hash_name). Ported the removal/expiry connected component — the half of the hostent cache that deletes entries — toircd-common/res.rs, built directly on the P6q Rust hashes.res.c's only remaining strands are the resolver proper (socket + request queue + answer parser) and the cache build/insert/lookup half.- Cluster choice. Bottom-up from the cache floor:
rem_list(static),rem_cache(static),expire_cache(global, res_ext.h),flush_cache(global, res_ext.h). They form a clean connected component —expire_cache/flush_cacheboth callrem_cache;rem_cache/rem_listcall only the P6q hashes + touch the cache data structures — with no dependency on the still-C build/insert/lookup half (add_to_cache/update_list/find_cache_*/make_cache). The two public entry points give cleancref_L1 symbols; the two statics are exposed via the generalizedRES_CACHE_LINKAGE. - Seam.
-DPORT_RES_CACHE_REM_P6radded to theres_link.orecipe.RES_CACHE_LINKAGEnow firesexternunder eitherPORT_RES_CACHE_P6qorPORT_RES_CACHE_REM_P6r(res_link.o defines both), so therem_cache/rem_listforward decls + definitions de-static; their bodies (+expire_cache/flush_cache) are wrapped in#ifndef PORT_RES_CACHE_REM_P6r. RED (after fixing a static-vs-non-static decl mismatch on the definitions) was the four undefined refs:flush_cache(Rusts_conf.rsrehash),expire_cache(Circd.c:1195),rem_list(Cres.c:1379find_cache_number),rem_cache(Cres.c:1119add_to_cache) — the exact port checklist. GREEN confirmed vianm target/debug/ircd(all fourTfrom Rust). - Data-global exposure. The Rust removal core + the L1 live world need
cachetop/cainfo(alreadyRES_DNS_STATIC-exposed for m_dns) plushashtable[]/incache.RES_DNS_STATICnow also blanksstaticunderRES_CACHE_EXPOSE(so the cref oracle res.o exposescref_cachetop/cref_cainfo/cref_hashtable/cref_incache/cref_reinfofor the L1 isolated worlds), andhashtable/incachemoved onto it. In res_link.o (which hasPORT_DNS_M_DNS) all four stay defined-global — C owns the storage, the Rust core + the L1 live world extern them, same as P6k'sconf. No extern/definition split needed (so no= NULL/array-initializer churn).res.rsaddsstatic mut hashtable: [CacheTable; 1009]+static mut incache: c_intto its extern block +local_base()/hashtable_base()helpers. - Faithfulness. Raw-pointer walks mirroring the C
aCache **chains exactly.rem_listcapturescp->list_nextfirst, unlinks fromcachetoponly,MyFrees, returns the saved successor — and (the C quirk preserved) does not decrementincache, touchcainfo, or unlink the hash buckets (leaving the freed entry's bucket refs dangling).rem_cache's hostp-clearing loop walkslocal[]fromhighest_fddown; the bucket-by-hash_name/hash_numberunlinks usehp->h_addr=h_addr_list[0];MyFree(x)→ guardedlibc::free(the macro's re-NULL is moot — the struct is freed;*h_addr_listis one contiguous block freed once, then the pointer array).expire_cachecaptureslist_nextbefore the possible free and returns(next > now) ? next : now + AR_TTL. - Classification. Utility/leaf — none in
msgtab. L1 only — the resolver is inert underNO_DNS_LOOKUP, the cache never populates at runtime, so no client/server command path reaches these. No S2S (noIsServer/remote-field path). - L1 (
ircd-testkit/tests/res_cache_rem_diff.rs): the P6k two-isolated-worlds pattern. AWorldbundles fn-pointers + global base pointers (cachetop/hashtable/incache/cainfoandlocal/highest_fd— the hostp loop reads s_bsd globals, which are renamed tocref_local/cref_highest_fdin the archive, so each world must own its own). Entries are hand-built mimickingadd_to_cache(one contiguous 16×n address block withh_addr_list[i]pointing into it + per-name strdup'd aliases, sorem_cache's frees matchmake_cache's layout), then the public fns are driven andVec<i64>observations asserted equal. 4 tests, zero diff: flush_cache (3 mixed single/multi-alias/multi-addr entries →cachetop/buckets empty,incachedelta 0,ca_dels==3; round-trip re-insert + re-flush clean), expire_cache (two past + two future atnow=5000→ past removed, survivors kept in order,incache-=2,ca_expiresdelta 2, return = min-future 7000; the all-future inverse returns min expireat notnow+AR_TTL; empty-cache returnsnow+AR_TTL), rem_cache hostp (a per-worldlocal[0]->hostpat the doomed entry is NULLed, an unrelatedlocal[1]->hostpuntouched), rem_list (returns the successor, unlinks the middle fromcachetop,incacheunchanged — the leak-by-design quirk — then head + last removal empties the list). A process-globalMutex(poison-tolerant) serializes the#[test]s (shared cache + s_bsd globals). - No L2/S2S. Resolver inert under
NO_DNS_LOOKUP;golden_s_dns(oper DNS stats + emptyDNS lcache dump) +golden_registrationstay byte-identical, confirming the still-Cadd_to_cache/find_cache_numbernow call the Rustrem_cache/rem_list(+ the P6q hashes) without regression. End-to-end DNS coverage arrives when the resolver proper is ported under a fixture nameserver. - Plan —
docs/superpowers/plans/2026-06-05-p6r-res-cache-rem.md.
- Cluster choice. Bottom-up from the cache floor:
-
2026-06-05 — P6s (
res.ccache lookup/reorder half) merged. Third bite of theres.cport (after P6q's hashes + P6r's removal core). Ported the lookup/reorder connected component — the half of the hostent cache that finds entries and reorders/merges them — toircd-common/res.rs.res.c's only remaining strands are the resolver proper (socket + request queue + answer parser) and the cache build/insert half (make_cache/add_to_cache, → P6t).- Cluster choice. Bottom-up from the cache floor:
update_list(static) is the shared leaf both finders call;find_cache_name/find_cache_number(static) are the lookup entry points. A clean connected component — the finders callupdate_list(+ the already-Rust P6rrem_listinfind_cache_number's degenerate branch) and touch onlycachetop/hashtable/cainfo+mycmp/MyRealloc/mystrdup; no dependency on the still-C build/insert half. Splitting the cache "build/insert/lookup" remainder into lookup (P6s) then build/insert (P6t) keeps the bite small, matching the P6q/P6r cadence. - Seam.
-DPORT_RES_CACHE_LOOKUP_P6sadded to theres_link.orecipe; the three-wayRES_CACHE_LINKAGEnow firesexternunderPORT_RES_CACHE_P6qorPORT_RES_CACHE_REM_P6rorPORT_RES_CACHE_LOOKUP_P6s(res_link.o defines all three guards), GLOBAL underRES_CACHE_EXPOSE(cref oracle), else static. The three forward decls (res.c:84/85/91) + definitions flipstatic→RES_CACHE_LINKAGE; the bodies (update_list … find_cache_number, res.c:1140–1406) wrap in#ifndef PORT_RES_CACHE_LOOKUP_P6s. RED was the undefined-symbol link error forfind_cache_name/find_cache_number(still-C callersgethost_byname_type:410,proc_answer:776,gethost_byaddr:440,make_cache:1440/1456 — the exact port checklist;update_listhad no still-C reference once the finders dropped, but Rust defines it for the finders to call). GREEN vianm target/debug/ircd | grep -E ' T (update_list|find_cache_name|find_cache_number)$'. (A C-comment gotcha:gethost_*/procandfind_cache_*/crefeach contain a literal*/that prematurely closed the seam comment → reworded.) - Config-resolved bodies. No gates touch these (
DEBUGoff → theDebug(...)blocks vanish).IN_ADDR=in6_addr(sizeof 16, AFINET=AF_INET6);WHOSTENTP(x)=OR of the 16s6_addrbytes;S_ADDR=s6_addr.ResRQ.heis the inlinehent(h_addr_list:[in6_addr;35],h_aliases:[*char;35]);aCache.heis the libchostent(**charlists). Constants T_A=1/T_PTR=12/T_AAAA=28, MAXALIASES/MAXADDRS=35, FLG_*. - Faithfulness. Raw-pointer translation of the C for-loops with post-increment index walks (
for (i=0,s=h_name; s; s=h_aliases[i++])→s = *aliases.offset(i); i+=1at the loop tail), incl. thefind_cache_namefull-list fallback's fixed-s=alias[0] quirk (sset only in the init, never in the increment → it only ever compares alias[0]).update_list's T_PTR alias-merge reallocsh_aliasesby one +mystrdups the new name; the T_A/T_AAAA addr-merge reproduces the double-MyReallocexactly — realloc the contiguous IP block (*h_addr_list[0]) toaddrcount*16, realloc the pointer array toaddrcount+1, repoint each pointer into the (possibly moved) block, NULL-terminate,bcopythe new IP into the last slot (*--ab) — then theaddrcount>1→ clearFLG_PTR_FWD|VALIDvs the single-addrFLG_PTR_PEND_REV/FLG_PTR_PEND_FWDlogic.find_cache_number's second loop preserves the C for-loopcontinue→increment semantics (every guard incl. thecp = rem_list(cp)degenerate-reap falls through to the trailingcp = cp->list_next).bcmp→16-byte slice compare;MyRealloc/mystrdup/mycmpare the Rust ports resolved at link. - Classification. Utility/leaf — none in
msgtab. → L1 only. NoIsServer/remote-field path; the resolver is inert underNO_DNS_LOOKUPso no client/server command populates the cache at runtime. - L1 (
ircd-testkit/tests/res_cache_lookup_diff.rs): the P6k/P6r two-isolated-Worlds pattern (fn-pointers + global base pointers:cachetop/hashtable/cainfo+find_cache_*/update_list/hash_*).insert()builds entries à laadd_to_cache(contiguous addr block);make_rptr()builds aResRQper world (each owns its pointers → compare CONTENTS not pointers). 6 tests, zero diff: update_list reorder-only (rptr=NULL: head move keeping relative order,ca_updatesbump, repeat on the head = no-op); find_cache_name hashed hit + reorder +ca_na_hits/ name-miss (counter unchanged, order kept) / flags-gate miss / the alias full-list fallback (h_name & alias[0] in distinct hash buckets); find_cache_number hashed hit +ca_nu_hits/ absent-addr miss / FLG_PTR_VALID gate miss / the multi-addr fallback (lookup the 2nd of two addresses, found only via the full-list scan); update_list T_PTR alias merge (append a new name) + the already-present (= cached h_name) no-growth inverse; update_list T_A addr merge (append a new IP, addrcount→2 flag clear) + the already-present no-growth inverse (single-addrFLG_PTR_PEND_FWDset). A process-globalMutexserializes the#[test]s; each scenarioreset()s its world's cache + buckets and snapshots counter deltas. - No L2/S2S. Resolver inert under
NO_DNS_LOOKUP;golden_s_dns(oper DNS stats + emptyDNS l) +golden_registrationstay byte-identical, confirming the still-Cproc_answer/gethost_*/make_cachenow call the Rust lookup/reorder core (+ the P6q hashes + P6r removal) without regression. End-to-end DNS coverage arrives when the resolver proper is ported under a fixture nameserver. - Plan —
docs/superpowers/plans/2026-06-05-p6s-res-cache-lookup.md.
- Cluster choice. Bottom-up from the cache floor:
-
2026-06-05 — P6t (
res.ccache build/insert halfadd_to_cache/make_cache) merged. The fourth and final bite of theres.chostent-cache cluster — the two functions that create and link in cache entries — closing the cache out: with thisres.c's hostent cache is now FULLY Rust, and only the resolver proper (request queue + answer parser + socket I/O) remains C.- Cluster choice / why now. The dependency floor was already in place: P6q (the
hash_number/hash_nameleaves), P6r (therem_list/rem_cache/expire_cache/flush_cacheremoval core), P6s (theupdate_list/find_cache_name/find_cache_numberlookup/reorder half).add_to_cache/make_cacheare the only remaining cache functions and form a tight connected component (make_cache→find_cache_*/MyMalloc→add_to_cache→rem_cache), every callee already Rust → the natural last bite. - Guard / partial port.
#ifndef PORT_RES_CACHE_BUILD_P6twraps both C bodies (res.c:1086add_to_cache, res.c:1416make_cache);-DPORT_RES_CACHE_BUILD_P6tadded to theres_link.ocompile inbuild.rsalongside the P6q/r/s defines. Both functions' decl+def flipped fromstaticto the existing three-wayRES_CACHE_LINKAGEmacro (→externunder anyPORT_RES_CACHE_*in res_link.o so the still-Cproc_answercaller ofmake_cacheresolves to the Rust def; → GLOBAL under-DRES_CACHE_EXPOSEin the cref oracle res.o socref_add_to_cache/cref_make_cacheexist for L1).make_cache's forward decl at res.c:88 updated to match. - Config-resolved body.
DEBUGoff → allDebug((…))blocks drop out (noinetntop/ipv6stringdebug formatting inadd_to_cache).AFINET=AF_INET6→struct IN_ADDR=in6_addr(16 bytes);WHOSTENTPtests all 16s6_addrbytes;make_cache's address-count loop + contiguous-block fill usesizeof(struct IN_ADDR)= 16.MAXCACHED 512,MAXADDRS/MAXALIASES 35. - Faithfulness notes.
make_cachesteals the ResRQ's alias pointers (rptr->he.h_aliases[n]→ cache, source nulled) andh_name(nulled after copy) — preserved exactly, so the L1 asserts the source ResRQ fields go NULL. The*t++ = s; s += sizeof(IN_ADDR)two-array block-fill is reproduced with raw-pointert.add(1)/s.add(IN_ADDR_SIZE)walks;bzero→write_bytes(_,0,_). Thettl < 600clamp bumpsreinfo.re_shortttland forces 600;expireat = timeofday + ttl.add_to_cache's++incache > MAXCACHEDLRU evict walkscachetopto the tail (while !(*cp).list_next.is_null()) andrem_caches it.MyMallocdeclared returning*mut c_charto match dbuf.rs's decl (avoids the redeclared-signature warning), cast at each use. - Classification. Utility/leaf — not in
msgtab. → L1 only: no client/server command path under the locked config (NO_DNS_LOOKUPon → the resolver is inert, the cache never populates at runtime). End-to-end DNS coverage arrives when the resolver proper is ported under a fixture nameserver. - L1.
ircd-testkit/tests/res_cache_build_diff.rs— two fully-isolatedWorlds of fn-pointers + global base pointers (cachetop/hashtable/incache/cainfo/reinfo/timeofday), copyingres_cache_lookup_diff.rs: LIVE (#[link_name]→ the Rust port over the res_link.o globals) vs CREF (cref_*over the renamed oracle). 8 tests with inverse/round-trip invariants — build-by-name T_A (entry contents + linkage +ca_adds/incachedeltas + the steal inverse) and T_AAAA (flag), the short-ttl clamp (ttl<600→600 +re_shortttl) with the ≥600 inverse, dedup-by-name (T_A HIT → existing entry, no add) and dedup-by-addr (T_PTR HIT viafind_cache_number→ name merged as alias, no add), multi-addr contiguous-block build (distinct pointers, correct bytes),add_to_cacheLRU boundary at MAXCACHED (incache pre-set near the cap; evict observed viaca_delsdelta + list length) with the under-cap inverse, and both-bucket linkage (a single add is findable via bothfind_cache_nameandfind_cache_number).timeofdaypinned to a fixed value per scenario soexpireatis deterministic; process-globalLOCKserializes the parallel#[test]threads. Zero diff.make_cache/add_to_cacheconfirmedT(Rust-defined) intarget/debug/ircd. - No L2/S2S. Resolver inert under
NO_DNS_LOOKUP(cache never populates at runtime); noIsServer/remote-field path.golden_s_dns(oper DNS stats /DNS lempty dump / non-oper 481) +golden_registrationconfirm the still-C resolver calls the Rust build/insert core without regression.
- Cluster choice / why now. The dependency floor was already in place: P6q (the
-
2026-06-05 — P6u (res.c request-queue management core) merged. First bite into the
res.cresolver proper — the only DNS file still in C after the P6q–P6t hostent-cache cluster — ported bottom-up from the outstanding-DNS-request queue floor.- Cluster choice.
add_request/rem_request/make_request/find_id: the connected component over the file-staticsfirst/last(the request queue heads) +reinfo.re_requests. No socket I/O, no answer parsing (none callresend_query/query_name/do_query_*/proc_answer), so it isolates cleanly as the floor the rest of the resolver builds on. The public queue walkersdel_queries/timeout_query_liststay C this bite (timeout_query_listpulls inresend_query→ the socket half). - Guard / seam.
res_link.ogains-DPORT_RES_REQ_P6u. A new three-wayRES_REQ_LINKAGEmacro (parallel toRES_CACHE_LINKAGE):externunderPORT_RES_REQ_P6u(res_link.o — the four bodies#ifdef'd out, Rust defines them, the still-C callersdel_queries/timeout_query_list/gethost_*/do_query_*/get_resresolve at link), blank underRES_CACHE_EXPOSE(cref oracle — GLOBAL socref_add_request/cref_rem_request/cref_make_request/cref_find_idexist for L1), elsestatic. Thefirst/lastqueue heads flip fromstatic ResRQ *last, *first;toRES_DNS_STATIC(already exposed in res_link.o viaPORT_DNS_M_DNS+ the cref oracle viaRES_CACHE_EXPOSE) so the Rust port and the still-C walkers share one storage — exactly the P6r/P6t cache-globals pattern. - Config-resolved body. AFINET=AF_INET6 →
he.h_addrtype = AF_INET6; DEBUG off → theDebug(...)traces inrem_request/timeout_query_listdrop out;MyFreeis a macro →libc::free;MyMalloc+bzero→MyMalloc+ptr::write_bytes(…,0,…); thebcopy(lp, &cinfo, sizeof(Link))→ptr::copy_nonoverlapping(or a zero-fill whenlp==NULL). - Faithfulness notes.
rem_request'sfor (rptr=&first; *rptr; r2ptr=*rptr, rptr=&(*rptr)->next)rendered as a raw*mut *mut reslistwalk advancingr2ptr/rptrat the bottom so thelast==old → last=r2ptrtail fix-up matches; the post-loop free order (he.h_name, theMAXALIASESh_aliases[],name, the node) preserved.make_requestsetsnext=NULLbeforeadd_request(which sets it again) verbatim.add_request(NULL)returns -1 (the C no-op) before touching the queue. - Classification. Utility/callees — neither in
msgtab. → L1 only, NO L2, NO S2S. The resolver is inert underNO_DNS_LOOKUP(no client/server command path populates the queue at runtime; end-to-end queue coverage arrives under a fixture nameserver when the socket/answer half lands). - L1.
res_request_diff— two fully-isolatedWorlds of fn-pointers +first/last/reinfobase pointers (LIVE#[link_name]→ the Rust port over the res_link.o globals; CREFcref_*overcref_first/cref_last/cref_reinfo). Positive + inverse/round-trip:make_requestseeds every default field & appends (NULLlpzeroescinfo);add_requestarrival ordering +re_requestsincrements, andadd_request(NULL)→ -1 with the queue untouched;find_idhit (first/middle/tail) / miss / misses-after-its-target-is-rem_request'd;rem_requesthead/middle/tail unlink withlastrepointed on tail removal + the node freed; a drained queue isfirst==last==NULLand reusable by a freshmake_request.timeofdaypinned (deterministicsentat); a process-globalLOCKserializes the parallel#[test]threads over the shared queue. Zero diff over 8 tests. - L2/S2S. None (see Classification).
cargo test -p ircd-goldengolden_s_dns(2) +golden_registration(2) green — the still-C resolverinit_resolver/walkers call the Rust queue primitives at boot without regression. 0 warnings. - Still C in
res.c.del_queries/timeout_query_list(the public queue walkers) + the resolver socket/answer half:send_res_msg,do_query_name/do_query_number,query_name,resend_query,proc_answer,get_res,gethost_byname/gethost_byname_type/gethost_byaddr,init_resolver,cres_mem,bad_hostname.
- Cluster choice.
-
2026‑06‑05 — P6v (
res.cqueue walkersdel_queries/timeout_query_list) merged. The second bite of theres.cresolver proper, built on the P6u queue core — the reaper/timeout layer that walks the outstanding-DNS request queue (first/last).- Cluster choice. Continuing bottom-up after the P6u request-queue floor:
del_queries/timeout_query_listare the two queue walkers the PLAN names as the next nameable unit.del_queriesdeps are purely Rust (rem_requestP6u) + the sharedfirstglobal → fully portable.timeout_query_listadditionally calls the still-Cresend_query(the resend/socket path) — the one inverse-direction dependency. - Guard / seam.
res_link.ogains-DPORT_RES_WALK_P6v(build.rs);res.cwraps both bodies in#ifndef PORT_RES_WALK_P6v. NewRES_RESEND_LINKAGEmacro:externunderPORT_RES_WALK_P6v(res_link.o exposesresend_queryas a global so the Rusttimeout_query_listlinks against it),staticotherwise — the cref oracle keepsresend_querystatic, andcref_timeout_query_listresolves to the objcopy-renamed localcref_resend_queryinside libcref.a (self-contained:resend_queryhas no out-of-TU caller). Bothdel_queries/timeout_query_listare already public (res_ext.h) → the still-C callers (s_bsd.c:1361/3288,ircd.c:580/1193) and the Rustlist.rs:free_confresolve to the Rust defs at link. - Config‑resolved body. DEBUG off → the
Debug(())/myctimetraces drop out of both functions. USE_IAUTH on → the ASYNC_CLIENT exhausted arm callssendto_iauth("%d d", cptr->fd).ClearDNS(x)=(*x).flags &= ~FLAGS_DOINGDNS(0x100;flagsis c_long). Thecinfo.flagsswitch: ASYNC_CLIENT=0 (iauth+ClearDNS), ASYNC_CONNECT=1 (sendto_flag SCH_ERROR), default (ASYNC_CONF=2 etc.) → reap only. Platform note:reslist.retries/resendrender asc_char= u8 here, so--rptr->retriesis ported aswrapping_sub(1)(faithful to C's modular char; the<= 0test then means== 0, matching unsigned C on the same platform) — panic-free even though the live values (1‑3) never underflow. - Faithfulness.
timeout_query_listmirrors the Cfor (rptr=first; rptr; rptr=r2ptr)walk withr2ptrcaptured beforerem_requestfrees the node; the exhausted arm usescontinue(skipping thenextupdate) exactly as C;nextseeded 0 with the!next || tout < nextmin; return(next > now) ? next : now + AR_TTL.resend_queryshort-circuits onresend == 0, so the L1 retry test exercises the call with no socket I/O. - Classification. Utility/callees — neither is in
msgtab(callers: s_bsd.c registration/cleanup, ircd.c io_loop/exit, list.rs free_conf). L1 only. - L1.
ircd-testkit/tests/res_walk_diff.rs(modeled onres_request_diff.rs): two fully-isolatedWorlds (LIVE#[link_name]→ the Rust port over res_link.o globals; CREFcref_*over the renamed oracle), each carrying base pointers tofirst/last/reinfo+ the four fn-pointers. 9 tests, positive + inverse:del_querieshead/middle/tail match, absent-cp no-op, all-match drain (first==last==NULL);timeout_query_listfuture-dated survive (returns mintout), exhausted ASYNC_CONF (no-notify reap →now+AR_TTL), exhausted ASYNC_CLIENT (ClearDNS clears the DNS bit but leaves an unrelated bit, diffed on two per-world clients), exhausted ASYNC_CONNECT, retry back-off (retries--,sentat=now,timeout4→8,resend_queryno-op via resend=0), and a mixed survivor+reaped+retried walk — return value, surviving ids, per-noderetries/sentat/timeout, andre_timeoutsall diffed.timeofdaypinned;adfd/cref_adfd=-1sosendto_iauthno-ops;svchansBSS-zero sosendto_flagno-ops; processLOCKserializes the parallel#[test]threads. Zero diff. - No L2/S2S. The resolver is inert under
NO_DNS_LOOKUP— the request queue never populates at runtime, so there's no client/server command path that reaches these walkers.golden_s_dns(2) +golden_registration(2) stay green, confirming the still-C resolver calls the Rust walkers without boot regression. End-to-end DNS coverage arrives when the answer parser + socket I/O half is ported under a fixture nameserver. - Still C in
res.c: only the answer parser + socket I/O half —send_res_msg/do_query_name/do_query_number/query_name/resend_query/proc_answer/get_res/gethost_byname/gethost_byname_type/gethost_byaddr/init_resolver/cres_mem/bad_hostname.
- Cluster choice. Continuing bottom-up after the P6u request-queue floor:
-
2026-06-05 — P6w (
res.cbad_hostname) merged. The next bottom-up leaf of the still-Cres.cresolver proper.- Cluster choice —
bad_hostname(res.c:1832), the pure hostname-character validator used by the answer parserproc_answer(res.c:802,851) to reject malformed PTR/A results. No socket, no globals, no allocation — the cleanest remainingres.cleaf to extract before the socket/answer-parser core. Single function → atomic cluster. - Config-resolved body —
RESTRICT_HOSTNAMESis defined (cbuild/config.h:608),HOSTNAMES_UNDERSCOREis not. So the compiled body is the restrictive branch without the underscore escape hatch:isalnumok; interior hyphen ok unless leading / after-dot / trailing (len==1) / before-dot;.ok unless leading or doubled; else-1. - Guard / seam — upstream file-static, so a new three-way
RES_HOSTNAME_LINKAGE(mirroring P6q/P6r/P6sRES_CACHE_LINKAGE):externwhen building res_link.o (-DPORT_RES_HOSTNAME_P6w, body#ifndef'd out → Rust def wins, still-Cproc_answerresolves at link), GLOBAL in the cref oracle (-DRES_CACHE_EXPOSE→cref_bad_hostnamefor L1),staticin the plain build. Applied to the forward decl (res.c:132) + the definition.build.rsappends-DPORT_RES_HOSTNAME_P6wto the res_link.o compile line. - Faithfulness notes — the C
for (s=name; (c=*s) && len; s++, len--)runs thes++, len--increment on everycontinue(Ccontinuejumps to the increment) and never falls through to it (each pathcontinues orreturn -1s); the Rust port advancess/lenbefore eachcontinueand skips it on the-1exit.isalnum(c)is called aslibc::isalnum(c as c_int)with the same (signed-char-promoted) value → byte-identical shared-libc table lookup.s[1]is read only whenlen != 1— the Rust&&short-circuits identically, so*s.add(1)is never evaluated at the cap. - Classification — utility/callee TU (not in
msgtab; called only from the still-Cproc_answer). L1 is the gate. No client command reaches it under the locked config without the still-C answer parser running. - L1 —
ircd-testkit/tests/res_bad_hostname_diff.rs: declarec_bad_hostname(#[link_name="bad_hostname"]→ Rust def via res_link.o) +cref_bad_hostname(oracle), drive both over a corpus × every prefix length0..=len+1(thelencap is load-bearing — only the firstlenbytes are scanned) and assert identical return. Covers valid names (incl. punycode interior double hyphen), hyphen edge cases (leading/trailing/after-dot/before-dot + thelen==1trailing-hyphen guard), dot edge cases (leading/trailing/doubled), illegal chars (space, underscore,/,*,:, control\x07, high-bit), and thelenboundary (illegal char atlenvslen-1,len==0, empty string). 6 tests, zero diff. - S2S — no path (the function is unreachable without the still-C answer parser; no
IsServer/remote-field/propagation path exists).
- Cluster choice —
-
2026-06-05 — P6x (
res.ccres_mem) merged. The DNS-cache memory-usage reporter — the next bottom-up leaf of theres.cresolver proper, ported toircd-common/src/res.rs.- Cluster choice —
cres_mem(aClient *sptr, char *nick)(res.c:1810): walks the hostent cache list (cachetop, vialist_next) summing eachaCache/hostent's struct bytes (sm), IP storage (im—sizeof(char*)+sizeof(struct IN_ADDR)per addr, +1 for the NULL terminator) and name storage (nm—sizeof(char*)+strlenper alias, then thei-1/+sizeof(char*)tail, thenstrlen(h_name));ts = ARES_CACSIZE(1009) * sizeof(CacheTable)(16) = 16144; sends two RPL_STATSDEBUG (249) lines and returnsts+sm+im+nm. - Seam / Guard —
cres_memis non-static, exported inres_ext.h, so the cref oracle already carriescref_cres_mem→ no newRES_*_LINKAGE/*_EXPOSEseam (contrast the file-static cache/queue leaves P6q–P6w).#ifndef PORT_RES_CRES_MEM_P6xwraps the C body;ircd-sys/build.rsadds-DPORT_RES_CRES_MEM_P6xto theres_link.ocompile so the body is dropped and the Rust#[no_mangle] cres_memresolves it fors_debug.c'scount_memoryat link. The cref oracleres.ois untouched. - Config-resolved body — DEBUG off; the whole body compiles unconditionally.
me(bindgenstatic mut me) read for the server name;sendto_onevia the existing res.rs variadic extern. - Faithfulness notes — the
nm += i - 1step is unsigned modular arithmetic: with 0 aliasesi == 0, so it adds(int)-1widened tou_long(=u_long::MAX, i.e.nm -= 1), and the followingnm += sizeof(char*)nets+7. Reproduced withwrapping_addthroughout thenmaccumulation so it may transitu_long::MAXwithout a debug-mode overflow panic. The%dformat applied to theu_longargs (ts/sm/im/nm) is preserved verbatim (reads the low 32 bits) — the values pass asu_longso the live trampoline andcref_sendto_onepush identical bytes. - Classification — utility/callee (not in
msgtab); reached fromcount_memoryviaSTATS z. Read-only on the cache. - L1 (
ircd-testkit/tests/res_cres_mem_diff.rs) — two-world (LIVE overcachetop/me/the Rustcres_mem; CREF overcref_cachetop/cref_me/cref_cres_mem) build of identical caches + a buffering local client (make_client, P2) per world with a pinnedme.name. Diffs both the return value and the sendQ bytes (the two RPL lines). Cases: empty cache (return ==ts= 16144,Structs 0 IP storage 0 Name storage 0); single entry / 0 aliases (thenm += i-1wrap →+7); single entry / 3 aliases / 2 addrs; NULLh_name(trailingstrlenskipped); 3 entries (smscales per node). Hand-computed totals match the cref oracle. Zero diff. - L2 — already covered by
golden_debug::stats_memory_match_reference(STATS z →count_memory→cres_mem); stays green (empty boot cache under NO_DNS_LOOKUP →ts-only walk). - S2S — none. No
IsServer/remote-field path (oper STATS only). - Still C in res.c — the socket/answer-parser core only:
send_res_msg/do_query_*/query_name/resend_query/proc_answer/get_res/gethost_*/init_resolver.
- Cluster choice —
-
2026-06-05 — P6y (
res.csend_res_msg) merged. The resolver UDP datagram-send leaf — the literal bottom of the send tree (do_query_name/do_query_number/resend_query→query_name→send_res_msg). Continues the bottom-upres.cresolver-proper port; after this only the answer-parser + socket-I/O core remains C (query_name/do_query_*/resend_query/proc_answer/get_res/gethost_*/init_resolver).- Seam.
send_res_msgis upstream file-static → new three-wayRES_SEND_LINKAGE(same shape asRES_RESEND_LINKAGE/RES_HOSTNAME_LINKAGE):externin res_link.o (-DPORT_RES_SEND_P6y; body#ifndef-dropped, Rust defines it, the still-Cquery_nameresolves at link), GLOBAL in the cref oracle (-DRES_CACHE_EXPOSE→cref_send_res_msgfor L1), elsestatic. The resolver socket fdresfdit sends on is a real exported global (s_bsd_ext.h) — declared extern in res.rs, no extra seam;ircd_resis the Rust res_init.rs global (P6p). - Config-resolved body / faithfulness.
max = MIN(nscount, rcount), overridden to 1 onRES_PRIMARY(=16), then floored to 1 — the clamp order is load-bearing (rcount=0→ 1 send;RES_PRIMARYwith nscount=3 → 1 send). Each iteration force-setsnsaddr_list[i].sin6_family = AF_INET6(=AFINET) thensendto(resfd, msg, len, 0, &nsaddr_list[i], sizeof(sockaddr_in6));reinfo.re_sent/sentbump only on a full-length send (== len); the DEBUGMODE-off failure branch is a no-op. Returnssent, or -1 on NULL msg / all-failed. - Classification. Utility/callee — not in
msgtab; only caller isquery_name(res.c). No client command reaches it under the locked config (NO_DNS_LOOKUPdisables the DNS path). - L1.
ircd-testkit/tests/res_send_msg_diff.rs: two fully isolatedWorlds (LIVE via#[link_name]→ Rust over res_link.o/res_init globals; CREF viacref_*). Each world+scenario binds its own loopback (::1) UDP recv socket(s) with a 300msSO_RCVTIMEO+ its own send socket, pointsircd_res.nsaddr_list[]at the recv port(s), zeroesreinfo.re_sent, drivessend_res_msg, thenrecvfroms and snapshots{return value, datagram bytes received, re_sent delta, nsaddr_list[0].sin6_family}. Six cases diff byte-identical: single-nameserver one-send; NULL msg → -1/no send;rcount=0floor-to-1;RES_PRIMARY(nscount=2) override-to-1 (only ns[0] receives); two-nameserver both-receive; badresfd(-1) → all sends fail → -1, counter untouched. - L2/S2S. None. No DNS command path under
NO_DNS_LOOKUP; boot/registration (golden_registration) and oper-DNS (golden_s_dns) goldens stay byte-identical, confirming the dropped C body doesn't perturb the binary. NoIsServer/remote-field path.
- Seam.
-
2026-06-05 — P6z (
res.cquery_name— the DNS query builder/sender) merged. The next bottom-up leaf of the resolver proper, sitting directly above the P6ysend_res_msgleaf in the send tree. Ported toircd-common/res.rsvia theres_link.opartial port (-DPORT_RES_QUERY_P6z).- Cluster choice —
static int query_name(char *name, int class, int type, ResRQ *rptr)(res.c:618). Its only callees are already Rust:ircd_res_mkquery(P6o),find_id(P6u),send_res_msg(P6y). Its callers are still C:do_query_name/do_query_number/resend_query/get_res. So it is the next clean leaf up the tree. - Linkage / seam — upstream file-static → new three-way
RES_QUERY_LINKAGE(mirrorsRES_SEND_LINKAGE):externunder-DPORT_RES_QUERY_P6z(res_link.o body#ifdef'd out → Rust def; still-Cdo_query_*/get_resresolve at link), GLOBAL under-DRES_CACHE_EXPOSE(cref oracle →cref_query_namefor L1), static otherwise. No new data-global seam — it touches onlyircd_res/resfd/first/reinfo, all already exposed (P6p/P6u/P6y). (Gotcha hit while writing the C comment:do_query_*/contains a literal*/that closes the block comment early → reworded todo_query_name/do_query_number.) - Config-resolved body —
LRAND48absent fromcbuild/setup.h/config.h→ the id source isgettimeofday(&tv,NULL)+tv.tv_usec & 0xffff(thelrand48()branch is dead);DEBUGoff → noDebug()trace.MAXPACKET=1024,QUERY=0,NO_RECOVERY=3,TRY_AGAIN=2.reslist.sendsis a signedchar→sends++is ani8wrapping_add, sign-extended tointassend_res_msg'srcount.hptr->idis the HEADER 16-bit bitfield →(*hp).id()/set_id(stored big-endian;ntohs=u16::from_be), the res_mkquery.rs:84 pattern. The do/while collision loop is a faithfulloop { …; k += 1; if find_id(...).is_null() { break } }. - Classification — utility/callee leaf, not in
msgtab; reachable only through the still-C resolver entry points, inert project-wide underNO_DNS_LOOKUP. L1 is the gate. - L1 —
ircd-testkit/tests/res_query_name_diff.rs: two fully-isolatedWorlds (LIVE = Rust port over res_link.o/res_init globals; CREF =cref_*oracle), each with its own loopback::1UDP recv+send sockets +ircd_res/reinfo/resfd/firstbase pointers (theres_send_msg_diff.rsshape). Determinism via a strong interposed#[no_mangle] gettimeofdayreturning a fixedtv— both worlds (the Rustlibc::gettimeofdaycall and the cref-archive call) resolve to it at link, so packets are byte-identical including the id field and thefind_idcollision loop is reproducible (ircd_res.idpinned to 0 → mkquery writeshtons(1), first computed id =1 + (tv_usec & 0xffff)). Diff on{ret, datagram bytes, rptr.{id,sends,sent}, re_sent delta, h_errno}over: A-record query (1 send, id == header id, sends 0→1), PTR query (qtype trailer == T_PTR), a forced one-collision id advance (seed the queue with a node whose id == the first computed id) vs the no-collision inverse (k stays 0 → id == first computed) with the exact second-iteration id checked against the C formula, mkquery-failure (300-char single label → dn_comp fails →r<=0, NO_RECOVERY, no send,sends/sent/re_sentuntouched), and send-failure (resfd=-1→send_res_msg-1 → TRY_AGAIN, butsendsstill bumped before the send,sent/re_sentuntouched). Zero diff over 5 tests. - L2 / S2S — none. The DNS path is disabled project-wide via
NO_DNS_LOOKUP; the boot goldensgolden_registration+golden_s_dnsstay byte-identical, confirming the still-C resolver calls the Rustquery_namewithout regression. End-to-end DNS coverage arrives when the resolver proper (answer parser + socket I/O) is ported under a fixture nameserver.
- Cluster choice —
-
2026-06-05 — P6aa (
res.cquery-dispatch layerdo_query_name/do_query_number) merged. The next bottom-up cluster of the still-Cres.cresolver proper, sitting directly above the P6zquery_nameleaf and below the still-Cgethost_*/get_res/resend_querycallers.- Cluster choice.
do_query_name(res.c:541) builds the forward-lookup hostname anddo_query_number(res.c:580) builds the reverse-lookup name; both allocate theResRQviamake_request(P6u) when none was passed and then callquery_name(P6z) — all three callees are already Rust, so this is the clean next leaf up the send tree.resend_query(which dispatches to both) is the next unit above and stays C this bite (now calling the Rust do_query_*). - Guard / seam.
res_link.ogains-DPORT_RES_DOQUERY_P6aa. Both functions are upstream file-static → a new three-wayRES_DOQUERY_LINKAGE(mirrorsRES_QUERY_LINKAGE):externunderPORT_RES_DOQUERY_P6aa(res_link.o — the two bodies#ifndef'd out, Rust defines them, the still-C callersgethost_byname_type/gethost_byaddr/get_res/resend_queryresolve at link), GLOBAL underRES_CACHE_EXPOSE(cref oracle —cref_do_query_name/cref_do_query_numberfor L1), elsestatic. No new data-global seam — they touch onlyircd_res/first/last/reinfo/resfd, all already exposed (P6p/P6u/P6y). (Gotcha re-hit from P6z: agethost_*/in the macro comment closes the block comment early via the literal*/— reworded to spell out the caller names.) - Config-resolved body.
AFINET=AF_INET6→rptr->addr/he.h_addrarein6_addr(16 bytes);IN6ADDRSZ=sizeof(struct IN_ADDR)= 16.RES_DEFNAMES = 0x80(resolv_def.h). DEBUG off → theDebug((…))trace drops out.strncpyzt(x,y,N)=strncpy(x,y,N); x[N-1]=0(struct_def.h:871);HOSTLEN=63→hname[HOSTLEN+1]=[c_char; 64].index=strchr;MyMallocreturns*mut c_char. - Faithfulness notes. The domain-append branch is gated on
rptr != NULL(the resend path) in the C — faithful: a freshdo_query_name(rptr==NULL) never appendsdefdname, and stores the original (un-truncated/un-appended)name, nothname.do_query_number's twosprintfbranches are reproduced by building the exact byte string in Rust ({}decimal for the IPv4 form, single-nibble lowercase{:x}for the ip6.arpa form, byte-reversed low-then-high nibble) andcopy_nonoverlappinginto the Cipbuf;bcopy→copy_nonoverlapping;&rptr->he.h_addr=&he.h_addr_list[0]. - Classification. Utility/callee leaf — neither in
msgtab; reachable only through the still-C resolver entry points, inert project-wide underNO_DNS_LOOKUP. → L1 only, NO L2, NO S2S (noIsServer/remote-field path). - L1.
ircd-testkit/tests/res_do_query_diff.rs— two fully-isolatedWorlds (LIVE#[link_name]→ the Rust port over the res_link.o/res_init globals; CREFcref_*over the renamed oracle), each with its own loopback::1UDP recv+send sockets +ircd_res/reinfo/resfd/first/lastbase pointers (theres_query_name_diff.rsshape). A strong interposedgettimeofdaypinsquery_name's id randomization so packets are byte-identical including the id. Diffs{ret, datagram bytes, the resulting ResRQ (type/name/id/sends/sent), he.h_addr bytes, he.h_length, addr bytes, re_sent delta, h_errno}over 7 tests: do_query_name fresh (original name stored verbatim, no append, 1 send); do_query_name resend + RES_DEFNAMES (.example.orgappended → longer packet) vs the already-dotted inverse (no append); the overlong-defdname overflow → -1/no send; do_query_number IPv4-mapped (::ffff:127.0.0.1→in-addr.arpa,he.h_addr/addr/he.h_length=16seeded) + the all-zero-prefix arm + the full-IPv6ip6.arpanibble form; and do_query_number resend (no make_request, he/addr untouched, queue stays empty). Zero diff.do_query_name/do_query_numberconfirmedT(Rust-defined) intarget/debug/ircd. - L2/S2S. None (see Classification).
cargo test -p ircd-goldengolden_s_dns(2) +golden_registration(2) stay byte-identical — the still-Cgethost_*/get_res/resend_querycall the Rust do_query_* without boot regression. 0 warnings. - Still C in
res.c. Only the answer parser + remaining socket I/O:resend_query/proc_answer/get_res/gethost_byname/gethost_byname_type/gethost_byaddr/init_resolver.
- Cluster choice.
-
2026-06-05 — P6bb (
res.cgethost_byname_type/gethost_byname/gethost_byaddr) merged. The public host-lookup entry points — the next bottom-up cluster of the resolver proper, sitting directly above the Rustfind_cache_*(P6s) +do_query_*(P6aa) and below the still-Cget_res/s_bsdcallers.- Cluster choice. The three exported lookup fns are thin dispatchers: bump a counter → try the cache → on a miss issue an async query (when a client
lpis present) or give up. They depend only on already-ported Rust (find_cache_name/find_cache_number,do_query_name/do_query_number), so they are the clean next leaf above the do_query layer. - Mechanism / no seam. Ported into
ircd-common/res.rs;res_link.odrops the C bodies via-DPORT_RES_GETHOST_P6bb. Because all three are exportedres_ext.hsymbols (not file-statics), no linkage seam is needed — the cref oracle's unguardedres.oalready exportscref_gethost_*for L1 (same pattern as P6xcres_mem). - Config-resolved body.
DEBUGoff → theDebug(...)trace in the miss path vanishes.gethost_bynamedelegates withT_AAAA(AFINET=AF_INET6 → IPv6-first;get_resfalls back toT_A).gethost_byname_typerejects any non-T_A/T_AAAAtype with NULL after bumpingre_na_look; the cache flag isFLG_AAAA_VALID/FLG_A_VALIDby type.gethost_byaddrgates the number cache onFLG_PTR_VALID(viafind_cache_number).(struct hostent *)&(cp->he)→addr_of_mut!((*cp).he)(already*mut hostent, no cast). - Faithfulness. Counter bump precedes the type guard; cache consulted before any query;
lp == NULLshort-circuits to NULL beforedo_query_*; the async query path always returns NULL (the result arrives later viaget_res). - Compiled-out caller.
gethost_byaddr's sole C caller (s_bsd.c:1681) lives in the#elseof#ifdef NO_DNS_LOOKUP; the project builds withNO_DNS_LOOKUP, so the call is compiled out and the linker omits the (unreferenced) Rust fn from theircdbinary. It is still ported faithfully and L1-verified —nmconfirmsgethost_byname/gethost_byname_typeresolve to Rust (T) in the binary;gethost_byaddrsimply has no caller to pull it in. - Classification. Utility/leaf — none in
msgtab→ L1 only. NoIsServer/remote-field path; the resolver is inert underNO_DNS_LOOKUP, so no client/server command reaches these at runtime. - L1.
ircd-testkit/tests/res_gethost_diff.rs(8 cases, zero diff). Two fully isolated worlds (live#[link_name]Rust port vscref_*oracle) spanning both the DNS cache globals (cachetop/hashtable/cainfo) and the resolver query globals (ircd_res/reinfo/resfd/first/last), each binding its own loopback::1UDP recv socket + a pinnedgettimeofday(deterministic query id, matching res_do_query_diff). Branch matrix + inverses: bad-type early-out (re_na_look bumped, cache untouched, no query), cache HIT (returns&he,ca_na_hits/ca_nu_hitsbumped, no datagram) vs MISS,lp==NULLshort-circuit vslp!=NULLasync query (AAAA datagram + queuedT_AAAAnode for byname; PTR/ip6.arpadatagram + queuedT_PTRnode for byaddr), the byname→byname_type(T_AAAA) delegation, and there_na_look(byname) vsre_nu_look(byaddr) split. - S2S. None — no server-reachable path (see Classification).
- Cluster choice. The three exported lookup fns are thin dispatchers: bump a counter → try the cache → on a miss issue an async query (when a client
-
2026-06-05 — P6cc (
res.cresend_query) merged. The DNS request-retry dispatcher — the next bottom-up unit of the still-C resolver proper, sitting directly above the P6aa query-dispatch layer (do_query_name/do_query_number) and below the still-Cget_res. Ported toircd-common/res.rsvia theres_link.opartial port (-DPORT_RES_RESEND_P6cc); after this only the answer parser + socket I/O remain C (proc_answer/get_res/init_resolver).- Cluster choice.
RES_RESEND_LINKAGE void resend_query(ResRQ *rptr)(res.c:703). Its callees are all already Rust —do_query_number/do_query_name(P6aa) →query_name(P6z) →send_res_msg(P6y) — so it is the clean next leaf up the tree. Its callers are the Rusttimeout_query_list(P6v, the retry branch) and the still-Cget_res(res.c:1149, on a truncated/failed reply). - Seam.
resend_queryis upstream file-static. TheRES_RESEND_LINKAGEmacro already existed (P6v, where it was 2-way:externunderPORT_RES_WALK_P6vto satisfy the Rusttimeout_query_list, elsestatic). P6cc promotes it to the standard three-way shape (mirrorsRES_QUERY_LINKAGE/RES_DOQUERY_LINKAGE):externunderPORT_RES_RESEND_P6cc(res_link.o — the body#ifndef'd out, Rust defines it, the still-Cget_res+ the Rusttimeout_query_listresolve at link), GLOBAL underRES_CACHE_EXPOSE(cref oracle →cref_resend_queryfor L1), elsestatic. res_link.o sets bothPORT_RES_WALK_P6vandPORT_RES_RESEND_P6cc; the P6cc arm is ordered first so it wins (the legacy P6v arm is kept for clarity but superseded). No new data-global seam — it touches onlyreinfo/first/last/ircd_res/resfd, all already exposed (P6p/P6u/P6y). - Config-resolved body. Faithful 1:1 with the C: early-return on
resend == 0; bumpreinfo.re_resends;match (*rptr).type_onT_PTR→do_query_number(NULL, &rptr->addr, rptr),T_AAAA | T_A→do_query_name(NULL, rptr->name, rptr, type),_→ no-op (the counter still bumped before the switch, matching the C ordering). The Rust extern decl forresend_query(added in P6v sotimeout_query_listcould call the still-C fn) was removed — the module now defines it. - Classification. Utility/callee (file-static, not in
res_ext.h→ not inmsgtab). L1 is the gate. No L2 path — the resolver is dead code under the lockedNO_DNS_LOOKUPconfig (the golden binary never drives a live lookup), same as every P6q–P6bb resolver bite. No S2S (noIsServer/remote-field/propagation path). - L1.
ircd-testkit/tests/res_resend_query_diff.rs(modeled onres_do_query_diff.rs): two fully isolatedWorlds (LIVE#[link_name]→ the Rust port over the res_link.o/res_init globals; CREFcref_*→ the renamed reference-C oracle), each binding its own loopback::1UDP recv+send sockets so the two never cross-talk.gettimeofdayinterposed (fixedtv) to pinquery_name's id randomization → byte-identical datagrams. Snapshots{datagram bytes, node.{type,name,id,sends,sent}, re_resends delta, re_sent delta, h_errno}. Branch matrix + inverses:resend==0→ no-op (no datagram, zero counter deltas,sendsuntouched);T_A→do_query_namedispatch (datagram,re_resends+1,re_sent+1,sends+1);T_PTR→do_query_numberdispatch (in-addr.arpa reverse datagram,re_resends+1); default (type=99) → counter bumps but nothing dispatched (no datagram,re_sentdelta 0). Zero-diff. The P6vres_walk_diff(which callsresend_queryviatimeout_query_list, now resolving to the Rust def) +res_do_query_diffre-run green.
- Cluster choice.
-
2026-06-05 — P6dd (
res.cproc_answer) merged. The DNS-reply answer-section parser — the next bottom-up unit of the still-C resolver proper — is now Rust (ircd-common/res.rs), leaving only the socket I/O (get_res/init_resolver) inres.c.- Cluster choice —
proc_answer(res.c:732) is the floor below the still-Cget_res(its only caller): a pure parse-into-rptr->heroutine (no socket I/O), so it is fully L1-testable by feeding constructed DNS wire buffers and diffing the resultinghent+ return value. All of its callees were already Rust:ircd_dn_expand/__ircd_dn_skipname/ircd_getshort/ircd_getlong(res_comp, P6n),bad_hostname(P6w),find_cache_name(P6s),MyMalloc(P2); plus the still-C variadicsendto_flag(wrong-type / bad-IP-length error notices). - Seam —
proc_answeris upstream file-static → new three-wayRES_PROC_ANSWER_LINKAGE(mirrorsRES_DOQUERY_LINKAGE):externunder-DPORT_RES_PROC_ANSWER_P6dd(res_link.o drops the#ifndef'd body so the still-Cget_resresolves to the Rust def at link), GLOBAL under-DRES_CACHE_EXPOSE(cref oracle exportscref_proc_answerfor L1), elsestatic. Added-DPORT_RES_PROC_ANSWER_P6ddto the res_link.o compile inircd-sys/build.rs. The res.c-private scratchhostbuf[HOSTLEN+1+100]is touched ONLY byproc_answer(verified by grep) → it moved into the Rust module as a privatestatic mut [c_char; 164](NOT ABI, likedebugbuf);get_resstays C and never reads it, so its#ifndef PORT_RES_PROC_ANSWER_P6dd-guarded C definition simply drops. - Config-resolved body — AFINET=AF_INET6 → A/AAAA addresses are 16-byte
in6_addr; T_A builds the v4-mapped::ffff:a.b.c.dform.sizeof(HEADER)=12,QFIXEDSZ=4,C_IN=1,HOSTLEN=63,RES_DEFNAMES=0x80,MAXALIASES=35.hptr->qdcount/->ancountread+decremented as loop counters via the bindgen bitfield*_rawaccessors (get_res already ntohs-converted them to host order before the call — faithful). DEBUG off → theDebug((…))traces drop. - Classification — utility/callee (file-static, not in
msgtab, not inres_ext.h); sole caller the still-Cget_res. L1 is the gate; no L2 path (the resolver is dead code under the lockedNO_DNS_LOOKUPconfig — the golden binary never drives a live lookup, same as every P6q–P6cc bite). No S2S (noIsServer/remote-field/propagation). - L1 —
res_proc_answer_diff(12 cases): two isolated worlds (LIVE#[link_name="proc_answer"]→ the Rust port over res_link.o globals; CREFcref_proc_answerovercref_ircd_res) parse constructed DNS reply buffers (a small uncompressed-name wire encoder + a header whose qd/ancount are set host-order via the bindgen accessors) and diff the return value + a fingerprint ofrptr->he(h_name, alias bytes/count, addr-slot byte-sums/count, h_addrtype, h_length). All scenarios keep the DNS cache empty sofind_cache_namemisses identically. Cases + inverses: single A; single AAAA; multi-A (two addrs, equal name NOT re-added as a duplicate alias); good PTR (ans=1) vs bad-hostname PTR (-1); CNAME-then-A; wrong-reply-type record skipped (ans unchanged); bad-IP-length A (-2); RES_DEFNAMES dotless-append (host→host.example.org) vs dotted name untouched; two-question header skip; empty answer section. Zero diff vscref_. - No L2/S2S path — resolver dead-code under
NO_DNS_LOOKUP; covered later whenget_reslands with fixture DNS.
- Cluster choice —
-
2026-06-05 — P6ee (
res.cinit_resolver) merged. The resolver subsystem's bit-flag-driven one-shot bootstrap ported toircd-common/res.rs— the second-to-last function in res.c.- Cluster choice —
init_resolver(int op)(res.c:243),opan OR ofRES_INITLIST/RES_CALLINIT/RES_INITSOCK/RES_INITDEBG/RES_INITCACH(res_def.h:7-11). Called only from the still-C I/O layer (s_bsd.cboot/rehash,ircd.c). The recvfrom-drivenget_resis now the only function left C in res.c. - Guard / seam —
#ifndef PORT_RES_INIT_P6eearound the C body;-DPORT_RES_INIT_P6eeadded to theres_link.ocompile inircd-sys/build.rs.init_resolveris an exportedres_ext.hsymbol → thecref_init_resolveroracle already exists, so no linkage seam (cf. P6x/P6bb); onlyminus_one(support.c:32,unsigned char[17]) was added to res.rs's extern block. - Config-resolved body —
LRAND48off → thesrand48(time(NULL))line drops;DEBUGoff → theRES_INITDEBGRES_DEBUGblock + allDebug()drop. What ports: INITLIST (bzeroreinfo, nullfirst/last), CALLINIT (ircd_res_initthen thenscount==0→::1/ all-onesminus_onefallback nameserver), INITSOCK (socket(AF_INET6,SOCK_DGRAM,0)→resfd, clearSO_BROADCASTwith optlen 0,bindtoin6addr_any), INITCACH (bzerocainfo+hashtable), and the bareop==0→returnresfd. Calls the Rustircd_res_init(P6p) +inetpton(P1/support). - Classification — utility/callee (no
msgtabentry; the call sites are the still-C I/O bootstrap, unreachable from a scripted client under the locked config) → L1 is the gate, no L2/L2-S2S path. - L1 —
res_init_resolver_diff(isolated-worlds, the P6p/P6bb pattern):c_init_resolverwrites the real globals,cref_init_resolverthecref_*twins; diff per op flag. INITLIST/INITCACH pre-dirty then assert both worlds zeroed/nulled + identical; CALLINIT zeroes bothircd_res, diffs retrans/retry/options/nscount/nsaddr_listbytes masking the nondeterministic.id(covers the ::1 fallback host-robustly); INITSOCK asserts return==resfd>=0 andgetsockname→AF_INET6 in both worlds (fd values legitimately differ → structural assert); op==0 pins resfd and asserts the return. Zero diff (5/5). - S2S — none (no
IsServer/remote-field/propagation path; pure state-init + socket setup).
- Cluster choice —
-
2026-06-05 — P6ff (
res.cget_res— the LAST C function in res.c) merged. The recvfrom DNS answer driver ported toircd-common/res.rs; after itres.chas zero C function bodies left.- Cluster choice —
get_resis the final C function inres.cand the top of the resolver read path:s_bsd.c's io‑loop calls it whenresfdis readable. Every function it calls was already ported bottom‑up across P6q–P6ee (find_id,proc_answer,make_cache,rem_request,gethost_byname_type,query_name,resend_query), so this port is the keystone that closes the resolver. - Guard / linkage —
#ifndef PORT_RES_GET_RES_P6ffaround the C body; no linkage seam (exportedres_ext.hsymbol →cref_get_resalready in the oracle, and the still‑Cs_bsd.ccaller resolves to the Rust def at link).-DPORT_RES_GET_RES_P6ffappended to theres_link.orecipe inircd-sys/build.rs. After this,res_link.odefines only the resolver data globals (reinfo/cachetop/cainfo/hashtable/incache/first/last); the finalres.odrop (move those to Rust) is the remaining P6 res step. - Config‑resolved body — DEBUG off → all
Debug(...)traces gone.AFINET = AF_INET6→SOCKADDR_IN = sockaddr_in6, the anti‑spoofbcmps over the 16‑bytesin6_addr. The C!nsaddr_list[a].SIN_ADDR.S_ADDRclause is dead (array decays to a non‑NULL address) → faithfully replicated as a purememcmpmatch.rptr->he.h_addr=h_addr_list[0]. Header counts are 16‑bit bitfields → ntohs via the bindgen*_raw/set_*_rawaccessors;rcode_rawfor the nibble. h_errno values TRY_AGAIN=2/NO_RECOVERY=3/NO_DATA=4. - Faithfulness note —
getres_err:(the C goto label) factored into a smallunsafe fn getres_err(rptr, lp)returning NULL, so eachgoto getres_errbecomes an earlyreturn. The&&short‑circuit in the retry path (options & RES_DEFNAMES && ++srch == 0) is preserved —srchis bumped only when RES_DEFNAMES is set. Verified behavior: TRY_AGAIN (NXDOMAIN/SERVFAIL) takes theelse if (lp)branch → no resend and the request is kept; the resend/query_nameretry fires only on the non‑TRY_AGAIN goto paths whereresendis still 1. - Classification — utility/callee, no
msgtabentry; sole callers_bsd.c:3272is the still‑C io‑loop, inert project‑wide underNO_DNS_LOOKUP. L1 is the gate; no L2/S2S path. - L1 —
ircd-testkit/tests/res_get_res_diff.rs: two fully isolatedWorlds (LIVE = Rust port over res_link.o/res_init globals; CREF =cref_*oracle), each with its ownresfdrecv socket + an::1"nameserver" socket that both sends the crafted reply (so the anti‑spoof source =nsaddr_list[0]=::1) and captures any follow‑up query. A strong interposedgettimeofdaypins thequery_nameid randomization so follow‑up packets are byte‑identical across worlds. Crafted network‑order DNS reply datagrams; diffed post‑state{ret null?, returned hostent (h_name fp + addr count + addrtype/length), reinfo deltas (replies/errors/unkrep/resends/na_look/requests/sent), incache, cainfo.ca_adds, request‑removed?, h_errno, follow‑up bytes}across 9 scenarios: A‑success (entry cached + request rem_request'd), unknown‑id (find_id miss, re_replies still bumped), short‑packet (bail before re_replies++), NXDOMAIN/SERVFAIL (TRY_AGAIN, no resend, request kept), REFUSED (NO_RECOVERY, resend zeroed), NOERROR/ancount‑0 (NO_DATA), anti‑spoof mismatch (re_unkrep++, nothing cached), and PTR‑relookup (asyncgethost_byname_typefires a follow‑up, original rem_request'd). Zero diff.cargo build0 warnings;golden_registration/golden_s_dnsstay green (still‑Cs_bsd.ccalls the Rustget_res).
- Cluster choice —
-
2026-06-05 — P6gg (
res.cdata globals → Rust; the finalres.odrop) merged.res.cis now FULLY Rust.- Cluster choice — after P6ff (
get_res, the last C function),res_link.odefined ONLY the resolver's seven private data globals:cachetop(aCache*cache list head),incache(intcache count),hashtable(CacheTable[ARES_CACSIZE]),first/last(ResRQ*request-queue heads),cainfo(struct cacheinfo, 7×int),reinfo(struct resinfo, 10×int). All upstream res.c file-statics (no*_ext.hsymbol), de-static'd viaRES_DNS_STATICunder-DPORT_DNS_M_DNS. This step moves their storage into Rust and dropsres.o. - Port — removed the seven
static mut …lines from theextern "C"block inircd-common/src/res.rs; added seven#[no_mangle] pub static mutdefinitions: pointers= null_mut(),incache = 0, andhashtable/cainfo/reinfo= unsafe { MaybeUninit::zeroed().assume_init() }(thes_misc.rs::ircstidiom). C BSS zero-init → these reproduce it byte-for-byte.CacheInfo/ResInfopromoted topub(now inpub staticsignatures).ircd_res/resfd/minus_onestay extern (owned by res_init.rs / s_bsd.c / support.c). - build.rs —
"res.o"added toPORTED; the"res.o" => "res_link.o"arm dropped fromlink_objs(); theres_link.ocompilerun()block deleted (the whole-DPORT_RES_CACHE_*..P6ffchain). Kept the cref-oracleres.ocompile (-DRES_CACHE_EXPOSE) — the existing res L1 tests still diff againstcref_*. - Classification — data-global move, no new function → no new L1 test. The seven globals carry the same symbol names, so the L1 tests'
#[link_name=…]live-world decls now resolve to the Rust storage instead of res_link.o's. - L1 — all 19
res_*_diffsuites zero-diff (130 assertions):nm target/debug/ircdconfirms the seven globals defined (B) by the Rust object;res_get_res_diff/res_proc_answer_diff/res_request_diff/etc. exercise live-worldreinfo/cachetop/cainfo/hashtable/incache/first/lastdeltas and still matchcref_*. - L2 —
golden_registration+golden_s_dnsbyte-identical (the latter drives theDNS/DNS lcommand that readscachetop/cainfo/reinfo). No further L2 resolver path: reverse-DNS is dead-code underNO_DNS_LOOKUP. - S2S — none (no
IsServer/remote-field path in a data-global move). - Build —
cargo build -p ircd-rs0 warnings;res.chas zero remaining C symbols in the link set.
- Cluster choice — after P6ff (
-
2026-06-05 — P6hh (
chkconf.c→ standalonechkconf-rsbinary) merged. The IRCnet config-file checker — the final C translation unit in P6 — ported to its own workspace crate. With this, P6 (Config & DNS) is COMPLETE.- Scope / classification.
chkconfis a standalone validator binary (CHKCONF_OBJS in support/Makefile.in), NOT amsgtabcommand handler, so the command-cluster-port seam (drop the.o, L1 vscref_) does not apply — it is a greenfield-style standalone port likeiauth-rs, verified black-box by running both binaries and diffing output. It#includesconfig_read.c; under the locked config (M4_PREPROC/CONFIG_DIRECTIVE_INCLUDE/DEBUGMODE/XLINE/UNIXPORT/CLIENTS_CHANNEL/ENABLE_SIDTRACEOFF,TKLINEON) the compiled surface is:main,new_class/get_class,dgets,getfield,initconf,validate,confchar,checkSID,openconf,showconf, andconfig_read.c'sconfig_error(the CHKCONF_COMPILE non-CDI branch). The M4 (simulateM4Include/mystrinclude) and CDI (config_read/config_free/new_config_file/findConfLineNumber-CDI) bodies are#ifdef'd out and not ported. - Crate. New
chkconf-rsworkspace member (Cargo.toml[[bin]] chkconf), pure-std, NOircd-sysdependency (per the PLAN: chkconf is never routed throughircd-sys). Constants resolved to the locked config and probed from a C harness:IRCDCONF_DELIMITER='|',SIDLEN=4,QUEUELEN=19120,SPLIT_SERVERS=10,SPLIT_USERS=5000,CONF_SERVER_MASK=0x38,CONF_CLIENT_MASK=0x20ba,IRCDCONF_PATH/IRCDCONF_DIRfrom the Makefile. - Single-file collapse. Under the locked config there is exactly one config file, so
findConfLineNumberalways returns the lonefilesnode (min=0) → every per-lineconfig_errorlocation is the constant pair ("ircd.conf", physical linenr). Themywc/files/wordcountmachinery produces NO output (DEBUGMODE off) and only resets itsdgetsbuffer beforeinitconf(which resets it itself), so it is collapsed away — output-equivalent and verified.config_read.cneeds no separate Rust lib: the ircd binary already uses the P6fconfig_parse.rs, andchkconf-rsreimplementsconfig_errorinline. - Faithfulness notes (reproduced byte-for-byte). (1) The
getfieldescape/#-comment loop quirk where theswitchtranslation of\n/\r/\t/\0is immediately overwritten by the subsequent left-shift (net effect: backslash removed, next char kept literally; a trailing backslash truncates). (2)aconf->portis the ONE field C does NOT reset per line (the struct is reused; only host/passwd/name/class/clients/status/flags are cleared each iteration), so under-d9a line with <5 fields prints the previous line's port — modelled as a carriedport_regregister, zeroed after an append (the Caconf=NULL→re-malloc). (3)get_classalways returns the same static&cls, sovalidate'sbconf->class == aconf->classpointer compare is "both non-NULL" — modelled byclass_set: bool. (4)mycmpuses libctoupper(ASCII fold), not a table. (5)ctopis prepend-order, so the unmatched-conf warnings print in reverse file order. (6)maxsendqis a file static carried across lines (debug-only read). - C UB excluded (documented). The reference
dgetsreturns a buffer that is NOT NUL-terminated, so the-d/-sline echo reads uninitialised/stale stack in two cases: a line ≥511 bytes echoed under-d/-d9(stack-frame-dependent;-sand non-debug happen to match), and a config file with no trailing final newline (last-line echo). Both are genuine UB and non-reproducible; all fixtures end with a newline, andlong.confis only run without an echoing flag. The Rust port implements the well-defined NUL-terminated behaviour, which matches C on every non-UB path. - L1.
chkconf-rs/tests/differential.rs: builds the reference C oracle (make -C cbuild chkconf, linksmatch.o+chkconf.o+-lcrypt) and the Rust binary (CARGO_BIN_EXE_chkconf), runs both over 13 committed fixtures (tests/fixtures/: empty, fully-valid + matching C/N pair, all conf letters incl. unknown + I/O-line flags + unmatched, SID validity matrix, M-line split-servers/users checks, wrong-delimiter/blank/comment/bare-letter, class-not-found + P-line slash/null-host, V-line slash warnings, unmatched servers + hub/leaf, backslash escapes + inline#+ line continuation, CRLF, multiple M/A, overlong-line) × {none,-d,-d3,-d9,-s} (long.conf: none only) plus an argv0-masked-htest — asserting byte-identical stdout, stderr and exit status. Zero diff.cargo build -p chkconf-rs0 warnings; clippy clean (faithful-port allows documented). No L2/S2S path (standalone CLI, noIsServer/remote-field/msgtabentry). - P6 status. With
chkconf-rslanded, every P6 module —s_conf.c(+config_read.c),res.c+res_comp.c+res_init.c+res_mkquery.c,chkconf.c— is ported. P6 (Config & DNS) is COMPLETE. Remaining migration: P7 (I/O core + event loop +s_bsd.c/packet.c/bsd.c/fileio.c/ircd.c).
- Scope / classification.