| 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 | 667 | netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize, |
| 668 | 668 | netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize, |
| 669 | 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 | 674 | pub const Cancelable = error{ |
lib/std/Io/ThreadPool.zig+71| ... | ... | @@ -135,6 +135,7 @@ pub fn io(pool: *Pool) Io { |
| 135 | 135 | else => netWritePosix, |
| 136 | 136 | }, |
| 137 | 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 | 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 | 1189 | const PosixAddress = extern union { |
| 1126 | 1190 | any: posix.sockaddr, |
| 1127 | 1191 | in: posix.sockaddr.in, |
| ... | ... | @@ -1187,3 +1251,10 @@ fn address6ToPosix(a: Io.net.Ip6Address) posix.sockaddr.in6 { |
| 1187 | 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 | 4 | const Io = std.Io; |
| 5 | 5 | const assert = std.debug.assert; |
| 6 | 6 | |
| 7 | pub const HostName = @import("net/HostName.zig"); | |
| 8 | ||
| 7 | 9 | pub const ListenError = std.net.Address.ListenError || Io.Cancelable; |
| 8 | 10 | |
| 9 | 11 | pub const ListenOptions = struct { |
| ... | ... | @@ -17,298 +19,17 @@ pub const ListenOptions = struct { |
| 17 | 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 | 22 | pub const IpAddress = union(enum) { |
| 304 | 23 | ip4: Ip4Address, |
| 305 | 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 | 28 | /// Parse the given IP address string into an `IpAddress` value. |
| 310 | 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 | 33 | error.Overflow, |
| 313 | 34 | error.InvalidEnd, |
| 314 | 35 | error.InvalidCharacter, |
| ... | ... | @@ -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 | 44 | error.Overflow, |
| 322 | 45 | error.InvalidEnd, |
| 323 | 46 | error.InvalidCharacter, |
| ... | ... | @@ -859,3 +582,14 @@ pub const Server = struct { |
| 859 | 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 | 1678 | /// needle.len must be > 0 |
| 1679 | 1679 | /// does not count overlapping needles |
| 1680 | 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 | 1682 | assert(needle.len > 0); |
| 1682 | 1683 | var i: usize = 0; |
| 1683 | 1684 | var found: usize = 0; |
| ... | ... | @@ -1704,6 +1705,26 @@ test count { |
| 1704 | 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 | 1728 | /// Returns true if the haystack contains expected_count or more needles |
| 1708 | 1729 | /// needle.len must be > 0 |
| 1709 | 1730 | /// does not count overlapping needles |
lib/std/net.zig+5-5| ... | ... | @@ -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 | 111 | pub fn resolveIp(name: []const u8, port: u16) !Address { |
| ... | ... | @@ -128,7 +128,7 @@ pub const Address = extern union { |
| 128 | 128 | else => return err, |
| 129 | 129 | } |
| 130 | 130 | |
| 131 | return error.InvalidIPAddressFormat; | |
| 131 | return error.InvalidIpAddressFormat; | |
| 132 | 132 | } |
| 133 | 133 | |
| 134 | 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 | 360 | error.NonCanonical, |
| 361 | 361 | => {}, |
| 362 | 362 | } |
| 363 | return error.InvalidIPAddressFormat; | |
| 363 | return error.InvalidIpAddressFormat; | |
| 364 | 364 | } |
| 365 | 365 | |
| 366 | 366 | pub fn init(addr: [4]u8, port: u16) Ip4Address { |
| ... | ... | @@ -885,7 +885,7 @@ const GetAddressListError = Allocator.Error || File.OpenError || File.ReadError |
| 885 | 885 | Overflow, |
| 886 | 886 | Incomplete, |
| 887 | 887 | InvalidIpv4Mapping, |
| 888 | InvalidIPAddressFormat, | |
| 888 | InvalidIpAddressFormat, | |
| 889 | 889 | |
| 890 | 890 | InterfaceNotFound, |
| 891 | 891 | FileSystem, |
| ... | ... | @@ -1426,7 +1426,7 @@ fn parseHosts( |
| 1426 | 1426 | error.InvalidEnd, |
| 1427 | 1427 | error.InvalidCharacter, |
| 1428 | 1428 | error.Incomplete, |
| 1429 | error.InvalidIPAddressFormat, | |
| 1429 | error.InvalidIpAddressFormat, | |
| 1430 | 1430 | error.InvalidIpv4Mapping, |
| 1431 | 1431 | error.NonCanonical, |
| 1432 | 1432 | => continue, |
lib/std/net/test.zig+4-4| ... | ... | @@ -12,10 +12,10 @@ test "parse and render IP addresses at comptime" { |
| 12 | 12 | const ipv4addr = net.Address.parseIp("127.0.0.1", 0) catch unreachable; |
| 13 | 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)); | |
| 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)); | |
| 18 | try testing.expectError(error.InvalidIPAddressFormat, net.Address.resolveIp("127.01.0.1", 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)); | |
| 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)); | |
| 19 | 19 | } |
| 20 | 20 | } |
| 21 | 21 |
lib/std/posix.zig-99| ... | ... | @@ -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 | 6104 | pub const SendError = error{ |
| 6154 | 6105 | /// (For UNIX domain sockets, which are identified by pathname) Write permission is denied |
| 6155 | 6106 | /// on the destination socket file, or search permission is denied for one of the |
| ... | ... | @@ -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 | 6675 | pub const SetSockOptError = error{ |
| 6775 | 6676 | /// The socket is already connected, and a specified option cannot be set while the socket is connected. |
| 6776 | 6677 | AlreadyConnected, |