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 {...@@ -1378,7 +1378,7 @@ pub const Stream = struct {
1378 pub const Reader = struct {1378 pub const Reader = struct {
1379 io: Io,1379 io: Io,
1380 interface: Io.Reader,1380 interface: Io.Reader,
1381 control_buffer: []u8,1381 control_buffer: []align(cmsg_align) u8,
1382 control_len: usize,1382 control_len: usize,
1383 control_truncated: bool,1383 control_truncated: bool,
1384 stream: Stream,1384 stream: Stream,
...@@ -1391,7 +1391,12 @@ pub const Stream = struct {...@@ -1391,7 +1391,12 @@ pub const Stream = struct {
1391 }1391 }
13921392
1393 /// Same as `init`, but also provides a buffer for storing control data.1393 /// 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 {
1395 return .{1400 return .{
1396 .io = io,1401 .io = io,
1397 .interface = .{1402 .interface = .{
...@@ -1442,11 +1447,16 @@ pub const Stream = struct {...@@ -1442,11 +1447,16 @@ pub const Stream = struct {
1442 return result.data_len;1447 return result.data_len;
1443 }1448 }
14441449
1445 /// Get buffered control data.1450 /// Access raw buffered control data.
1446 pub fn controlSlice(r: *const Reader) []u8 {1451 pub fn controlSlice(r: *const Reader) []align(cmsg_align) u8 {
1447 return r.control_buffer[0..r.control_len];1452 return r.control_buffer[0..r.control_len];
1448 }1453 }
14491454
1455 /// Iterate over buffered control messages.
1456 pub fn controlIterator(r: *const Reader) cmsg.Iterator {
1457 return .{ .control = r.controlSlice() };
1458 }
1459
1450 /// Clear buffered control data.1460 /// Clear buffered control data.
1451 pub fn clearControl(r: *Reader) void {1461 pub fn clearControl(r: *Reader) void {
1452 r.control_len = 0;1462 r.control_len = 0;
...@@ -1542,7 +1552,12 @@ pub const Stream = struct {...@@ -1542,7 +1552,12 @@ pub const Stream = struct {
1542 }1552 }
15431553
1544 /// Same as `reader`, but also provides a buffer for storing control data.1554 /// 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 {
1546 return .initWithControl(stream, io, buffer, control_buffer);1561 return .initWithControl(stream, io, buffer, control_buffer);
1547 }1562 }
15481563
...@@ -1551,6 +1566,53 @@ pub const Stream = struct {...@@ -1551,6 +1566,53 @@ pub const Stream = struct {
1551 }1566 }
1552};1567};
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
1554pub const Server = struct {1616pub const Server = struct {
1555 socket: Socket,1617 socket: Socket,
1556 options: AcceptOptions,1618 options: AcceptOptions,
lib/std/Io/net/test.zig+16-36
...@@ -4,6 +4,7 @@ const std = @import("std");...@@ -4,6 +4,7 @@ const std = @import("std");
4const Io = std.Io;4const Io = std.Io;
5const net = std.Io.net;5const net = std.Io.net;
6const mem = std.mem;6const mem = std.mem;
7const posix = std.posix;
7const testing = std.testing;8const testing = std.testing;
8const Allocator = std.mem.Allocator;9const Allocator = std.mem.Allocator;
910
...@@ -321,26 +322,6 @@ test "listen on a unix socket, pass file descriptor" {...@@ -321,26 +322,6 @@ test "listen on a unix socket, pass file descriptor" {
321 };322 };
322 defer server.socket.close(io);323 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
344 const S = struct {325 const S = struct {
345 fn clientFn(path: []const u8, file_path: []const u8) !void {326 fn clientFn(path: []const u8, file_path: []const u8) !void {
346 const temp_file = try Io.Dir.cwd().createFile(io, file_path, .{});327 const temp_file = try Io.Dir.cwd().createFile(io, file_path, .{});
...@@ -350,16 +331,16 @@ test "listen on a unix socket, pass file descriptor" {...@@ -350,16 +331,16 @@ test "listen on a unix socket, pass file descriptor" {
350 var stream = try server_path.connect(io);331 var stream = try server_path.connect(io);
351 defer stream.close(io);332 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);336 const header: *align(Io.net.cmsg_align) posix.cmsghdr = @ptrCast(&cmsg_buf);
356 header_ptr.* = .{337 header.* = .{
357 .len = @intCast(cmsghdr_aligned_len + fd_t_len),338 .len = net.cmsg.len(@sizeOf(posix.fd_t)),
358 .level = std.posix.SOL.SOCKET,339 .level = posix.SOL.SOCKET,
359 .type = std.posix.SCM.RIGHTS,340 .type = posix.SCM.RIGHTS,
360 };341 };
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));
363 fds[0] = temp_file.handle;344 fds[0] = temp_file.handle;
364345
365 var stream_writer = stream.writer(io, &.{});346 var stream_writer = stream.writer(io, &.{});
...@@ -376,27 +357,26 @@ test "listen on a unix socket, pass file descriptor" {...@@ -376,27 +357,26 @@ test "listen on a unix socket, pass file descriptor" {
376 var stream = try server.accept(io);357 var stream = try server.accept(io);
377 defer stream.close(io);358 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;
380 var buf: [16]u8 = undefined;361 var buf: [16]u8 = undefined;
381 var stream_reader = stream.readerWithControl(io, &.{}, &control_buf);362 var stream_reader = stream.readerWithControl(io, &.{}, &control_buf);
382363
383 const n = try stream_reader.interface.readSliceShort(&buf);364 const n = try stream_reader.interface.readSliceShort(&buf);
384 const cmsg_buf = stream_reader.controlSlice();
385365
386 try testing.expect(!stream_reader.control_truncated); // The control buffer should be big enough to receive the entire control message366 try testing.expect(!stream_reader.control_truncated); // The control buffer should be big enough to receive the entire control message
387367
388 try testing.expectEqual(12, n);368 try testing.expectEqual(12, n);
389 try testing.expectEqualStrings("Hello world!", buf[0..n]);369 try testing.expectEqualStrings("Hello world!", buf[0..n]);
390370
391 try testing.expect(cmsg_buf.len >= cmsghdr_aligned_len + fd_t_len);371 var it = stream_reader.controlIterator();
392372 const control = it.next() orelse return error.TestUnexpectedResult;
393 const header_ptr: *const std.posix.cmsghdr = @ptrCast(@alignCast(cmsg_buf));373 try testing.expectEqual(null, it.next());
394374
395 try testing.expectEqual(cmsghdr_aligned_len + fd_t_len, header_ptr.len);375 try testing.expectEqual(net.cmsg.len(@sizeOf(posix.fd_t)), control.header.len);
396 try testing.expectEqual(std.posix.SOL.SOCKET, header_ptr.level);376 try testing.expectEqual(posix.SOL.SOCKET, control.header.level);
397 try testing.expectEqual(std.posix.SCM.RIGHTS, header_ptr.type);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
401 const temp_file: Io.File = .{381 const temp_file: Io.File = .{
402 .handle = fds[0],382 .handle = fds[0],
lib/std/c.zig+62
...@@ -4432,6 +4432,68 @@ const posix_cmsghdr = extern struct {...@@ -4432,6 +4432,68 @@ const posix_cmsghdr = extern struct {
4432 type: c_int,4432 type: c_int,
4433};4433};
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
4435pub const nfds_t = switch (native_os) {4497pub const nfds_t = switch (native_os) {
4436 .linux => linux.nfds_t,4498 .linux => linux.nfds_t,
4437 .emscripten => emscripten.nfds_t,4499 .emscripten => emscripten.nfds_t,
lib/std/os/linux.zig+3
...@@ -11244,6 +11244,9 @@ pub const cmsghdr = extern struct {...@@ -11244,6 +11244,9 @@ pub const cmsghdr = extern struct {
11244 type: i32,11244 type: i32,
11245};11245};
1124611246
11247// https://github.com/torvalds/linux/blob/72d3fcf802c45d00b300f25b848a93c3a2bd7c7e/include/linux/socket.h#L133
11248pub const cmsg_align = @sizeOf(c_long);
11249
11247pub const riscv_hwprobe = extern struct {11250pub const riscv_hwprobe = extern struct {
11248 key: i64,11251 key: i64,
11249 value: u64,11252 value: u64,
lib/std/posix.zig+1
...@@ -151,6 +151,7 @@ pub const blksize_t = system.blksize_t;...@@ -151,6 +151,7 @@ pub const blksize_t = system.blksize_t;
151pub const clock_t = system.clock_t;151pub const clock_t = system.clock_t;
152pub const clockid_t = system.clockid_t;152pub const clockid_t = system.clockid_t;
153pub const cmsghdr = system.cmsghdr;153pub const cmsghdr = system.cmsghdr;
154pub const cmsg_align = system.cmsg_align;
154pub const timerfd_clockid_t = system.timerfd_clockid_t;155pub const timerfd_clockid_t = system.timerfd_clockid_t;
155pub const cpu_set_t = system.cpu_set_t;156pub const cpu_set_t = system.cpu_set_t;
156pub const dev_t = system.dev_t;157pub const dev_t = system.dev_t;