Proof of concept mechanical port of ircnet/ircd to Rust as part of a bit about C being insecure for network services
0

Configure Feed

Select the types of activity you want to include in your feed.

ircd.rs / docs / blog / 011-the-dial-out-you-can-only-test-by-not-dialing.md
13 kB

title: "The dial-out you can only test by not dialing" desc: "connect_server's entire job is to make an outbound connection succeed. I wrote three differential tests for it, and not one of them is allowed to connect to anything. The interesting part is why that's the honest coverage and not a cop-out — plus the block of code I ported by deleting it." date: 2026-06-06#

The last post was about the front door: add_connection, the one function every inbound connection walks through. Its success path was already covered a hundred and eleven times over, because that's what the golden harness does — it dials into the daemon and types IRC. The trap there was the refusal branches, the ones no well-behaved client ever takes.

Now flip the arrow around. connect_server is the daemon picking up the phone and dialing out — the function that links you to another server because a C:line told it to, or an oper typed /CONNECT, or an autoconnect timer fired. It is the exact counterpart to the front door, and porting it was the third leg of an I/O trilogy: the leaves that read the kernel, the front door that accepts, and now the part that calls out.

Here's the function I had to match, byte-for-byte, against the original:

/* ircd/s_bsd.c:2516 */
int	connect_server(aConfItem *aconf, aClient *by, struct hostent *hp)
{
	...
	svp = connect_inet(aconf, cptr, &len);
	...
	if (connect(cptr->fd, (SAP)svp, len) < 0 && errno != EINPROGRESS)
	...
}

The whole reason this function exists is the connect() call near the bottom. It opens a socket, binds a source address, and reaches across the network to a peer that's listening. That's the success path. That's the point of the thing.

And I cannot test it.

Mara (hacker): "cannot" is doing some work there — you mean cannot at L1, in the per-function differential oracle. The success path is real, it just isn't deterministic without a live peer on the other end and the event loop to drive it.

Right. The L1 oracle — the cref_ rename trick from the oracle post — runs one C function and its Rust twin side by side in the same process and diffs the results. For a pure function that's clean. For a function whose real input is the kernel, you hand both impls the same real fd and diff what comes out. But connect_server's success means a socket actually connecting to a server that actually exists. To make that deterministic I'd need a peer listening on a known port, a C:line pointing at it, and the surrounding loop to notice the connection completed and burst it. That's not a unit test. That's a second daemon. That's the L2 golden harness, and it lives at a different layer.

So here's the funny part, and it's funny in the way that only makes sense at 11pm. The golden harness has a hundred and eleven scenarios. Every single one of them dials into my ircd. Not one of them makes my ircd dial out. The most-tested program in this whole project has zero coverage of the function I just ported, because nobody ever wrote a scenario where the server initiates the link. The inbound front door is covered to death by ubiquity; the outbound dial is covered by nothing.

Testing the negative space#

What's left, if you can't test the connection succeeding? The connection not happening. connect_server has three reachable ways to bail before — or instead of — completing a dial, and all three are deterministic. So that's the oracle: I diff every way the function refuses to connect.

The first one is the busy signal. If the server you're being told to dial is already linked, you don't dial it again:

/* ircd/s_bsd.c:2528 */
if ((c2ptr = find_server(aconf->name, NULL)))
    {
	sendto_flag(SCH_NOTICE, "Server %s already present from %s",
		    aconf->name, get_client_name(c2ptr, TRUE));
	if (by && IsPerson(by) && !MyClient(by))
	  sendto_one(by, ":%s NOTICE %s :Server %s already present from %s",
		     ME, by->name, aconf->name, get_client_name(c2ptr, TRUE));
	return -1;
    }

From that one if, two tests fall out. Register a STAT_SERVER named hub.test in the client hash, then call connect_server for hub.test: find_server hits, you get the SCH_NOTICE, you return -1, and you never touch a socket. That's case one. Case two adds a by — the client who asked for this link — and makes it a remote person (IsPerson, but fd < 0 so !MyClient), which lights up that second sendto_one arm so the requester upstream gets told too. Both worlds return -1, both emit the same notices.

Then there's the inverse: the server isn't present, so connect_server builds the client stub, calls connect_inet to set up the socket — and connect_inet fails. In the test, it fails at the bind(), because the outbound source address mysk is the zeroed, unconfigured all-AF_UNSPEC default and you can't bind a socket to a family that doesn't exist:

/* ircd/s_bsd.c:2726, inside connect_inet */
if (bind(cptr->fd, (SAP)&outip, sizeof(outip)) == -1)
    {
	report_error("error binding to local port for %s:%s", cptr);
	return NULL;
    }

A NULL back from connect_inet sends connect_server into its free_server: cleanup label: close the half-open fd, mark it -2, free the server struct, free the client, return -1. This is the case I care about most, because cleanup is where faithful ports go to die. A botched cleanup doesn't crash — it leaks. So the test asserts the boring thing twice: highest_fd is unchanged afterward (no descriptor got stranded in the registry), and find_server still misses (no half-built server got left in the hash). Both worlds, identical, nothing leaked.

Cadey (coffee): three tests for a function whose job is to connect, and the assertion in all three is some flavor of "...and then it didn't." I wrote a unit test suite that is, structurally, the daemon refusing to do its job correctly three different ways.

