authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-08 01:06:00-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-02 16:30:59-07:00
loge34bb9a4131c2f0f328a53419d237877152917fa
tree3da500b0bd4c4d57e046c043422ddccc1308beac
parent4e887625d45593f6053624858f85bcb848c13a62

Io.net: partial implementation of dns lookup


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};
671673
672pub const Cancelable = error{674pub 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}
11241125
1126fn 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
1125const PosixAddress = extern union {1189const 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
1255fn 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");
4const Io = std.Io;4const Io = std.Io;
5const assert = std.debug.assert;5const assert = std.debug.assert;
66
7pub const HostName = @import("net/HostName.zig");
8
7pub const ListenError = std.net.Address.ListenError || Io.Cancelable;9pub const ListenError = std.net.Address.ListenError || Io.Cancelable;
810
9pub const ListenOptions = struct {11pub 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};
1921
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 '.'.
24pub 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
303pub const IpAddress = union(enum) {22pub const IpAddress = union(enum) {
304 ip4: Ip4Address,23 ip4: Ip4Address,
305 ip6: Ip6Address,24 ip6: Ip6Address,
30625
307 pub const Tag = @typeInfo(IpAddress).@"union".tag_type.?;26 pub const Family = @typeInfo(IpAddress).@"union".tag_type.?;
30827
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 }
31940
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
586pub const InterfaceIndexError = error{
587 InterfaceNotFound,
588 AccessDenied,
589 SystemResources,
590} || Io.UnexpectedError || Io.Cancelable;
591
592/// Otherwise known as "if_nametoindex".
593pub fn interfaceIndex(io: Io, name: []const u8) InterfaceIndexError!u32 {
594 return io.vtable.netInterfaceIndex(io.userdata, name);
595}
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 '.'.
5const HostName = @This();
6
7const builtin = @import("builtin");
8const native_os = builtin.os.tag;
9
10const std = @import("../../std.zig");
11const Io = std.Io;
12const IpAddress = Io.net.IpAddress;
13const Ip6Address = Io.net.Ip6Address;
14const assert = std.debug.assert;
15const Stream = Io.net.Stream;
16
17/// Externally managed memory. Already checked to be valid.
18bytes: []const u8,
19
20pub const max_len = 255;
21
22pub const InitError = error{
23 NameTooLong,
24 InvalidHostName,
25};
26
27pub 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
40pub 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
49pub 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
59pub 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
70pub 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
133fn 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
188fn 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
221fn 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
302fn 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
320fn 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
387fn 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.
394fn 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
424pub const ExpandDomainNameError = error{InvalidDnsPacket};
425
426pub 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
474pub 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
496pub const ConnectTcpError = LookupError || IpAddress.ConnectTcpError;
497
498pub 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
519pub 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 > 01678/// needle.len must be > 0
1679/// does not count overlapping needles1679/// does not count overlapping needles
1680pub fn count(comptime T: type, haystack: []const T, needle: []const T) usize {1680pub 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}
17061707
1708/// Returns the number of times `element` appears in a slice of memory.
1709pub 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
1721test 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 needles1728/// Returns true if the haystack contains expected_count or more needles
1708/// needle.len must be > 01729/// needle.len must be > 0
1709/// does not count overlapping needles1730/// 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 }
107107
108 return error.InvalidIPAddressFormat;108 return error.InvalidIpAddressFormat;
109 }109 }
110110
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 }
130130
131 return error.InvalidIPAddressFormat;131 return error.InvalidIpAddressFormat;
132 }132 }
133133
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 }
365365
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,
889889
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});
1414
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}
2121
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}
61036103
6104pub 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
6153pub const SendError = error{6104pub const SendError = error{
6154 /// (For UNIX domain sockets, which are identified by pathname) Write permission is denied6105 /// (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 the6106 /// 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}
67236674
6724pub const DnExpandError = error{InvalidDnsPacket};
6725
6726pub 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
6774pub const SetSockOptError = error{6675pub 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,