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 {...@@ -135,7 +135,8 @@ pub fn io(pool: *Pool) Io {
135 else => netWritePosix,135 else => netWritePosix,
136 },136 },
137 .netClose = netClose,137 .netClose = netClose,
138 .netInterfaceIndex = netInterfaceIndex,138 .netInterfaceNameResolve = netInterfaceNameResolve,
139 .netInterfaceName = netInterfaceName,
139 },140 },
140 };141 };
141}142}
...@@ -1123,16 +1124,11 @@ fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void {...@@ -1123,16 +1124,11 @@ fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void {
1123 return net_stream.close();1124 return net_stream.close();
1124}1125}
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 {
1127 const pool: *Pool = @ptrCast(@alignCast(userdata));1128 const pool: *Pool = @ptrCast(@alignCast(userdata));
1128 try pool.checkCancel();1129 try pool.checkCancel();
11291130
1130 if (native_os == .linux) {1131 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
1136 const rc = posix.system.socket(posix.AF.UNIX, posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, 0);1132 const rc = posix.system.socket(posix.AF.UNIX, posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, 0);
1137 const sock_fd: posix.fd_t = switch (posix.errno(rc)) {1133 const sock_fd: posix.fd_t = switch (posix.errno(rc)) {
1138 .SUCCESS => @intCast(rc),1134 .SUCCESS => @intCast(rc),
...@@ -1145,10 +1141,15 @@ fn netInterfaceIndex(userdata: ?*anyopaque, name: []const u8) Io.net.InterfaceIn...@@ -1145,10 +1141,15 @@ fn netInterfaceIndex(userdata: ?*anyopaque, name: []const u8) Io.net.InterfaceIn
1145 };1141 };
1146 defer posix.close(sock_fd);1142 defer posix.close(sock_fd);
11471143
1144 var ifr: posix.ifreq = .{
1145 .ifrn = .{ .name = @bitCast(name.bytes) },
1146 .ifru = undefined,
1147 };
1148
1148 while (true) {1149 while (true) {
1149 try pool.checkCancel();1150 try pool.checkCancel();
1150 switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) {1151 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) },
1152 .INVAL => |err| return badErrno(err), // Bad parameters.1153 .INVAL => |err| return badErrno(err), // Bad parameters.
1153 .NOTTY => |err| return badErrno(err),1154 .NOTTY => |err| return badErrno(err),
1154 .NXIO => |err| return badErrno(err),1155 .NXIO => |err| return badErrno(err),
...@@ -1162,28 +1163,39 @@ fn netInterfaceIndex(userdata: ?*anyopaque, name: []const u8) Io.net.InterfaceIn...@@ -1162,28 +1163,39 @@ fn netInterfaceIndex(userdata: ?*anyopaque, name: []const u8) Io.net.InterfaceIn
1162 }1163 }
1163 }1164 }
11641165
1165 if (native_os.isDarwin()) {1166 if (native_os == .windows) {
1166 if (name.len >= posix.IFNAMESIZE) return error.InterfaceNotFound;1167 const index = std.os.windows.ws2_32.if_nametoindex(&name.bytes);
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);
1172 if (index == 0) return error.InterfaceNotFound;1168 if (index == 0) return error.InterfaceNotFound;
1173 return @bitCast(index);1169 return .{ .index = index };
1174 }1170 }
11751171
1176 if (native_os == .windows) {1172 if (builtin.link_libc) {
1177 if (name.len >= posix.IFNAMESIZE) return error.InterfaceNotFound;1173 const index = std.c.if_nametoindex(&name.bytes);
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));
1182 if (index == 0) return error.InterfaceNotFound;1174 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");
1184 }1196 }
11851197
1186 @compileError("std.net.if_nametoindex unimplemented for this OS");1198 @panic("unimplemented");
1187}1199}
11881200
1189const PosixAddress = extern union {1201const PosixAddress = extern union {
...@@ -1231,8 +1243,8 @@ fn address6FromPosix(in6: *posix.sockaddr.in6) Io.net.Ip6Address {...@@ -1231,8 +1243,8 @@ fn address6FromPosix(in6: *posix.sockaddr.in6) Io.net.Ip6Address {
1231 return .{1243 return .{
1232 .port = std.mem.bigToNative(u16, in6.port),1244 .port = std.mem.bigToNative(u16, in6.port),
1233 .bytes = in6.addr,1245 .bytes = in6.addr,
1234 .flowinfo = in6.flowinfo,1246 .flow = in6.flowinfo,
1235 .scope_id = in6.scope_id,1247 .interface = .{ .index = in6.scope_id },
1236 };1248 };
1237}1249}
12381250
...@@ -1246,9 +1258,9 @@ fn address4ToPosix(a: Io.net.Ip4Address) posix.sockaddr.in {...@@ -1246,9 +1258,9 @@ fn address4ToPosix(a: Io.net.Ip4Address) posix.sockaddr.in {
1246fn address6ToPosix(a: Io.net.Ip6Address) posix.sockaddr.in6 {1258fn address6ToPosix(a: Io.net.Ip6Address) posix.sockaddr.in6 {
1247 return .{1259 return .{
1248 .port = std.mem.nativeToBig(u16, a.port),1260 .port = std.mem.nativeToBig(u16, a.port),
1249 .flowinfo = a.flowinfo,1261 .flowinfo = a.flow,
1250 .addr = a.bytes,1262 .addr = a.bytes,
1251 .scope_id = a.scope_id,1263 .scope_id = a.interface.index,
1252 };1264 };
1253}1265}
12541266
lib/std/Io/net.zig+278-199
...@@ -26,10 +26,12 @@ pub const IpAddress = union(enum) {...@@ -26,10 +26,12 @@ pub const IpAddress = union(enum) {
26 pub const Family = @typeInfo(IpAddress).@"union".tag_type.?;26 pub const Family = @typeInfo(IpAddress).@"union".tag_type.?;
2727
28 /// Parse the given IP address string into an `IpAddress` value.28 /// 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.
29 pub fn parse(name: []const u8, port: u16) !IpAddress {33 pub fn parse(name: []const u8, port: u16) !IpAddress {
30 if (Ip4Address.parse(name, port)) |ip4| {34 if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
31 return .{ .ip4 = ip4 };
32 } else |err| switch (err) {
33 error.Overflow,35 error.Overflow,
34 error.InvalidEnd,36 error.InvalidEnd,
35 error.InvalidCharacter,37 error.InvalidCharacter,
...@@ -38,26 +40,41 @@ pub const IpAddress = union(enum) {...@@ -38,26 +40,41 @@ pub const IpAddress = union(enum) {
38 => {},40 => {},
39 }41 }
4042
41 if (Ip6Address.parse(name, port)) |ip6| {43 return parseIp6(name, port);
42 return .{ .ip6 = ip6 };44 }
43 } else |err| switch (err) {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) {
44 error.Overflow,65 error.Overflow,
45 error.InvalidEnd,66 error.InvalidEnd,
46 error.InvalidCharacter,67 error.InvalidCharacter,
47 error.Incomplete,68 error.Incomplete,
48 error.InvalidIpv4Mapping,69 error.NonCanonical,
49 => {},70 => {},
50 }71 }
5172
52 return error.InvalidIpAddressFormat;73 return resolveIp6(io, text, port);
53 }74 }
5475
55 pub fn parseIp6(buffer: []const u8, port: u16) Ip6Address.ParseError!IpAddress {76 pub fn resolveIp6(io: Io, text: []const u8, port: u16) Ip6Address.ResolveError!IpAddress {
56 return .{ .ip6 = try Ip6Address.parse(buffer, port) };77 return .{ .ip6 = try Ip6Address.resolve(io, text, port) };
57 }
58
59 pub fn parseIp4(buffer: []const u8, port: u16) Ip4Address.ParseError!IpAddress {
60 return .{ .ip4 = try Ip4Address.parse(buffer, port) };
61 }78 }
6279
63 /// Returns the port in native endian.80 /// Returns the port in native endian.
...@@ -74,6 +91,19 @@ pub const IpAddress = union(enum) {...@@ -74,6 +91,19 @@ pub const IpAddress = union(enum) {
74 }91 }
75 }92 }
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.
77 pub fn format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!void {107 pub fn format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!void {
78 switch (a) {108 switch (a) {
79 inline .ip4, .ip6 => |x| return x.format(w),109 inline .ip4, .ip6 => |x| return x.format(w),
...@@ -99,11 +129,12 @@ pub const IpAddress = union(enum) {...@@ -99,11 +129,12 @@ pub const IpAddress = union(enum) {
99 }129 }
100};130};
101131
132/// An IPv4 address in binary memory layout.
102pub const Ip4Address = struct {133pub const Ip4Address = struct {
103 bytes: [4]u8,134 bytes: [4]u8,
104 port: u16,135 port: u16,
105136
106 pub fn localhost(port: u16) Ip4Address {137 pub fn loopback(port: u16) Ip4Address {
107 return .{138 return .{
108 .bytes = .{ 127, 0, 0, 1 },139 .bytes = .{ 127, 0, 0, 1 },
109 .port = port,140 .port = port,
...@@ -162,21 +193,14 @@ pub const Ip4Address = struct {...@@ -162,21 +193,14 @@ pub const Ip4Address = struct {
162 }193 }
163};194};
164195
196/// An IPv6 address in binary memory layout.
165pub const Ip6Address = struct {197pub const Ip6Address = struct {
166 /// Native endian198 /// Native endian
167 port: u16,199 port: u16,
168 /// Big endian200 /// Big endian
169 bytes: [16]u8,201 bytes: [16]u8,
170 flowinfo: u32 = 0,202 flow: u32 = 0,
171 scope_id: u32 = 0,203 interface: Interface = .none,
172
173 pub const ParseError = error{
174 Overflow,
175 InvalidCharacter,
176 InvalidEnd,
177 InvalidIpv4Mapping,
178 Incomplete,
179 };
180204
181 pub const Policy = struct {205 pub const Policy = struct {
182 addr: [16]u8,206 addr: [16]u8,
...@@ -186,192 +210,205 @@ pub const Ip6Address = struct {...@@ -186,192 +210,205 @@ pub const Ip6Address = struct {
186 label: u8,210 label: u8,
187 };211 };
188212
189 pub fn localhost(port: u16) Ip6Address {213 pub fn loopback(port: u16) Ip6Address {
190 return .{214 return .{
191 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },215 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
192 .port = port,216 .port = port,
193 };217 };
194 }218 }
195219
196 pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address {220 /// An IPv6 address but with `Interface` as a name rather than index.
197 var result: Ip6Address = .{221 pub const Unresolved = struct {
198 .port = port,222 /// Big endian
199 .bytes = undefined,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,
200 };230 };
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;268 pub fn fromAddress(a: *const Ip6Address, io: Io) FromAddressError!Unresolved {
206 var saw_any_digits = false;269 if (a.interface.isNone()) return .{
207 var index: u8 = 0;270 .bytes = a.bytes,
208 var scope_id = false;271 .interface_name = null,
209 var abbrv = false;272 };
210 for (buffer, 0..) |c, i| {273 return .{
211 if (scope_id) {274 .bytes = a.bytes,
212 if (c >= '0' and c <= '9') {275 .interface_name = try a.interface.name(io),
213 const digit = c - '0';276 };
214 {277 }
215 const ov = @mulWithOverflow(result.scope_id, 10);278
216 if (ov[1] != 0) return error.Overflow;279 pub fn format(u: *const Unresolved, w: *Io.Writer) Io.Writer.Error!void {
217 result.scope_id = ov[0];280 const bytes = &u.bytes;
218 }281 if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
219 {282 try w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] });
220 const ov = @addWithOverflow(result.scope_id, digit);283 } else {
221 if (ov[1] != 0) return error.Overflow;284 const parts: [8]u16 = .{
222 result.scope_id = ov[0];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;
223 }313 }
224 } else {
225 return error.InvalidCharacter;
226 }314 }
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;316 // Only compress if the longest zero run is 2 or more
245 saw_any_digits = false;317 if (longest_len < 2) {
246 } else if (c == '%') {318 longest_start = 8;
247 if (!saw_any_digits) {319 longest_len = 0;
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;
256 }320 }
257 const start_index = std.mem.lastIndexOfScalar(u8, buffer[0..i], ':').? + 1;321
258 const addr = (Ip4Address.parse(buffer[start_index..], 0) catch {322 try w.writeAll("[");
259 return error.InvalidIpv4Mapping;323 var i: usize = 0;
260 }).bytes;324 var abbrv = false;
261 ip_slice = result.bytes[0..];325 while (i < parts.len) : (i += 1) {
262 ip_slice[10] = 0xff;326 if (i == longest_start) {
263 ip_slice[11] = 0xff;327 // Emit "::" for the longest zero run
264328 if (!abbrv) {
265 ip_slice[12] = addr[0];329 try w.writeAll(if (i == 0) "::" else ":");
266 ip_slice[13] = addr[1];330 abbrv = true;
267 ip_slice[14] = addr[2];331 }
268 ip_slice[15] = addr[3];332 i += longest_len - 1; // Skip the compressed range
269 return result;333 continue;
270 } else {334 }
271 const digit = try std.fmt.charToDigit(c, 16);335 if (abbrv) {
272 {336 abbrv = false;
273 const ov = @mulWithOverflow(x, 16);337 }
274 if (ov[1] != 0) return error.Overflow;338 try w.print("{x}", .{parts[i]});
275 x = ov[0];339 if (i != parts.len - 1) {
276 }340 try w.writeAll(":");
277 {341 }
278 const ov = @addWithOverflow(x, digit);
279 if (ov[1] != 0) return error.Overflow;
280 x = ov[0];
281 }342 }
282 saw_any_digits = true;
283 }343 }
344 if (u.interface_name) |n| try w.print("%{s}", .{n.toSlice()});
284 }345 }
346 };
285347
286 if (!saw_any_digits and !abbrv) {348 pub const ParseError = error{
287 return error.Incomplete;349 /// If this is returned, more detailed diagnostics can be obtained by
288 }350 /// calling `Ip6Address.Parsed.init`.
289 if (!abbrv and index < 14) {351 ParseFailed,
290 return error.Incomplete;352 /// If this is returned, the IPv6 address had a scope id on it ("%foo"
291 }353 /// at the end) which requires calling `resolve`.
354 UnresolvedScope,
355 };
292356
293 if (index == 14) {357 /// This is a pure function but it cannot handle IPv6 addresses that have
294 ip_slice[14] = @as(u8, @truncate(x >> 8));358 /// scope ids ("%foo" at the end). To also handle those, `resolve` must be
295 ip_slice[15] = @as(u8, @truncate(x));359 /// called instead.
296 return result;360 pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address {
297 } else {361 switch (Unresolved.parse(buffer)) {
298 ip_slice[index] = @as(u8, @truncate(x >> 8));362 .success => |p| return .{
299 index += 1;363 .bytes = p.bytes,
300 ip_slice[index] = @as(u8, @truncate(x));364 .port = port,
301 index += 1;365 .interface = if (p.interface_name != null) return error.UnresolvedScope else .none,
302 @memcpy(result.bytes[16 - index ..][0..index], ip_slice[0..index]);366 },
303 return result;367 else => return error.ParseFailed,
304 }368 }
369 return .{ .ip6 = try Ip6Address.parse(buffer, port) };
305 }370 }
306371
307 pub fn format(a: Ip6Address, w: *Io.Writer) Io.Writer.Error!void {372 pub const ResolveError = error{
308 const bytes = &a.bytes;373 /// If this is returned, more detailed diagnostics can be obtained by
309 if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {374 /// calling the `Parsed.init` function.
310 try w.print("[::ffff:{d}.{d}.{d}.{d}]:{d}", .{375 ParseFailed,
311 bytes[12], bytes[13], bytes[14], bytes[15], a.port,376 } || Interface.Name.ResolveError;
312 });377
313 return;378 /// This function requires an `Io` parameter because it must query the operating
314 }379 /// system to convert interface name to index. For example, in
315 const parts: [8]u16 = .{380 /// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by
316 std.mem.readInt(u16, bytes[0..2], .big),381 /// creating a socket and then using an `ioctl` syscall.
317 std.mem.readInt(u16, bytes[2..4], .big),382 pub fn resolve(io: Io, buffer: []const u8, port: u16) ResolveError!Ip6Address {
318 std.mem.readInt(u16, bytes[4..6], .big),383 return switch (Unresolved.parse(buffer)) {
319 std.mem.readInt(u16, bytes[6..8], .big),384 .success => |p| return .{
320 std.mem.readInt(u16, bytes[8..10], .big),385 .bytes = p.bytes,
321 std.mem.readInt(u16, bytes[10..12], .big),386 .port = port,
322 std.mem.readInt(u16, bytes[12..14], .big),387 .interface = if (p.interface_name) |n| try n.resolve(io) else .none,
323 std.mem.readInt(u16, bytes[14..16], .big),388 },
389 else => return error.ParseFailed,
324 };390 };
391 }
325392
326 // Find the longest zero run393 pub const FormatError = Io.Writer.Error || Unresolved.FromAddressError;
327 var longest_start: usize = 8;
328 var longest_len: usize = 0;
329 var current_start: usize = 0;
330 var current_len: usize = 0;
331394
332 for (parts, 0..) |part, i| {395 /// Includes the optional scope ("%foo" at the end).
333 if (part == 0) {396 ///
334 if (current_len == 0) {397 /// See `format` for an alternative that omits scopes and does
335 current_start = i;398 /// not require an `Io` parameter.
336 }399 pub fn formatResolved(a: Ip6Address, io: Io, w: *Io.Writer) FormatError!void {
337 current_len += 1;400 const u: Unresolved = try .fromAddress(io);
338 if (current_len > longest_len) {401 try w.print("[{f}]:{d}", .{ u, a.port });
339 longest_start = current_start;402 }
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 }
352403
353 try w.writeAll("[");404 /// See `formatResolved` for an alternative that additionally prints the optional
354 var i: usize = 0;405 /// scope at the end of addresses and requires an `Io` parameter.
355 var abbrv = false;406 pub fn format(a: Ip6Address, w: *Io.Writer) Io.Writer.Error!void {
356 while (i < parts.len) : (i += 1) {407 const u: Unresolved = .{
357 if (i == longest_start) {408 .bytes = a.bytes,
358 // Emit "::" for the longest zero run409 .interface_name = null,
359 if (!abbrv) {410 };
360 try w.writeAll(if (i == 0) "::" else ":");411 try w.print("[{f}]:{d}", .{ u, a.port });
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});
375 }412 }
376413
377 pub fn eql(a: Ip6Address, b: Ip6Address) bool {414 pub fn eql(a: Ip6Address, b: Ip6Address) bool {
...@@ -471,11 +508,64 @@ pub const Ip6Address = struct {...@@ -471,11 +508,64 @@ pub const Ip6Address = struct {
471 };508 };
472};509};
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.
474pub const Stream = struct {564pub const Stream = struct {
475 /// Underlying platform-defined type which may or may not be
476 /// interchangeable with a file system file descriptor.
477 handle: Handle,565 handle: Handle,
478566
567 /// Underlying platform-defined type which may or may not be
568 /// interchangeable with a file system file descriptor.
479 pub const Handle = switch (native_os) {569 pub const Handle = switch (native_os) {
480 .windows => std.windows.ws2_32.SOCKET,570 .windows => std.windows.ws2_32.SOCKET,
481 else => std.posix.fd_t,571 else => std.posix.fd_t,
...@@ -583,17 +673,6 @@ pub const Server = struct {...@@ -583,17 +673,6 @@ pub const Server = struct {
583 }673 }
584};674};
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
597test {676test {
598 _ = HostName;677 _ = HostName;
599}678}
lib/std/Io/net/HostName.zig+3-3
...@@ -105,11 +105,11 @@ pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!L...@@ -105,11 +105,11 @@ pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!L
105 {105 {
106 var i: usize = 0;106 var i: usize = 0;
107 if (options.family != .ip6) {107 if (options.family != .ip6) {
108 options.addresses_buffer[i] = .{ .ip4 = .localhost(options.port) };108 options.addresses_buffer[i] = .{ .ip4 = .loopback(options.port) };
109 i += 1;109 i += 1;
110 }110 }
111 if (options.family != .ip4) {111 if (options.family != .ip4) {
112 options.addresses_buffer[i] = .{ .ip6 = .localhost(options.port) };112 options.addresses_buffer[i] = .{ .ip6 = .loopback(options.port) };
113 i += 1;113 i += 1;
114 }114 }
115 const canon_name = "localhost";115 const canon_name = "localhost";
...@@ -166,7 +166,7 @@ fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult...@@ -166,7 +166,7 @@ fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult
166 switch (a) {166 switch (a) {
167 .ip6 => |ip6| {167 .ip6 => |ip6| {
168 da6.bytes = ip6.bytes;168 da6.bytes = ip6.bytes;
169 da6.scope_id = ip6.scope_id;169 da6.interface = ip6.interface;
170 },170 },
171 .ip4 => |ip4| {171 .ip4 => |ip4| {
172 da6.bytes[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;172 da6.bytes[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;