That's not a gap I'm apologizing for. It's the honest shape of the thing. The L1 oracle proves the decisions — when to refuse, what to say when you refuse, how to clean up when the setup fails — match the C exactly. The one decision it can't prove is "the bytes that go on a successfully-connected wire," and that decision belongs to a test that has a real wire. Pretending otherwise by mocking connect() would just make both impls agree with my mock instead of with the kernel, which is the exact failure mode the same-socket trick exists to avoid.

The block I ported by deleting it#

Here's the part that messes with the project's whole premise. This is a faithful port. The standing rule, the one I keep shipping bugs to honor, is: if the C does something weird, you do the weird thing too. I've reproduced a deliberate over-read. Earlier phases reproduced an rfc931 parser bug on purpose. Faithful means faithful to the warts.

So what do you do with this?

/* ircd/s_bsd.c:2544 */
if (!aconf->ipnum.S_ADDR && *aconf->host != '/')
    {
	... /* resolve the hostname via DNS, fill in aconf->ipnum */
    }

Reads fine. If we don't have an IP for this conf yet, go resolve one. Except S_ADDR is a macro, and on this build it expands to s6_addr:

/* common/os.h:686 */
# define	S_ADDR		s6_addr

That s6_addr is the 16-byte array inside the in6_addr. It's not a pointer field you can null-check — it's an embedded array, and !aconf->ipnum.s6_addr is ! of the address of that array. The address of a struct member is never null. The condition is always false. The block never runs. Not "rarely," not "only in IPv4 mode" — this build is AF_INET6-only, and under that config the entire DNS branch inside connect_server is dead code. The real address resolution moved down into connect_inet, where there's a proper AND16(...) == 255 sentinel check against the unresolved value. The block in connect_server is a fossil.

So I ported it by not porting it. The Rust function has a comment where the block would go and nothing else:

// ircd-common/src/s_bsd.rs
// NB: the DNS-resolution block (s_bsd.c:2544) is dead under AF_INET6 — S_ADDR =
// s6_addr (an embedded array), so !aconf->ipnum.s6_addr is the address of that
// array → always non-NULL → the guard is always false. The block never runs; hp
// is used directly. (Same array-address quirk as send_ping/P7m.)

I went back and forth on this. Faithfulness to a bug means reproducing its behavior, and dead code has no behavior — there's no observable difference between a port that contains an unreachable block and one that omits it, so the oracle would call them identical no matter which I picked. Omitting it is the honest call and it's the one the project's own ethos points at, once you say it slowly: I'm matching what the program does, and what this program does here is nothing.

But I want to be straight about the limit, because this is the kind of claim the whole series is supposed to be careful with. The oracle did not prove this block is dead. No test I wrote enters that branch in either world, so the differential harness is silent on it — it can't catch me being wrong here. The proof is a reading: the macro expands to an array, !&array is a constant false, done. If I'd misread the config and S_ADDR were actually a pointer on some build I forgot about, the oracle wouldn't save me. I'm trusting the macro, not the test. That's a weaker kind of sure than I'd like, and it's the kind I'm willing to write down.

A lookup function that scribbles on your input#

One last thing, because it cost me twenty minutes and it's deeply funny. Setting up the "already present" case means calling find_server("hub.test"). Innocuous, right? It's a lookup. Lookups read.

This one doesn't. find_server falls through to a suffix-mask search that walks the hostname looking for wildcard matches, and it does that by overwriting the dots with stars, in place, in the string you handed it. It puts them back after. But for the duration of the call, your lookup key is being mutated. Hand it a c"hub.test" literal — which Rust parks in read-only memory — and you don't get a wrong answer, you get a segfault, because the function tried to write a * into a page it's not allowed to touch.

Mara (hacker): a function named find_* that takes a const char *-shaped argument and writes to it. There's a whole generation of C in that one detail.

Fixing it is a one-liner — heap-allocate the test strings so they're writable. But finding it meant reading hash_find_server closely enough to notice it scribbles on its argument, which is not where you look first when a lookup segfaults. I left a comment in the test harness so the next person doesn't lose the twenty minutes I did.


So the trilogy's closed. And the thing that actually defines this last piece — a connect() reaching a real peer that answers — is the one part none of it tests yet. That waits for a /CONNECT scenario in the golden harness, where there's a second daemon to dial and the event loop to notice it picked up. It's the next layer, it's the test that matters most for this function, and I haven't written it. I'd rather say that out loud than let three tests-that-refuse pass for coverage they don't give.

The port is ircd-common/src/s_bsd.rs (connect_server and its private connect_inet twin); the refuse-three-ways differential is ircd-testkit/tests/connect_server_diff.rs; and the per-leaf plan that scoped it is p7cc-s_bsd-connect_server, under the phase plan in PLAN.md. The front door is the inbound twin this mirrors, the same-socket trick is why the L1 can read one real fd into two worlds, and the oracle is the cref_ rename these all lean on.

And the standing reminder, true on every post here: this is an unsanctioned experiment on someone else's code. Leave the IRCNet team out of it. The dead block I deleted, the cleanup I asserted twice, and the lookup that wrote a star into a read-only page are mine.