authorgravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-09-30 14:36:03+02:00
committergravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-10-01 11:21:28+02:00
logc53cb65568e80a1d9c20a07373db86377ee9421b
treeccb31ca66a231b9f78dc908ff05b727f7e244d4a
parentb3e8eb657772e46058ab011eb875b43d181e56c0
signature Commit is signed but in an unrecognized format.

std.Io.net: add control message utilities


5 files changed, 149 insertions(+), 41 deletions(-)

lib/std/Io/net.zig+67-5
......@@ -1378,7 +1378,7 @@ pub const Stream = struct {
13781378 pub const Reader = struct {
13791379 io: Io,
13801380 interface: Io.Reader,
1381 control_buffer: []u8,
1381 control_buffer: []align(cmsg_align) u8,
13821382 control_len: usize,
13831383 control_truncated: bool,
13841384 stream: Stream,
......@@ -1391,7 +1391,12 @@ pub const Stream = struct {
13911391 }
13921392
13931393 /// Same as `init`, but also provides a buffer for storing control data.
1394 pub fn initWithControl(stream: Stream, io: Io, buffer: []u8, control_buffer: []u8) Reader {
1394 pub fn initWithControl(
1395 stream: Stream,
1396 io: Io,
1397 buffer: []u8,
1398 control_buffer: []align(cmsg_align) u8,
1399 ) Reader {
13951400 return .{
13961401 .io = io,
13971402 .interface = .{
......@@ -1442,11 +1447,16 @@ pub const Stream = struct {
14421447 return result.data_len;
14431448 }
14441449
1445 /// Get buffered control data.
1446 pub fn controlSlice(r: *const Reader) []u8 {
1450 /// Access raw buffered control data.
1451 pub fn controlSlice(r: *const Reader) []align(cmsg_align) u8 {
14471452 return r.control_buffer[0..r.control_len];
14481453 }
14491454
1455 /// Iterate over buffered control messages.
1456 pub fn controlIterator(r: *const Reader) cmsg.Iterator {
1457 return .{ .control = r.controlSlice() };
1458 }
1459
14501460 /// Clear buffered control data.
14511461 pub fn clearControl(r: *Reader) void {
14521462 r.control_len = 0;
......@@ -1542,7 +1552,12 @@ pub const Stream = struct {
15421552 }
15431553
15441554 /// Same as `reader`, but also provides a buffer for storing control data.
1545 pub fn readerWithControl(stream: Stream, io: Io, buffer: []u8, control_buffer: []u8) Reader {
1555 pub fn readerWithControl(
1556 stream: Stream,
1557 io: Io,
1558 buffer: []u8,
1559 control_buffer: []align(cmsg_align) u8,
1560 ) Reader {
15461561 return .initWithControl(stream, io, buffer, control_buffer);
15471562 }
15481563
......@@ -1551,6 +1566,53 @@ pub const Stream = struct {
15511566 }
15521567};
15531568
1569pub const cmsg_align = if (@TypeOf(std.posix.cmsg_align) == void) 1 else std.posix.cmsg_align;
1570/// Utility Functions for interacting with POSIX socket control messages.
1571/// See also https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_socket.h.html
1572pub const cmsg = struct {
1573 const cmsghdr = std.posix.cmsghdr;
1574
1575 /// Equivalent to `CMSG_SPACE` in C.
1576 pub fn space(data_len: usize) usize {
1577 return std.mem.alignForward(usize, @sizeOf(cmsghdr), cmsg_align) +
1578 std.mem.alignForward(usize, data_len, cmsg_align);
1579 }
1580
1581 /// Equivalent to `CMSG_LEN` in C.
1582 pub fn len(data_len: @FieldType(cmsghdr, "len")) @TypeOf(data_len) {
1583 return std.mem.alignForward(@TypeOf(data_len), @sizeOf(cmsghdr), cmsg_align) + data_len;
1584 }
1585
1586 /// Equivalent to `CMSG_DATA` in C.
1587 pub fn data(header: *align(cmsg_align) cmsghdr) []align(cmsg_align) u8 {
1588 const bytes: [*]u8 = @ptrCast(header);
1589 return @alignCast(bytes[0..header.len][std.mem.alignForward(usize, @sizeOf(cmsghdr), cmsg_align)..]);
1590 }
1591
1592 /// Equivalent to `CMSG_FIRSTHDR` and `CMSG_NXTHDR` in C.
1593 pub const Iterator = struct {
1594 control: []align(cmsg_align) u8,
1595
1596 pub const Message = struct {
1597 header: *cmsghdr,
1598 data: []align(cmsg_align) u8,
1599 };
1600
1601 pub fn next(it: *Iterator) ?Message {
1602 if (it.control.len < @sizeOf(cmsghdr)) return null;
1603 const header: *align(cmsg_align) cmsghdr = @ptrCast(it.control.ptr);
1604 if (it.control.len < header.len) return null;
1605 const next_header = std.mem.alignForward(usize, header.len, cmsg_align);
1606 if (it.control.len < next_header) {
1607 it.control = &.{};
1608 } else {
1609 it.control = @alignCast(it.control[next_header..]);
1610 }
1611 return .{ .header = header, .data = data(header) };
1612 }
1613 };
1614};
1615
15541616pub const Server = struct {
15551617 socket: Socket,
15561618 options: AcceptOptions,
lib/std/Io/net/test.zig+16-36
......@@ -4,6 +4,7 @@ const std = @import("std");
44const Io = std.Io;
55const net = std.Io.net;
66const mem = std.mem;
7const posix = std.posix;
78const testing = std.testing;
89const Allocator = std.mem.Allocator;
910
......@@ -321,26 +322,6 @@ test "listen on a unix socket, pass file descriptor" {
321322 };
322323 defer server.socket.close(io);
323324
324 const cmsg_alignment = switch (builtin.os.tag) {
325 .driverkit,
326 .ios,
327 .maccatalyst,
328 .macos,
329 .tvos,
330 .visionos,
331 .watchos,
332 => 4,
333 else => @sizeOf(usize),
334 };
335
336 const cmsghdr_aligned_len = comptime mem.alignForward(usize, @sizeOf(std.posix.cmsghdr), cmsg_alignment);
337 const cmsghdr_align = @alignOf(std.posix.cmsghdr);
338 const fd_t_len = @sizeOf(std.posix.fd_t);
339
340 const cmsg_buf_len =
341 cmsghdr_aligned_len +
342 comptime mem.alignForward(usize, fd_t_len, cmsg_alignment);
343
344325 const S = struct {
345326 fn clientFn(path: []const u8, file_path: []const u8) !void {
346327 const temp_file = try Io.Dir.cwd().createFile(io, file_path, .{});
......@@ -350,16 +331,16 @@ test "listen on a unix socket, pass file descriptor" {
350331 var stream = try server_path.connect(io);
351332 defer stream.close(io);
352333
353 var cmsg_buf: [cmsg_buf_len]u8 align(cmsghdr_align) = @splat(0);
334 var cmsg_buf: [net.cmsg.space(@sizeOf(posix.fd_t))]u8 align(Io.net.cmsg_align) = @splat(0);
354335
355 const header_ptr: *std.posix.cmsghdr = @ptrCast(&cmsg_buf);
356 header_ptr.* = .{
357 .len = @intCast(cmsghdr_aligned_len + fd_t_len),
358 .level = std.posix.SOL.SOCKET,
359 .type = std.posix.SCM.RIGHTS,
336 const header: *align(Io.net.cmsg_align) posix.cmsghdr = @ptrCast(&cmsg_buf);
337 header.* = .{
338 .len = net.cmsg.len(@sizeOf(posix.fd_t)),
339 .level = posix.SOL.SOCKET,
340 .type = posix.SCM.RIGHTS,
360341 };
361342
362 const fds = mem.bytesAsSlice(std.posix.fd_t, cmsg_buf[cmsghdr_aligned_len..]);
343 const fds: []posix.fd_t = @ptrCast(net.cmsg.data(header));
363344 fds[0] = temp_file.handle;
364345
365346 var stream_writer = stream.writer(io, &.{});
......@@ -376,27 +357,26 @@ test "listen on a unix socket, pass file descriptor" {
376357 var stream = try server.accept(io);
377358 defer stream.close(io);
378359
379 var control_buf: [32]u8 align(cmsghdr_align) = undefined;
360 var control_buf: [32]u8 align(Io.net.cmsg_align) = undefined;
380361 var buf: [16]u8 = undefined;
381362 var stream_reader = stream.readerWithControl(io, &.{}, &control_buf);
382363
383364 const n = try stream_reader.interface.readSliceShort(&buf);
384 const cmsg_buf = stream_reader.controlSlice();
385365
386366 try testing.expect(!stream_reader.control_truncated); // The control buffer should be big enough to receive the entire control message
387367
388368 try testing.expectEqual(12, n);
389369 try testing.expectEqualStrings("Hello world!", buf[0..n]);
390370
391 try testing.expect(cmsg_buf.len >= cmsghdr_aligned_len + fd_t_len);
392
393 const header_ptr: *const std.posix.cmsghdr = @ptrCast(@alignCast(cmsg_buf));
371 var it = stream_reader.controlIterator();
372 const control = it.next() orelse return error.TestUnexpectedResult;
373 try testing.expectEqual(null, it.next());
394374
395 try testing.expectEqual(cmsghdr_aligned_len + fd_t_len, header_ptr.len);
396 try testing.expectEqual(std.posix.SOL.SOCKET, header_ptr.level);
397 try testing.expectEqual(std.posix.SCM.RIGHTS, header_ptr.type);
375 try testing.expectEqual(net.cmsg.len(@sizeOf(posix.fd_t)), control.header.len);
376 try testing.expectEqual(posix.SOL.SOCKET, control.header.level);
377 try testing.expectEqual(posix.SCM.RIGHTS, control.header.type);
398378
399 const fds = mem.bytesAsSlice(std.posix.fd_t, cmsg_buf[cmsghdr_aligned_len..]);
379 const fds: []posix.fd_t = @ptrCast(control.data);
400380
401381 const temp_file: Io.File = .{
402382 .handle = fds[0],
lib/std/c.zig+62
......@@ -4432,6 +4432,68 @@ const posix_cmsghdr = extern struct {
44324432 type: c_int,
44334433};
44344434
4435// Alignment used by POSIX specified CMSG_* Macros
4436// https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_socket.h.html
4437pub const cmsg_align = switch (native_os) {
4438 .linux => linux.cmsg_align,
4439 // https://github.com/emscripten-core/emscripten/blob/96371ed7888fc78c040179f4d4faa82a6a07a116/system/lib/libc/musl/include/sys/socket.h#L362
4440 .emscripten => @sizeOf(usize),
4441 // https://github.com/freebsd/freebsd-src/blob/b197d2abcb6895d78bc9df8404e374397aa44748/sys/sys/socket.h#L584
4442 // https://github.com/freebsd/freebsd-src/blob/d2018cedb414ef17e77b7fc2fa19fd81328da46b/sys/sys/_align.h#L30
4443 .freebsd => @sizeOf(*anyopaque),
4444 // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/d1f4fb943c73e2b61ddabff6aa48ce7551db72f4/sys/sys/socket.h#L441
4445 // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/d1f4fb943c73e2b61ddabff6aa48ce7551db72f4/sys/cpu/x86_64/include/alignbytes.h#L35
4446 .dragonfly => switch (builtin.cpu.arch) {
4447 .x86_64 => @sizeOf(c_long),
4448 else => {},
4449 },
4450 // https://github.com/NetBSD/src/blob/ba8e1774fd9c0c26ecca461c07bc95d9ebb69579/sys/sys/socket.h#L544
4451 .netbsd => switch (builtin.cpu.arch) {
4452 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/arm/include/cdefs.h#L63
4453 .aarch64, .aarch64_be => @sizeOf(i128),
4454 .arm, .armeb => @sizeOf(c_longlong),
4455 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/i386/include/cdefs.h#L9
4456 .x86 => @sizeOf(c_int),
4457 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/amd64/include/cdefs.h#L6
4458 .x86_64 => @sizeOf(c_long),
4459 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/mips/include/cdefs.h#L74
4460 .mips, .mips64, .mipsel, .mips64el => 8,
4461 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/sparc/include/cdefs.h#L8
4462 .sparc64 => 16,
4463 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/sparc/include/cdefs.h#L10
4464 .sparc => 8,
4465 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/alpha/include/cdefs.h#L6
4466 .alpha => 8,
4467 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/m68k/include/cdefs.h#L6
4468 .m68k => @sizeOf(c_int),
4469 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/hppa/include/cdefs.h#L12
4470 .hppa => 8,
4471 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/riscv/include/cdefs.h#L5
4472 .riscv32, .riscv32be, .riscv64, .riscv64be => @as(comptime_int, builtin.target.cMaxIntAlignment()),
4473 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/powerpc/include/cdefs.h#L6
4474 .powerpc, .powerpcle, .powerpc64, .powerpc64le => @sizeOf(f64),
4475 // https://github.com/NetBSD/src/blob/ac016822f750dc9166c84b6d2657479992622192/sys/arch/or1k/include/cdefs.h#L6
4476 .or1k => @as(comptime_int, builtin.target.cMaxIntAlignment()),
4477 else => {},
4478 },
4479 // https://github.com/openbsd/src/blob/4a7ecdb29021f26ee0ef397afdd2931e20e63071/sys/sys/socket.h#L564
4480 // https://github.com/openbsd/src/blob/4a7ecdb29021f26ee0ef397afdd2931e20e63071/sys/arch/amd64/include/_types.h#L50
4481 .openbsd => @sizeOf(c_long),
4482 // https://github.com/illumos/illumos-gate/blob/c1e950d976948602134f0e0e5dadf3d48a7ee996/usr/src/uts/common/sys/socket.h#L469
4483 .illumos => 4,
4484 // https://github.com/haiku/haiku/blob/b54f586058fd6623645512e4631468cede9933b9/headers/posix/sys/socket.h#L152
4485 // https://github.com/haiku/haiku/blob/6059d64cb7cd145f0f1eb5b89c383f71e4b6bebf/headers/posix/sys/param.h#L28
4486 .haiku => @sizeOf(c_long),
4487 // https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/socket.h#L1077
4488 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => 4,
4489 else => {},
4490};
4491comptime {
4492 if (@TypeOf(cmsg_align) != void and @TypeOf(cmsghdr) != void) {
4493 assert(cmsg_align >= @alignOf(cmsghdr));
4494 }
4495}
4496
44354497pub const nfds_t = switch (native_os) {
44364498 .linux => linux.nfds_t,
44374499 .emscripten => emscripten.nfds_t,
lib/std/os/linux.zig+3
......@@ -11244,6 +11244,9 @@ pub const cmsghdr = extern struct {
1124411244 type: i32,
1124511245};
1124611246
11247// https://github.com/torvalds/linux/blob/72d3fcf802c45d00b300f25b848a93c3a2bd7c7e/include/linux/socket.h#L133
11248pub const cmsg_align = @sizeOf(c_long);
11249
1124711250pub const riscv_hwprobe = extern struct {
1124811251 key: i64,
1124911252 value: u64,
lib/std/posix.zig+1
......@@ -151,6 +151,7 @@ pub const blksize_t = system.blksize_t;
151151pub const clock_t = system.clock_t;
152152pub const clockid_t = system.clockid_t;
153153pub const cmsghdr = system.cmsghdr;
154pub const cmsg_align = system.cmsg_align;
154155pub const timerfd_clockid_t = system.timerfd_clockid_t;
155156pub const cpu_set_t = system.cpu_set_t;
156157pub const dev_t = system.dev_t;