From dac7ae1e438bcd9d5051fc53874002ee06278466 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 21 Sep 2025 21:13:26 -0700 Subject: [PATCH] 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 --- lib/std/Io/Threaded.zig | 68 +++-- lib/std/Io/net.zig | 499 +++++++++++++++++++++--------------- lib/std/Io/net/HostName.zig | 6 +- 3 files changed, 332 insertions(+), 241 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 619525435facca384fe35c3f8d7701c22051ae8c..1ac56469df1764437ccd5f1fde9adfc431762896 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -135,7 +135,8 @@ pub fn io(pool: *Pool) Io { else => netWritePosix, }, .netClose = netClose, - .netInterfaceIndex = netInterfaceIndex, + .netInterfaceNameResolve = netInterfaceNameResolve, + .netInterfaceName = netInterfaceName, }, }; } @@ -1123,16 +1124,11 @@ fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void { return net_stream.close(); } -fn netInterfaceIndex(userdata: ?*anyopaque, name: []const u8) Io.net.InterfaceIndexError!u32 { +fn netInterfaceNameResolve(userdata: ?*anyopaque, name: Io.net.Interface.Name) Io.net.Interface.Name.ResolveError!Io.net.Interface { const pool: *Pool = @ptrCast(@alignCast(userdata)); try pool.checkCancel(); if (native_os == .linux) { - if (name.len >= posix.IFNAMESIZE) return error.InterfaceNotFound; - var ifr: posix.ifreq = undefined; - @memcpy(ifr.ifrn.name[0..name.len], name); - ifr.ifrn.name[name.len] = 0; - const rc = posix.system.socket(posix.AF.UNIX, posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, 0); const sock_fd: posix.fd_t = switch (posix.errno(rc)) { .SUCCESS => @intCast(rc), @@ -1145,10 +1141,15 @@ fn netInterfaceIndex(userdata: ?*anyopaque, name: []const u8) Io.net.InterfaceIn }; defer posix.close(sock_fd); + var ifr: posix.ifreq = .{ + .ifrn = .{ .name = @bitCast(name.bytes) }, + .ifru = undefined, + }; + while (true) { try pool.checkCancel(); switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) { - .SUCCESS => return @bitCast(ifr.ifru.ivalue), + .SUCCESS => return .{ .index = @bitCast(ifr.ifru.ivalue) }, .INVAL => |err| return badErrno(err), // Bad parameters. .NOTTY => |err| return badErrno(err), .NXIO => |err| return badErrno(err), @@ -1162,28 +1163,39 @@ fn netInterfaceIndex(userdata: ?*anyopaque, name: []const u8) Io.net.InterfaceIn } } - if (native_os.isDarwin()) { - if (name.len >= posix.IFNAMESIZE) return error.InterfaceNotFound; - var if_name: [posix.IFNAMESIZE:0]u8 = undefined; - @memcpy(if_name[0..name.len], name); - if_name[name.len] = 0; - const if_slice = if_name[0..name.len :0]; - const index = std.c.if_nametoindex(if_slice); + if (native_os == .windows) { + const index = std.os.windows.ws2_32.if_nametoindex(&name.bytes); if (index == 0) return error.InterfaceNotFound; - return @bitCast(index); + return .{ .index = index }; } - if (native_os == .windows) { - if (name.len >= posix.IFNAMESIZE) return error.InterfaceNotFound; - var interface_name: [posix.IFNAMESIZE:0]u8 = undefined; - @memcpy(interface_name[0..name.len], name); - interface_name[name.len] = 0; - const index = std.os.windows.ws2_32.if_nametoindex(@as([*:0]const u8, &interface_name)); + if (builtin.link_libc) { + const index = std.c.if_nametoindex(&name.bytes); if (index == 0) return error.InterfaceNotFound; - return index; + return .{ .index = @bitCast(index) }; + } + + @panic("unimplemented"); +} + +fn netInterfaceName(userdata: ?*anyopaque, interface: Io.net.Interface) Io.net.Interface.NameError!Io.net.Interface.Name { + const pool: *Pool = @ptrCast(@alignCast(userdata)); + try pool.checkCancel(); + + if (native_os == .linux) { + _ = interface; + @panic("TODO"); + } + + if (native_os == .windows) { + @panic("TODO"); + } + + if (builtin.link_libc) { + @panic("TODO"); } - @compileError("std.net.if_nametoindex unimplemented for this OS"); + @panic("unimplemented"); } const PosixAddress = extern union { @@ -1231,8 +1243,8 @@ fn address6FromPosix(in6: *posix.sockaddr.in6) Io.net.Ip6Address { return .{ .port = std.mem.bigToNative(u16, in6.port), .bytes = in6.addr, - .flowinfo = in6.flowinfo, - .scope_id = in6.scope_id, + .flow = in6.flowinfo, + .interface = .{ .index = in6.scope_id }, }; } @@ -1246,9 +1258,9 @@ fn address4ToPosix(a: Io.net.Ip4Address) posix.sockaddr.in { fn address6ToPosix(a: Io.net.Ip6Address) posix.sockaddr.in6 { return .{ .port = std.mem.nativeToBig(u16, a.port), - .flowinfo = a.flowinfo, + .flowinfo = a.flow, .addr = a.bytes, - .scope_id = a.scope_id, + .scope_id = a.interface.index, }; } diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index 0881fdc30c12b16abee958df326b54bc3ba9b8f7..c8555db48c40a91da736f2da2d7c301d1bba246d 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -26,10 +26,12 @@ pub const IpAddress = union(enum) { pub const Family = @typeInfo(IpAddress).@"union".tag_type.?; /// Parse the given IP address string into an `IpAddress` value. + /// + /// This is a pure function but it cannot handle IPv6 addresses that have + /// scope ids ("%foo" at the end). To also handle those, `resolve` must be + /// called instead. pub fn parse(name: []const u8, port: u16) !IpAddress { - if (Ip4Address.parse(name, port)) |ip4| { - return .{ .ip4 = ip4 }; - } else |err| switch (err) { + if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) { error.Overflow, error.InvalidEnd, error.InvalidCharacter, @@ -38,26 +40,41 @@ pub const IpAddress = union(enum) { => {}, } - if (Ip6Address.parse(name, port)) |ip6| { - return .{ .ip6 = ip6 }; - } else |err| switch (err) { + return parseIp6(name, port); + } + + pub fn parseIp4(text: []const u8, port: u16) Ip4Address.ParseError!IpAddress { + return .{ .ip4 = try Ip4Address.parse(text, port) }; + } + + /// This is a pure function but it cannot handle IPv6 addresses that have + /// scope ids ("%foo" at the end). To also handle those, `resolveIp6` must be + /// called instead. + pub fn parseIp6(text: []const u8, port: u16) Ip6Address.ParseError!IpAddress { + return .{ .ip6 = try Ip6Address.parse(text, port) }; + } + + /// This function requires an `Io` parameter because it must query the operating + /// system to convert interface name to index. For example, in + /// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by + /// creating a socket and then using an `ioctl` syscall. + /// + /// For a pure function that cannot handle scopes, see `parse`. + pub fn resolve(io: Io, text: []const u8, port: u16) !IpAddress { + if (parseIp4(text, port)) |ip4| return ip4 else |err| switch (err) { error.Overflow, error.InvalidEnd, error.InvalidCharacter, error.Incomplete, - error.InvalidIpv4Mapping, + error.NonCanonical, => {}, } - return error.InvalidIpAddressFormat; - } - - pub fn parseIp6(buffer: []const u8, port: u16) Ip6Address.ParseError!IpAddress { - return .{ .ip6 = try Ip6Address.parse(buffer, port) }; + return resolveIp6(io, text, port); } - pub fn parseIp4(buffer: []const u8, port: u16) Ip4Address.ParseError!IpAddress { - return .{ .ip4 = try Ip4Address.parse(buffer, port) }; + pub fn resolveIp6(io: Io, text: []const u8, port: u16) Ip6Address.ResolveError!IpAddress { + return .{ .ip6 = try Ip6Address.resolve(io, text, port) }; } /// Returns the port in native endian. @@ -74,6 +91,19 @@ pub const IpAddress = union(enum) { } } + /// Includes the optional scope ("%foo" at the end) in IPv6 addresses. + /// + /// See `format` for an alternative that omits scopes and does + /// not require an `Io` parameter. + pub fn formatResolved(a: IpAddress, io: Io, w: *Io.Writer) Ip6Address.FormatError!void { + switch (a) { + .ip4 => |x| return x.format(w), + .ip6 => |x| return x.formatResolved(io, w), + } + } + + /// See `formatResolved` for an alternative that additionally prints the optional + /// scope at the end of IPv6 addresses and requires an `Io` parameter. pub fn format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!void { switch (a) { inline .ip4, .ip6 => |x| return x.format(w), @@ -99,11 +129,12 @@ pub const IpAddress = union(enum) { } }; +/// An IPv4 address in binary memory layout. pub const Ip4Address = struct { bytes: [4]u8, port: u16, - pub fn localhost(port: u16) Ip4Address { + pub fn loopback(port: u16) Ip4Address { return .{ .bytes = .{ 127, 0, 0, 1 }, .port = port, @@ -162,21 +193,14 @@ pub const Ip4Address = struct { } }; +/// An IPv6 address in binary memory layout. pub const Ip6Address = struct { /// Native endian port: u16, /// Big endian bytes: [16]u8, - flowinfo: u32 = 0, - scope_id: u32 = 0, - - pub const ParseError = error{ - Overflow, - InvalidCharacter, - InvalidEnd, - InvalidIpv4Mapping, - Incomplete, - }; + flow: u32 = 0, + interface: Interface = .none, pub const Policy = struct { addr: [16]u8, @@ -186,192 +210,205 @@ pub const Ip6Address = struct { label: u8, }; - pub fn localhost(port: u16) Ip6Address { + pub fn loopback(port: u16) Ip6Address { return .{ .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, .port = port, }; } + /// An IPv6 address but with `Interface` as a name rather than index. + pub const Unresolved = struct { + /// Big endian + bytes: [16]u8, + interface_name: ?Interface.Name, + + pub const Parsed = union(enum) { + success: Unresolved, + invalid_byte: usize, + unexpected_end, + }; + + pub fn parse(buffer: []const u8) Parsed { + if (buffer.len < 2) return .unexpected_end; + var parts: [8]u16 = @splat(0); + var parts_i: usize = 0; + var i: usize = 0; + var digit_i: usize = 0; + const State = union(enum) { digit, colon, end }; + state: switch (State.digit) { + .digit => c: switch (buffer[i]) { + 'a'...'f' => |c| { + const digit = c - 'a'; + parts[parts_i] = parts[parts_i] * 16 + digit; + if (digit_i == 3) { + digit_i = 0; + parts_i += 1; + i += 1; + if (parts.len - parts_i == 0) continue :state .end; + continue :state .colon; + } + digit_i += 1; + if (buffer.len - i == 0) return .unexpected_end; + i += 1; + continue :c buffer[i]; + }, + 'A'...'F' => |c| continue :c c + ('a' - 'A'), + '0'...'9' => |c| continue :c c + ('a' - '0'), + ':' => @panic("TODO"), + else => return .{ .invalid_byte = i }, + }, + .colon => @panic("TODO"), + .end => @panic("TODO"), + } + } + + pub const FromAddressError = Interface.NameError; + + pub fn fromAddress(a: *const Ip6Address, io: Io) FromAddressError!Unresolved { + if (a.interface.isNone()) return .{ + .bytes = a.bytes, + .interface_name = null, + }; + return .{ + .bytes = a.bytes, + .interface_name = try a.interface.name(io), + }; + } + + pub fn format(u: *const Unresolved, w: *Io.Writer) Io.Writer.Error!void { + const bytes = &u.bytes; + if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) { + try w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] }); + } else { + const parts: [8]u16 = .{ + std.mem.readInt(u16, bytes[0..2], .big), + std.mem.readInt(u16, bytes[2..4], .big), + std.mem.readInt(u16, bytes[4..6], .big), + std.mem.readInt(u16, bytes[6..8], .big), + std.mem.readInt(u16, bytes[8..10], .big), + std.mem.readInt(u16, bytes[10..12], .big), + std.mem.readInt(u16, bytes[12..14], .big), + std.mem.readInt(u16, bytes[14..16], .big), + }; + + // Find the longest zero run + var longest_start: usize = 8; + var longest_len: usize = 0; + var current_start: usize = 0; + var current_len: usize = 0; + + for (parts, 0..) |part, i| { + if (part == 0) { + if (current_len == 0) { + current_start = i; + } + current_len += 1; + if (current_len > longest_len) { + longest_start = current_start; + longest_len = current_len; + } + } else { + current_len = 0; + } + } + + // Only compress if the longest zero run is 2 or more + if (longest_len < 2) { + longest_start = 8; + longest_len = 0; + } + + try w.writeAll("["); + var i: usize = 0; + var abbrv = false; + while (i < parts.len) : (i += 1) { + if (i == longest_start) { + // Emit "::" for the longest zero run + if (!abbrv) { + try w.writeAll(if (i == 0) "::" else ":"); + abbrv = true; + } + i += longest_len - 1; // Skip the compressed range + continue; + } + if (abbrv) { + abbrv = false; + } + try w.print("{x}", .{parts[i]}); + if (i != parts.len - 1) { + try w.writeAll(":"); + } + } + } + if (u.interface_name) |n| try w.print("%{s}", .{n.toSlice()}); + } + }; + + pub const ParseError = error{ + /// If this is returned, more detailed diagnostics can be obtained by + /// calling `Ip6Address.Parsed.init`. + ParseFailed, + /// If this is returned, the IPv6 address had a scope id on it ("%foo" + /// at the end) which requires calling `resolve`. + UnresolvedScope, + }; + + /// This is a pure function but it cannot handle IPv6 addresses that have + /// scope ids ("%foo" at the end). To also handle those, `resolve` must be + /// called instead. pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address { - var result: Ip6Address = .{ - .port = port, - .bytes = undefined, + switch (Unresolved.parse(buffer)) { + .success => |p| return .{ + .bytes = p.bytes, + .port = port, + .interface = if (p.interface_name != null) return error.UnresolvedScope else .none, + }, + else => return error.ParseFailed, + } + return .{ .ip6 = try Ip6Address.parse(buffer, port) }; + } + + pub const ResolveError = error{ + /// If this is returned, more detailed diagnostics can be obtained by + /// calling the `Parsed.init` function. + ParseFailed, + } || Interface.Name.ResolveError; + + /// This function requires an `Io` parameter because it must query the operating + /// system to convert interface name to index. For example, in + /// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by + /// creating a socket and then using an `ioctl` syscall. + pub fn resolve(io: Io, buffer: []const u8, port: u16) ResolveError!Ip6Address { + return switch (Unresolved.parse(buffer)) { + .success => |p| return .{ + .bytes = p.bytes, + .port = port, + .interface = if (p.interface_name) |n| try n.resolve(io) else .none, + }, + else => return error.ParseFailed, }; - var ip_slice: *[16]u8 = &result.bytes; - - var tail: [16]u8 = undefined; - - var x: u16 = 0; - var saw_any_digits = false; - var index: u8 = 0; - var scope_id = false; - var abbrv = false; - for (buffer, 0..) |c, i| { - if (scope_id) { - if (c >= '0' and c <= '9') { - const digit = c - '0'; - { - const ov = @mulWithOverflow(result.scope_id, 10); - if (ov[1] != 0) return error.Overflow; - result.scope_id = ov[0]; - } - { - const ov = @addWithOverflow(result.scope_id, digit); - if (ov[1] != 0) return error.Overflow; - result.scope_id = ov[0]; - } - } else { - return error.InvalidCharacter; - } - } else if (c == ':') { - if (!saw_any_digits) { - if (abbrv) return error.InvalidCharacter; // ':::' - if (i != 0) abbrv = true; - @memset(ip_slice[index..], 0); - ip_slice = tail[0..]; - index = 0; - continue; - } - if (index == 14) { - return error.InvalidEnd; - } - ip_slice[index] = @as(u8, @truncate(x >> 8)); - index += 1; - ip_slice[index] = @as(u8, @truncate(x)); - index += 1; - - x = 0; - saw_any_digits = false; - } else if (c == '%') { - if (!saw_any_digits) { - return error.InvalidCharacter; - } - scope_id = true; - saw_any_digits = false; - } else if (c == '.') { - if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) { - // must start with '::ffff:' - return error.InvalidIpv4Mapping; - } - const start_index = std.mem.lastIndexOfScalar(u8, buffer[0..i], ':').? + 1; - const addr = (Ip4Address.parse(buffer[start_index..], 0) catch { - return error.InvalidIpv4Mapping; - }).bytes; - ip_slice = result.bytes[0..]; - ip_slice[10] = 0xff; - ip_slice[11] = 0xff; - - ip_slice[12] = addr[0]; - ip_slice[13] = addr[1]; - ip_slice[14] = addr[2]; - ip_slice[15] = addr[3]; - return result; - } else { - const digit = try std.fmt.charToDigit(c, 16); - { - const ov = @mulWithOverflow(x, 16); - if (ov[1] != 0) return error.Overflow; - x = ov[0]; - } - { - const ov = @addWithOverflow(x, digit); - if (ov[1] != 0) return error.Overflow; - x = ov[0]; - } - saw_any_digits = true; - } - } + } - if (!saw_any_digits and !abbrv) { - return error.Incomplete; - } - if (!abbrv and index < 14) { - return error.Incomplete; - } + pub const FormatError = Io.Writer.Error || Unresolved.FromAddressError; - if (index == 14) { - ip_slice[14] = @as(u8, @truncate(x >> 8)); - ip_slice[15] = @as(u8, @truncate(x)); - return result; - } else { - ip_slice[index] = @as(u8, @truncate(x >> 8)); - index += 1; - ip_slice[index] = @as(u8, @truncate(x)); - index += 1; - @memcpy(result.bytes[16 - index ..][0..index], ip_slice[0..index]); - return result; - } + /// Includes the optional scope ("%foo" at the end). + /// + /// See `format` for an alternative that omits scopes and does + /// not require an `Io` parameter. + pub fn formatResolved(a: Ip6Address, io: Io, w: *Io.Writer) FormatError!void { + const u: Unresolved = try .fromAddress(io); + try w.print("[{f}]:{d}", .{ u, a.port }); } + /// See `formatResolved` for an alternative that additionally prints the optional + /// scope at the end of addresses and requires an `Io` parameter. pub fn format(a: Ip6Address, w: *Io.Writer) Io.Writer.Error!void { - const bytes = &a.bytes; - if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) { - try w.print("[::ffff:{d}.{d}.{d}.{d}]:{d}", .{ - bytes[12], bytes[13], bytes[14], bytes[15], a.port, - }); - return; - } - const parts: [8]u16 = .{ - std.mem.readInt(u16, bytes[0..2], .big), - std.mem.readInt(u16, bytes[2..4], .big), - std.mem.readInt(u16, bytes[4..6], .big), - std.mem.readInt(u16, bytes[6..8], .big), - std.mem.readInt(u16, bytes[8..10], .big), - std.mem.readInt(u16, bytes[10..12], .big), - std.mem.readInt(u16, bytes[12..14], .big), - std.mem.readInt(u16, bytes[14..16], .big), + const u: Unresolved = .{ + .bytes = a.bytes, + .interface_name = null, }; - - // Find the longest zero run - var longest_start: usize = 8; - var longest_len: usize = 0; - var current_start: usize = 0; - var current_len: usize = 0; - - for (parts, 0..) |part, i| { - if (part == 0) { - if (current_len == 0) { - current_start = i; - } - current_len += 1; - if (current_len > longest_len) { - longest_start = current_start; - longest_len = current_len; - } - } else { - current_len = 0; - } - } - - // Only compress if the longest zero run is 2 or more - if (longest_len < 2) { - longest_start = 8; - longest_len = 0; - } - - try w.writeAll("["); - var i: usize = 0; - var abbrv = false; - while (i < parts.len) : (i += 1) { - if (i == longest_start) { - // Emit "::" for the longest zero run - if (!abbrv) { - try w.writeAll(if (i == 0) "::" else ":"); - abbrv = true; - } - i += longest_len - 1; // Skip the compressed range - continue; - } - if (abbrv) { - abbrv = false; - } - try w.print("{x}", .{parts[i]}); - if (i != parts.len - 1) { - try w.writeAll(":"); - } - } - try w.print("]:{d}", .{a.port}); + try w.print("[{f}]:{d}", .{ u, a.port }); } pub fn eql(a: Ip6Address, b: Ip6Address) bool { @@ -471,11 +508,64 @@ pub const Ip6Address = struct { }; }; +pub const Interface = struct { + /// Value 0 indicates `none`. + index: u32, + + pub const none: Interface = .{ .index = 0 }; + + pub const Name = struct { + bytes: [max_len:0]u8, + + pub const max_len = std.posix.IFNAMESIZE - 1; + + pub fn toSlice(n: *const Name) []const u8 { + return std.mem.sliceTo(&n.bytes, 0); + } + + pub fn fromSlice(bytes: []const u8) error{NameTooLong}!Name { + if (bytes.len > max_len) return error.NameTooLong; + var result: Name = undefined; + @memcpy(result.bytes[0..bytes.len], bytes); + result.bytes[bytes.len] = 0; + return result; + } + + pub const ResolveError = error{ + InterfaceNotFound, + AccessDenied, + SystemResources, + } || Io.UnexpectedError || Io.Cancelable; + + /// Corresponds to "if_nametoindex" in libc. + pub fn resolve(n: []const u8, io: Io) ResolveError!Interface { + return io.vtable.netInterfaceNameResolve(io.userdata, n); + } + }; + + pub const NameError = Io.UnexpectedError || Io.Cancelable; + + /// Asserts not `none`. + /// + /// Corresponds to "if_indextoname" in libc. + pub fn name(i: Interface, io: Io) NameError!Name { + assert(i.index != 0); + return io.vtable.netInterfaceName(io.userdata, i); + } + + pub fn isNone(i: Interface) bool { + return i.index == 0; + } +}; + +/// An open socket connection with a network protocol that guarantees +/// sequencing, delivery, and prevents repetition. Typically TCP or UNIX domain +/// socket. pub const Stream = struct { + handle: Handle, + /// Underlying platform-defined type which may or may not be /// interchangeable with a file system file descriptor. - handle: Handle, - pub const Handle = switch (native_os) { .windows => std.windows.ws2_32.SOCKET, else => std.posix.fd_t, @@ -583,17 +673,6 @@ pub const Server = struct { } }; -pub const InterfaceIndexError = error{ - InterfaceNotFound, - AccessDenied, - SystemResources, -} || Io.UnexpectedError || Io.Cancelable; - -/// Otherwise known as "if_nametoindex". -pub fn interfaceIndex(io: Io, name: []const u8) InterfaceIndexError!u32 { - return io.vtable.netInterfaceIndex(io.userdata, name); -} - test { _ = HostName; } diff --git a/lib/std/Io/net/HostName.zig b/lib/std/Io/net/HostName.zig index 06cf4219b3dc1c4a60465d5960ad4a2df6912653..710f0cb43c33f5e63405f0c80430030e8e6c0eee 100644 --- a/lib/std/Io/net/HostName.zig +++ b/lib/std/Io/net/HostName.zig @@ -105,11 +105,11 @@ pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!L { var i: usize = 0; if (options.family != .ip6) { - options.addresses_buffer[i] = .{ .ip4 = .localhost(options.port) }; + options.addresses_buffer[i] = .{ .ip4 = .loopback(options.port) }; i += 1; } if (options.family != .ip4) { - options.addresses_buffer[i] = .{ .ip6 = .localhost(options.port) }; + options.addresses_buffer[i] = .{ .ip6 = .loopback(options.port) }; i += 1; } const canon_name = "localhost"; @@ -166,7 +166,7 @@ fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult switch (a) { .ip6 => |ip6| { da6.bytes = ip6.bytes; - da6.scope_id = ip6.scope_id; + da6.interface = ip6.interface; }, .ip4 => |ip4| { da6.bytes[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*; -- 2.54.0