Skip to content

DNS lookup fails when ndots option present #25948

@kfprimm

Description

@kfprimm

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?

zig/lib/std/Io/Threaded.zig

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behavior

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions