Episode 005 – Reverse lookups with Asio

After publishing the last episode I realized that a discussion of Asio’s resolver class is incomplete without touching on reverse lookups. So this will be a quick episode to cover what we couldn’t get to last time.

A reverse lookup is the inverse operation of domain resolution. It turns an IP address and optional port into a domain name and service name, with the help of DNS. Be aware that the results you get will not always be what you expect, since DNS operations are not guaranteed to be reversible.

We can quickly extend our resolver app to also handle lookups. The resolver class offers an overload of async_resolve() to make it easy. Instead of passing a query object, we’ll pass an endpoint object populated with the IP address and port (or 0 if we don’t care about the service name).

Here’s an excerpt from the resolver that creates the Asio endpoint objects and stores them in a vector to pass to the resolver later. We could have passed the query and endpoint objects directly to the resolver but that would have added unnecessary complexity.

try {
    json j = json::parse(json_line);
    if (j.find("host") != j.end()) {
        // Extract host (and service) from parsed JSON object
        // ...
    } else if (j.find("address") != j.end()) {
        // New code to construct an IP endpoint from an IP address
        // (and port)
        if (j.find("port") == j.end()) {
            endpoints.push_back(
                asio::ip::tcp::endpoint(
                    asio::ip::address::from_string(
                        j.at("address").get<std::string>()), 0));
        } else {
            endpoints.push_back(
                asio::ip::tcp::endpoint(
                    asio::ip::address::from_string(
                        j.at("address").get<std::string>()),
                    j.at("port").get<unsigned short>()));
        }
    }
} catch (std::exception e) {
    LOG(ERROR) << "Error accessing JSON: " << e.what();
}

One more excerpt that handles replies from the resolver. It’s more or less the same code to handle query replies but the pretty-printed output is adjusted to reflect that we’re taking IP endpoints and turning them into host and service names.

for (const auto& e : endpoints) {
    resolver.async_resolve(
        e, [&timer, &queries_remaining, &e](
            const boost::system::error_code& ec,
            asio::ip::tcp::resolver::iterator it) {
            if (--queries_remaining == 0) {
                if (timer.cancel()) {
                    LOG(INFO) << "All queries finished, cancelling timer";
                }
            }

            if (ec) {
                LOG(ERROR) << "Error resolving " << e << ": "
                           << ec.message();
                return;
            }

            do {
                std::cout << it->endpoint() << " -> "
                          << (!it->endpoint().port() ? it->host_name() :
                              it->host_name() + "," + it->service_name())
                          << std::endl;
            } while (++it != boost::asio::ip::tcp::resolver::iterator());
        });
}

Full source code is always available on my GitHub repo.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s