authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-21 21:13:26-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-02 16:30:59-07:00
logdac7ae1e438bcd9d5051fc53874002ee06278466
tree0a34ba87624989eb0f4ab0f56fe05924218b2dc6
parent0f2427c5d2e16626c6c88621e8aa3aa1b4aa8785

Io.net: rework IPv6 parsing and printing

extract pure functional logic into pure functions and then layer the scope crap on top properly the formatting code incorrectly didn't do the reverse operation (if_indextoname). fix that with some TODO panics

3 files changed, 321 insertions(+), 230 deletions(-)

lib/std/Io/Threaded.zig+40-28
......@@ -135,7 +135,8 @@ pub fn io(pool: *Pool) Io {
135135 else => netWritePosix,
136136 },
137137 .netClose = netClose,
138 .netInterfaceIndex = netInterfaceIndex,
138 .netInterfaceNameResolve = netInterfaceNameResolve,
139 .netInterfaceName = netInterfaceName,
139140 },
140141 };
141142}
......@@ -1123,16 +1124,11 @@ fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void {
11231124 return net_stream.close();
11241125}
11251126
1126fn netInterfaceIndex(userdata: ?*anyopaque, name: []const u8) Io.net.InterfaceIndexError!u32 {
1127fn netInterfaceNameResolve(userdata: ?*anyopaque, name: Io.net.Interface.Name) Io.net.Interface.Name.ResolveError!Io.net.Interface {
11271128 const pool: *Pool = @ptrCast(@alignCast(userdata));
11281129 try pool.checkCancel();
11291130
11301131 if (native_os == .linux) {
1131 if (name.len >= posix.IFNAMESIZE) return error.InterfaceNotFound;
1132 var ifr: posix.ifreq = undefined;
1133 @memcpy(ifr.ifrn.name[0..name.len], name);
1134 ifr.ifrn.name[name.len] = 0;
1135
11361132 const rc = posix.system.socket(posix.AF.UNIX, posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, 0);
11371133 const sock_fd: posix.fd_t = switch (posix.errno(rc)) {
11381134 .SUCCESS => @intCast(rc),
......@@ -1145,10 +1141,15 @@ fn netInterfaceIndex(userdata: ?*anyopaque, name: []const u8) Io.net.InterfaceIn
11451141 };
11461142 defer posix.close(sock_fd);
11471143
1144 var ifr: posix.ifreq = .{
1145 .ifrn = .{ .name = @bitCast(name.bytes) },
1146 .ifru = undefined,
1147 };
1148
11481149 while (true) {
11491150 try pool.checkCancel();
11501151 switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) {
1151 .SUCCESS => return @bitCast(ifr.ifru.ivalue),
1152 .SUCCESS => return .{ .index = @bitCast(ifr.ifru.ivalue) },
11521153 .INVAL => |err| return badErrno(err), // Bad parameters.
11531154 .NOTTY => |err| return badErrno(err),
11541155 .NXIO => |err| return badErrno(err),
......@@ -1162,28 +1163,39 @@ fn netInterfaceIndex(userdata: ?*anyopaque, name: []const u8) Io.net.InterfaceIn
11621163 }
11631164 }
11641165
1165 if (native_os.isDarwin()) {
1166 if (name.len >= posix.IFNAMESIZE) return error.InterfaceNotFound;
1167 var if_name: [posix.IFNAMESIZE:0]u8 = undefined;
1168 @memcpy(if_name[0..name.len], name);
1169 if_name[name.len] = 0;
1170 const if_slice = if_name[0..name.len :0];
1171 const index = std.c.if_nametoindex(if_slice);
1166 if (native_os == .windows) {
1167 const index = std.os.windows.ws2_32.if_nametoindex(&name.bytes);
11721168 if (index == 0) return error.InterfaceNotFound;
1173 return @bitCast(index);
1169 return .{ .index = index };
11741170 }
11751171
1176 if (native_os == .windows) {
1177 if (name.len >= posix.IFNAMESIZE) return error.InterfaceNotFound;
1178 var interface_name: [posix.IFNAMESIZE:0]u8 = undefined;
1179 @memcpy(interface_name[0..name.len], name);
1180 interface_name[name.len] = 0;
1181 const index = std.os.windows.ws2_32.if_nametoindex(@as([*:0]const u8, &interface_name));
1172 if (builtin.link_libc) {
1173 const index = std.c.if_nametoindex(&name.bytes);
11821174 if (index == 0) return error.InterfaceNotFound;
1183 return index;
1175 return .{ .index = @bitCast(index) };
1176 }
1177
1178 @panic("unimplemented");
1179}
1180
1181fn netInterfaceName(userdata: ?*anyopaque, interface: Io.net.Interface) Io.net.Interface.NameError!Io.net.Interface.Name {
1182 const pool: *Pool = @ptrCast(@alignCast(userdata));
1183 try pool.checkCancel();
1184
1185 if (native_os == .linux) {
1186 _ = interface;
1187 @panic("TODO");
1188 }
1189
1190 if (native_os == .windows) {
1191 @panic("TODO");
1192 }
1193
1194 if (builtin.link_libc) {
1195 @panic("TODO");
11841196 }
11851197
1186 @compileError("std.net.if_nametoindex unimplemented for this OS");
1198 @panic("unimplemented");
11871199}
11881200
11891201const PosixAddress = extern union {
......@@ -1231,8 +1243,8 @@ fn address6FromPosix(in6: *posix.sockaddr.in6) Io.net.Ip6Address {
12311243 return .{
12321244 .port = std.mem.bigToNative(u16, in6.port),
12331245 .bytes = in6.addr,
1234 .flowinfo = in6.flowinfo,
1235 .scope_id = in6.scope_id,
1246 .flow = in6.flowinfo,
1247 .interface = .{ .index = in6.scope_id },
12361248 };
12371249}
12381250
......@@ -1246,9 +1258,9 @@ fn address4ToPosix(a: Io.net.Ip4Address) posix.sockaddr.in {
12461258fn address6ToPosix(a: Io.net.Ip6Address) posix.sockaddr.in6 {
12471259 return .{
12481260 .port = std.mem.nativeToBig(u16, a.port),
1249 .flowinfo = a.flowinfo,
1261 .flowinfo = a.flow,
12501262 .addr = a.bytes,
1251 .scope_id = a.scope_id,
1263 .scope_id = a.interface.index,
12521264 };
12531265}
12541266
lib/std/Io/net.zig+278-199
......@@ -26,10 +26,12 @@ pub const IpAddress = union(enum) {
2626 pub const Family = @typeInfo(IpAddress).@"union".tag_type.?;
2727
2828 /// Parse the given IP address string into an `IpAddress` value.
29 ///
30 /// This is a pure function but it cannot handle IPv6 addresses that have
31 /// scope ids ("%foo" at the end). To also handle those, `resolve` must be
32 /// called instead.
2933 pub fn parse(name: []const u8, port: u16) !IpAddress {
30 if (Ip4Address.parse(name, port)) |ip4| {
31 return .{ .ip4 = ip4 };
32 } else |err| switch (err) {
34 if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
3335 error.Overflow,
3436 error.InvalidEnd,
3537 error.InvalidCharacter,
......@@ -38,26 +40,41 @@ pub const IpAddress = union(enum) {
3840 => {},
3941 }
4042
41 if (Ip6Address.parse(name, port)) |ip6| {
42 return .{ .ip6 = ip6 };
43 } else |err| switch (err) {
43 return parseIp6(name, port);
44 }
45
46 pub fn parseIp4(text: []const u8, port: u16) Ip4Address.ParseError!IpAddress {
47 return .{ .ip4 = try Ip4Address.parse(text, port) };
48 }
49
50 /// This is a pure function but it cannot handle IPv6 addresses that have
51 /// scope ids ("%foo" at the end). To also handle those, `resolveIp6` must be
52 /// called instead.
53 pub fn parseIp6(text: []const u8, port: u16) Ip6Address.ParseError!IpAddress {
54 return .{ .ip6 = try Ip6Address.parse(text, port) };
55 }
56
57 /// This function requires an `Io` parameter because it must query the operating
58 /// system to convert interface name to index. For example, in
59 /// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by
60 /// creating a socket and then using an `ioctl` syscall.
61 ///
62 /// For a pure function that cannot handle scopes, see `parse`.
63 pub fn resolve(io: Io, text: []const u8, port: u16) !IpAddress {
64 if (parseIp4(text, port)) |ip4| return ip4 else |err| switch (err) {
4465 error.Overflow,
4566 error.InvalidEnd,
4667 error.InvalidCharacter,
4768 error.Incomplete,
48 error.InvalidIpv4Mapping,
69 error.NonCanonical,
4970 => {},
5071 }
5172
52 return error.InvalidIpAddressFormat;
73 return resolveIp6(io, text, port);
5374 }
5475
55 pub fn parseIp6(buffer: []const u8, port: u16) Ip6Address.ParseError!IpAddress {
56 return .{ .ip6 = try Ip6Address.parse(buffer, port) };
57 }
58
59 pub fn parseIp4(buffer: []const u8, port: u16) Ip4Address.ParseError!IpAddress {
60 return .{ .ip4 = try Ip4Address.parse(buffer, port) };
76 pub fn resolveIp6(io: Io, text: []const u8, port: u16) Ip6Address.ResolveError!IpAddress {
77 return .{ .ip6 = try Ip6Address.resolve(io, text, port) };
6178 }
6279
6380 /// Returns the port in native endian.
......@@ -74,6 +91,19 @@ pub const IpAddress = union(enum) {
7491 }
7592 }
7693
94 /// Includes the optional scope ("%foo" at the end) in IPv6 addresses.
95 ///
96 /// See `format` for an alternative that omits scopes and does
97 /// not require an `Io` parameter.
98 pub fn formatResolved(a: IpAddress, io: Io, w: *Io.Writer) Ip6Address.FormatError!void {
99 switch (a) {
100 .ip4 => |x| return x.format(w),
101 .ip6 => |x| return x.formatResolved(io, w),
102 }
103 }
104
105 /// See `formatResolved` for an alternative that additionally prints the optional
106 /// scope at the end of IPv6 addresses and requires an `Io` parameter.
77107 pub fn format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!void {
78108 switch (a) {
79109 inline .ip4, .ip6 => |x| return x.format(w),
......@@ -99,11 +129,12 @@ pub const IpAddress = union(enum) {
99129 }
100130};
101131
132/// An IPv4 address in binary memory layout.
102133pub const Ip4Address = struct {
103134 bytes: [4]u8,
104135 port: u16,
105136
106 pub fn localhost(port: u16) Ip4Address {
137 pub fn loopback(port: u16) Ip4Address {
107138 return .{
108139 .bytes = .{ 127, 0, 0, 1 },
109140 .port = port,
......@@ -162,21 +193,14 @@ pub const Ip4Address = struct {
162193 }
163194};
164195
196/// An IPv6 address in binary memory layout.
165197pub const Ip6Address = struct {
166198 /// Native endian
167199 port: u16,
168200 /// Big endian
169201 bytes: [16]u8,
170 flowinfo: u32 = 0,
171 scope_id: u32 = 0,
172
173 pub const ParseError = error{
174 Overflow,
175 InvalidCharacter,
176 InvalidEnd,
177 InvalidIpv4Mapping,
178 Incomplete,
179 };
202 flow: u32 = 0,
203 interface: Interface = .none,
180204
181205 pub const Policy = struct {
182206 addr: [16]u8,
......@@ -186,192 +210,205 @@ pub const Ip6Address = struct {
186210 label: u8,
187211 };
188212
189 pub fn localhost(port: u16) Ip6Address {
213 pub fn loopback(port: u16) Ip6Address {
190214 return .{
191215 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
192216 .port = port,
193217 };
194218 }
195219
196 pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address {
197 var result: Ip6Address = .{
198 .port = port,
199 .bytes = undefined,
220 /// An IPv6 address but with `Interface` as a name rather than index.
221 pub const Unresolved = struct {
222 /// Big endian
223 bytes: [16]u8,
224 interface_name: ?Interface.Name,
225
226 pub const Parsed = union(enum) {
227 success: Unresolved,
228 invalid_byte: usize,
229 unexpected_end,
200230 };
201 var ip_slice: *[16]u8 = &result.bytes;
202231
203 var tail: [16]u8 = undefined;
232 pub fn parse(buffer: []const u8) Parsed {
233 if (buffer.len < 2) return .unexpected_end;
234 var parts: [8]u16 = @splat(0);
235 var parts_i: usize = 0;
236 var i: usize = 0;
237 var digit_i: usize = 0;
238 const State = union(enum) { digit, colon, end };
239 state: switch (State.digit) {
240 .digit => c: switch (buffer[i]) {
241 'a'...'f' => |c| {
242 const digit = c - 'a';
243 parts[parts_i] = parts[parts_i] * 16 + digit;
244 if (digit_i == 3) {
245 digit_i = 0;
246 parts_i += 1;
247 i += 1;
248 if (parts.len - parts_i == 0) continue :state .end;
249 continue :state .colon;
250 }
251 digit_i += 1;
252 if (buffer.len - i == 0) return .unexpected_end;
253 i += 1;
254 continue :c buffer[i];
255 },
256 'A'...'F' => |c| continue :c c + ('a' - 'A'),
257 '0'...'9' => |c| continue :c c + ('a' - '0'),
258 ':' => @panic("TODO"),
259 else => return .{ .invalid_byte = i },
260 },
261 .colon => @panic("TODO"),
262 .end => @panic("TODO"),
263 }
264 }
265
266 pub const FromAddressError = Interface.NameError;
204267
205 var x: u16 = 0;
206 var saw_any_digits = false;
207 var index: u8 = 0;
208 var scope_id = false;
209 var abbrv = false;
210 for (buffer, 0..) |c, i| {
211 if (scope_id) {
212 if (c >= '0' and c <= '9') {
213 const digit = c - '0';
214 {
215 const ov = @mulWithOverflow(result.scope_id, 10);
216 if (ov[1] != 0) return error.Overflow;
217 result.scope_id = ov[0];
218 }
219 {
220 const ov = @addWithOverflow(result.scope_id, digit);
221 if (ov[1] != 0) return error.Overflow;
222 result.scope_id = ov[0];
268 pub fn fromAddress(a: *const Ip6Address, io: Io) FromAddressError!Unresolved {
269 if (a.interface.isNone()) return .{
270 .bytes = a.bytes,
271 .interface_name = null,
272 };
273 return .{
274 .bytes = a.bytes,
275 .interface_name = try a.interface.name(io),
276 };
277 }
278
279 pub fn format(u: *const Unresolved, w: *Io.Writer) Io.Writer.Error!void {
280 const bytes = &u.bytes;
281 if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
282 try w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] });
283 } else {
284 const parts: [8]u16 = .{
285 std.mem.readInt(u16, bytes[0..2], .big),
286 std.mem.readInt(u16, bytes[2..4], .big),
287 std.mem.readInt(u16, bytes[4..6], .big),
288 std.mem.readInt(u16, bytes[6..8], .big),
289 std.mem.readInt(u16, bytes[8..10], .big),
290 std.mem.readInt(u16, bytes[10..12], .big),
291 std.mem.readInt(u16, bytes[12..14], .big),
292 std.mem.readInt(u16, bytes[14..16], .big),
293 };
294
295 // Find the longest zero run
296 var longest_start: usize = 8;
297 var longest_len: usize = 0;
298 var current_start: usize = 0;
299 var current_len: usize = 0;
300
301 for (parts, 0..) |part, i| {
302 if (part == 0) {
303 if (current_len == 0) {
304 current_start = i;
305 }
306 current_len += 1;
307 if (current_len > longest_len) {
308 longest_start = current_start;
309 longest_len = current_len;
310 }
311 } else {
312 current_len = 0;
223313 }
224 } else {
225 return error.InvalidCharacter;
226314 }
227 } else if (c == ':') {
228 if (!saw_any_digits) {
229 if (abbrv) return error.InvalidCharacter; // ':::'
230 if (i != 0) abbrv = true;
231 @memset(ip_slice[index..], 0);
232 ip_slice = tail[0..];
233 index = 0;
234 continue;
235 }
236 if (index == 14) {
237 return error.InvalidEnd;
238 }
239 ip_slice[index] = @as(u8, @truncate(x >> 8));
240 index += 1;
241 ip_slice[index] = @as(u8, @truncate(x));
242 index += 1;
243315
244 x = 0;
245 saw_any_digits = false;
246 } else if (c == '%') {
247 if (!saw_any_digits) {
248 return error.InvalidCharacter;
249 }
250 scope_id = true;
251 saw_any_digits = false;
252 } else if (c == '.') {
253 if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) {
254 // must start with '::ffff:'
255 return error.InvalidIpv4Mapping;
316 // Only compress if the longest zero run is 2 or more
317 if (longest_len < 2) {
318 longest_start = 8;
319 longest_len = 0;
256320 }
257 const start_index = std.mem.lastIndexOfScalar(u8, buffer[0..i], ':').? + 1;
258 const addr = (Ip4Address.parse(buffer[start_index..], 0) catch {
259 return error.InvalidIpv4Mapping;
260 }).bytes;
261 ip_slice = result.bytes[0..];
262 ip_slice[10] = 0xff;
263 ip_slice[11] = 0xff;
264
265 ip_slice[12] = addr[0];
266 ip_slice[13] = addr[1];
267 ip_slice[14] = addr[2];
268 ip_slice[15] = addr[3];
269 return result;
270 } else {
271 const digit = try std.fmt.charToDigit(c, 16);
272 {
273 const ov = @mulWithOverflow(x, 16);
274 if (ov[1] != 0) return error.Overflow;
275 x = ov[0];
276 }
277 {
278 const ov = @addWithOverflow(x, digit);
279 if (ov[1] != 0) return error.Overflow;
280 x = ov[0];
321
322 try w.writeAll("[");
323 var i: usize = 0;
324 var abbrv = false;
325 while (i < parts.len) : (i += 1) {
326 if (i == longest_start) {
327 // Emit "::" for the longest zero run
328 if (!abbrv) {
329 try w.writeAll(if (i == 0) "::" else ":");
330 abbrv = true;
331 }
332 i += longest_len - 1; // Skip the compressed range
333 continue;
334 }
335 if (abbrv) {
336 abbrv = false;
337 }
338 try w.print("{x}", .{parts[i]});
339 if (i != parts.len - 1) {
340 try w.writeAll(":");
341 }
281342 }
282 saw_any_digits = true;
283343 }
344 if (u.interface_name) |n| try w.print("%{s}", .{n.toSlice()});
284345 }
346 };
285347
286 if (!saw_any_digits and !abbrv) {
287 return error.Incomplete;
288 }
289 if (!abbrv and index < 14) {
290 return error.Incomplete;
291 }
348 pub const ParseError = error{
349 /// If this is returned, more detailed diagnostics can be obtained by
350 /// calling `Ip6Address.Parsed.init`.
351 ParseFailed,
352 /// If this is returned, the IPv6 address had a scope id on it ("%foo"
353 /// at the end) which requires calling `resolve`.
354 UnresolvedScope,
355 };
292356
293 if (index == 14) {
294 ip_slice[14] = @as(u8, @truncate(x >> 8));
295 ip_slice[15] = @as(u8, @truncate(x));
296 return result;
297 } else {
298 ip_slice[index] = @as(u8, @truncate(x >> 8));
299 index += 1;
300 ip_slice[index] = @as(u8, @truncate(x));
301 index += 1;
302 @memcpy(result.bytes[16 - index ..][0..index], ip_slice[0..index]);
303 return result;
357 /// This is a pure function but it cannot handle IPv6 addresses that have
358 /// scope ids ("%foo" at the end). To also handle those, `resolve` must be
359 /// called instead.
360 pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address {
361 switch (Unresolved.parse(buffer)) {
362 .success => |p| return .{
363 .bytes = p.bytes,
364 .port = port,
365 .interface = if (p.interface_name != null) return error.UnresolvedScope else .none,
366 },
367 else => return error.ParseFailed,
304368 }
369 return .{ .ip6 = try Ip6Address.parse(buffer, port) };
305370 }
306371
307 pub fn format(a: Ip6Address, w: *Io.Writer) Io.Writer.Error!void {
308 const bytes = &a.bytes;
309 if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
310 try w.print("[::ffff:{d}.{d}.{d}.{d}]:{d}", .{
311 bytes[12], bytes[13], bytes[14], bytes[15], a.port,
312 });
313 return;
314 }
315 const parts: [8]u16 = .{
316 std.mem.readInt(u16, bytes[0..2], .big),
317 std.mem.readInt(u16, bytes[2..4], .big),
318 std.mem.readInt(u16, bytes[4..6], .big),
319 std.mem.readInt(u16, bytes[6..8], .big),
320 std.mem.readInt(u16, bytes[8..10], .big),
321 std.mem.readInt(u16, bytes[10..12], .big),
322 std.mem.readInt(u16, bytes[12..14], .big),
323 std.mem.readInt(u16, bytes[14..16], .big),
372 pub const ResolveError = error{
373 /// If this is returned, more detailed diagnostics can be obtained by
374 /// calling the `Parsed.init` function.
375 ParseFailed,
376 } || Interface.Name.ResolveError;
377
378 /// This function requires an `Io` parameter because it must query the operating
379 /// system to convert interface name to index. For example, in
380 /// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by
381 /// creating a socket and then using an `ioctl` syscall.
382 pub fn resolve(io: Io, buffer: []const u8, port: u16) ResolveError!Ip6Address {
383 return switch (Unresolved.parse(buffer)) {
384 .success => |p| return .{
385 .bytes = p.bytes,
386 .port = port,
387 .interface = if (p.interface_name) |n| try n.resolve(io) else .none,
388 },
389 else => return error.ParseFailed,
324390 };
391 }
325392
326 // Find the longest zero run
327 var longest_start: usize = 8;
328 var longest_len: usize = 0;
329 var current_start: usize = 0;
330 var current_len: usize = 0;
393 pub const FormatError = Io.Writer.Error || Unresolved.FromAddressError;
331394
332 for (parts, 0..) |part, i| {
333 if (part == 0) {
334 if (current_len == 0) {
335 current_start = i;
336 }
337 current_len += 1;
338 if (current_len > longest_len) {
339 longest_start = current_start;
340 longest_len = current_len;
341 }
342 } else {
343 current_len = 0;
344 }
345 }
346
347 // Only compress if the longest zero run is 2 or more
348 if (longest_len < 2) {
349 longest_start = 8;
350 longest_len = 0;
351 }
395 /// Includes the optional scope ("%foo" at the end).
396 ///
397 /// See `format` for an alternative that omits scopes and does
398 /// not require an `Io` parameter.
399 pub fn formatResolved(a: Ip6Address, io: Io, w: *Io.Writer) FormatError!void {
400 const u: Unresolved = try .fromAddress(io);
401 try w.print("[{f}]:{d}", .{ u, a.port });
402 }
352403
353 try w.writeAll("[");
354 var i: usize = 0;
355 var abbrv = false;
356 while (i < parts.len) : (i += 1) {
357 if (i == longest_start) {
358 // Emit "::" for the longest zero run
359 if (!abbrv) {
360 try w.writeAll(if (i == 0) "::" else ":");
361 abbrv = true;
362 }
363 i += longest_len - 1; // Skip the compressed range
364 continue;
365 }
366 if (abbrv) {
367 abbrv = false;
368 }
369 try w.print("{x}", .{parts[i]});
370 if (i != parts.len - 1) {
371 try w.writeAll(":");
372 }
373 }
374 try w.print("]:{d}", .{a.port});
404 /// See `formatResolved` for an alternative that additionally prints the optional
405 /// scope at the end of addresses and requires an `Io` parameter.
406 pub fn format(a: Ip6Address, w: *Io.Writer) Io.Writer.Error!void {
407 const u: Unresolved = .{
408 .bytes = a.bytes,
409 .interface_name = null,
410 };
411 try w.print("[{f}]:{d}", .{ u, a.port });
375412 }
376413
377414 pub fn eql(a: Ip6Address, b: Ip6Address) bool {
......@@ -471,11 +508,64 @@ pub const Ip6Address = struct {
471508 };
472509};
473510
511pub const Interface = struct {
512 /// Value 0 indicates `none`.
513 index: u32,
514
515 pub const none: Interface = .{ .index = 0 };
516
517 pub const Name = struct {
518 bytes: [max_len:0]u8,
519
520 pub const max_len = std.posix.IFNAMESIZE - 1;
521
522 pub fn toSlice(n: *const Name) []const u8 {
523 return std.mem.sliceTo(&n.bytes, 0);
524 }
525
526 pub fn fromSlice(bytes: []const u8) error{NameTooLong}!Name {
527 if (bytes.len > max_len) return error.NameTooLong;
528 var result: Name = undefined;
529 @memcpy(result.bytes[0..bytes.len], bytes);
530 result.bytes[bytes.len] = 0;
531 return result;
532 }
533
534 pub const ResolveError = error{
535 InterfaceNotFound,
536 AccessDenied,
537 SystemResources,
538 } || Io.UnexpectedError || Io.Cancelable;
539
540 /// Corresponds to "if_nametoindex" in libc.
541 pub fn resolve(n: []const u8, io: Io) ResolveError!Interface {
542 return io.vtable.netInterfaceNameResolve(io.userdata, n);
543 }
544 };
545
546 pub const NameError = Io.UnexpectedError || Io.Cancelable;
547
548 /// Asserts not `none`.
549 ///
550 /// Corresponds to "if_indextoname" in libc.
551 pub fn name(i: Interface, io: Io) NameError!Name {
552 assert(i.index != 0);
553 return io.vtable.netInterfaceName(io.userdata, i);
554 }
555
556 pub fn isNone(i: Interface) bool {
557 return i.index == 0;
558 }
559};
560
561/// An open socket connection with a network protocol that guarantees
562/// sequencing, delivery, and prevents repetition. Typically TCP or UNIX domain
563/// socket.
474564pub const Stream = struct {
475 /// Underlying platform-defined type which may or may not be
476 /// interchangeable with a file system file descriptor.
477565 handle: Handle,
478566
567 /// Underlying platform-defined type which may or may not be
568 /// interchangeable with a file system file descriptor.
479569 pub const Handle = switch (native_os) {
480570 .windows => std.windows.ws2_32.SOCKET,
481571 else => std.posix.fd_t,
......@@ -583,17 +673,6 @@ pub const Server = struct {
583673 }
584674};
585675
586pub const InterfaceIndexError = error{
587 InterfaceNotFound,
588 AccessDenied,
589 SystemResources,
590} || Io.UnexpectedError || Io.Cancelable;
591
592/// Otherwise known as "if_nametoindex".
593pub fn interfaceIndex(io: Io, name: []const u8) InterfaceIndexError!u32 {
594 return io.vtable.netInterfaceIndex(io.userdata, name);
595}
596
597676test {
598677 _ = HostName;
599678}
lib/std/Io/net/HostName.zig+3-3
......@@ -105,11 +105,11 @@ pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!L
105105 {
106106 var i: usize = 0;
107107 if (options.family != .ip6) {
108 options.addresses_buffer[i] = .{ .ip4 = .localhost(options.port) };
108 options.addresses_buffer[i] = .{ .ip4 = .loopback(options.port) };
109109 i += 1;
110110 }
111111 if (options.family != .ip4) {
112 options.addresses_buffer[i] = .{ .ip6 = .localhost(options.port) };
112 options.addresses_buffer[i] = .{ .ip6 = .loopback(options.port) };
113113 i += 1;
114114 }
115115 const canon_name = "localhost";
......@@ -166,7 +166,7 @@ fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult
166166 switch (a) {
167167 .ip6 => |ip6| {
168168 da6.bytes = ip6.bytes;
169 da6.scope_id = ip6.scope_id;
169 da6.interface = ip6.interface;
170170 },
171171 .ip4 => |ip4| {
172172 da6.bytes[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;