DNS Resolution
Learn how to resolve hostnames to IP addresses using coroutines.
Basic Resolution
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
using asio::awaitable;
using asio::use_awaitable;
awaitable<void> resolve_hostname()
{
auto executor = co_await asio::this_coro::executor;
tcp::resolver resolver(executor);
// Resolve hostname and service name
auto results = co_await resolver.async_resolve(
"www.example.com", "http", use_awaitable);
for (const auto& entry : results)
{
std::cout << entry.endpoint() << "\n";
}
}
int main()
{
asio::io_context ctx;
asio::co_spawn(ctx, resolve_hostname(), asio::detached);
ctx.run();
}
Output might be:
93.184.216.34:80
2606:2800:220:1:248:1893:25c8:1946:80
Understanding Results
async_resolve returns a range of resolver::results_type, containing
resolver::endpoint_type entries. Each entry includes:
-
Endpoint — IP address and port
-
Host name — The canonical name (if available)
-
Service name — The service (e.g., "http")
A single hostname may resolve to multiple addresses (IPv4 and IPv6, or multiple servers for load balancing).
Connecting with Resolution
The typical pattern is to resolve, then try each address until one works:
awaitable<tcp::socket> connect_to_host(std::string host, std::string service)
{
auto executor = co_await asio::this_coro::executor;
tcp::resolver resolver(executor);
auto endpoints = co_await resolver.async_resolve(host, service, use_awaitable);
tcp::socket socket(executor);
co_await asio::async_connect(socket, endpoints, use_awaitable);
co_return socket;
}
async_connect with an endpoint range automatically tries each address in
sequence until one succeeds.
Resolver Flags
Control resolution behavior with flags:
awaitable<void> resolve_with_flags()
{
auto executor = co_await asio::this_coro::executor;
tcp::resolver resolver(executor);
// Only return IPv4 addresses
auto results = co_await resolver.async_resolve(
"www.example.com", "443",
tcp::resolver::numeric_service, // "443" is a port number, not a service name
use_awaitable);
// Or combine flags
auto results2 = co_await resolver.async_resolve(
"www.example.com", "https",
tcp::resolver::passive | // For binding (servers)
tcp::resolver::address_configured, // Only configured addresses
use_awaitable);
}
Common flags:
| Flag | Meaning |
|---|---|
|
The host string is an IP address, don’t resolve |
|
The service string is a port number |
|
For server sockets (bind, not connect) |
|
Request the canonical name |
|
Return IPv4-mapped IPv6 addresses if no IPv6 |
|
Return all matching addresses |
|
Only return addresses the system can use |
IPv4 Only or IPv6 Only
To restrict to a specific IP version:
awaitable<void> resolve_ipv4_only()
{
auto executor = co_await asio::this_coro::executor;
// TCP IPv4 resolver
asio::ip::tcp::resolver resolver(executor);
// Resolve with IPv4 protocol hint
auto results = co_await resolver.async_resolve(
asio::ip::tcp::v4(), // Protocol hint
"www.example.com",
"http",
use_awaitable);
// All results will be IPv4
}
Reverse DNS Lookup
Look up the hostname for an IP address:
awaitable<void> reverse_lookup()
{
auto executor = co_await asio::this_coro::executor;
tcp::resolver resolver(executor);
tcp::endpoint endpoint(asio::ip::make_address("93.184.216.34"), 80);
auto results = co_await resolver.async_resolve(endpoint, use_awaitable);
for (const auto& entry : results)
{
std::cout << entry.host_name() << "\n";
}
}
Error Handling
Resolution can fail for various reasons:
awaitable<void> resolve_with_error_handling()
{
auto executor = co_await asio::this_coro::executor;
tcp::resolver resolver(executor);
try
{
auto results = co_await resolver.async_resolve(
"nonexistent.invalid", "http", use_awaitable);
}
catch (const boost::system::system_error& e)
{
// e.code() might be:
// - asio::error::host_not_found
// - asio::error::host_not_found_try_again
// - asio::error::no_data
// - asio::error::no_recovery
std::cerr << "Resolution failed: " << e.what() << "\n";
}
}
Caching
Asio doesn’t cache DNS results. Each async_resolve call queries the system
resolver. For high-volume applications, consider:
-
Caching results yourself (respecting TTL)
-
Using connection pools that reuse existing connections
-
Resolving once at startup for known hosts
UDP Resolution
Works the same way, just use udp::resolver:
awaitable<void> resolve_udp()
{
auto executor = co_await asio::this_coro::executor;
asio::ip::udp::resolver resolver(executor);
auto results = co_await resolver.async_resolve(
"dns.google", "53", use_awaitable);
for (const auto& entry : results)
{
std::cout << entry.endpoint() << "\n";
}
}
Common Mistakes
Resolving every time — Resolution has overhead. Resolve once and reuse the endpoint, or use connection pooling.
Ignoring multiple results — A host may have several addresses. Using
async_connect with the results range handles this correctly.
Not handling errors — Network issues, DNS server problems, and typos in hostnames all cause resolution to fail.
Next Steps
-
TCP Client — Use resolved addresses
-
Error Handling — Handle resolution failures