#!/usr/bin/env dub
/+ dub.sdl:
name "io_uring_uring_cmd_socket"
dependency "during" version="~>0.5.0"
platforms "linux"
targetPath "build"
+/
/**
* `io_uring` — `IORING_OP_URING_CMD` passthrough on a socket (Linux 5.19).
*
* `IORING_OP_URING_CMD` (Linux 5.19) is a generic "command" channel that lets a
* file's underlying driver service an `ioctl`/`setsockopt`-like request straight
* out of the ring, with no per-call syscall. The socket sub-commands
* (`SOCKET_URING_OP_*`, the `cmd_op` field — socket support landed in Linux 6.7)
* ride that channel. Here we drive a `getsockopt`/`setsockopt` round-trip on a
* loopback TCP socket entirely through the ring via `prepCmdSock`, instead of
* calling `getsockopt(2)` / `setsockopt(2)` directly.
*
* What it does:
* 1. Creates and binds a TCP socket to 127.0.0.1:0 with libc.
* 2. Submits one `uring_cmd` SQE: `SOCKET_URING_OP_GETSOCKOPT` of
* `SO_REUSEADDR`, reading the option value through the ring; the option
* length comes back in the CQE `res` and the value buffer is filled
* in-place.
* 3. Submits a `SOCKET_URING_OP_SETSOCKOPT` to flip `SO_REUSEADDR` on, then a
* second `GETSOCKOPT` to confirm the new value round-tripped — proving the
* passthrough moves data into the kernel and back out, not just succeeds.
*
* `uring_cmd` on a socket needs no special hardware (unlike NVMe/block or ZCRX
* passthrough), so it runs on any 6.7+ loopback host — including this 6.18 box,
* where the feature MUST be exercised.
*
* (Note: a `SOCKET_URING_OP_GETSOCKNAME` sub-command was proposed but never
* merged into mainline; on a live kernel it returns `-EOPNOTSUPP`. We therefore
* demonstrate the getsockopt/setsockopt sub-commands that the kernel actually
* implements.)
*
* Companion to the io_uring chronology:
* see docs/research/async-io/io-uring/timeline.md
* § "5.19 — Buffer rings, zero-copy groundwork, big SQE/CQE".
*
* Run with: `dub run --single uring-cmd-socket.d`
*
* Portability: if the running kernel lacks `io_uring` (setup < 0) or this
* specific op is unsupported (`-EINVAL` / `-EOPNOTSUPP` / `-ENOSYS`), the
* program prints a `SKIP:` line and exits 0 so it stays green in CI.
*/
module (module) io_uring_uring_cmd_socketio_uring — IORING_OP_URING_CMD passthrough on a socket (Linux 5.19).
IORING_OP_URING_CMD (Linux 5.19) is a generic "command" channel that lets a
file's underlying driver service an ioctl/setsockopt-like request straight
out of the ring, with no per-call syscall. The socket sub-commands
(SOCKET_URING_OP_*, the cmd_op field — socket support landed in Linux 6.7)
ride that channel. Here we drive a getsockopt/setsockopt round-trip on a
loopback TCP socket entirely through the ring via prepCmdSock, instead of
calling getsockopt(2) / setsockopt(2) directly.
What it does:
Creates and binds a TCP socket to 127.0.0.1:0 with libc.
Submits one uring_cmd SQE: SOCKET_URING_OP_GETSOCKOPT of
SO_REUSEADDR, reading the option value through the ring; the option
length comes back in the CQE res and the value buffer is filled
in-place.
Submits a SOCKET_URING_OP_SETSOCKOPT to flip SO_REUSEADDR on, then a
second GETSOCKOPT to confirm the new value round-tripped — proving the
passthrough moves data into the kernel and back out, not just succeeds.
uring_cmd on a socket needs no special hardware (unlike NVMe/block or ZCRX
passthrough), so it runs on any 6.7+ loopback host — including this 6.18 box,
where the feature MUST be exercised.
(Note: a SOCKET_URING_OP_GETSOCKNAME sub-command was proposed but never
merged into mainline; on a live kernel it returns -EOPNOTSUPP. We therefore
demonstrate the getsockopt/setsockopt sub-commands that the kernel actually
implements.)
Companion to the io_uring chronology:
see docs/research/async-io/io-uring/timeline.md
§ "5.19 — Buffer rings, zero-copy groundwork, big SQE/CQE".
Run with: dub run --single uring-cmd-socket.d
Portability
if the running kernel lacks io_uring (setup < 0) or this
specific op is unsupported (-EINVAL / -EOPNOTSUPP / -ENOSYS), the
program prints a SKIP: line and exits 0 so it stays green in CI.
io_uring_uring_cmd_socket;
import (module) duringSimple idiomatic dlang wrapper around linux io_uring
(see: https://kernel.dk/io_uring.pdf) asynchronous API.
during;
import (package) corecore.(package) core.syssys.(package) core.sys.linuxlinux.(module) core.sys.linux.errnoD header file for GNU/Linux
errno : (alias constant) io_uring_uring_cmd_socket.EINVAL = int core.stdc.errno.EINVAL = 22EINVAL, (alias constant) io_uring_uring_cmd_socket.EOPNOTSUPP = int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP, (alias constant) io_uring_uring_cmd_socket.ENOSYS = int core.stdc.errno.ENOSYS = 38ENOSYS;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(package) core.sys.posix.arpaarpa.(module) core.sys.posix.arpa.inetD header file for POSIX.
inet : (alias) io_uring_uring_cmd_socket.htonl = uint core.sys.posix.arpa.inet.htonl(uint) pure nothrow @nogc @trustedhtonl;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(package) core.sys.posix.netinetnetinet.(module) core.sys.posix.netinet.in_D header file for POSIX.
in_ : (struct) core.sys.posix.netinet.in_.sockaddr_insockaddr_in, (alias enum value) io_uring_uring_cmd_socket.AF_INET = core.sys.posix.sys.socket.AF_INET = 2AF_INET, (alias enum value) io_uring_uring_cmd_socket.IPPROTO_TCP = core.sys.posix.netinet.in_.IPPROTO_TCP = 6IPPROTO_TCP;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(package) core.sys.posix.syssys.(module) core.sys.posix.sys.socketD header file for POSIX.
socket : (alias) io_uring_uring_cmd_socket.socket = int core.sys.posix.sys.socket.socket(int, int, int) nothrow @nogc @safesocket, (alias) io_uring_uring_cmd_socket.bind = int core.sys.posix.sys.socket.bind(int, scope const(core.sys.posix.sys.socket.sockaddr*), uint) nothrow @nogcbind, (struct) core.sys.posix.sys.socket.sockaddrsockaddr, socklen_t,
(alias enum value) io_uring_uring_cmd_socket.SOCK_STREAM = core.sys.posix.sys.socket.SOCK_STREAM = 1SOCK_STREAM, (alias enum value) io_uring_uring_cmd_socket.SOL_SOCKET = core.sys.posix.sys.socket.SOL_SOCKET = 1SOL_SOCKET, (alias enum value) io_uring_uring_cmd_socket.SO_REUSEADDR = core.sys.posix.sys.socket.SO_REUSEADDR = 2SO_REUSEADDR;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.unistdD header file for POSIX.
unistd : (alias) io_uring_uring_cmd_socket.close = int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose;
import (package) stdstd.(module) std.stdioCategory Symbols File handles _popen File isFileHandle openNetwork stderr stdin stdout Reading chunks lines readf readfln readln Writing toFile write writef writefln writeln Misc KeepTerminator LockType StdioException
Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio
is publically imported when importing std.stdio.
There are three layers of I/O:
The lowest layer is the operating system layer. The two main schemes are Windows and Posix.
C's stdio.h which unifies the two operating system schemes.
std.stdio, this module, unifies the various stdio.h implementations into
a high level package for D programs.
Source
std/stdio.d
stdio : (alias template) io_uring_uring_cmd_socket.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, stderr;
// during exposes the socket uring_cmd sub-commands as plain enums; spell them
// out here so the example is self-contained and the cmd_op values are explicit.
enum uint (constant) uint io_uring_uring_cmd_socket.SOCKET_URING_OP_GETSOCKOPT = 2uSOCKET_URING_OP_GETSOCKOPT = 2;
enum uint (constant) uint io_uring_uring_cmd_socket.SOCKET_URING_OP_SETSOCKOPT = 3uSOCKET_URING_OP_SETSOCKOPT = 3;
/// Submit one socket `uring_cmd` and return its CQE `res` (or a negative -errno).
int int io_uring_uring_cmd_socket.runCmdSock(ref during.Uring io, uint cmdOp, int fd, uint level, uint optname, void* optval, uint optlen)Submit one socket uring_cmd and return its CQE res (or a negative -errno).
runCmdSock(ref (struct) during.UringMain entry point to work with io_uring.
It hides SubmissionQueue and CompletionQueue behind standard range interface.
We put in SubmissionEntry entries and take out CompletionEntry entries.
Use predefined prepXX methods to fill required fields of SubmissionEntry before put or during putWith.
Note
prepXX functions doesn't touch previous entry state, just fills in operation properties. This is because for
less error prone interface it is cleared automatically when prepared using putWith. So when using on own SubmissionEntry
(outside submission queue), that would be added to the submission queue using put, be sure its cleared if it's
reused for multiple operations.
Uring (parameter) during.Uring ioio, uint (parameter) uint cmdOpcmdOp, int (parameter) int fdfd, uint (parameter) uint levellevel, uint (parameter) uint optnameoptname,
void* (parameter) void* optvaloptval, uint (parameter) uint optlenoptlen)
{
// Pass everything as explicit args so the prep lambda captures nothing — a
// closure would force a GC-allocated dual-context delegate (rejected under
// putWith's @nogc).
(parameter) during.Uring ioio.putWith!((ref SubmissionEntry e, uint c, int f, uint lv, uint on, ulong ov, uint ol) {
e.prepCmdSock(c, f, lv, on, cast(void*)ov, ol);
})(during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, uint c, int f, uint lv, uint on, ulong ov, uint ol) nothrow @nogc @safe
{
prepCmdSock(e, c, f, lv, on, cast(void*)ov, ol);
}
, uint, int, uint, uint, ulong, uint)(ref uint __param_0, ref int __param_1, ref uint __param_2, ref uint __param_3, ulong __param_4, ref uint __param_5) nothrow @nogc return ref @safeAdds new entry to the SubmissionQueue.
Note that this just adds entry to the queue and doesn't advance the tail
marker kernel sees. For that finishSq() is needed to be called next.
Also note that to actually enter new entries to kernel,
it's needed to call submit().
cmdOp, (parameter) int fdfd, (parameter) uint levellevel, (parameter) uint optnameoptname, cast(ulong)(parameter) void* optvaloptval, (parameter) uint optlenoptlen);
const (local variable) const(int) submittedsubmitted = (parameter) during.Uring ioio.int during.Uring.submit(uint want) nothrow @nogc @safeSubmits qued SubmissionEntry to be processed by kernel.
submit(1);
if ((local variable) const(int) submittedsubmitted < 0)
return (local variable) const(int) submittedsubmitted; // -errno from submit
(parameter) during.Uring ioio.int during.Uring.wait(uint want = 1u) nothrow @nogcSimmilar to submit but with this method we just wait for required number
of CompletionEntries.
wait(1);
const (local variable) const(int) resres = (parameter) during.Uring ioio.during.io_uring.CompletionEntry during.Uring.front() pure nothrow @nogc return ref @safeGet first CompletionEntry from cq ring
front.(field) int during.io_uring.CompletionEntry.resresult code for this event
res;
(parameter) during.Uring ioio.void during.Uring.popFront() pure nothrow @nogc @safeMove to next CompletionEntry
popFront();
return (local variable) const(int) resres;
}
int int D main()main()
{
(struct) during.UringMain entry point to work with io_uring.
It hides SubmissionQueue and CompletionQueue behind standard range interface.
We put in SubmissionEntry entries and take out CompletionEntry entries.
Use predefined prepXX methods to fill required fields of SubmissionEntry before put or during putWith.
Note
prepXX functions doesn't touch previous entry state, just fills in operation properties. This is because for
less error prone interface it is cleared automatically when prepared using putWith. So when using on own SubmissionEntry
(outside submission queue), that would be added to the submission queue using put, be sure its cleared if it's
reused for multiple operations.
Uring (local variable) during.Uring ioio;
const (local variable) const(int) setupRetsetupRet = (local variable) during.Uring ioio.int during.setup(ref during.Uring uring, uint entries = 128u, during.io_uring.SetupFlags flags = SetupFlags.NONE) nothrow @nogc @safeSetup new instance of io_uring into provided Uring structure.
setup(8);
if ((local variable) const(int) setupRetsetupRet < 0)
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: io_uring_setup failed (errno %d) — io_uring unavailable on this host",
-(local variable) const(int) setupRetsetupRet);
return 0;
}
// --- Bind a TCP socket to an ephemeral loopback port with plain libc. ---
const int (local variable) const(int) fdfd = int core.sys.posix.sys.socket.socket(int, int, int) nothrow @nogc @safesocket((enum value) core.sys.posix.sys.socket.AF_INET = 2AF_INET, (enum value) core.sys.posix.sys.socket.SOCK_STREAM = 1SOCK_STREAM, (enum value) core.sys.posix.netinet.in_.IPPROTO_TCP = 6IPPROTO_TCP);
if ((local variable) const(int) fdfd < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("socket() failed");
return 1;
}
scope (exit) int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose((local variable) const(int) fdfd);
(struct) core.sys.posix.netinet.in_.sockaddr_insockaddr_in (local variable) core.sys.posix.netinet.in_.sockaddr_in addraddr;
(local variable) core.sys.posix.netinet.in_.sockaddr_in addraddr.(field) ushort core.sys.posix.netinet.in_.sockaddr_in.sin_familysin_family = (enum value) core.sys.posix.sys.socket.AF_INET = 2AF_INET;
(local variable) core.sys.posix.netinet.in_.sockaddr_in addraddr.(field) ushort core.sys.posix.netinet.in_.sockaddr_in.sin_portsin_port = 0; // 0 => kernel picks an ephemeral port
(local variable) core.sys.posix.netinet.in_.sockaddr_in addraddr.(field) core.sys.posix.arpa.inet.in_addr core.sys.posix.netinet.in_.sockaddr_in.sin_addrsin_addr.(field) uint core.sys.posix.arpa.inet.in_addr.s_addrs_addr = uint core.sys.posix.arpa.inet.htonl(uint) pure nothrow @nogc @trustedhtonl(0x7f00_0001); // 127.0.0.1
if (int core.sys.posix.sys.socket.bind(int, scope const(core.sys.posix.sys.socket.sockaddr*), uint) nothrow @nogcbind((local variable) const(int) fdfd, cast((struct) core.sys.posix.sys.socket.sockaddrsockaddr*)&(local variable) core.sys.posix.netinet.in_.sockaddr_in addraddr, (struct) core.sys.posix.netinet.in_.sockaddr_insockaddr_in.(constant) ulong core.sys.posix.netinet.in_.sockaddr_in.sizeof = 16LUsizeof) != 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("bind() failed");
return 1;
}
// --- 1) Read SO_REUSEADDR through the ring (SOCKET_URING_OP_GETSOCKOPT). ---
// The socket driver fills `before` in-place; the returned optlen lands in res.
int (local variable) int beforebefore = -1;
socklen_t (local variable) uint vlenvlen = (local variable) int beforebefore.(constant) ulong int.sizeof = 4LUsizeof;
const (local variable) const(int) getResgetRes = int io_uring_uring_cmd_socket.runCmdSock(ref during.Uring io, uint cmdOp, int fd, uint level, uint optname, void* optval, uint optlen)Submit one socket uring_cmd and return its CQE res (or a negative -errno).
runCmdSock((local variable) during.Uring ioio, (constant) uint io_uring_uring_cmd_socket.SOCKET_URING_OP_GETSOCKOPT = 2uSOCKET_URING_OP_GETSOCKOPT, (local variable) const(int) fdfd,
(enum value) core.sys.posix.sys.socket.SOL_SOCKET = 1SOL_SOCKET, (enum value) core.sys.posix.sys.socket.SO_REUSEADDR = 2SO_REUSEADDR, &(local variable) int beforebefore, (local variable) uint vlenvlen);
// URING_CMD itself, or the socket sub-command, may be absent on older
// kernels — treat those as a clean SKIP rather than a hard failure.
if ((local variable) const(int) getResgetRes == -(constant) int core.stdc.errno.EINVAL = 22EINVAL || (local variable) const(int) getResgetRes == -(constant) int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP || (local variable) const(int) getResgetRes == -(constant) int core.stdc.errno.ENOSYS = 38ENOSYS)
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: IORING_OP_URING_CMD / SOCKET_URING_OP_GETSOCKOPT unsupported (errno %d)",
-(local variable) const(int) getResgetRes);
return 0;
}
if ((local variable) const(int) getResgetRes < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("uring_cmd getsockopt failed: errno %d", -(local variable) const(int) getResgetRes);
return 1;
}
// On success, getsockopt reports the option length (sizeof(int)) in res.
if ((local variable) const(int) getResgetRes != cast(int) (local variable) int beforebefore.(constant) ulong int.sizeof = 4LUsizeof)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("unexpected getsockopt optlen: res=%d (expected %d)",
(local variable) const(int) getResgetRes, cast(int) (local variable) int beforebefore.(constant) ulong int.sizeof = 4LUsizeof);
return 1;
}
// --- 2) Flip SO_REUSEADDR on through the ring (SOCKET_URING_OP_SETSOCKOPT). ---
int (local variable) int onon = 1;
const (local variable) const(int) setRessetRes = int io_uring_uring_cmd_socket.runCmdSock(ref during.Uring io, uint cmdOp, int fd, uint level, uint optname, void* optval, uint optlen)Submit one socket uring_cmd and return its CQE res (or a negative -errno).
runCmdSock((local variable) during.Uring ioio, (constant) uint io_uring_uring_cmd_socket.SOCKET_URING_OP_SETSOCKOPT = 3uSOCKET_URING_OP_SETSOCKOPT, (local variable) const(int) fdfd,
(enum value) core.sys.posix.sys.socket.SOL_SOCKET = 1SOL_SOCKET, (enum value) core.sys.posix.sys.socket.SO_REUSEADDR = 2SO_REUSEADDR, &(local variable) int onon, (local variable) int onon.(constant) ulong int.sizeof = 4LUsizeof);
if ((local variable) const(int) setRessetRes < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("uring_cmd setsockopt failed: errno %d", -(local variable) const(int) setRessetRes);
return 1;
}
// --- 3) Read it back to confirm the write took effect (round-trip proof). ---
int (local variable) int afterafter = -1;
const (local variable) const(int) getRes2getRes2 = int io_uring_uring_cmd_socket.runCmdSock(ref during.Uring io, uint cmdOp, int fd, uint level, uint optname, void* optval, uint optlen)Submit one socket uring_cmd and return its CQE res (or a negative -errno).
runCmdSock((local variable) during.Uring ioio, (constant) uint io_uring_uring_cmd_socket.SOCKET_URING_OP_GETSOCKOPT = 2uSOCKET_URING_OP_GETSOCKOPT, (local variable) const(int) fdfd,
(enum value) core.sys.posix.sys.socket.SOL_SOCKET = 1SOL_SOCKET, (enum value) core.sys.posix.sys.socket.SO_REUSEADDR = 2SO_REUSEADDR, &(local variable) int afterafter, (local variable) uint vlenvlen);
if ((local variable) const(int) getRes2getRes2 < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("uring_cmd getsockopt (after set) failed: errno %d", -(local variable) const(int) getRes2getRes2);
return 1;
}
if ((local variable) int afterafter == 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("SO_REUSEADDR still off after uring_cmd setsockopt (got %d)", (local variable) int afterafter);
return 1;
}
void std.stdio.writefln!(char, int, int)(in char[] fmt, int __param_1, int __param_2) @safeEquivalent to writef(fmt, args, '\n').
writefln("ok: IORING_OP_URING_CMD socket passthrough — getsockopt SO_REUSEADDR=%d, "
~ "setsockopt=1, re-read=%d (all through the ring)", (local variable) int beforebefore, (local variable) int afterafter);
return 0;
}