Como Fazer Lookup DNS em Zig

Introdução

DNS (Domain Name System) é o sistema que traduz nomes de domínio legíveis (como example.com) em endereços IP numéricos (como 93.184.216.34). A resolução DNS é fundamental para qualquer aplicação de rede.

Nesta receita, você aprenderá a fazer lookups DNS em Zig usando a biblioteca padrão, resolver nomes de domínio e trabalhar com diferentes tipos de endereços.

Pré-requisitos

Resolução DNS Básica

Use std.net.Address.resolveIp para resolver nomes de domínio:

const std = @import("std");
const net = std.net;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Resolver nome de domínio para endereço IP
    const hostname = "example.com";
    const port: u16 = 80;

    const addrs = try net.getAddressList(allocator, hostname, port);
    defer addrs.deinit();

    std.debug.print("Endereços para '{s}':\n", .{hostname});

    for (addrs.addrs) |addr| {
        // Formatar o endereço como string
        var buf: [64]u8 = undefined;
        const addr_str = try std.fmt.bufPrint(&buf, "{}", .{addr});
        std.debug.print("  {s}\n", .{addr_str});
    }
}

Saída esperada

Endereços para 'example.com':
  93.184.216.34:80
  2606:2800:220:1:248:1893:25c8:1946:80

Resolver e Conectar

Combine resolução DNS com conexão TCP:

const std = @import("std");
const net = std.net;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const hostname = "example.com";
    const port: u16 = 80;

    // Resolver e tentar conectar em cada endereço
    const addrs = try net.getAddressList(allocator, hostname, port);
    defer addrs.deinit();

    var connected = false;
    for (addrs.addrs) |addr| {
        const stream = net.tcpConnectToAddress(addr) catch |err| {
            std.debug.print("Falha ao conectar em {}: {}\n", .{ addr, err });
            continue;
        };
        defer stream.close();

        std.debug.print("Conectado a {s} em {}\n", .{ hostname, addr });
        connected = true;

        // Fazer algo com a conexão
        const request =
            "GET / HTTP/1.1\r\n" ++
            "Host: example.com\r\n" ++
            "Connection: close\r\n" ++
            "\r\n";
        _ = try stream.write(request);

        var buffer: [1024]u8 = undefined;
        const n = try stream.read(&buffer);
        std.debug.print("Primeiros bytes da resposta:\n{s}\n", .{buffer[0..@min(n, 200)]});
        break;
    }

    if (!connected) {
        std.debug.print("Não foi possível conectar a {s}\n", .{hostname});
    }
}

Resolver Múltiplos Domínios

Resolva vários domínios e compare seus endereços:

const std = @import("std");
const net = std.net;

fn resolveDomain(allocator: std.mem.Allocator, hostname: []const u8) void {
    const addrs = net.getAddressList(allocator, hostname, 0) catch |err| {
        std.debug.print("{s}: erro - {}\n", .{ hostname, err });
        return;
    };
    defer addrs.deinit();

    std.debug.print("{s}:\n", .{hostname});

    if (addrs.addrs.len == 0) {
        std.debug.print("  Nenhum endereço encontrado\n", .{});
        return;
    }

    for (addrs.addrs) |addr| {
        var buf: [64]u8 = undefined;
        const addr_str = std.fmt.bufPrint(&buf, "{}", .{addr}) catch "???";
        std.debug.print("  {s}\n", .{addr_str});
    }
    std.debug.print("\n", .{});
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const domains = [_][]const u8{
        "google.com",
        "github.com",
        "ziglang.org",
        "cloudflare.com",
    };

    for (&domains) |domain| {
        resolveDomain(allocator, domain);
    }
}

Verificar Disponibilidade de Host

Use resolução DNS para verificar se um host está acessível:

const std = @import("std");
const net = std.net;

const HostStatus = enum {
    resolvido,
    nao_encontrado,
    erro_rede,
};

fn checkHost(allocator: std.mem.Allocator, hostname: []const u8) HostStatus {
    const addrs = net.getAddressList(allocator, hostname, 0) catch {
        return .nao_encontrado;
    };
    defer addrs.deinit();

    if (addrs.addrs.len == 0) {
        return .nao_encontrado;
    }

    // Tentar conectar na porta 80 para verificar acessibilidade
    for (addrs.addrs) |addr| {
        const check_addr = net.Address.initIp4(
            if (addr.any.family == std.posix.AF.INET)
                @as(*const [4]u8, @ptrCast(&addr.in.sa.addr)).*
            else
                continue,
            80,
        );

        const stream = net.tcpConnectToAddress(check_addr) catch continue;
        stream.close();
        return .resolvido;
    }

    return .erro_rede;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const hosts = [_][]const u8{
        "google.com",
        "dominio-inexistente-xyz.com",
        "github.com",
    };

    for (&hosts) |host| {
        const status = checkHost(allocator, host);
        const status_str = switch (status) {
            .resolvido => "OK - Acessível",
            .nao_encontrado => "FALHA - Não encontrado",
            .erro_rede => "AVISO - DNS ok, rede indisponível",
        };
        std.debug.print("{s}: {s}\n", .{ host, status_str });
    }
}

Saída esperada

google.com: OK - Acessível
dominio-inexistente-xyz.com: FALHA - Não encontrado
github.com: OK - Acessível

Criar Endereço IP Diretamente

Quando você já sabe o IP, pode criar o endereço sem resolução DNS:

const std = @import("std");
const net = std.net;

pub fn main() !void {
    // IPv4
    const ipv4_addr = net.Address.initIp4(.{ 192, 168, 1, 1 }, 8080);
    std.debug.print("IPv4: {}\n", .{ipv4_addr});

    // IPv6
    const ipv6_addr = net.Address.initIp6(
        .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, // ::1 (localhost)
        8080,
        0,
        0,
    );
    std.debug.print("IPv6: {}\n", .{ipv6_addr});

    // Parsear string de IP
    const parsed = try net.Address.parseIp4("10.0.0.1", 443);
    std.debug.print("Parseado: {}\n", .{parsed});
}

Dicas e Boas Práticas

  1. Cache de DNS: Considere cachear resultados de DNS para evitar lookups repetidos em aplicações de alto desempenho.

  2. Trate falhas: A resolução DNS pode falhar por diversos motivos. Sempre trate erros com o sistema de erros do Zig.

  3. Múltiplos endereços: Um domínio pode resolver para vários IPs. Tente todos antes de falhar.

  4. IPv4 e IPv6: Esteja preparado para lidar com ambos os protocolos.

  5. Timeouts: DNS geralmente é rápido, mas pode ser lento em redes problemáticas.

Receitas Relacionadas

Tutoriais Relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.