diff options
| -rw-r--r-- | lib/std/buffer.zig | 16 | ||||
| -rw-r--r-- | lib/std/c.zig | 22 | ||||
| -rw-r--r-- | lib/std/event/loop.zig | 4 | ||||
| -rw-r--r-- | lib/std/fs.zig | 2 | ||||
| -rw-r--r-- | lib/std/fs/file.zig | 2 | ||||
| -rw-r--r-- | lib/std/net.zig | 600 | ||||
| -rw-r--r-- | lib/std/os.zig | 259 | ||||
| -rw-r--r-- | lib/std/os/bits/linux.zig | 20 | ||||
| -rw-r--r-- | lib/std/os/linux.zig | 22 |
9 files changed, 805 insertions, 142 deletions
diff --git a/lib/std/buffer.zig b/lib/std/buffer.zig index bc6aa254da..24bd23fa74 100644 --- a/lib/std/buffer.zig +++ b/lib/std/buffer.zig @@ -72,11 +72,11 @@ pub const Buffer = struct { self.list.deinit(); } - pub fn toSlice(self: *const Buffer) []u8 { + pub fn toSlice(self: Buffer) []u8 { return self.list.toSlice()[0..self.len()]; } - pub fn toSliceConst(self: *const Buffer) []const u8 { + pub fn toSliceConst(self: Buffer) []const u8 { return self.list.toSliceConst()[0..self.len()]; } @@ -91,11 +91,11 @@ pub const Buffer = struct { self.list.items[self.len()] = 0; } - pub fn isNull(self: *const Buffer) bool { + pub fn isNull(self: Buffer) bool { return self.list.len == 0; } - pub fn len(self: *const Buffer) usize { + pub fn len(self: Buffer) usize { return self.list.len - 1; } @@ -111,16 +111,16 @@ pub const Buffer = struct { self.list.toSlice()[old_len] = byte; } - pub fn eql(self: *const Buffer, m: []const u8) bool { + pub fn eql(self: Buffer, m: []const u8) bool { return mem.eql(u8, self.toSliceConst(), m); } - pub fn startsWith(self: *const Buffer, m: []const u8) bool { + pub fn startsWith(self: Buffer, m: []const u8) bool { if (self.len() < m.len) return false; return mem.eql(u8, self.list.items[0..m.len], m); } - pub fn endsWith(self: *const Buffer, m: []const u8) bool { + pub fn endsWith(self: Buffer, m: []const u8) bool { const l = self.len(); if (l < m.len) return false; const start = l - m.len; @@ -133,7 +133,7 @@ pub const Buffer = struct { } /// For passing to C functions. - pub fn ptr(self: *const Buffer) [*]u8 { + pub fn ptr(self: Buffer) [*]u8 { return self.list.items.ptr; } }; diff --git a/lib/std/c.zig b/lib/std/c.zig index 1e8839bb6b..bc8b2e643a 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -116,6 +116,26 @@ pub extern "c" fn getsockname(sockfd: fd_t, noalias addr: *sockaddr, noalias add pub extern "c" fn connect(sockfd: fd_t, sock_addr: *const sockaddr, addrlen: socklen_t) c_int; pub extern "c" fn accept4(sockfd: fd_t, addr: *sockaddr, addrlen: *socklen_t, flags: c_uint) c_int; pub extern "c" fn getsockopt(sockfd: fd_t, level: c_int, optname: c_int, optval: *c_void, optlen: *socklen_t) c_int; +pub extern "c" fn send(sockfd: fd_t, buf: *const c_void, len: usize, flags: u32) isize; +pub extern "c" fn sendto( + sockfd: fd_t, + buf: *const c_void, + len: usize, + flags: u32, + dest_addr: *const sockaddr, + addrlen: socklen_t, +) isize; + +pub extern fn recv(sockfd: fd_t, arg1: ?*c_void, arg2: usize, arg3: c_int) isize; +pub extern fn recvfrom( + sockfd: fd_t, + noalias buf: *c_void, + len: usize, + flags: u32, + noalias src_addr: ?*sockaddr, + noalias addrlen: ?*socklen_t, +) isize; + pub extern "c" fn kill(pid: pid_t, sig: c_int) c_int; pub extern "c" fn getdirentries(fd: fd_t, buf_ptr: [*]u8, nbytes: usize, basep: *i64) isize; pub extern "c" fn setgid(ruid: c_uint, euid: c_uint) c_int; @@ -169,3 +189,5 @@ pub extern "c" fn getnameinfo( ) c_int; pub extern "c" fn gai_strerror(errcode: c_int) [*]const u8; + +pub extern "c" fn poll(fds: [*]pollfd, nfds: nfds_t, timeout: c_int) c_int; diff --git a/lib/std/event/loop.zig b/lib/std/event/loop.zig index d0d36abc0c..cec722985b 100644 --- a/lib/std/event/loop.zig +++ b/lib/std/event/loop.zig @@ -466,6 +466,10 @@ pub const Loop = struct { return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLIN); } + pub fn waitUntilFdWritable(self: *Loop, fd: os.fd_t) !void { + return self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLOUT); + } + pub async fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, fflags: u32) !os.Kevent { var resume_node = ResumeNode.Basic{ .base = ResumeNode{ diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 5517f6f280..a17ac613f0 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -704,7 +704,7 @@ pub const Dir = struct { /// Call `File.close` on the result when done. pub fn openReadC(self: Dir, sub_path: [*]const u8) File.OpenError!File { - const flags = os.O_LARGEFILE | os.O_RDONLY; + const flags = os.O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC; const fd = try os.openatC(self.fd, sub_path, flags, 0); return File.openHandle(fd); } diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 06a26ed481..431b89bebf 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -41,7 +41,7 @@ pub const File = struct { const path_w = try windows.cStrToPrefixedFileW(path); return openReadW(&path_w); } - const flags = os.O_LARGEFILE | os.O_RDONLY; + const flags = os.O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC; const fd = try os.openC(path, flags, 0); return openHandle(fd); } diff --git a/lib/std/net.zig b/lib/std/net.zig index 817e6e67e5..2ca0e1fc26 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -4,6 +4,7 @@ const assert = std.debug.assert; const net = @This(); const mem = std.mem; const os = std.os; +const fs = std.fs; pub const TmpWinAddr = struct { family: u8, @@ -285,7 +286,7 @@ test "std.net.parseIp6" { std.testing.expect(mem.eql(u8, "[ff01::fb]:80", printed)); } -pub fn connectUnixSocket(path: []const u8) !std.fs.File { +pub fn connectUnixSocket(path: []const u8) !fs.File { const opt_non_block = if (std.event.Loop.instance != null) os.SOCK_NONBLOCK else 0; const sockfd = try os.socket( os.AF_UNIX, @@ -312,7 +313,7 @@ pub fn connectUnixSocket(path: []const u8) !std.fs.File { try os.connect(sockfd, &sock_addr, size); } - return std.fs.File.openHandle(sockfd); + return fs.File.openHandle(sockfd); } pub const AddressList = struct { @@ -356,7 +357,7 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !* const hints = os.addrinfo{ .flags = c.AI_NUMERICSERV, - .family = os.AF_UNSPEC, + .family = os.AF_INET, // TODO os.AF_UNSPEC, .socktype = os.SOCK_STREAM, .protocol = os.IPPROTO_TCP, .canonname = null, @@ -413,40 +414,41 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !* return result; } if (builtin.os == .linux) { - const flags = os.AI_NUMERICSERV; + const flags = std.c.AI_NUMERICSERV; const family = os.AF_INET; //TODO os.AF_UNSPEC; // The limit of 48 results is a non-sharp bound on the number of addresses // that can fit in one 512-byte DNS packet full of v4 results and a second // packet full of v6 results. Due to headers, the actual limit is lower. - var buf: [48]LookupAddr = undefined; - var canon_buf: [256]u8 = undefined; - var canon_len: usize = 0; - const cnt = try linuxLookupName(buf[0..], &canon_buf, &canon_len, name, family, flags); + var addrs = std.ArrayList(LookupAddr).init(allocator); + defer addrs.deinit(); - result.addrs = try arena.alloc(Address, cnt); + var canon = std.Buffer.initNull(allocator); + defer canon.deinit(); - if (canon_len != 0) { - result.canon_name = try mem.dupe(arena, u8, canon_buf[0..canon_len]); + try linuxLookupName(&addrs, &canon, name, family, flags); + + result.addrs = try arena.alloc(Address, addrs.len); + if (!canon.isNull()) { + result.canon_name = canon.toOwnedSlice(); } - var i: usize = 0; - while (i < cnt) : (i += 1) { - const os_addr = if (buf[i].family == os.AF_INET6) + for (addrs.toSliceConst()) |addr, i| { + const os_addr = if (addr.family == os.AF_INET6) os.sockaddr{ .in6 = os.sockaddr_in6{ - .family = buf[i].family, + .family = addr.family, .port = mem.nativeToBig(u16, port), .flowinfo = 0, - .addr = buf[i].addr, - .scope_id = buf[i].scope_id, + .addr = addr.addr, + .scope_id = addr.scope_id, }, } else os.sockaddr{ .in = os.sockaddr_in{ - .family = buf[i].family, + .family = addr.family, .port = mem.nativeToBig(u16, port), - .addr = @ptrCast(*align(1) u32, &buf[i].addr).*, + .addr = @ptrCast(*align(1) const u32, &addr.addr).*, .zero = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }, }, }; @@ -466,51 +468,52 @@ const LookupAddr = struct { }; fn linuxLookupName( - buf: []LookupAddr, - canon_buf: []u8, - canon_len: *usize, + addrs: *std.ArrayList(LookupAddr), + canon: *std.Buffer, opt_name: ?[]const u8, family: i32, flags: u32, -) !usize { - var cnt: usize = 0; +) !void { if (opt_name) |name| { // reject empty name and check len so it fits into temp bufs - if (name.len >= 254) return error.UnknownName; - mem.copy(u8, canon_buf, name); - canon_len.* = name.len; - - cnt = (linuxLookupNameFromNumeric(buf, name, family) catch |err| switch (err) { - error.ExpectedIPv6ButFoundIPv4 => unreachable, - error.ExpectedIPv4ButFoundIPv6 => unreachable, - }); - if (cnt == 0 and (flags & os.AI_NUMERICHOST) == 0) { - cnt = try linuxLookupNameFromHosts(buf, canon_buf, canon_len, name, family); - if (cnt == 0) { - cnt = try linuxLookupNameFromDnsSearch(buf, canon_buf, canon_len, name, family); + try canon.replaceContents(name); + try linuxLookupNameFromNumeric(addrs, name, family); + if (addrs.len == 0 and (flags & std.c.AI_NUMERICHOST) == 0) { + try linuxLookupNameFromHosts(addrs, canon, name, family); + if (addrs.len == 0) { + try linuxLookupNameFromDnsSearch(addrs, canon, name, family); } } } else { - canon_len.* = 0; - cnt = linuxLookupNameFromNull(buf, family, flags); + try canon.resize(0); + try linuxLookupNameFromNull(addrs, family, flags); } - if (cnt == 0) return error.UnknownName; + if (addrs.len == 0) return error.UnknownName; // No further processing is needed if there are fewer than 2 // results or if there are only IPv4 results. - if (cnt == 1 or family == os.AF_INET) return cnt; + if (addrs.len == 1 or family == os.AF_INET) return; @panic("port the RFC 3484/6724 destination address selection from musl libc"); } -fn linuxLookupNameFromNumeric(buf: []LookupAddr, name: []const u8, family: i32) !usize { +fn linuxLookupNameFromNumericUnspec(addrs: *std.ArrayList(LookupAddr), name: []const u8) !void { + return linuxLookupNameFromNumeric(addrs, name, os.AF_UNSPEC) catch |err| switch (err) { + error.ExpectedIPv6ButFoundIPv4 => unreachable, + error.ExpectedIPv4ButFoundIPv6 => unreachable, + else => |e| return e, + }; +} + +fn linuxLookupNameFromNumeric(addrs: *std.ArrayList(LookupAddr), name: []const u8, family: i32) !void { if (parseIp4(name)) |ip4| { if (family == os.AF_INET6) return error.ExpectedIPv6ButFoundIPv4; + const item = try addrs.addOne(); // TODO [0..4] should return *[4]u8, making this pointer cast unnecessary - mem.writeIntNative(u32, @ptrCast(*[4]u8, &buf[0].addr), ip4); - buf[0].family = os.AF_INET; - buf[0].scope_id = 0; - return 1; + mem.writeIntNative(u32, @ptrCast(*[4]u8, &item.addr), ip4); + item.family = os.AF_INET; + item.scope_id = 0; + return; } else |err| switch (err) { error.Overflow, error.InvalidEnd, @@ -521,10 +524,11 @@ fn linuxLookupNameFromNumeric(buf: []LookupAddr, name: []const u8, family: i32) if (parseIp6(name)) |ip6| { if (family == os.AF_INET) return error.ExpectedIPv4ButFoundIPv6; - @memcpy(&buf[0].addr, &ip6.addr, 16); - buf[0].family = os.AF_INET6; - buf[0].scope_id = ip6.scope_id; - return 1; + const item = try addrs.addOne(); + @memcpy(&item.addr, &ip6.addr, 16); + item.family = os.AF_INET6; + item.scope_id = ip6.scope_id; + return; } else |err| switch (err) { error.Overflow, error.InvalidEnd, @@ -532,64 +536,54 @@ fn linuxLookupNameFromNumeric(buf: []LookupAddr, name: []const u8, family: i32) error.Incomplete, => {}, } - - return 0; } -fn linuxLookupNameFromNull(buf: []LookupAddr, family: i32, flags: u32) usize { - var cnt: usize = 0; - if ((flags & os.AI_PASSIVE) != 0) { +fn linuxLookupNameFromNull(addrs: *std.ArrayList(LookupAddr), family: i32, flags: u32) !void { + if ((flags & std.c.AI_PASSIVE) != 0) { if (family != os.AF_INET6) { - buf[cnt] = LookupAddr{ + (try addrs.addOne()).* = LookupAddr{ .family = os.AF_INET, .addr = [1]u8{0} ** 16, }; - cnt += 1; } if (family != os.AF_INET) { - buf[cnt] = LookupAddr{ + (try addrs.addOne()).* = LookupAddr{ .family = os.AF_INET6, .addr = [1]u8{0} ** 16, }; - cnt += 1; } } else { if (family != os.AF_INET6) { - buf[cnt] = LookupAddr{ + (try addrs.addOne()).* = LookupAddr{ .family = os.AF_INET, .addr = [4]u8{ 127, 0, 0, 1 } ++ ([1]u8{0} ** 12), }; - cnt += 1; } if (family != os.AF_INET) { - buf[cnt] = LookupAddr{ + (try addrs.addOne()).* = LookupAddr{ .family = os.AF_INET6, .addr = ([1]u8{0} ** 15) ++ [1]u8{1}, }; - cnt += 1; } } - return cnt; } fn linuxLookupNameFromHosts( - buf: []LookupAddr, - canon_buf: []u8, - canon_len: *usize, + addrs: *std.ArrayList(LookupAddr), + canon: *std.Buffer, name: []const u8, family: i32, -) !usize { - const file = std.fs.File.openReadC(c"/etc/hosts") catch |err| switch (err) { +) !void { + const file = fs.File.openReadC(c"/etc/hosts") catch |err| switch (err) { error.FileNotFound, error.NotDir, error.AccessDenied, - => return 0, + => return, else => |e| return e, }; defer file.close(); - var cnt: usize = 0; - const stream = &std.io.BufferedInStream(std.fs.File.ReadError).init(&file.inStream().stream).stream; + const stream = &std.io.BufferedInStream(fs.File.ReadError).init(&file.inStream().stream).stream; var line_buf: [512]u8 = undefined; while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) { error.StreamTooLong => blk: { @@ -612,26 +606,20 @@ fn linuxLookupNameFromHosts( } } else continue; - switch (linuxLookupNameFromNumeric(buf[cnt..], ip_text, family) catch |err| switch (err) { + const prev_len = addrs.len; + linuxLookupNameFromNumeric(addrs, ip_text, family) catch |err| switch (err) { error.ExpectedIPv6ButFoundIPv4 => continue, error.ExpectedIPv4ButFoundIPv6 => continue, - }) { - 0 => continue, - 1 => { - // first name is canonical name - const name_text = first_name_text.?; - if (isValidHostName(name_text)) { - mem.copy(u8, canon_buf, name_text); - canon_len.* = name_text.len; - } - - cnt += 1; - if (cnt == buf.len) break; - }, - else => unreachable, + error.OutOfMemory => |e| return e, + }; + if (addrs.len > prev_len) { + // first name is canonical name + const name_text = first_name_text.?; + if (isValidHostName(name_text)) { + try canon.replaceContents(name_text); + } } } - return cnt; } pub fn isValidHostName(hostname: []const u8) bool { @@ -647,50 +635,414 @@ pub fn isValidHostName(hostname: []const u8) bool { } fn linuxLookupNameFromDnsSearch( - buf: []LookupAddr, - canon_buf: []u8, - canon_len: *usize, + addrs: *std.ArrayList(LookupAddr), + canon: *std.Buffer, name: []const u8, family: i32, -) !usize { - var search: [256]u8 = undefined; - const resolv_conf = try getResolvConf(&search); +) !void { + var rc: ResolvConf = undefined; + try getResolvConf(addrs.allocator, &rc); + defer rc.deinit(); // Count dots, suppress search when >=ndots or name ends in // a dot, which is an explicit request for global scope. - //var dots: usize = 0; - //for (name) |byte| { - // if (byte == '.') dots += 1; - //} + var dots: usize = 0; + for (name) |byte| { + if (byte == '.') dots += 1; + } + + const search = if (rc.search.isNull() or dots >= rc.ndots or mem.endsWith(u8, name, ".")) + [_]u8{} + else + rc.search.toSliceConst(); + + var canon_name = name; + + // Strip final dot for canon, fail if multiple trailing dots. + if (mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1; + if (mem.endsWith(u8, canon_name, ".")) return error.UnknownName; + + // Name with search domain appended is setup in canon[]. This both + // provides the desired default canonical name (if the requested + // name is not a CNAME record) and serves as a buffer for passing + // the full requested name to name_from_dns. + try canon.resize(canon_name.len); + mem.copy(u8, canon.toSlice(), canon_name); + try canon.appendByte('.'); + + var tok_it = mem.tokenize(search, " \t"); + while (tok_it.next()) |tok| { + canon.shrink(canon_name.len + 1); + try canon.append(tok); + try linuxLookupNameFromDns(addrs, canon, canon.toSliceConst(), family, rc); + if (addrs.len != 0) return; + } + + canon.shrink(canon_name.len); + return linuxLookupNameFromDns(addrs, canon, name, family, rc); +} + +const dpc_ctx = struct { + addrs: *std.ArrayList(LookupAddr), + canon: *std.Buffer, +}; + +fn linuxLookupNameFromDns( + addrs: *std.ArrayList(LookupAddr), + canon: *std.Buffer, + name: []const u8, + family: i32, + rc: ResolvConf, +) !void { + var ctx = dpc_ctx{ + .addrs = addrs, + .canon = canon, + }; + const AfRr = struct { + af: i32, + rr: u8, + }; + const afrrs = [_]AfRr{ + AfRr{ .af = os.AF_INET6, .rr = os.RR_A }, + AfRr{ .af = os.AF_INET, .rr = os.RR_AAAA }, + }; + var qbuf: [2][280]u8 = undefined; + var abuf: [2][512]u8 = undefined; + var qp: [2][]const u8 = undefined; + const apbuf = [2][]u8{ &abuf[0], &abuf[1] }; + var nq: usize = 0; + + for (afrrs) |afrr| { + if (family != afrr.af) { + const len = os.res_mkquery(0, name, 1, afrr.rr, [_]u8{}, null, &qbuf[nq]); + qp[nq] = qbuf[nq][0..len]; + nq += 1; + } + } + + var ap = [2][]u8{ apbuf[0][0..0], apbuf[1][0..0] }; + try resMSendRc(qp[0..nq], ap[0..nq], apbuf[0..nq], rc); + + var i: usize = 0; + while (i < nq) : (i += 1) { + dnsParse(ap[i], ctx, dnsParseCallback) catch {}; + } + + if (addrs.len != 0) return; + if (ap[0].len < 4 or (ap[0][3] & 15) == 2) return error.TemporaryNameServerFailure; + if ((ap[0][3] & 15) == 0) return error.UnknownName; + if ((ap[0][3] & 15) == 3) return; + return error.NameServerFailure; +} + +const ResolvConf = struct { + attempts: u32, + ndots: u32, + timeout: u32, + search: std.Buffer, + ns: std.ArrayList(LookupAddr), + + fn deinit(rc: *ResolvConf) void { + rc.ns.deinit(); + rc.search.deinit(); + rc.* = undefined; + } +}; + +/// Ignores lines longer than 512 bytes. +/// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761 +fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void { + rc.* = ResolvConf{ + .ns = std.ArrayList(LookupAddr).init(allocator), + .search = std.Buffer.initNull(allocator), + .ndots = 1, + .timeout = 5, + .attempts = 2, + }; + errdefer rc.deinit(); + + const file = fs.File.openReadC(c"/etc/resolv.conf") catch |err| switch (err) { + error.FileNotFound, + error.NotDir, + error.AccessDenied, + => return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1"), + else => |e| return e, + }; + defer file.close(); + + var cnt: usize = 0; + const stream = &std.io.BufferedInStream(fs.File.ReadError).init(&file.inStream().stream).stream; + var line_buf: [512]u8 = undefined; + while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) { + error.StreamTooLong => blk: { + // Skip to the delimiter in the stream, to fix parsing + try stream.skipUntilDelimiterOrEof('\n'); + // Give an empty line to the while loop, which will be skipped. + break :blk line_buf[0..0]; + }, + else => |e| return e, + }) |line| { + const no_comment_line = mem.separate(line, "#").next().?; + var line_it = mem.tokenize(no_comment_line, " \t"); + + const token = line_it.next() orelse continue; + if (mem.eql(u8, token, "options")) { + while (line_it.next()) |sub_tok| { + var colon_it = mem.separate(sub_tok, ":"); + const name = colon_it.next().?; + const value_txt = colon_it.next() orelse continue; + const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) { + error.Overflow => 255, + error.InvalidCharacter => continue, + }; + if (mem.eql(u8, name, "ndots")) { + rc.ndots = std.math.min(value, 15); + } else if (mem.eql(u8, name, "attempts")) { + rc.attempts = std.math.min(value, 10); + } else if (mem.eql(u8, name, "timeout")) { + rc.timeout = std.math.min(value, 60); + } + } + } else if (mem.eql(u8, token, "nameserver")) { + const ip_txt = line_it.next() orelse continue; + try linuxLookupNameFromNumericUnspec(&rc.ns, ip_txt); + } else if (mem.eql(u8, token, "domain") or mem.eql(u8, token, "search")) { + try rc.search.replaceContents(line_it.rest()); + } + } + + if (rc.ns.len == 0) { + return linuxLookupNameFromNumericUnspec(&rc.ns, "127.0.0.1"); + } +} + +fn eqlSockAddr(a: *const os.sockaddr, b: *const os.sockaddr, len: usize) bool { + const a_bytes = @ptrCast([*]const u8, a)[0..len]; + const b_bytes = @ptrCast([*]const u8, b)[0..len]; + return mem.eql(u8, a_bytes, b_bytes); +} + +fn resMSendRc( + queries: []const []const u8, + answers: [][]u8, + answer_bufs: []const []u8, + rc: ResolvConf, +) !void { + const timeout = 1000 * rc.timeout; + const attempts = rc.attempts; + + var sl: os.socklen_t = @sizeOf(os.sockaddr_in); + var family: os.sa_family_t = os.AF_INET; + + var ns_list = std.ArrayList(os.sockaddr).init(rc.ns.allocator); + defer ns_list.deinit(); - //if (dots >= conf.ndots || name[l-1]=='.') *search = 0; - - //// Strip final dot for canon, fail if multiple trailing dots. - //if (name[l-1]=='.') l--; - //if (!l || name[l-1]=='.') return EAI_NONAME; - - //// This can never happen; the caller already checked length. - //if (l >= 256) return EAI_NONAME; - - //// Name with search domain appended is setup in canon[]. This both - //// provides the desired default canonical name (if the requested - //// name is not a CNAME record) and serves as a buffer for passing - //// the full requested name to name_from_dns. - //memcpy(canon, name, l); - //canon[l] = '.'; - - //for (p=search; *p; p=z) { - // for (; isspace(*p); p++); - // for (z=p; *z && !isspace(*z); z++); - // if (z==p) break; - // if (z-p < 256 - l - 1) { - // memcpy(canon+l+1, p, z-p); - // canon[z-p+1+l] = 0; - // int cnt = name_from_dns(buf, canon, canon, family, &conf); - // if (cnt) return cnt; + try ns_list.resize(rc.ns.len); + const ns = ns_list.toSlice(); + + for (rc.ns.toSliceConst()) |iplit, i| { + if (iplit.family == os.AF_INET) { + ns[i] = os.sockaddr{ + .in = os.sockaddr_in{ + .family = os.AF_INET, + .port = mem.nativeToBig(u16, 53), + .addr = mem.readIntNative(u32, @ptrCast(*const [4]u8, &iplit.addr)), + .zero = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }, + }, + }; + } else { + ns[i] = os.sockaddr{ + .in6 = os.sockaddr_in6{ + .family = os.AF_INET6, + .port = mem.nativeToBig(u16, 53), + .flowinfo = 0, + .addr = iplit.addr, + .scope_id = iplit.scope_id, + }, + }; + sl = @sizeOf(os.sockaddr_in6); + family = os.AF_INET6; + } + } + + // Get local address and open/bind a socket + var sa: os.sockaddr = undefined; + @memset(@ptrCast([*]u8, &sa), 0, @sizeOf(os.sockaddr)); + sa.in.family = family; + const flags = os.SOCK_DGRAM | os.SOCK_CLOEXEC | os.SOCK_NONBLOCK; + const fd = os.socket(family, flags, 0) catch |err| switch (err) { + error.AddressFamilyNotSupported => blk: { + // Handle case where system lacks IPv6 support + if (family == os.AF_INET6) { + family = os.AF_INET; + break :blk try os.socket(os.AF_INET, flags, 0); + } + return err; + }, + else => |e| return e, + }; + defer os.close(fd); + try os.bind(fd, &sa, sl); + + // Past this point, there are no errors. Each individual query will + // yield either no reply (indicated by zero length) or an answer + // packet which is up to the caller to interpret. + + // Convert any IPv4 addresses in a mixed environment to v4-mapped + // TODO + //if (family == AF_INET6) { + // setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &(int){0}, sizeof 0); + // for (i=0; i<nns; i++) { + // if (ns[i].sin.sin_family != AF_INET) continue; + // memcpy(ns[i].sin6.sin6_addr.s6_addr+12, + // &ns[i].sin.sin_addr, 4); + // memcpy(ns[i].sin6.sin6_addr.s6_addr, + // "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12); + // ns[i].sin6.sin6_family = AF_INET6; + // ns[i].sin6.sin6_flowinfo = 0; + // ns[i].sin6.sin6_scope_id = 0; // } //} - //canon[l] = 0; - //return name_from_dns(buf, canon, name, family, &conf); + var pfd = [1]os.pollfd{os.pollfd{ + .fd = fd, + .events = os.POLLIN, + .revents = undefined, + }}; + const retry_interval = timeout / attempts; + var next: u32 = 0; + var t2: usize = std.time.milliTimestamp(); + var t0 = t2; + var t1 = t2 - retry_interval; + + var servfail_retry: usize = undefined; + + outer: while (t2 - t0 < timeout) : (t2 = std.time.milliTimestamp()) { + if (t2 - t1 >= retry_interval) { + // Query all configured nameservers in parallel + var i: usize = 0; + while (i < queries.len) : (i += 1) { + if (answers[i].len == 0) { + var j: usize = 0; + while (j < ns.len) : (j += 1) { + _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j], sl) catch undefined; + } + } + } + t1 = t2; + servfail_retry = 2 * queries.len; + } + + // Wait for a response, or until time to retry + const clamped_timeout = std.math.min(u31(std.math.maxInt(u31)), t1 + retry_interval - t2); + const nevents = os.poll(&pfd, clamped_timeout) catch 0; + if (nevents == 0) continue; + + while (true) { + var sl_copy = sl; + const rlen = os.recvfrom(fd, answer_bufs[next], 0, &sa, &sl_copy) catch break; + + // Ignore non-identifiable packets + if (rlen < 4) continue; + + // Ignore replies from addresses we didn't send to + var j: usize = 0; + while (j < ns.len and !eqlSockAddr(&ns[j], &sa, sl)) : (j += 1) {} + if (j == ns.len) continue; + + // Find which query this answer goes with, if any + var i: usize = next; + while (i < queries.len and (answer_bufs[next][0] != queries[i][0] or + answer_bufs[next][1] != queries[i][1])) : (i += 1) + {} + + if (i == queries.len) continue; + if (answers[i].len != 0) continue; + + // Only accept positive or negative responses; + // retry immediately on server failure, and ignore + // all other codes such as refusal. + switch (answer_bufs[next][3] & 15) { + 0, 3 => {}, + 2 => if (servfail_retry != 0) { + servfail_retry -= 1; + _ = os.sendto(fd, queries[i], os.MSG_NOSIGNAL, &ns[j], sl) catch undefined; + }, + else => continue, + } + + // Store answer in the right slot, or update next + // available temp slot if it's already in place. + answers[i].len = rlen; + if (i == next) { + while (next < queries.len and answers[next].len != 0) : (next += 1) {} + } else { + mem.copy(u8, answer_bufs[i], answer_bufs[next][0..rlen]); + } + + if (next == queries.len) break :outer; + } + } +} + +fn dnsParse( + r: []const u8, + ctx: var, + comptime callback: var, +) !void { + if (r.len < 12) return error.InvalidDnsPacket; + if ((r[3] & 15) != 0) return; + var p = r.ptr + 12; + var qdcount = r[4] * usize(256) + r[5]; + var ancount = r[6] * usize(256) + r[7]; + if (qdcount + ancount > 64) return error.InvalidDnsPacket; + while (qdcount != 0) { + qdcount -= 1; + while (@ptrToInt(p) - @ptrToInt(r.ptr) < r.len and p[0] -% 1 < 127) p += 1; + if (p[0] > 193 or (p[0] == 193 and p[1] > 254) or @ptrToInt(p) > @ptrToInt(r.ptr) + r.len - 6) + return error.InvalidDnsPacket; + p += usize(5) + @boolToInt(p[0] != 0); + } + while (ancount != 0) { + ancount -= 1; + while (@ptrToInt(p) - @ptrToInt(r.ptr) < r.len and p[0] -% 1 < 127) p += 1; + if (p[0] > 193 or (p[0] == 193 and p[1] > 254) or @ptrToInt(p) > @ptrToInt(r.ptr) + r.len - 6) + return error.InvalidDnsPacket; + p += usize(1) + @boolToInt(p[0] != 0); + const len = p[8] * usize(256) + p[9]; + if (@ptrToInt(p) + len > @ptrToInt(r.ptr) + r.len) return error.InvalidDnsPacket; + try callback(ctx, p[1], p[10 .. 10 + len], r); + p += 10 + len; + } +} + +fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void { + var tmp: [256]u8 = undefined; + switch (rr) { + os.RR_A => { + if (data.len != 4) return error.InvalidDnsARecord; + const new_addr = try ctx.addrs.addOne(); + new_addr.* = LookupAddr{ + .family = os.AF_INET, + .addr = undefined, + }; + mem.copy(u8, &new_addr.addr, data); + }, + os.RR_AAAA => { + if (data.len != 16) return error.InvalidDnsAAAARecord; + const new_addr = try ctx.addrs.addOne(); + new_addr.* = LookupAddr{ + .family = os.AF_INET6, + .addr = undefined, + }; + mem.copy(u8, &new_addr.addr, data); + }, + os.RR_CNAME => { + @panic("TODO dn_expand"); + //if (__dn_expand(packet, (const unsigned char *)packet + 512, + // data, tmp, sizeof tmp) > 0 && is_valid_hostname(tmp)) + // strcpy(ctx->canon, tmp); + }, + else => return, + } } diff --git a/lib/std/os.zig b/lib/std/os.zig index 40041c8ef7..d858f40106 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1508,16 +1508,17 @@ pub const SocketError = error{ ProtocolNotSupported, } || UnexpectedError; -pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!i32 { +pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!fd_t { const rc = system.socket(domain, socket_type, protocol); switch (errno(rc)) { - 0 => return @intCast(i32, rc), + 0 => return @intCast(fd_t, rc), EACCES => return error.PermissionDenied, EAFNOSUPPORT => return error.AddressFamilyNotSupported, EINVAL => return error.ProtocolFamilyNotAvailable, EMFILE => return error.ProcessFdQuotaExceeded, ENFILE => return error.SystemFdQuotaExceeded, - ENOBUFS, ENOMEM => return error.SystemResources, + ENOBUFS => return error.SystemResources, + ENOMEM => return error.SystemResources, EPROTONOSUPPORT => return error.ProtocolNotSupported, else => |err| return unexpectedErrno(err), } @@ -1559,17 +1560,17 @@ pub const BindError = error{ } || UnexpectedError; /// addr is `*const T` where T is one of the sockaddr -pub fn bind(fd: i32, addr: *const sockaddr) BindError!void { - const rc = system.bind(fd, addr, @sizeOf(sockaddr)); +pub fn bind(sockfd: fd_t, addr: *const sockaddr, len: socklen_t) BindError!void { + const rc = system.bind(sockfd, addr, len); switch (errno(rc)) { 0 => return, EACCES => return error.AccessDenied, EADDRINUSE => return error.AddressInUse, EBADF => unreachable, // always a race condition if this error is returned - EINVAL => unreachable, - ENOTSOCK => unreachable, + EINVAL => unreachable, // invalid parameters + ENOTSOCK => unreachable, // invalid `sockfd` EADDRNOTAVAIL => return error.AddressNotAvailable, - EFAULT => unreachable, + EFAULT => unreachable, // invalid `addr` pointer ELOOP => return error.SymLinkLoop, ENAMETOOLONG => return error.NameTooLong, ENOENT => return error.FileNotFound, @@ -2833,3 +2834,245 @@ pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 { @compileError("TODO implement gethostname for this OS"); } + +pub fn res_mkquery( + op: u4, + dname: []const u8, + class: u8, + ty: u8, + data: []const u8, + newrr: ?[*]const u8, + buf: []u8, +) usize { + var name = dname; + if (mem.endsWith(u8, name, ".")) name.len -= 1; + assert(name.len <= 253); + const n = 17 + name.len + @boolToInt(name.len != 0); + + // Construct query template - ID will be filled later + var q: [280]u8 = undefined; + @memset(&q, 0, n); + q[2] = u8(op) * 8 + 1; + q[5] = 1; + mem.copy(u8, q[13..], name); + var i: usize = 13; + var j: usize = undefined; + while (q[i] != 0) : (i = j + 1) { + j = i; + while (q[j] != 0 and q[j] != '.') : (j += 1) {} + // TODO determine the circumstances for this and whether or + // not this should be an error. + if (j - i - 1 > 62) unreachable; + q[i - 1] = @intCast(u8, j - i); + } + q[i + 1] = ty; + q[i + 3] = class; + + // Make a reasonably unpredictable id + var ts: timespec = undefined; + clock_gettime(CLOCK_REALTIME, &ts) catch {}; + const UInt = @IntType(false, @typeOf(ts.tv_nsec).bit_count); + const unsec = @bitCast(UInt, ts.tv_nsec); + const id = @truncate(u32, unsec + unsec / 65536); + q[0] = @truncate(u8, id / 256); + q[1] = @truncate(u8, id); + + mem.copy(u8, buf, q[0..n]); + return n; +} + +pub const SendError = error{ + /// (For UNIX domain sockets, which are identified by pathname) Write permission is denied + /// on the destination socket file, or search permission is denied for one of the + /// directories the path prefix. (See path_resolution(7).) + /// (For UDP sockets) An attempt was made to send to a network/broadcast address as though + /// it was a unicast address. + AccessDenied, + + /// The socket is marked nonblocking and the requested operation would block, and + /// there is no global event loop configured. + /// It's also possible to get this error under the following condition: + /// (Internet domain datagram sockets) The socket referred to by sockfd had not previously + /// been bound to an address and, upon attempting to bind it to an ephemeral port, it was + /// determined that all port numbers in the ephemeral port range are currently in use. See + /// the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7). + WouldBlock, + + /// Another Fast Open is already in progress. + FastOpenAlreadyInProgress, + + /// Connection reset by peer. + ConnectionResetByPeer, + + /// The socket type requires that message be sent atomically, and the size of the message + /// to be sent made this impossible. The message is not transmitted. + /// + MessageTooBig, + + /// The output queue for a network interface was full. This generally indicates that the + /// interface has stopped sending, but may be caused by transient congestion. (Normally, + /// this does not occur in Linux. Packets are just silently dropped when a device queue + /// overflows.) + /// This is also caused when there is not enough kernel memory available. + SystemResources, + + /// The local end has been shut down on a connection oriented socket. In this case, the + /// process will also receive a SIGPIPE unless MSG_NOSIGNAL is set. + BrokenPipe, +} || UnexpectedError; + +/// Transmit a message to another socket. +/// +/// The `sendto` call may be used only when the socket is in a connected state (so that the intended +/// recipient is known). The following call +/// +/// send(sockfd, buf, len, flags); +/// +/// is equivalent to +/// +/// sendto(sockfd, buf, len, flags, NULL, 0); +/// +/// If sendto() is used on a connection-mode (`SOCK_STREAM`, `SOCK_SEQPACKET`) socket, the arguments +/// `dest_addr` and `addrlen` are asserted to be `null` and `0` respectively, and asserted +/// that the socket was actually connected. +/// Otherwise, the address of the target is given by `dest_addr` with `addrlen` specifying its size. +/// +/// If the message is too long to pass atomically through the underlying protocol, +/// `SendError.MessageTooBig` is returned, and the message is not transmitted. +/// +/// There is no indication of failure to deliver. +/// +/// When the message does not fit into the send buffer of the socket, `sendto` normally blocks, +/// unless the socket has been placed in nonblocking I/O mode. In nonblocking mode it would fail +/// with `SendError.WouldBlock`. The `select` call may be used to determine when it is +/// possible to send more data. +pub fn sendto( + /// The file descriptor of the sending socket. + sockfd: fd_t, + /// Message to send. + buf: []const u8, + flags: u32, + dest_addr: ?*const sockaddr, + addrlen: socklen_t, +) SendError!usize { + while (true) { + const rc = system.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen); + switch (errno(rc)) { + 0 => return rc, + + EACCES => return error.AccessDenied, + EAGAIN => if (std.event.Loop.instance) |loop| { + loop.waitUntilFdWritable(sockfd) catch return error.WouldBlock; + continue; + } else { + return error.WouldBlock; + }, + EALREADY => return error.FastOpenAlreadyInProgress, + EBADF => unreachable, // always a race condition + ECONNRESET => return error.ConnectionResetByPeer, + EDESTADDRREQ => unreachable, // The socket is not connection-mode, and no peer address is set. + EFAULT => unreachable, // An invalid user space address was specified for an argument. + EINTR => continue, + EINVAL => unreachable, // Invalid argument passed. + EISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified + EMSGSIZE => return error.MessageTooBig, + ENOBUFS => return error.SystemResources, + ENOMEM => return error.SystemResources, + ENOTCONN => unreachable, // The socket is not connected, and no target has been given. + ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. + EOPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type. + EPIPE => return error.BrokenPipe, + else => |err| return unexpectedErrno(err), + } + } +} + +/// Transmit a message to another socket. +/// +/// The `send` call may be used only when the socket is in a connected state (so that the intended +/// recipient is known). The only difference between `send` and `write` is the presence of +/// flags. With a zero flags argument, `send` is equivalent to `write`. Also, the following +/// call +/// +/// send(sockfd, buf, len, flags); +/// +/// is equivalent to +/// +/// sendto(sockfd, buf, len, flags, NULL, 0); +/// +/// There is no indication of failure to deliver. +/// +/// When the message does not fit into the send buffer of the socket, `send` normally blocks, +/// unless the socket has been placed in nonblocking I/O mode. In nonblocking mode it would fail +/// with `SendError.WouldBlock`. The `select` call may be used to determine when it is +/// possible to send more data. +pub fn send( + /// The file descriptor of the sending socket. + sockfd: fd_t, + buf: []const u8, + flags: u32, +) SendError!usize { + return sendto(sockfd, buf, flags, null, 0); +} + +pub const PollError = error{ + /// The kernel had no space to allocate file descriptor tables. + SystemResources, +} || UnexpectedError; + +pub fn poll(fds: []pollfd, timeout: i32) PollError!usize { + while (true) { + const rc = system.poll(fds.ptr, fds.len, timeout); + switch (errno(rc)) { + 0 => return rc, + EFAULT => unreachable, + EINTR => continue, + EINVAL => unreachable, + ENOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } + } +} + +pub const RecvFromError = error{ + /// The socket is marked nonblocking and the requested operation would block, and + /// there is no global event loop configured. + WouldBlock, + + /// A remote host refused to allow the network connection, typically because it is not + /// running the requested service. + ConnectionRefused, + + /// Could not allocate kernel memory. + SystemResources, +} || UnexpectedError; + +pub fn recvfrom( + sockfd: fd_t, + buf: []u8, + flags: u32, + src_addr: ?*sockaddr, + addrlen: ?*socklen_t, +) RecvFromError!usize { + while (true) { + const rc = system.recvfrom(sockfd, buf.ptr, buf.len, flags, src_addr, addrlen); + switch (errno(rc)) { + 0 => return rc, + EBADF => unreachable, // always a race condition + EFAULT => unreachable, + EINVAL => unreachable, + ENOTCONN => unreachable, + ENOTSOCK => unreachable, + EINTR => continue, + EAGAIN => if (std.event.Loop.instance) |loop| { + loop.waitUntilFdReadable(sockfd) catch return error.WouldBlock; + continue; + } else { + return error.WouldBlock; + }, + ENOMEM => return error.SystemResources, + ECONNREFUSED => return error.ConnectionRefused, + else => |err| return unexpectedErrno(err), + } + } +} diff --git a/lib/std/os/bits/linux.zig b/lib/std/os/bits/linux.zig index e6d3404f19..3f3b5e8cbe 100644 --- a/lib/std/os/bits/linux.zig +++ b/lib/std/os/bits/linux.zig @@ -1431,3 +1431,23 @@ pub const IPPROTO_UDPLITE = 136; pub const IPPROTO_MPLS = 137; pub const IPPROTO_RAW = 255; pub const IPPROTO_MAX = 256; + +pub const RR_A = 1; +pub const RR_CNAME = 5; +pub const RR_AAAA = 28; + +pub const nfds_t = usize; +pub const pollfd = extern struct { + fd: fd_t, + events: i16, + revents: i16, +}; + +pub const POLLIN = 0x001; +pub const POLLPRI = 0x002; +pub const POLLOUT = 0x004; +pub const POLLERR = 0x008; +pub const POLLHUP = 0x010; +pub const POLLNVAL = 0x020; +pub const POLLRDNORM = 0x040; +pub const POLLRDBAND = 0x080; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 2c8500574d..cf7a5dffb0 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -226,6 +226,28 @@ pub fn munmap(address: [*]const u8, length: usize) usize { return syscall2(SYS_munmap, @ptrToInt(address), length); } +pub fn poll(fds: [*]pollfd, n: nfds_t, timeout: i32) usize { + if (@hasDecl(@This(), "SYS_poll")) { + return syscall3(SYS_poll, @ptrToInt(fds), n, @bitCast(u32, timeout)); + } else { + return syscall6( + SYS_ppoll, + @ptrToInt(fds), + n, + @ptrToInt(if (timeout >= 0) + ×pec{ + .tv_sec = timeout / 1000, + .tv_nsec = (timeout % 1000) * 1000000, + } + else + null), + 0, + 0, + NSIG / 8, + ); + } +} + pub fn read(fd: i32, buf: [*]u8, count: usize) usize { return syscall3(SYS_read, @bitCast(usize, isize(fd)), @ptrToInt(buf), count); } |
