-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
Zig Version
0.16.0-dev.1334+06d08daba
Steps to Reproduce and Observed Behavior
I am running zig build during a container build on https://render.com. The build process fails when trying to download dependencies:
#19 0.224 /rails/lib/code/libs/zlib/build.zig.zon:7:20: error: unable to connect to server: NameServerFailure
#19 0.224 .url = "https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz",
I've traced the issue down to (what I believe is) the DNS search resolution logic. The resolv.conf in that environment specifies options ndots:5.
Here's a simple way to recreate. You'll need to mark std.Io.Threaded.lookupDns as public in order to compile.
const std = @import("std");
const Io = std.Io;
const ResolvConf = std.Io.net.HostName.ResolvConf;
pub fn main() !void {
var threaded = Io.Threaded.init(std.heap.page_allocator);
const io = threaded.io();
const t = &threaded;
var rc: ResolvConf = .{
.nameservers_buffer = undefined,
.nameservers_len = 0,
.search_buffer = undefined,
.search_len = 0,
.ndots = 1,
.timeout_seconds = 5,
.attempts = 2,
};
const resolv_conf =
\\nameserver 1.1.1.1
\\search lan
\\# comment out the option to below and it will work
\\options ndots:5
\\
;
var file_reader = std.Io.Reader.fixed(resolv_conf);
try ResolvConf.parse(&rc, io, &file_reader);
for (rc.nameservers()) |ns| {
std.debug.print("nameserver = {f}\n", .{ns});
}
var name_buf: [255]u8 = undefined;
const host_name = try std.Io.net.HostName.init("github.com");
const options = std.Io.net.HostName.LookupOptions{
.port = 443,
.canonical_name_buffer = &name_buf,
};
var results: [1024]Io.net.HostName.LookupResult = undefined;
var resolved = Io.Queue(Io.net.HostName.LookupResult).init(&results);
var canon_name = host_name.bytes;
// ---------- Copy/paste from Threaded.zig
// Count dots, suppress search when >=ndots or name ends in
// a dot, which is an explicit request for global scope.
const dots = std.mem.countScalar(u8, host_name.bytes, '.');
const search_len = if (dots >= rc.ndots or std.mem.endsWith(u8, host_name.bytes, ".")) 0 else rc.search_len;
const search = rc.search_buffer[0..search_len];
// Strip final dot for canon, fail if multiple trailing dots.
if (std.mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1;
if (std.mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName;
// Name with search domain appended is set up in `canon_name`. 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 `lookupDns`.
@memcpy(options.canonical_name_buffer[0..canon_name.len], canon_name);
options.canonical_name_buffer[canon_name.len] = '.';
var it = std.mem.tokenizeAny(u8, search, " \t");
while (it.next()) |token| {
@memcpy(options.canonical_name_buffer[canon_name.len + 1 ..][0..token.len], token);
const lookup_canon_name = options.canonical_name_buffer[0 .. canon_name.len + 1 + token.len];
if (Io.Threaded.lookupDns(t, lookup_canon_name, &rc, &resolved, options)) |result| {
return result;
} else |err| switch (err) {
error.UnknownHostName => continue,
else => |e| return e,
}
}
const lookup_canon_name = options.canonical_name_buffer[0..canon_name.len];
try Io.Threaded.lookupDns(t, lookup_canon_name, &rc, &resolved, options);
// ---------- End if copy/paste from Threaded.zig
const res = try resolved.getOne(io);
std.debug.print("ip = {f}\n", .{res.address});
}When executed, the following error occurs:
nameserver = 1.1.1.1:53
error: NameServerFailure
/Users/kevin/.zvm/master/lib/std/Io/Threaded.zig:5499:29: 0x1009268bb in lookupDns (resolv)
if (addresses_len == 0) return error.NameServerFailure;
^
/Users/kevin/code/resolv.zig:73:25: 0x100923af3 in main (resolv)
else => |e| return e,
Expected Behavior
I have a basic understanding of DNS so please let me know if I'm misunderstanding anything.
Looking at the present stdlib code, the behavior makes sense: github.com.lan is not a valid address and therefore raises the error because addresses_len is 0.
It appears the current implementation will only attempt to resolve search paths if the ndots option is present. That might be why this hasn't come up yet. For example, my resolv.conf for my macOS machine has just a name server and search option specified.
Perhaps NameServerFailure should be caught in the search loop in order to move on to the next search domain?
Lines 5302 to 5307 in 9ab7eec
| if (lookupDns(t, lookup_canon_name, &rc, resolved, options)) |result| { | |
| return result; | |
| } else |err| switch (err) { | |
| error.UnknownHostName => continue, | |
| else => |e| return e, | |
| } |
Happy to do any work needed to correct this, I just don't have a complete understanding of the scope (yet).
Thanks.