| author | |
| committer | |
| log | e34bb9a4131c2f0f328a53419d237877152917fa |
| tree | 3da500b0bd4c4d57e046c043422ddccc1308beac |
| parent | 4e887625d45593f6053624858f85bcb848c13a62 |
8 files changed, 754 insertions(+), 394 deletions(-)
lib/std/Io.zig+2| ... | @@ -667,6 +667,8 @@ pub const VTable = struct { | ... | @@ -667,6 +667,8 @@ pub const VTable = struct { |
| 667 | netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize, | 667 | netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize, |
| 668 | netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize, | 668 | netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize, |
| 669 | netClose: *const fn (?*anyopaque, stream: net.Stream) void, | 669 | netClose: *const fn (?*anyopaque, stream: net.Stream) void, |
| 670 | /// Equivalent to libc "if_nametoindex". | ||
| 671 | netInterfaceIndex: *const fn (?*anyopaque, name: []const u8) net.InterfaceIndexError!u32, | ||
| 670 | }; | 672 | }; |
| 671 | 673 | ||
| 672 | pub const Cancelable = error{ | 674 | pub const Cancelable = error{ |
lib/std/Io/ThreadPool.zig+71| ... | @@ -135,6 +135,7 @@ pub fn io(pool: *Pool) Io { | ... | @@ -135,6 +135,7 @@ pub fn io(pool: *Pool) Io { |
| 135 | else => netWritePosix, | 135 | else => netWritePosix, |
| 136 | }, | 136 | }, |
| 137 | .netClose = netClose, | 137 | .netClose = netClose, |
| 138 | .netInterfaceIndex = netInterfaceIndex, | ||
| 138 | }, | 139 | }, |
| 139 | }; | 140 | }; |
| 140 | } | 141 | } |
| ... | @@ -1122,6 +1123,69 @@ fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void { | ... | @@ -1122,6 +1123,69 @@ fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void { |
| 1122 | return net_stream.close(); | 1123 | return net_stream.close(); |
| 1123 | } | 1124 | } |
| 1124 | 1125 | ||
| 1126 | fn netInterfaceIndex(userdata: ?*anyopaque, name: []const u8) Io.net.InterfaceIndexError!u32 { | ||
| 1127 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | ||
| 1128 | try pool.checkCancel(); | ||
| 1129 | |||
| 1130 | 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); | ||
| 1137 | const sock_fd: posix.fd_t = switch (posix.errno(rc)) { | ||
| 1138 | .SUCCESS => @intCast(rc), | ||
| 1139 | .ACCES => return error.AccessDenied, | ||
| 1140 | .MFILE => return error.SystemResources, | ||
| 1141 | .NFILE => return error.SystemResources, | ||
| 1142 | .NOBUFS => return error.SystemResources, | ||
| 1143 | .NOMEM => return error.SystemResources, | ||
| 1144 | else => |err| return posix.unexpectedErrno(err), | ||
| 1145 | }; | ||
| 1146 | defer posix.close(sock_fd); | ||
| 1147 | |||
| 1148 | while (true) { | ||
| 1149 | try pool.checkCancel(); | ||
| 1150 | switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) { | ||
| 1151 | .SUCCESS => return @bitCast(ifr.ifru.ivalue), | ||
| 1152 | .INVAL => |err| return badErrno(err), // Bad parameters. | ||
| 1153 | .NOTTY => |err| return badErrno(err), | ||
| 1154 | .NXIO => |err| return badErrno(err), | ||
| 1155 | .BADF => |err| return badErrno(err), // Always a race condition. | ||
| 1156 | .FAULT => |err| return badErrno(err), // Bad pointer parameter. | ||
| 1157 | .INTR => continue, | ||
| 1158 | .IO => |err| return badErrno(err), // sock_fd is not a file descriptor | ||
| 1159 | .NODEV => return error.InterfaceNotFound, | ||
| 1160 | else => |err| return posix.unexpectedErrno(err), | ||
| 1161 | } | ||
| 1162 | } | ||
| 1163 | } | ||
| 1164 | |||
| 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); | ||
| 1172 | if (index == 0) return error.InterfaceNotFound; | ||
| 1173 | return @bitCast(index); | ||
| 1174 | } | ||
| 1175 | |||
| 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)); | ||
| 1182 | if (index == 0) return error.InterfaceNotFound; | ||
| 1183 | return index; | ||
| 1184 | } | ||
| 1185 | |||
| 1186 | @compileError("std.net.if_nametoindex unimplemented for this OS"); | ||
| 1187 | } | ||
| 1188 | |||
| 1125 | const PosixAddress = extern union { | 1189 | const PosixAddress = extern union { |
| 1126 | any: posix.sockaddr, | 1190 | any: posix.sockaddr, |
| 1127 | in: posix.sockaddr.in, | 1191 | in: posix.sockaddr.in, |
| ... | @@ -1187,3 +1251,10 @@ fn address6ToPosix(a: Io.net.Ip6Address) posix.sockaddr.in6 { | ... | @@ -1187,3 +1251,10 @@ fn address6ToPosix(a: Io.net.Ip6Address) posix.sockaddr.in6 { |
| 1187 | .scope_id = a.scope_id, | 1251 | .scope_id = a.scope_id, |
| 1188 | }; | 1252 | }; |
| 1189 | } | 1253 | } |
| 1254 | |||
| 1255 | fn badErrno(err: posix.E) Io.UnexpectedError { | ||
| 1256 | switch (builtin.mode) { | ||
| 1257 | .Debug => std.debug.panic("programmer bug caused syscall error: {t}", .{err}), | ||
| 1258 | else => return error.Unexpected, | ||
| 1259 | } | ||
| 1260 | } |
lib/std/Io/net.zig+20-286| ... | @@ -4,6 +4,8 @@ const std = @import("../std.zig"); | ... | @@ -4,6 +4,8 @@ const std = @import("../std.zig"); |
| 4 | const Io = std.Io; | 4 | const Io = std.Io; |
| 5 | const assert = std.debug.assert; | 5 | const assert = std.debug.assert; |
| 6 | 6 | ||
| 7 | pub const HostName = @import("net/HostName.zig"); | ||
| 8 | |||
| 7 | pub const ListenError = std.net.Address.ListenError || Io.Cancelable; | 9 | pub const ListenError = std.net.Address.ListenError || Io.Cancelable; |
| 8 | 10 | ||
| 9 | pub const ListenOptions = struct { | 11 | pub const ListenOptions = struct { |
| ... | @@ -17,298 +19,17 @@ pub const ListenOptions = struct { | ... | @@ -17,298 +19,17 @@ pub const ListenOptions = struct { |
| 17 | force_nonblocking: bool = false, | 19 | force_nonblocking: bool = false, |
| 18 | }; | 20 | }; |
| 19 | 21 | ||
| 20 | /// An already-validated host name. A valid host name: | ||
| 21 | /// * Has length less than or equal to `max_len`. | ||
| 22 | /// * Is valid UTF-8. | ||
| 23 | /// * Lacks ASCII characters other than alphanumeric, '-', and '.'. | ||
| 24 | pub const HostName = struct { | ||
| 25 | /// Externally managed memory. Already checked to be valid. | ||
| 26 | bytes: []const u8, | ||
| 27 | |||
| 28 | pub const max_len = 255; | ||
| 29 | |||
| 30 | pub const InitError = error{ | ||
| 31 | NameTooLong, | ||
| 32 | InvalidHostName, | ||
| 33 | }; | ||
| 34 | |||
| 35 | pub fn init(bytes: []const u8) InitError!HostName { | ||
| 36 | if (bytes.len > max_len) return error.NameTooLong; | ||
| 37 | if (!std.unicode.utf8ValidateSlice(bytes)) return error.InvalidHostName; | ||
| 38 | for (bytes) |byte| { | ||
| 39 | if (!std.ascii.isAscii(byte) or byte == '.' or byte == '-' or std.ascii.isAlphanumeric(byte)) { | ||
| 40 | continue; | ||
| 41 | } | ||
| 42 | return error.InvalidHostName; | ||
| 43 | } | ||
| 44 | return .{ .bytes = bytes }; | ||
| 45 | } | ||
| 46 | |||
| 47 | pub const LookupOptions = struct { | ||
| 48 | port: u16, | ||
| 49 | /// Must have at least length 2. | ||
| 50 | addresses_buffer: []IpAddress, | ||
| 51 | /// If a buffer of at least `max_len` is not provided, `lookup` may | ||
| 52 | /// return successfully with zero-length `LookupResult.canonical_name_len`. | ||
| 53 | /// | ||
| 54 | /// Suggestion: if not interested in canonical name, pass an empty buffer; | ||
| 55 | /// otherwise pass a buffer of size `max_len`. | ||
| 56 | canonical_name_buffer: []u8, | ||
| 57 | /// `null` means either. | ||
| 58 | family: ?IpAddress.Tag = null, | ||
| 59 | }; | ||
| 60 | |||
| 61 | pub const LookupError = Io.Cancelable || Io.File.OpenError || Io.File.Reader.Error || error{ | ||
| 62 | UnknownHostName, | ||
| 63 | }; | ||
| 64 | |||
| 65 | pub const LookupResult = struct { | ||
| 66 | /// How many `LookupOptions.addresses_buffer` elements are populated. | ||
| 67 | addresses_len: usize = 0, | ||
| 68 | canonical_name: ?HostName = null, | ||
| 69 | }; | ||
| 70 | |||
| 71 | pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult { | ||
| 72 | const name = host_name.bytes; | ||
| 73 | assert(name.len <= max_len); | ||
| 74 | assert(options.addresses_buffer.len >= 2); | ||
| 75 | |||
| 76 | if (native_os == .windows) @compileError("TODO"); | ||
| 77 | if (builtin.link_libc) @compileError("TODO"); | ||
| 78 | if (native_os == .linux) { | ||
| 79 | if (options.family != .ip6) { | ||
| 80 | if (IpAddress.parseIp4(name, options.port)) |addr| { | ||
| 81 | options.addresses_buffer[0] = addr; | ||
| 82 | return .{ .addresses_len = 1 }; | ||
| 83 | } else |_| {} | ||
| 84 | } | ||
| 85 | if (options.family != .ip4) { | ||
| 86 | if (IpAddress.parseIp6(name, options.port)) |addr| { | ||
| 87 | options.addresses_buffer[0] = addr; | ||
| 88 | return .{ .addresses_len = 1 }; | ||
| 89 | } else |_| {} | ||
| 90 | } | ||
| 91 | { | ||
| 92 | const result = try lookupHosts(host_name, io, options); | ||
| 93 | if (result.addresses_len > 0) return sortLookupResults(options, result); | ||
| 94 | } | ||
| 95 | { | ||
| 96 | // RFC 6761 Section 6.3.3 | ||
| 97 | // Name resolution APIs and libraries SHOULD recognize | ||
| 98 | // localhost names as special and SHOULD always return the IP | ||
| 99 | // loopback address for address queries and negative responses | ||
| 100 | // for all other query types. | ||
| 101 | |||
| 102 | // Check for equal to "localhost(.)" or ends in ".localhost(.)" | ||
| 103 | const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost"; | ||
| 104 | if (std.mem.endsWith(u8, name, localhost) and | ||
| 105 | (name.len == localhost.len or name[name.len - localhost.len] == '.')) | ||
| 106 | { | ||
| 107 | var i: usize = 0; | ||
| 108 | if (options.family != .ip6) { | ||
| 109 | options.addresses_buffer[i] = .{ .ip4 = .localhost(options.port) }; | ||
| 110 | i += 1; | ||
| 111 | } | ||
| 112 | if (options.family != .ip4) { | ||
| 113 | options.addresses_buffer[i] = .{ .ip6 = .localhost(options.port) }; | ||
| 114 | i += 1; | ||
| 115 | } | ||
| 116 | const canon_name = "localhost"; | ||
| 117 | const canon_name_dest = options.canonical_name_buffer[0..canon_name.len]; | ||
| 118 | canon_name_dest.* = canon_name.*; | ||
| 119 | return sortLookupResults(options, .{ | ||
| 120 | .addresses_len = i, | ||
| 121 | .canonical_name = .{ .bytes = canon_name_dest }, | ||
| 122 | }); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | { | ||
| 126 | const result = try lookupDns(io, options); | ||
| 127 | if (result.addresses_len > 0) return sortLookupResults(options, result); | ||
| 128 | } | ||
| 129 | return error.UnknownHostName; | ||
| 130 | } | ||
| 131 | @compileError("unimplemented"); | ||
| 132 | } | ||
| 133 | |||
| 134 | fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult { | ||
| 135 | const addresses = options.addresses_buffer[0..result.addresses_len]; | ||
| 136 | // No further processing is needed if there are fewer than 2 results or | ||
| 137 | // if there are only IPv4 results. | ||
| 138 | if (addresses.len < 2) return result; | ||
| 139 | const all_ip4 = for (addresses) |a| switch (a) { | ||
| 140 | .ip4 => continue, | ||
| 141 | .ip6 => break false, | ||
| 142 | } else true; | ||
| 143 | if (all_ip4) return result; | ||
| 144 | |||
| 145 | // RFC 3484/6724 describes how destination address selection is | ||
| 146 | // supposed to work. However, to implement it requires making a bunch | ||
| 147 | // of networking syscalls, which is unnecessarily high latency, | ||
| 148 | // especially if implemented serially. Furthermore, rules 3, 4, and 7 | ||
| 149 | // have excessive runtime and code size cost and dubious benefit. | ||
| 150 | // | ||
| 151 | // Therefore, this logic sorts only using values available without | ||
| 152 | // doing any syscalls, relying on the calling code to have a | ||
| 153 | // meta-strategy such as attempting connection to multiple results at | ||
| 154 | // once and keeping the fastest response while canceling the others. | ||
| 155 | |||
| 156 | const S = struct { | ||
| 157 | pub fn lessThan(s: @This(), lhs: IpAddress, rhs: IpAddress) bool { | ||
| 158 | return sortKey(s, lhs) < sortKey(s, rhs); | ||
| 159 | } | ||
| 160 | |||
| 161 | fn sortKey(s: @This(), a: IpAddress) i32 { | ||
| 162 | _ = s; | ||
| 163 | var da6: Ip6Address = .{ | ||
| 164 | .port = 65535, | ||
| 165 | .bytes = undefined, | ||
| 166 | }; | ||
| 167 | switch (a) { | ||
| 168 | .ip6 => |ip6| { | ||
| 169 | da6.bytes = ip6.bytes; | ||
| 170 | da6.scope_id = ip6.scope_id; | ||
| 171 | }, | ||
| 172 | .ip4 => |ip4| { | ||
| 173 | da6.bytes[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*; | ||
| 174 | da6.bytes[12..].* = ip4.bytes; | ||
| 175 | }, | ||
| 176 | } | ||
| 177 | const da6_scope: i32 = da6.scope(); | ||
| 178 | const da6_prec: i32 = da6.policy().prec; | ||
| 179 | var key: i32 = 0; | ||
| 180 | key |= da6_prec << 20; | ||
| 181 | key |= (15 - da6_scope) << 16; | ||
| 182 | return key; | ||
| 183 | } | ||
| 184 | }; | ||
| 185 | std.mem.sort(IpAddress, addresses, @as(S, .{}), S.lessThan); | ||
| 186 | return result; | ||
| 187 | } | ||
| 188 | |||
| 189 | fn lookupDns(io: Io, options: LookupOptions) !LookupResult { | ||
| 190 | _ = io; | ||
| 191 | _ = options; | ||
| 192 | @panic("TODO"); | ||
| 193 | } | ||
| 194 | |||
| 195 | fn lookupHosts(host_name: HostName, io: Io, options: LookupOptions) !LookupResult { | ||
| 196 | const file = Io.File.openAbsolute(io, "/etc/hosts", .{}) catch |err| switch (err) { | ||
| 197 | error.FileNotFound, | ||
| 198 | error.NotDir, | ||
| 199 | error.AccessDenied, | ||
| 200 | => return .{}, | ||
| 201 | |||
| 202 | else => |e| return e, | ||
| 203 | }; | ||
| 204 | defer file.close(io); | ||
| 205 | |||
| 206 | var line_buf: [512]u8 = undefined; | ||
| 207 | var file_reader = file.reader(io, &line_buf); | ||
| 208 | return lookupHostsReader(host_name, options, &file_reader.interface) catch |err| switch (err) { | ||
| 209 | error.ReadFailed => return file_reader.err.?, | ||
| 210 | }; | ||
| 211 | } | ||
| 212 | |||
| 213 | fn lookupHostsReader(host_name: HostName, options: LookupOptions, reader: *Io.Reader) error{ReadFailed}!LookupResult { | ||
| 214 | var addresses_len: usize = 0; | ||
| 215 | var canonical_name: ?HostName = null; | ||
| 216 | while (true) { | ||
| 217 | const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) { | ||
| 218 | error.StreamTooLong => { | ||
| 219 | // Skip lines that are too long. | ||
| 220 | _ = reader.discardDelimiterInclusive('\n') catch |e| switch (e) { | ||
| 221 | error.EndOfStream => break, | ||
| 222 | error.ReadFailed => return error.ReadFailed, | ||
| 223 | }; | ||
| 224 | continue; | ||
| 225 | }, | ||
| 226 | error.ReadFailed => return error.ReadFailed, | ||
| 227 | error.EndOfStream => break, | ||
| 228 | }; | ||
| 229 | var split_it = std.mem.splitScalar(u8, line, '#'); | ||
| 230 | const no_comment_line = split_it.first(); | ||
| 231 | |||
| 232 | var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t"); | ||
| 233 | const ip_text = line_it.next() orelse continue; | ||
| 234 | var first_name_text: ?[]const u8 = null; | ||
| 235 | while (line_it.next()) |name_text| { | ||
| 236 | if (std.mem.eql(u8, name_text, host_name.bytes)) { | ||
| 237 | if (first_name_text == null) first_name_text = name_text; | ||
| 238 | break; | ||
| 239 | } | ||
| 240 | } else continue; | ||
| 241 | |||
| 242 | if (canonical_name == null) { | ||
| 243 | if (HostName.init(first_name_text.?)) |name_text| { | ||
| 244 | if (name_text.bytes.len <= options.canonical_name_buffer.len) { | ||
| 245 | const canonical_name_dest = options.canonical_name_buffer[0..name_text.bytes.len]; | ||
| 246 | @memcpy(canonical_name_dest, name_text.bytes); | ||
| 247 | canonical_name = .{ .bytes = canonical_name_dest }; | ||
| 248 | } | ||
| 249 | } else |_| {} | ||
| 250 | } | ||
| 251 | |||
| 252 | if (options.family != .ip6) { | ||
| 253 | if (IpAddress.parseIp4(ip_text, options.port)) |addr| { | ||
| 254 | options.addresses_buffer[addresses_len] = addr; | ||
| 255 | addresses_len += 1; | ||
| 256 | if (options.addresses_buffer.len - addresses_len == 0) return .{ | ||
| 257 | .addresses_len = addresses_len, | ||
| 258 | .canonical_name = canonical_name, | ||
| 259 | }; | ||
| 260 | } else |_| {} | ||
| 261 | } | ||
| 262 | if (options.family != .ip4) { | ||
| 263 | if (IpAddress.parseIp6(ip_text, options.port)) |addr| { | ||
| 264 | options.addresses_buffer[addresses_len] = addr; | ||
| 265 | addresses_len += 1; | ||
| 266 | if (options.addresses_buffer.len - addresses_len == 0) return .{ | ||
| 267 | .addresses_len = addresses_len, | ||
| 268 | .canonical_name = canonical_name, | ||
| 269 | }; | ||
| 270 | } else |_| {} | ||
| 271 | } | ||
| 272 | } | ||
| 273 | return .{ | ||
| 274 | .addresses_len = addresses_len, | ||
| 275 | .canonical_name = canonical_name, | ||
| 276 | }; | ||
| 277 | } | ||
| 278 | |||
| 279 | pub const ConnectTcpError = LookupError || IpAddress.ConnectTcpError; | ||
| 280 | |||
| 281 | pub fn connectTcp(host_name: HostName, io: Io, port: u16) ConnectTcpError!Stream { | ||
| 282 | var addresses_buffer: [32]IpAddress = undefined; | ||
| 283 | |||
| 284 | const results = try lookup(host_name, .{ | ||
| 285 | .port = port, | ||
| 286 | .addresses_buffer = &addresses_buffer, | ||
| 287 | .canonical_name_buffer = &.{}, | ||
| 288 | }); | ||
| 289 | const addresses = addresses_buffer[0..results.addresses_len]; | ||
| 290 | |||
| 291 | if (addresses.len == 0) return error.UnknownHostName; | ||
| 292 | |||
| 293 | for (addresses) |addr| { | ||
| 294 | return addr.connectTcp(io) catch |err| switch (err) { | ||
| 295 | error.ConnectionRefused => continue, | ||
| 296 | else => |e| return e, | ||
| 297 | }; | ||
| 298 | } | ||
| 299 | return error.ConnectionRefused; | ||
| 300 | } | ||
| 301 | }; | ||
| 302 | |||
| 303 | pub const IpAddress = union(enum) { | 22 | pub const IpAddress = union(enum) { |
| 304 | ip4: Ip4Address, | 23 | ip4: Ip4Address, |
| 305 | ip6: Ip6Address, | 24 | ip6: Ip6Address, |
| 306 | 25 | ||
| 307 | pub const Tag = @typeInfo(IpAddress).@"union".tag_type.?; | 26 | pub const Family = @typeInfo(IpAddress).@"union".tag_type.?; |
| 308 | 27 | ||
| 309 | /// Parse the given IP address string into an `IpAddress` value. | 28 | /// Parse the given IP address string into an `IpAddress` value. |
| 310 | pub fn parse(name: []const u8, port: u16) !IpAddress { | 29 | pub fn parse(name: []const u8, port: u16) !IpAddress { |
| 311 | if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) { | 30 | if (Ip4Address.parse(name, port)) |ip4| { |
| 31 | return .{ .ip4 = ip4 }; | ||
| 32 | } else |err| switch (err) { | ||
| 312 | error.Overflow, | 33 | error.Overflow, |
| 313 | error.InvalidEnd, | 34 | error.InvalidEnd, |
| 314 | error.InvalidCharacter, | 35 | error.InvalidCharacter, |
| ... | @@ -317,7 +38,9 @@ pub const IpAddress = union(enum) { | ... | @@ -317,7 +38,9 @@ pub const IpAddress = union(enum) { |
| 317 | => {}, | 38 | => {}, |
| 318 | } | 39 | } |
| 319 | 40 | ||
| 320 | if (parseIp6(name, port)) |ip6| return ip6 else |err| switch (err) { | 41 | if (Ip6Address.parse(name, port)) |ip6| { |
| 42 | return .{ .ip6 = ip6 }; | ||
| 43 | } else |err| switch (err) { | ||
| 321 | error.Overflow, | 44 | error.Overflow, |
| 322 | error.InvalidEnd, | 45 | error.InvalidEnd, |
| 323 | error.InvalidCharacter, | 46 | error.InvalidCharacter, |
| ... | @@ -859,3 +582,14 @@ pub const Server = struct { | ... | @@ -859,3 +582,14 @@ pub const Server = struct { |
| 859 | return io.vtable.accept(io, s); | 582 | return io.vtable.accept(io, s); |
| 860 | } | 583 | } |
| 861 | }; | 584 | }; |
| 585 | |||
| 586 | pub const InterfaceIndexError = error{ | ||
| 587 | InterfaceNotFound, | ||
| 588 | AccessDenied, | ||
| 589 | SystemResources, | ||
| 590 | } || Io.UnexpectedError || Io.Cancelable; | ||
| 591 | |||
| 592 | /// Otherwise known as "if_nametoindex". | ||
| 593 | pub fn interfaceIndex(io: Io, name: []const u8) InterfaceIndexError!u32 { | ||
| 594 | return io.vtable.netInterfaceIndex(io.userdata, name); | ||
| 595 | } |
lib/std/Io/net/HostName.zig created+631| ... | @@ -0,0 +1,631 @@ | ||
| 1 | //! An already-validated host name. A valid host name: | ||
| 2 | //! * Has length less than or equal to `max_len`. | ||
| 3 | //! * Is valid UTF-8. | ||
| 4 | //! * Lacks ASCII characters other than alphanumeric, '-', and '.'. | ||
| 5 | const HostName = @This(); | ||
| 6 | |||
| 7 | const builtin = @import("builtin"); | ||
| 8 | const native_os = builtin.os.tag; | ||
| 9 | |||
| 10 | const std = @import("../../std.zig"); | ||
| 11 | const Io = std.Io; | ||
| 12 | const IpAddress = Io.net.IpAddress; | ||
| 13 | const Ip6Address = Io.net.Ip6Address; | ||
| 14 | const assert = std.debug.assert; | ||
| 15 | const Stream = Io.net.Stream; | ||
| 16 | |||
| 17 | /// Externally managed memory. Already checked to be valid. | ||
| 18 | bytes: []const u8, | ||
| 19 | |||
| 20 | pub const max_len = 255; | ||
| 21 | |||
| 22 | pub const InitError = error{ | ||
| 23 | NameTooLong, | ||
| 24 | InvalidHostName, | ||
| 25 | }; | ||
| 26 | |||
| 27 | pub fn init(bytes: []const u8) InitError!HostName { | ||
| 28 | if (bytes.len > max_len) return error.NameTooLong; | ||
| 29 | if (!std.unicode.utf8ValidateSlice(bytes)) return error.InvalidHostName; | ||
| 30 | for (bytes) |byte| { | ||
| 31 | if (!std.ascii.isAscii(byte) or byte == '.' or byte == '-' or std.ascii.isAlphanumeric(byte)) { | ||
| 32 | continue; | ||
| 33 | } | ||
| 34 | return error.InvalidHostName; | ||
| 35 | } | ||
| 36 | return .{ .bytes = bytes }; | ||
| 37 | } | ||
| 38 | |||
| 39 | /// TODO add a retry field here | ||
| 40 | pub const LookupOptions = struct { | ||
| 41 | port: u16, | ||
| 42 | /// Must have at least length 2. | ||
| 43 | addresses_buffer: []IpAddress, | ||
| 44 | canonical_name_buffer: *[max_len]u8, | ||
| 45 | /// `null` means either. | ||
| 46 | family: ?IpAddress.Family = null, | ||
| 47 | }; | ||
| 48 | |||
| 49 | pub const LookupError = Io.Cancelable || Io.File.OpenError || Io.File.Reader.Error || error{ | ||
| 50 | UnknownHostName, | ||
| 51 | ResolvConfParseFailed, | ||
| 52 | // TODO remove from error set; retry a few times then report a different error | ||
| 53 | TemporaryNameServerFailure, | ||
| 54 | InvalidDnsARecord, | ||
| 55 | InvalidDnsAAAARecord, | ||
| 56 | NameServerFailure, | ||
| 57 | }; | ||
| 58 | |||
| 59 | pub const LookupResult = struct { | ||
| 60 | /// How many `LookupOptions.addresses_buffer` elements are populated. | ||
| 61 | addresses_len: usize, | ||
| 62 | canonical_name: HostName, | ||
| 63 | |||
| 64 | pub const empty: LookupResult = .{ | ||
| 65 | .addresses_len = 0, | ||
| 66 | .canonical_name = undefined, | ||
| 67 | }; | ||
| 68 | }; | ||
| 69 | |||
| 70 | pub fn lookup(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult { | ||
| 71 | const name = host_name.bytes; | ||
| 72 | assert(name.len <= max_len); | ||
| 73 | assert(options.addresses_buffer.len >= 2); | ||
| 74 | |||
| 75 | if (native_os == .windows) @compileError("TODO"); | ||
| 76 | if (builtin.link_libc) @compileError("TODO"); | ||
| 77 | if (native_os == .linux) { | ||
| 78 | if (options.family != .ip6) { | ||
| 79 | if (IpAddress.parseIp4(name, options.port)) |addr| { | ||
| 80 | options.addresses_buffer[0] = addr; | ||
| 81 | return .{ .addresses_len = 1, .canonical_name = copyCanon(options.canonical_name_buffer, name) }; | ||
| 82 | } else |_| {} | ||
| 83 | } | ||
| 84 | if (options.family != .ip4) { | ||
| 85 | if (IpAddress.parseIp6(name, options.port)) |addr| { | ||
| 86 | options.addresses_buffer[0] = addr; | ||
| 87 | return .{ .addresses_len = 1, .canonical_name = copyCanon(options.canonical_name_buffer, name) }; | ||
| 88 | } else |_| {} | ||
| 89 | } | ||
| 90 | { | ||
| 91 | const result = try lookupHosts(host_name, io, options); | ||
| 92 | if (result.addresses_len > 0) return sortLookupResults(options, result); | ||
| 93 | } | ||
| 94 | { | ||
| 95 | // RFC 6761 Section 6.3.3 | ||
| 96 | // Name resolution APIs and libraries SHOULD recognize | ||
| 97 | // localhost names as special and SHOULD always return the IP | ||
| 98 | // loopback address for address queries and negative responses | ||
| 99 | // for all other query types. | ||
| 100 | |||
| 101 | // Check for equal to "localhost(.)" or ends in ".localhost(.)" | ||
| 102 | const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost"; | ||
| 103 | if (std.mem.endsWith(u8, name, localhost) and | ||
| 104 | (name.len == localhost.len or name[name.len - localhost.len] == '.')) | ||
| 105 | { | ||
| 106 | var i: usize = 0; | ||
| 107 | if (options.family != .ip6) { | ||
| 108 | options.addresses_buffer[i] = .{ .ip4 = .localhost(options.port) }; | ||
| 109 | i += 1; | ||
| 110 | } | ||
| 111 | if (options.family != .ip4) { | ||
| 112 | options.addresses_buffer[i] = .{ .ip6 = .localhost(options.port) }; | ||
| 113 | i += 1; | ||
| 114 | } | ||
| 115 | const canon_name = "localhost"; | ||
| 116 | const canon_name_dest = options.canonical_name_buffer[0..canon_name.len]; | ||
| 117 | canon_name_dest.* = canon_name.*; | ||
| 118 | return sortLookupResults(options, .{ | ||
| 119 | .addresses_len = i, | ||
| 120 | .canonical_name = .{ .bytes = canon_name_dest }, | ||
| 121 | }); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | { | ||
| 125 | const result = try lookupDnsSearch(host_name, io, options); | ||
| 126 | if (result.addresses_len > 0) return sortLookupResults(options, result); | ||
| 127 | } | ||
| 128 | return error.UnknownHostName; | ||
| 129 | } | ||
| 130 | @compileError("unimplemented"); | ||
| 131 | } | ||
| 132 | |||
| 133 | fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult { | ||
| 134 | const addresses = options.addresses_buffer[0..result.addresses_len]; | ||
| 135 | // No further processing is needed if there are fewer than 2 results or | ||
| 136 | // if there are only IPv4 results. | ||
| 137 | if (addresses.len < 2) return result; | ||
| 138 | const all_ip4 = for (addresses) |a| switch (a) { | ||
| 139 | .ip4 => continue, | ||
| 140 | .ip6 => break false, | ||
| 141 | } else true; | ||
| 142 | if (all_ip4) return result; | ||
| 143 | |||
| 144 | // RFC 3484/6724 describes how destination address selection is | ||
| 145 | // supposed to work. However, to implement it requires making a bunch | ||
| 146 | // of networking syscalls, which is unnecessarily high latency, | ||
| 147 | // especially if implemented serially. Furthermore, rules 3, 4, and 7 | ||
| 148 | // have excessive runtime and code size cost and dubious benefit. | ||
| 149 | // | ||
| 150 | // Therefore, this logic sorts only using values available without | ||
| 151 | // doing any syscalls, relying on the calling code to have a | ||
| 152 | // meta-strategy such as attempting connection to multiple results at | ||
| 153 | // once and keeping the fastest response while canceling the others. | ||
| 154 | |||
| 155 | const S = struct { | ||
| 156 | pub fn lessThan(s: @This(), lhs: IpAddress, rhs: IpAddress) bool { | ||
| 157 | return sortKey(s, lhs) < sortKey(s, rhs); | ||
| 158 | } | ||
| 159 | |||
| 160 | fn sortKey(s: @This(), a: IpAddress) i32 { | ||
| 161 | _ = s; | ||
| 162 | var da6: Ip6Address = .{ | ||
| 163 | .port = 65535, | ||
| 164 | .bytes = undefined, | ||
| 165 | }; | ||
| 166 | switch (a) { | ||
| 167 | .ip6 => |ip6| { | ||
| 168 | da6.bytes = ip6.bytes; | ||
| 169 | da6.scope_id = ip6.scope_id; | ||
| 170 | }, | ||
| 171 | .ip4 => |ip4| { | ||
| 172 | da6.bytes[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*; | ||
| 173 | da6.bytes[12..].* = ip4.bytes; | ||
| 174 | }, | ||
| 175 | } | ||
| 176 | const da6_scope: i32 = da6.scope(); | ||
| 177 | const da6_prec: i32 = da6.policy().prec; | ||
| 178 | var key: i32 = 0; | ||
| 179 | key |= da6_prec << 20; | ||
| 180 | key |= (15 - da6_scope) << 16; | ||
| 181 | return key; | ||
| 182 | } | ||
| 183 | }; | ||
| 184 | std.mem.sort(IpAddress, addresses, @as(S, .{}), S.lessThan); | ||
| 185 | return result; | ||
| 186 | } | ||
| 187 | |||
| 188 | fn lookupDnsSearch(host_name: HostName, io: Io, options: LookupOptions) !LookupResult { | ||
| 189 | const rc = ResolvConf.init(io) catch return error.ResolvConfParseFailed; | ||
| 190 | |||
| 191 | // Count dots, suppress search when >=ndots or name ends in | ||
| 192 | // a dot, which is an explicit request for global scope. | ||
| 193 | const dots = std.mem.countScalar(u8, host_name.bytes, '.'); | ||
| 194 | const search_len = if (dots >= rc.ndots or std.mem.endsWith(u8, host_name.bytes, ".")) 0 else rc.search_len; | ||
| 195 | const search = rc.search_buffer[0..search_len]; | ||
| 196 | |||
| 197 | var canon_name = host_name.bytes; | ||
| 198 | |||
| 199 | // Strip final dot for canon, fail if multiple trailing dots. | ||
| 200 | if (std.mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1; | ||
| 201 | if (std.mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName; | ||
| 202 | |||
| 203 | // Name with search domain appended is set up in `canon_name`. This | ||
| 204 | // both provides the desired default canonical name (if the requested | ||
| 205 | // name is not a CNAME record) and serves as a buffer for passing the | ||
| 206 | // full requested name to `lookupDns`. | ||
| 207 | @memcpy(options.canonical_name_buffer[0..canon_name.len], canon_name); | ||
| 208 | options.canonical_name_buffer[canon_name.len] = '.'; | ||
| 209 | var it = std.mem.tokenizeAny(u8, search, " \t"); | ||
| 210 | while (it.next()) |token| { | ||
| 211 | @memcpy(options.canonical_name_buffer[canon_name.len + 1 ..][0..token.len], token); | ||
| 212 | const lookup_canon_name = options.canonical_name_buffer[0 .. canon_name.len + 1 + token.len]; | ||
| 213 | const result = try lookupDns(io, lookup_canon_name, &rc, options); | ||
| 214 | if (result.addresses_len > 0) return sortLookupResults(options, result); | ||
| 215 | } | ||
| 216 | |||
| 217 | const lookup_canon_name = options.canonical_name_buffer[0..canon_name.len]; | ||
| 218 | return lookupDns(io, lookup_canon_name, &rc, options); | ||
| 219 | } | ||
| 220 | |||
| 221 | fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, options: LookupOptions) !LookupResult { | ||
| 222 | const family_records: [2]struct { af: IpAddress.Family, rr: u8 } = .{ | ||
| 223 | .{ .af = .ip6, .rr = std.posix.RR.A }, | ||
| 224 | .{ .af = .ip4, .rr = std.posix.RR.AAAA }, | ||
| 225 | }; | ||
| 226 | var query_buffers: [2][280]u8 = undefined; | ||
| 227 | var queries_buffer: [2][]const u8 = undefined; | ||
| 228 | var answer_buffers: [2][512]u8 = undefined; | ||
| 229 | var answers_buffer: [2][]u8 = .{ &answer_buffers[0], &answer_buffers[1] }; | ||
| 230 | var nq: usize = 0; | ||
| 231 | |||
| 232 | for (family_records) |fr| { | ||
| 233 | if (options.family != fr.af) { | ||
| 234 | const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr); | ||
| 235 | queries_buffer[nq] = query_buffers[nq][0..len]; | ||
| 236 | nq += 1; | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | const queries = queries_buffer[0..nq]; | ||
| 241 | const replies = answers_buffer[0..nq]; | ||
| 242 | try rc.sendMessage(io, queries, replies); | ||
| 243 | |||
| 244 | for (replies) |reply| { | ||
| 245 | if (reply.len < 4 or (reply[3] & 15) == 2) return error.TemporaryNameServerFailure; | ||
| 246 | if ((reply[3] & 15) == 3) return .empty; | ||
| 247 | if ((reply[3] & 15) != 0) return error.UnknownHostName; | ||
| 248 | } | ||
| 249 | |||
| 250 | var addresses_len: usize = 0; | ||
| 251 | var canonical_name: ?HostName = null; | ||
| 252 | |||
| 253 | for (replies) |reply| { | ||
| 254 | var it = DnsResponse.init(reply) catch { | ||
| 255 | // TODO accept a diagnostics struct and append warnings | ||
| 256 | continue; | ||
| 257 | }; | ||
| 258 | while (it.next() catch { | ||
| 259 | // TODO accept a diagnostics struct and append warnings | ||
| 260 | continue; | ||
| 261 | }) |answer| switch (answer.rr) { | ||
| 262 | std.posix.RR.A => { | ||
| 263 | if (answer.data.len != 4) return error.InvalidDnsARecord; | ||
| 264 | options.addresses_buffer[addresses_len] = .{ .ip4 = .{ | ||
| 265 | .bytes = answer.data[0..4].*, | ||
| 266 | .port = options.port, | ||
| 267 | } }; | ||
| 268 | addresses_len += 1; | ||
| 269 | }, | ||
| 270 | std.posix.RR.AAAA => { | ||
| 271 | if (answer.data.len != 16) return error.InvalidDnsAAAARecord; | ||
| 272 | options.addresses_buffer[addresses_len] = .{ .ip6 = .{ | ||
| 273 | .bytes = answer.data[0..16].*, | ||
| 274 | .port = options.port, | ||
| 275 | } }; | ||
| 276 | addresses_len += 1; | ||
| 277 | }, | ||
| 278 | std.posix.RR.CNAME => { | ||
| 279 | _ = &canonical_name; | ||
| 280 | @panic("TODO"); | ||
| 281 | //var tmp: [256]u8 = undefined; | ||
| 282 | //// Returns len of compressed name. strlen to get canon name. | ||
| 283 | //_ = try posix.dn_expand(packet, answer.data, &tmp); | ||
| 284 | //const canon_name = mem.sliceTo(&tmp, 0); | ||
| 285 | //if (isValidHostName(canon_name)) { | ||
| 286 | // ctx.canon.items.len = 0; | ||
| 287 | // try ctx.canon.appendSlice(gpa, canon_name); | ||
| 288 | //} | ||
| 289 | }, | ||
| 290 | else => continue, | ||
| 291 | }; | ||
| 292 | } | ||
| 293 | |||
| 294 | if (addresses_len != 0) return .{ | ||
| 295 | .addresses_len = addresses_len, | ||
| 296 | .canonical_name = canonical_name orelse .{ .bytes = lookup_canon_name }, | ||
| 297 | }; | ||
| 298 | |||
| 299 | return error.NameServerFailure; | ||
| 300 | } | ||
| 301 | |||
| 302 | fn lookupHosts(host_name: HostName, io: Io, options: LookupOptions) !LookupResult { | ||
| 303 | const file = Io.File.openAbsolute(io, "/etc/hosts", .{}) catch |err| switch (err) { | ||
| 304 | error.FileNotFound, | ||
| 305 | error.NotDir, | ||
| 306 | error.AccessDenied, | ||
| 307 | => return .empty, | ||
| 308 | |||
| 309 | else => |e| return e, | ||
| 310 | }; | ||
| 311 | defer file.close(io); | ||
| 312 | |||
| 313 | var line_buf: [512]u8 = undefined; | ||
| 314 | var file_reader = file.reader(io, &line_buf); | ||
| 315 | return lookupHostsReader(host_name, options, &file_reader.interface) catch |err| switch (err) { | ||
| 316 | error.ReadFailed => return file_reader.err.?, | ||
| 317 | }; | ||
| 318 | } | ||
| 319 | |||
| 320 | fn lookupHostsReader(host_name: HostName, options: LookupOptions, reader: *Io.Reader) error{ReadFailed}!LookupResult { | ||
| 321 | var addresses_len: usize = 0; | ||
| 322 | var canonical_name: ?HostName = null; | ||
| 323 | while (true) { | ||
| 324 | const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) { | ||
| 325 | error.StreamTooLong => { | ||
| 326 | // Skip lines that are too long. | ||
| 327 | _ = reader.discardDelimiterInclusive('\n') catch |e| switch (e) { | ||
| 328 | error.EndOfStream => break, | ||
| 329 | error.ReadFailed => return error.ReadFailed, | ||
| 330 | }; | ||
| 331 | continue; | ||
| 332 | }, | ||
| 333 | error.ReadFailed => return error.ReadFailed, | ||
| 334 | error.EndOfStream => break, | ||
| 335 | }; | ||
| 336 | var split_it = std.mem.splitScalar(u8, line, '#'); | ||
| 337 | const no_comment_line = split_it.first(); | ||
| 338 | |||
| 339 | var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t"); | ||
| 340 | const ip_text = line_it.next() orelse continue; | ||
| 341 | var first_name_text: ?[]const u8 = null; | ||
| 342 | while (line_it.next()) |name_text| { | ||
| 343 | if (std.mem.eql(u8, name_text, host_name.bytes)) { | ||
| 344 | if (first_name_text == null) first_name_text = name_text; | ||
| 345 | break; | ||
| 346 | } | ||
| 347 | } else continue; | ||
| 348 | |||
| 349 | if (canonical_name == null) { | ||
| 350 | if (HostName.init(first_name_text.?)) |name_text| { | ||
| 351 | if (name_text.bytes.len <= options.canonical_name_buffer.len) { | ||
| 352 | const canonical_name_dest = options.canonical_name_buffer[0..name_text.bytes.len]; | ||
| 353 | @memcpy(canonical_name_dest, name_text.bytes); | ||
| 354 | canonical_name = .{ .bytes = canonical_name_dest }; | ||
| 355 | } | ||
| 356 | } else |_| {} | ||
| 357 | } | ||
| 358 | |||
| 359 | if (options.family != .ip6) { | ||
| 360 | if (IpAddress.parseIp4(ip_text, options.port)) |addr| { | ||
| 361 | options.addresses_buffer[addresses_len] = addr; | ||
| 362 | addresses_len += 1; | ||
| 363 | if (options.addresses_buffer.len - addresses_len == 0) return .{ | ||
| 364 | .addresses_len = addresses_len, | ||
| 365 | .canonical_name = canonical_name orelse copyCanon(options.canonical_name_buffer, ip_text), | ||
| 366 | }; | ||
| 367 | } else |_| {} | ||
| 368 | } | ||
| 369 | if (options.family != .ip4) { | ||
| 370 | if (IpAddress.parseIp6(ip_text, options.port)) |addr| { | ||
| 371 | options.addresses_buffer[addresses_len] = addr; | ||
| 372 | addresses_len += 1; | ||
| 373 | if (options.addresses_buffer.len - addresses_len == 0) return .{ | ||
| 374 | .addresses_len = addresses_len, | ||
| 375 | .canonical_name = canonical_name orelse copyCanon(options.canonical_name_buffer, ip_text), | ||
| 376 | }; | ||
| 377 | } else |_| {} | ||
| 378 | } | ||
| 379 | } | ||
| 380 | if (canonical_name == null) assert(addresses_len == 0); | ||
| 381 | return .{ | ||
| 382 | .addresses_len = addresses_len, | ||
| 383 | .canonical_name = canonical_name orelse undefined, | ||
| 384 | }; | ||
| 385 | } | ||
| 386 | |||
| 387 | fn copyCanon(canonical_name_buffer: *[max_len]u8, name: []const u8) HostName { | ||
| 388 | const dest = canonical_name_buffer[0..name.len]; | ||
| 389 | @memcpy(dest, name); | ||
| 390 | return .{ .bytes = dest }; | ||
| 391 | } | ||
| 392 | |||
| 393 | /// Writes DNS resolution query packet data to `w`; at most 280 bytes. | ||
| 394 | fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: u8) usize { | ||
| 395 | // This implementation is ported from musl libc. | ||
| 396 | // A more idiomatic "ziggy" implementation would be welcome. | ||
| 397 | var name = dname; | ||
| 398 | if (std.mem.endsWith(u8, name, ".")) name.len -= 1; | ||
| 399 | assert(name.len <= 253); | ||
| 400 | const n = 17 + name.len + @intFromBool(name.len != 0); | ||
| 401 | |||
| 402 | // Construct query template - ID will be filled later | ||
| 403 | @memset(q[0..n], 0); | ||
| 404 | q[2] = @as(u8, op) * 8 + 1; | ||
| 405 | q[5] = 1; | ||
| 406 | @memcpy(q[13..][0..name.len], name); | ||
| 407 | var i: usize = 13; | ||
| 408 | var j: usize = undefined; | ||
| 409 | while (q[i] != 0) : (i = j + 1) { | ||
| 410 | j = i; | ||
| 411 | while (q[j] != 0 and q[j] != '.') : (j += 1) {} | ||
| 412 | // TODO determine the circumstances for this and whether or | ||
| 413 | // not this should be an error. | ||
| 414 | if (j - i - 1 > 62) unreachable; | ||
| 415 | q[i - 1] = @intCast(j - i); | ||
| 416 | } | ||
| 417 | q[i + 1] = ty; | ||
| 418 | q[i + 3] = class; | ||
| 419 | |||
| 420 | std.crypto.random.bytes(q[0..2]); | ||
| 421 | return n; | ||
| 422 | } | ||
| 423 | |||
| 424 | pub const ExpandDomainNameError = error{InvalidDnsPacket}; | ||
| 425 | |||
| 426 | pub fn expandDomainName( | ||
| 427 | msg: []const u8, | ||
| 428 | comp_dn: []const u8, | ||
| 429 | exp_dn: []u8, | ||
| 430 | ) ExpandDomainNameError!usize { | ||
| 431 | // This implementation is ported from musl libc. | ||
| 432 | // A more idiomatic "ziggy" implementation would be welcome. | ||
| 433 | var p = comp_dn.ptr; | ||
| 434 | var len: usize = std.math.maxInt(usize); | ||
| 435 | const end = msg.ptr + msg.len; | ||
| 436 | if (p == end or exp_dn.len == 0) return error.InvalidDnsPacket; | ||
| 437 | var dest = exp_dn.ptr; | ||
| 438 | const dend = dest + @min(exp_dn.len, 254); | ||
| 439 | // detect reference loop using an iteration counter | ||
| 440 | var i: usize = 0; | ||
| 441 | while (i < msg.len) : (i += 2) { | ||
| 442 | // loop invariants: p<end, dest<dend | ||
| 443 | if ((p[0] & 0xc0) != 0) { | ||
| 444 | if (p + 1 == end) return error.InvalidDnsPacket; | ||
| 445 | const j = @as(usize, p[0] & 0x3f) << 8 | p[1]; | ||
| 446 | if (len == std.math.maxInt(usize)) len = @intFromPtr(p) + 2 - @intFromPtr(comp_dn.ptr); | ||
| 447 | if (j >= msg.len) return error.InvalidDnsPacket; | ||
| 448 | p = msg.ptr + j; | ||
| 449 | } else if (p[0] != 0) { | ||
| 450 | if (dest != exp_dn.ptr) { | ||
| 451 | dest[0] = '.'; | ||
| 452 | dest += 1; | ||
| 453 | } | ||
| 454 | var j = p[0]; | ||
| 455 | p += 1; | ||
| 456 | if (j >= @intFromPtr(end) - @intFromPtr(p) or j >= @intFromPtr(dend) - @intFromPtr(dest)) { | ||
| 457 | return error.InvalidDnsPacket; | ||
| 458 | } | ||
| 459 | while (j != 0) { | ||
| 460 | j -= 1; | ||
| 461 | dest[0] = p[0]; | ||
| 462 | dest += 1; | ||
| 463 | p += 1; | ||
| 464 | } | ||
| 465 | } else { | ||
| 466 | dest[0] = 0; | ||
| 467 | if (len == std.math.maxInt(usize)) len = @intFromPtr(p) + 1 - @intFromPtr(comp_dn.ptr); | ||
| 468 | return len; | ||
| 469 | } | ||
| 470 | } | ||
| 471 | return error.InvalidDnsPacket; | ||
| 472 | } | ||
| 473 | |||
| 474 | pub const DnsResponse = struct { | ||
| 475 | bytes: []const u8, | ||
| 476 | |||
| 477 | pub const Answer = struct { | ||
| 478 | rr: u8, | ||
| 479 | data: []const u8, | ||
| 480 | packet: []const u8, | ||
| 481 | }; | ||
| 482 | |||
| 483 | pub const Error = error{InvalidDnsPacket}; | ||
| 484 | |||
| 485 | pub fn init(r: []const u8) Error!DnsResponse { | ||
| 486 | if (r.len < 12) return error.InvalidDnsPacket; | ||
| 487 | return .{ .bytes = r }; | ||
| 488 | } | ||
| 489 | |||
| 490 | pub fn next(dr: *DnsResponse) Error!?Answer { | ||
| 491 | _ = dr; | ||
| 492 | @panic("TODO"); | ||
| 493 | } | ||
| 494 | }; | ||
| 495 | |||
| 496 | pub const ConnectTcpError = LookupError || IpAddress.ConnectTcpError; | ||
| 497 | |||
| 498 | pub fn connectTcp(host_name: HostName, io: Io, port: u16) ConnectTcpError!Stream { | ||
| 499 | var addresses_buffer: [32]IpAddress = undefined; | ||
| 500 | |||
| 501 | const results = try lookup(host_name, .{ | ||
| 502 | .port = port, | ||
| 503 | .addresses_buffer = &addresses_buffer, | ||
| 504 | .canonical_name_buffer = &.{}, | ||
| 505 | }); | ||
| 506 | const addresses = addresses_buffer[0..results.addresses_len]; | ||
| 507 | |||
| 508 | if (addresses.len == 0) return error.UnknownHostName; | ||
| 509 | |||
| 510 | for (addresses) |addr| { | ||
| 511 | return addr.connectTcp(io) catch |err| switch (err) { | ||
| 512 | error.ConnectionRefused => continue, | ||
| 513 | else => |e| return e, | ||
| 514 | }; | ||
| 515 | } | ||
| 516 | return error.ConnectionRefused; | ||
| 517 | } | ||
| 518 | |||
| 519 | pub const ResolvConf = struct { | ||
| 520 | attempts: u32, | ||
| 521 | ndots: u32, | ||
| 522 | timeout: u32, | ||
| 523 | nameservers_buffer: [3]IpAddress, | ||
| 524 | nameservers_len: usize, | ||
| 525 | search_buffer: [max_len]u8, | ||
| 526 | search_len: usize, | ||
| 527 | |||
| 528 | /// Returns `error.StreamTooLong` if a line is longer than 512 bytes. | ||
| 529 | fn init(io: Io) !ResolvConf { | ||
| 530 | var rc: ResolvConf = .{ | ||
| 531 | .nameservers_buffer = undefined, | ||
| 532 | .nameservers_len = 0, | ||
| 533 | .search_buffer = undefined, | ||
| 534 | .search_len = 0, | ||
| 535 | .ndots = 1, | ||
| 536 | .timeout = 5, | ||
| 537 | .attempts = 2, | ||
| 538 | }; | ||
| 539 | |||
| 540 | const file = Io.File.openAbsolute(io, "/etc/resolv.conf", .{}) catch |err| switch (err) { | ||
| 541 | error.FileNotFound, | ||
| 542 | error.NotDir, | ||
| 543 | error.AccessDenied, | ||
| 544 | => { | ||
| 545 | try addNumeric(&rc, "127.0.0.1", 53); | ||
| 546 | return rc; | ||
| 547 | }, | ||
| 548 | |||
| 549 | else => |e| return e, | ||
| 550 | }; | ||
| 551 | defer file.close(io); | ||
| 552 | |||
| 553 | var line_buf: [512]u8 = undefined; | ||
| 554 | var file_reader = file.reader(io, &line_buf); | ||
| 555 | parse(&rc, &file_reader.interface) catch |err| switch (err) { | ||
| 556 | error.ReadFailed => return file_reader.err.?, | ||
| 557 | else => |e| return e, | ||
| 558 | }; | ||
| 559 | return rc; | ||
| 560 | } | ||
| 561 | |||
| 562 | const Directive = enum { options, nameserver, domain, search }; | ||
| 563 | const Option = enum { ndots, attempts, timeout }; | ||
| 564 | |||
| 565 | fn parse(rc: *ResolvConf, reader: *Io.Reader) !void { | ||
| 566 | while (reader.takeSentinel('\n')) |line_with_comment| { | ||
| 567 | const line = line: { | ||
| 568 | var split = std.mem.splitScalar(u8, line_with_comment, '#'); | ||
| 569 | break :line split.first(); | ||
| 570 | }; | ||
| 571 | var line_it = std.mem.tokenizeAny(u8, line, " \t"); | ||
| 572 | |||
| 573 | const token = line_it.next() orelse continue; | ||
| 574 | switch (std.meta.stringToEnum(Directive, token) orelse continue) { | ||
| 575 | .options => while (line_it.next()) |sub_tok| { | ||
| 576 | var colon_it = std.mem.splitScalar(u8, sub_tok, ':'); | ||
| 577 | const name = colon_it.first(); | ||
| 578 | const value_txt = colon_it.next() orelse continue; | ||
| 579 | const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) { | ||
| 580 | error.Overflow => 255, | ||
| 581 | error.InvalidCharacter => continue, | ||
| 582 | }; | ||
| 583 | switch (std.meta.stringToEnum(Option, name) orelse continue) { | ||
| 584 | .ndots => rc.ndots = @min(value, 15), | ||
| 585 | .attempts => rc.attempts = @min(value, 10), | ||
| 586 | .timeout => rc.timeout = @min(value, 60), | ||
| 587 | } | ||
| 588 | }, | ||
| 589 | .nameserver => { | ||
| 590 | const ip_txt = line_it.next() orelse continue; | ||
| 591 | try addNumeric(rc, ip_txt, 53); | ||
| 592 | }, | ||
| 593 | .domain, .search => { | ||
| 594 | const rest = line_it.rest(); | ||
| 595 | @memcpy(rc.search_buffer[0..rest.len], rest); | ||
| 596 | rc.search_len = rest.len; | ||
| 597 | }, | ||
| 598 | } | ||
| 599 | } else |err| switch (err) { | ||
| 600 | error.EndOfStream => if (reader.bufferedLen() != 0) return error.EndOfStream, | ||
| 601 | else => |e| return e, | ||
| 602 | } | ||
| 603 | |||
| 604 | if (rc.nameservers_len == 0) { | ||
| 605 | try addNumeric(rc, "127.0.0.1", 53); | ||
| 606 | } | ||
| 607 | } | ||
| 608 | |||
| 609 | fn addNumeric(rc: *ResolvConf, name: []const u8, port: u16) !void { | ||
| 610 | assert(rc.nameservers_len < rc.nameservers_buffer.len); | ||
| 611 | rc.nameservers_buffer[rc.nameservers_len] = try .parse(name, port); | ||
| 612 | rc.nameservers_len += 1; | ||
| 613 | } | ||
| 614 | |||
| 615 | fn nameservers(rc: *const ResolvConf) []IpAddress { | ||
| 616 | return rc.nameservers_buffer[0..rc.nameservers_len]; | ||
| 617 | } | ||
| 618 | |||
| 619 | fn sendMessage( | ||
| 620 | rc: *const ResolvConf, | ||
| 621 | io: Io, | ||
| 622 | queries: []const []const u8, | ||
| 623 | answers: [][]u8, | ||
| 624 | ) !void { | ||
| 625 | _ = rc; | ||
| 626 | _ = io; | ||
| 627 | _ = queries; | ||
| 628 | _ = answers; | ||
| 629 | @panic("TODO"); | ||
| 630 | } | ||
| 631 | }; | ||
lib/std/mem.zig+21| ... | @@ -1678,6 +1678,7 @@ test "indexOfPos empty needle" { | ... | @@ -1678,6 +1678,7 @@ test "indexOfPos empty needle" { |
| 1678 | /// needle.len must be > 0 | 1678 | /// needle.len must be > 0 |
| 1679 | /// does not count overlapping needles | 1679 | /// does not count overlapping needles |
| 1680 | pub fn count(comptime T: type, haystack: []const T, needle: []const T) usize { | 1680 | pub fn count(comptime T: type, haystack: []const T, needle: []const T) usize { |
| 1681 | if (needle.len == 1) return countScalar(T, haystack, needle[0]); | ||
| 1681 | assert(needle.len > 0); | 1682 | assert(needle.len > 0); |
| 1682 | var i: usize = 0; | 1683 | var i: usize = 0; |
| 1683 | var found: usize = 0; | 1684 | var found: usize = 0; |
| ... | @@ -1704,6 +1705,26 @@ test count { | ... | @@ -1704,6 +1705,26 @@ test count { |
| 1704 | try testing.expect(count(u8, "owowowu", "owowu") == 1); | 1705 | try testing.expect(count(u8, "owowowu", "owowu") == 1); |
| 1705 | } | 1706 | } |
| 1706 | 1707 | ||
| 1708 | /// Returns the number of times `element` appears in a slice of memory. | ||
| 1709 | pub fn countScalar(comptime T: type, list: []const T, element: T) usize { | ||
| 1710 | var i: usize = 0; | ||
| 1711 | var found: usize = 0; | ||
| 1712 | |||
| 1713 | while (indexOfScalarPos(T, list, i, element)) |next_index| { | ||
| 1714 | i = next_index + 1; | ||
| 1715 | found += 1; | ||
| 1716 | } | ||
| 1717 | |||
| 1718 | return found; | ||
| 1719 | } | ||
| 1720 | |||
| 1721 | test countScalar { | ||
| 1722 | try testing.expect(countScalar(u8, "", 'h') == 0); | ||
| 1723 | try testing.expect(countScalar(u8, "h", 'h') == 1); | ||
| 1724 | try testing.expect(countScalar(u8, "hh", 'h') == 2); | ||
| 1725 | try testing.expect(countScalar(u8, "ahhb", 'h') == 2); | ||
| 1726 | } | ||
| 1727 | |||
| 1707 | /// Returns true if the haystack contains expected_count or more needles | 1728 | /// Returns true if the haystack contains expected_count or more needles |
| 1708 | /// needle.len must be > 0 | 1729 | /// needle.len must be > 0 |
| 1709 | /// does not count overlapping needles | 1730 | /// does not count overlapping needles |
lib/std/net.zig+5-5| ... | @@ -105,7 +105,7 @@ pub const Address = extern union { | ... | @@ -105,7 +105,7 @@ pub const Address = extern union { |
| 105 | => {}, | 105 | => {}, |
| 106 | } | 106 | } |
| 107 | 107 | ||
| 108 | return error.InvalidIPAddressFormat; | 108 | return error.InvalidIpAddressFormat; |
| 109 | } | 109 | } |
| 110 | 110 | ||
| 111 | pub fn resolveIp(name: []const u8, port: u16) !Address { | 111 | pub fn resolveIp(name: []const u8, port: u16) !Address { |
| ... | @@ -128,7 +128,7 @@ pub const Address = extern union { | ... | @@ -128,7 +128,7 @@ pub const Address = extern union { |
| 128 | else => return err, | 128 | else => return err, |
| 129 | } | 129 | } |
| 130 | 130 | ||
| 131 | return error.InvalidIPAddressFormat; | 131 | return error.InvalidIpAddressFormat; |
| 132 | } | 132 | } |
| 133 | 133 | ||
| 134 | pub fn parseExpectingFamily(name: []const u8, family: posix.sa_family_t, port: u16) !Address { | 134 | pub fn parseExpectingFamily(name: []const u8, family: posix.sa_family_t, port: u16) !Address { |
| ... | @@ -360,7 +360,7 @@ pub const Ip4Address = extern struct { | ... | @@ -360,7 +360,7 @@ pub const Ip4Address = extern struct { |
| 360 | error.NonCanonical, | 360 | error.NonCanonical, |
| 361 | => {}, | 361 | => {}, |
| 362 | } | 362 | } |
| 363 | return error.InvalidIPAddressFormat; | 363 | return error.InvalidIpAddressFormat; |
| 364 | } | 364 | } |
| 365 | 365 | ||
| 366 | pub fn init(addr: [4]u8, port: u16) Ip4Address { | 366 | pub fn init(addr: [4]u8, port: u16) Ip4Address { |
| ... | @@ -885,7 +885,7 @@ const GetAddressListError = Allocator.Error || File.OpenError || File.ReadError | ... | @@ -885,7 +885,7 @@ const GetAddressListError = Allocator.Error || File.OpenError || File.ReadError |
| 885 | Overflow, | 885 | Overflow, |
| 886 | Incomplete, | 886 | Incomplete, |
| 887 | InvalidIpv4Mapping, | 887 | InvalidIpv4Mapping, |
| 888 | InvalidIPAddressFormat, | 888 | InvalidIpAddressFormat, |
| 889 | 889 | ||
| 890 | InterfaceNotFound, | 890 | InterfaceNotFound, |
| 891 | FileSystem, | 891 | FileSystem, |
| ... | @@ -1426,7 +1426,7 @@ fn parseHosts( | ... | @@ -1426,7 +1426,7 @@ fn parseHosts( |
| 1426 | error.InvalidEnd, | 1426 | error.InvalidEnd, |
| 1427 | error.InvalidCharacter, | 1427 | error.InvalidCharacter, |
| 1428 | error.Incomplete, | 1428 | error.Incomplete, |
| 1429 | error.InvalidIPAddressFormat, | 1429 | error.InvalidIpAddressFormat, |
| 1430 | error.InvalidIpv4Mapping, | 1430 | error.InvalidIpv4Mapping, |
| 1431 | error.NonCanonical, | 1431 | error.NonCanonical, |
| 1432 | => continue, | 1432 | => continue, |
lib/std/net/test.zig+4-4| ... | @@ -12,10 +12,10 @@ test "parse and render IP addresses at comptime" { | ... | @@ -12,10 +12,10 @@ test "parse and render IP addresses at comptime" { |
| 12 | const ipv4addr = net.Address.parseIp("127.0.0.1", 0) catch unreachable; | 12 | const ipv4addr = net.Address.parseIp("127.0.0.1", 0) catch unreachable; |
| 13 | try std.testing.expectFmt("127.0.0.1:0", "{f}", .{ipv4addr}); | 13 | try std.testing.expectFmt("127.0.0.1:0", "{f}", .{ipv4addr}); |
| 14 | 14 | ||
| 15 | try testing.expectError(error.InvalidIPAddressFormat, net.Address.parseIp("::123.123.123.123", 0)); | 15 | try testing.expectError(error.InvalidIpAddressFormat, net.Address.parseIp("::123.123.123.123", 0)); |
| 16 | try testing.expectError(error.InvalidIPAddressFormat, net.Address.parseIp("127.01.0.1", 0)); | 16 | try testing.expectError(error.InvalidIpAddressFormat, net.Address.parseIp("127.01.0.1", 0)); |
| 17 | try testing.expectError(error.InvalidIPAddressFormat, net.Address.resolveIp("::123.123.123.123", 0)); | 17 | try testing.expectError(error.InvalidIpAddressFormat, net.Address.resolveIp("::123.123.123.123", 0)); |
| 18 | try testing.expectError(error.InvalidIPAddressFormat, net.Address.resolveIp("127.01.0.1", 0)); | 18 | try testing.expectError(error.InvalidIpAddressFormat, net.Address.resolveIp("127.01.0.1", 0)); |
| 19 | } | 19 | } |
| 20 | } | 20 | } |
| 21 | 21 |
lib/std/posix.zig-99| ... | @@ -6101,55 +6101,6 @@ pub fn uname() utsname { | ... | @@ -6101,55 +6101,6 @@ pub fn uname() utsname { |
| 6101 | } | 6101 | } |
| 6102 | } | 6102 | } |
| 6103 | 6103 | ||
| 6104 | pub fn res_mkquery( | ||
| 6105 | op: u4, | ||
| 6106 | dname: []const u8, | ||
| 6107 | class: u8, | ||
| 6108 | ty: u8, | ||
| 6109 | data: []const u8, | ||
| 6110 | newrr: ?[*]const u8, | ||
| 6111 | buf: []u8, | ||
| 6112 | ) usize { | ||
| 6113 | _ = data; | ||
| 6114 | _ = newrr; | ||
| 6115 | // This implementation is ported from musl libc. | ||
| 6116 | // A more idiomatic "ziggy" implementation would be welcome. | ||
| 6117 | var name = dname; | ||
| 6118 | if (mem.endsWith(u8, name, ".")) name.len -= 1; | ||
| 6119 | assert(name.len <= 253); | ||
| 6120 | const n = 17 + name.len + @intFromBool(name.len != 0); | ||
| 6121 | |||
| 6122 | // Construct query template - ID will be filled later | ||
| 6123 | var q: [280]u8 = undefined; | ||
| 6124 | @memset(q[0..n], 0); | ||
| 6125 | q[2] = @as(u8, op) * 8 + 1; | ||
| 6126 | q[5] = 1; | ||
| 6127 | @memcpy(q[13..][0..name.len], name); | ||
| 6128 | var i: usize = 13; | ||
| 6129 | var j: usize = undefined; | ||
| 6130 | while (q[i] != 0) : (i = j + 1) { | ||
| 6131 | j = i; | ||
| 6132 | while (q[j] != 0 and q[j] != '.') : (j += 1) {} | ||
| 6133 | // TODO determine the circumstances for this and whether or | ||
| 6134 | // not this should be an error. | ||
| 6135 | if (j - i - 1 > 62) unreachable; | ||
| 6136 | q[i - 1] = @intCast(j - i); | ||
| 6137 | } | ||
| 6138 | q[i + 1] = ty; | ||
| 6139 | q[i + 3] = class; | ||
| 6140 | |||
| 6141 | // Make a reasonably unpredictable id | ||
| 6142 | const ts = clock_gettime(.REALTIME) catch unreachable; | ||
| 6143 | const UInt = std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(ts.nsec))); | ||
| 6144 | const unsec: UInt = @bitCast(ts.nsec); | ||
| 6145 | const id: u32 = @truncate(unsec + unsec / 65536); | ||
| 6146 | q[0] = @truncate(id / 256); | ||
| 6147 | q[1] = @truncate(id); | ||
| 6148 | |||
| 6149 | @memcpy(buf[0..n], q[0..n]); | ||
| 6150 | return n; | ||
| 6151 | } | ||
| 6152 | |||
| 6153 | pub const SendError = error{ | 6104 | pub const SendError = error{ |
| 6154 | /// (For UNIX domain sockets, which are identified by pathname) Write permission is denied | 6105 | /// (For UNIX domain sockets, which are identified by pathname) Write permission is denied |
| 6155 | /// on the destination socket file, or search permission is denied for one of the | 6106 | /// on the destination socket file, or search permission is denied for one of the |
| ... | @@ -6721,56 +6672,6 @@ pub fn recvmsg( | ... | @@ -6721,56 +6672,6 @@ pub fn recvmsg( |
| 6721 | } | 6672 | } |
| 6722 | } | 6673 | } |
| 6723 | 6674 | ||
| 6724 | pub const DnExpandError = error{InvalidDnsPacket}; | ||
| 6725 | |||
| 6726 | pub fn dn_expand( | ||
| 6727 | msg: []const u8, | ||
| 6728 | comp_dn: []const u8, | ||
| 6729 | exp_dn: []u8, | ||
| 6730 | ) DnExpandError!usize { | ||
| 6731 | // This implementation is ported from musl libc. | ||
| 6732 | // A more idiomatic "ziggy" implementation would be welcome. | ||
| 6733 | var p = comp_dn.ptr; | ||
| 6734 | var len: usize = maxInt(usize); | ||
| 6735 | const end = msg.ptr + msg.len; | ||
| 6736 | if (p == end or exp_dn.len == 0) return error.InvalidDnsPacket; | ||
| 6737 | var dest = exp_dn.ptr; | ||
| 6738 | const dend = dest + @min(exp_dn.len, 254); | ||
| 6739 | // detect reference loop using an iteration counter | ||
| 6740 | var i: usize = 0; | ||
| 6741 | while (i < msg.len) : (i += 2) { | ||
| 6742 | // loop invariants: p<end, dest<dend | ||
| 6743 | if ((p[0] & 0xc0) != 0) { | ||
| 6744 | if (p + 1 == end) return error.InvalidDnsPacket; | ||
| 6745 | const j = @as(usize, p[0] & 0x3f) << 8 | p[1]; | ||
| 6746 | if (len == maxInt(usize)) len = @intFromPtr(p) + 2 - @intFromPtr(comp_dn.ptr); | ||
| 6747 | if (j >= msg.len) return error.InvalidDnsPacket; | ||
| 6748 | p = msg.ptr + j; | ||
| 6749 | } else if (p[0] != 0) { | ||
| 6750 | if (dest != exp_dn.ptr) { | ||
| 6751 | dest[0] = '.'; | ||
| 6752 | dest += 1; | ||
| 6753 | } | ||
| 6754 | var j = p[0]; | ||
| 6755 | p += 1; | ||
| 6756 | if (j >= @intFromPtr(end) - @intFromPtr(p) or j >= @intFromPtr(dend) - @intFromPtr(dest)) { | ||
| 6757 | return error.InvalidDnsPacket; | ||
| 6758 | } | ||
| 6759 | while (j != 0) { | ||
| 6760 | j -= 1; | ||
| 6761 | dest[0] = p[0]; | ||
| 6762 | dest += 1; | ||
| 6763 | p += 1; | ||
| 6764 | } | ||
| 6765 | } else { | ||
| 6766 | dest[0] = 0; | ||
| 6767 | if (len == maxInt(usize)) len = @intFromPtr(p) + 1 - @intFromPtr(comp_dn.ptr); | ||
| 6768 | return len; | ||
| 6769 | } | ||
| 6770 | } | ||
| 6771 | return error.InvalidDnsPacket; | ||
| 6772 | } | ||
| 6773 | |||
| 6774 | pub const SetSockOptError = error{ | 6675 | pub const SetSockOptError = error{ |
| 6775 | /// The socket is already connected, and a specified option cannot be set while the socket is connected. | 6676 | /// The socket is already connected, and a specified option cannot be set while the socket is connected. |
| 6776 | AlreadyConnected, | 6677 | AlreadyConnected, |