DNS Lookup Tutorial

This tutorial builds a command-line DNS lookup tool similar to nslookup. You’ll learn to use the asynchronous resolver to convert hostnames to IP addresses.

Code snippets assume:
#include <boost/corosio.hpp>
#include <boost/capy/task.hpp>
#include <boost/capy/ex/run_async.hpp>

namespace corosio = boost::corosio;
namespace capy = boost::capy;

Overview

DNS resolution converts a hostname like www.example.com to one or more IP addresses. The resolver class performs this asynchronously:

corosio::resolver r(ioc);
auto [ec, results] = co_await r.resolve("www.example.com", "https");

The second argument is the service name (or port number as a string). It determines the port in the returned endpoints.

The Lookup Coroutine

capy::task<void> do_lookup(
    corosio::io_context& ioc,
    std::string_view host,
    std::string_view service)
{
    corosio::resolver r(ioc);

    auto [ec, results] = co_await r.resolve(host, service);
    if (ec)
    {
        std::cerr << "Resolve failed: " << ec.message() << "\n";
        co_return;
    }

    std::cout << "Results for " << host;
    if (!service.empty())
        std::cout << ":" << service;
    std::cout << "\n";

    for (auto const& entry : results)
    {
        auto ep = entry.get_endpoint();
        if (ep.is_v4())
        {
            std::cout << "  IPv4: " << ep.v4_address().to_string()
                      << ":" << ep.port() << "\n";
        }
        else
        {
            std::cout << "  IPv6: " << ep.v6_address().to_string()
                      << ":" << ep.port() << "\n";
        }
    }

    std::cout << "\nTotal: " << results.size() << " addresses\n";
}

Understanding Results

The resolver returns a resolver_results object containing resolver_entry elements. Each entry provides:

  • get_endpoint() — The resolved endpoint (address + port)

  • host_name() — The queried hostname

  • service_name() — The queried service

The endpoint class supports both IPv4 and IPv6:

auto ep = entry.get_endpoint();

if (ep.is_v4())
{
    // IPv4 address
    boost::urls::ipv4_address addr = ep.v4_address();
}
else
{
    // IPv6 address
    boost::urls::ipv6_address addr = ep.v6_address();
}

std::uint16_t port = ep.port();

Main Function

int main(int argc, char* argv[])
{
    if (argc < 2 || argc > 3)
    {
        std::cerr << "Usage: nslookup <hostname> [service]\n"
                  << "Examples:\n"
                  << "    nslookup www.google.com\n"
                  << "    nslookup www.google.com https\n"
                  << "    nslookup localhost 8080\n";
        return 1;
    }

    std::string_view host = argv[1];
    std::string_view service = (argc == 3) ? argv[2] : "";

    corosio::io_context ioc;
    capy::run_async(ioc.get_executor())(do_lookup(ioc, host, service));
    ioc.run();
}

Resolver Flags

The resolver accepts optional flags to control behavior:

auto [ec, results] = co_await r.resolve(
    host, service,
    corosio::resolve_flags::numeric_host |
    corosio::resolve_flags::numeric_service);

Available flags:

Flag Description

passive

Return endpoints suitable for binding (server use)

numeric_host

Host is a numeric address string, skip DNS

numeric_service

Service is a port number string

address_configured

Only return addresses if configured on the system

v4_mapped

Return IPv4-mapped IPv6 addresses if no IPv6 found

all_matching

With v4_mapped, return all matching addresses

Connecting to Resolved Addresses

After resolving, iterate through results to find a working connection:

capy::task<void> connect_to_host(
    corosio::io_context& ioc,
    std::string_view host,
    std::string_view service)
{
    corosio::resolver r(ioc);
    auto [resolve_ec, results] = co_await r.resolve(host, service);
    if (resolve_ec)
        throw boost::system::system_error(resolve_ec);

    corosio::socket sock(ioc);
    sock.open();

    // Try each address until one works
    boost::system::error_code last_ec;
    for (auto const& entry : results)
    {
        auto [ec] = co_await sock.connect(entry.get_endpoint());
        if (!ec)
        {
            std::cout << "Connected to " << host << "\n";
            co_return;
        }
        last_ec = ec;
    }

    throw boost::system::system_error(last_ec, "all addresses failed");
}

Running the Lookup Tool

$ ./nslookup www.google.com https
Results for www.google.com:https
  IPv4: 142.250.189.68:443
  IPv6: 2607:f8b0:4004:800::2004:443

Total: 2 addresses
$ ./nslookup localhost 8080
Results for localhost:8080
  IPv4: 127.0.0.1:8080

Total: 1 addresses

Cancellation

Resolver operations support cancellation via std::stop_token:

r.cancel();  // Cancel pending operation

Or through the affine awaitable protocol when using capy::jcancellable_task.

Next Steps