Skip to content

Latest commit

 

History

History
526 lines (321 loc) · 23.7 KB

README.md

File metadata and controls

526 lines (321 loc) · 23.7 KB

Non-blocking network name resolution library and tools

Netresolve is a package for nonblocking network name resolution via backends intended as a replacement for name service switch based name resolution in glibc.

Contact

Pavel Šimerda pavlix at pavlix.net (mail and jabber) pavlix at IRC Freenode

Build

The classic way:

./autogen.sh
make
make install

Build RPM package:

./autogen.sh
make rpm

Core features

  • File descriptor based nonblocking host/service name resolution API
    • suitable for various file descriptor based event loops
    • unlike glibc or POSIX APIs
  • Extensible request and result APIs
    • unlike glibc or POSIX APIs
    • host/service queries (like getaddrinfo)
    • SRV record support for host/service queries
    • reverse queries (like getnameinfo)
    • DNS style queries (like res_query but via backends)
  • Backend-based network name resolution somewhat similar to glibc's nsswitch
    • more flexible than DNS-only libraries
    • with configurable backend options (not available in nsswitch)
    • also supports nsswitch backends (via libnetresolve_nss.so)
  • Avoids limitations and bugs found in glibc
    • support for ifindex and scope_id (problematic in nsswitch)
    • support for TTL information (problematic in nsswitch)
  • DNS happy eyeballs implementation
    • concurent A/AAAA requests
    • quick timeout when there's no answer to one of A/AAAA requests
  • Socket API
    • callback based wrappers over socket(), bind() and connect()
    • the application receives a successfully bound or connected socket
  • TCP happy eyeballs implementation
    • concurrent IPv4/IPv6 connect
    • quick timeout when there's no answer to one of IPv4/IPv6 TCP SYN packets
  • Security information
    • well-known and locally configured data is considered secure
    • experimental support for DNSSEC authenticated data

Command line tool

The command line tool is useful for testing the netresolve library as well as testing various libraries supported by netresolve including glibc and libunbound.

Forward query:

netresolve --node localhost

netresolve --node www.sourceware.org --service http --protocol tcp

Forward query with SRV resolution:

netresolve --srv --node jabber.org --service xmpp-client --protocol tcp

Reverse query:

netresolve --address 192.228.79.201

netresolve --address 2001:503:ba3e::2:30

netresolve --port 80

netresolve --address 192.228.79.201 --port 80

DNS style query:

netresolve --type srv --node _xmpp-client._tcp._jabber.com

netresolve --class ch --type txt --node version.bind

Connection support similar to netcat or socat:

netresolve --connect --node localhost --service 22

Experimental DNSSEC support via command line

Query information using a DNS backend and trust the AD flag:

netresolve --backends aresdns:trust --node www.dnssec.cz

Query information using a validating DNS backend:

netresolve --backends ubdns:validate --node www.dnssec.cz

Library API – simple blocking queries

Program or library using netresolve has to allocate a netresolve context (or context) which is then used to perform all the queries. You may want to create multiple contexts to have different configurations prepared for use. By default the netresolve context is in blocking mode. Consult the next section about event loop integration on how to create a netresolve context in nonblocking mode.

#include <netresolve.h>

netresolve_t context = netresolve_context_new();

Then you tweak the context configuration to your liking.

netresolve_set_protocol(context, IPPROTO_TCP);
netresolve_set_dns_srv_lookup(context, true);

When you're happy with the configuration, you can run your queries using netresolve_query_forward(), netresolve_query_reverse() or netresolve_query_dns(). Any of the functions waits until the query is successfully finished, failed or timed out.

netresolve_query_t query = netresolve_query_forward(context, "www.sourceware.org", "http", NULL, NULL);

TODO: Error reporting.

You can pick up your query immediately.

size_t count = netresolve_query_get_count(query);

for (size_t idx = 0; idx < count; idx++) {
    int family;
    const void *address;
    int ifindex;
    int socktype;
    int protocol;
    int port;
    int priority;
    int weight;
    uint32_t ttl;

    netresolve_query_get_node_info(query, idx, &family, &address, &ifindex);
    netresolve_query_get_service_info(query, idx, &family, &address, &ifindex);
    netresolve_query_get_aux_info(query, idx, &family, &address, &ifindex);

    /* do something with the data */
}

You can free the query object when you no longer need it.

netresolve_query_free(query);

When you're not going to use the context to perform any queries nor use the existing query objects to examine the results, you can free the context together with all queries that haven't been freed as written above.

netresolve_context_free(context);

An example of using netresolve in blocking mode can be found in test/test-sync.c.

Library API – callback based nonblocking queries

The nonblocking mode is designed to be independent of a specific event loop implementation. You can use one of the existing event loop connectors or write your own easily. Connectors for libevent and glib are distributed as header files to avoid additional dependencies. Connectors using epoll-style and select-style file descriptor sets are built into the library.

Creating a libevent based context

Provided that you have a struct event_base *base pointer to the event base, create the netresolve context before issuing any queries.

#include <netresolve-event.h>

netresolve_t context = netresolve_event_new(base);

Free it as usual.

netresolve_context_free(context);

Creating a glib based context

With glib you can easily create a channel attached to the default context.

#include <netresolve-glib.h>

netresolve_t context = netresolve_glib_new();

Free it as usual.

netresolve_context_free(context);

Issue queries and pick up the data

Issue a query with a callback and user data.

netresolve_query_t query = netresolve_query_forward(context, "www.sourceware.org", "http", callbac, user_data);

Pickup the data in your callback and free the query if you no longer need it.

void
callback(netresolve_query_t query, void *user_data)
{
    size_t count = netresolve_query_get_count(query);

    for (size_t idx = 0; idx < count; idx++) {
        int family;
        const void *address;
        int ifindex;
        int socktype;
        int protocol;
        int port;
        int priority;
        int weight;
        uint32_t ttl;

        netresolve_query_get_node_info(query, idx, &family, &address, &ifindex);
        netresolve_query_get_service_info(query, idx, &family, &address, &ifindex);
        netresolve_query_get_aux_info(query, idx, &family, &address, &ifindex);

        /* do something with the data */
    }

    netresolve_query_free(query);
}

Note: You can use callbacks with blocking mode as well, although it's not as useful as with nonblocking mode. This feature is especially useful in code that is written to work with both blocking and nonblocking mode.

Context based on epoll kernel feature

Create the context.

#include <netresolve-epoll.h>

netresolve_t context = netresolve_epoll_new();

Retrieve the file descriptor.

int fd = netresolve_epoll_fd(context);

When the epoll file descriptor is ready ready for reading, dispatch.

netresolve_epoll_dispatch(context);

Free it as usual.

netresolve_context_free(context);

Context based on file descriptor sets

Create the context.

#include <netresolve-select.h>

netresolve_t context = netresolve_select_new()

Retrieve the file descriptors.

fd_set rfds, wfds;
int nfds;

FD_ZERO(&rfds);
FD_ZERO(&wfds);
nfds = netresolve_select_apply_fds(context, &rfds, &wfds);

When a file descriptor in rfds is ready for reading, dispatch.

netresolve_select_dispatch_read(context, rfds);

When a file descriptor in wfds is ready for writing, dispatch.

netresolve_select_dispatch_write(context, rfds);

Free it as usual.

netresolve_context_free(context);

Custom nonblocking context

If none of the included integration functions match your needs, you can create the context using netresolve_context_new() and then attach your own set of callbacks using netresolve_set_fd_callbacks(). You need to provide two callbacks. The watch_fd() function adds an event source consisting of a file descriptor, and a set of events (subset of POLLIN | POLLOUT) and an opaque pointer data and the unwatch_fd() function that removes the source. When a file descriptor event occurs, the event loop implementation calls netresolve_dispatch() with the respective source and the subset of events that occured. The integration code can optionally provide a user_data pointer that can ten be retrieved with netresolve_get_user_data() and a free_user_data() callback that will be called during the context destruction and a pointer for each event source returned by watch_fd() that will then be passed to unwatch_fd() as the handle argument. The user_data pointer typically points to an object representing the event loop and the handle pointer points to an object representing the event source.

void *watch_fd(netresolve_t context, int fd, int events, netresolve_source_t source);
void unwatch_fd(netresolve_t context, int fd, void *handle);
void free_user_data(void *user_data);

Install callbacks.

netresolve_set_fd_callbacks(context, watch_fd, unwatch_fd, user_data, free_user_data);

Dispatch an event.

netresolve_dispatch(context, source, events);

Thread safety

Use one context object per thread. Avoid accessing the context and query objects from different threads for now.

POSIX-like API

You can use a compatibility API most resembling the POSIX one but still allowing for nonblocking mode. The context object must be created as usual and you can also tweak its configuration and set up nonblocking mode and callbacks. This API can be nonblocking depending on the context configuration already described.

#include <netresolve-compat.h>

A special query constructor lets you use input arguments as with getaddrinfo().

struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = 0, .ai_protocol = IPPROTO_TCP, .ai_flags = 0 };
netresolve_query_t query = netresolve_query_getaddrinfo(context, "www.sourceware.org", "http", &hints, callback, user_data);

A special query result getter lets you use output arguments as with getaddrinfo() and destroys the query object for your convenience.

struct addrinfo *result;
uint32_t ttl;
int status = netresolve_query_addrinfo_done(query, &list, &ttl);

When you don't need the result object any more, free it.

netresolve_freeaddrinfo(result);

An alternative getaddrinfo() implementation using netresolve in compat/libc.c serves as an example of how to use this compatibility API.

GNU libc nsswitch backend

A backend for glibc nsswitch is provided as libnss_netresolve.so that exposes part of netresolve funcionality via the glibc name resolution API. The backend also supports a variant of _nss_*_getaddrinfo() API proposed by Alexandre Oliva.

POSIX/GNU libc API replacement

If your program doesn't need the full power of the provided API but you still want to use netresolve as your resolver implementation, you can simply link your program to libnetresolve-libc.so which overrides selected libc name resolution functions.

Supported functions:

  • getaddrinfo()
  • getnameinfo()
  • gethostbyname(), gethostbyname_r()
  • gethostbyname2(), gethostbyname2_r()
  • gethostbyaddr()

You can use the wrapresolve command to start a program with LD_PRELOAD configured so that your program uses netresolve functions instead of libc.

wrapresolve curl http://www.sourceware.org/

You can tweak netresolve configuration via environment variables, or you can use convenience options provided by wrapresolve. Currently there are options to force IPv4-only or IPv6-only resolution.

wrapresolve -4 curl http://www.nix.cz/

wrapresolve -6 curl http://www.nix.cz/

The netresolve package also provides a couple of testing tools for the above libc functions that can be used to test both libc and the replacement functions.

getaddrinfo --node localhost --service http

wrapresolve getaddrinfo --node localhost --service http

libasyncns API replacement

The wrapresolve command also pulls in libnetresolve-asyncns.so which implements libasyncns API using netresolve.

Socket API

Two non-blocking APIs wrapping name resolution together with the socket(), bind(), listen() and connect() calls are available.

The simpler one is netresolve_listen() that creates listening sockets for all available addresses and passes back sockets for accepted connections. You can stop the listening sockets and free the structures using netresolve_listen_free().

The more sophisticated one is netresolve_connect() that uses the list of addresses to get a single connected socket, maintaining address preference by family and other characteristics, and using a short timeout to bypass the preference when it would take too much time. A function called netresolve_connect_next() can be used to overcome application level issues with one of the addresses and to get a new connection using the next available address. Once happy with the connected socket or to abort the process, run netresolve_connect_free().

Backends

The list of backends can be chosen using netresolve_set_backend_string() or via the NETRESOLVE_BACKENDS environment variable. Backends are separated by a comma and accept options separated by a colon. A plus sign prepended to the backend name can be used to run that backend even if another backend already succeeded.

export NETRESOLVE_BACKENDS=any,loopback,numerichost,hosts,hostname,avahi,aresdns

The command line interface also supports an option to select the backend.

netresolve --backend any,loopback,numerichost,hosts,hostname,avahi,aresdns

The default list of backends is slightly wider then the example one above and attempts all sorts of name resolution tools in order to give you full results.

General purpose backends

Three backends, any, loopback and numerichost, are available that perform trivial translations. The hosts backends uses /etc/hosts database of nodes. Nonblocking API is most useful for remote services. We have two nonblocking DNS backends, aresdns and ubdns. We support special configuration of the two DNS backends, aresdns:trust reads the DNS AD flag and marks the query result secure and ubdns:validate instructs libunbound to perform the validation. On systems with Avahi service running, the avahi backend offers Multicast DNS name resolution.

POSIX and glibc compatibility backends

You can ask netresolve to call getaddrinfo() to gather the data using the getaddrinfo backend. This is useful for testing the libc API as well as comparing results of general purpose netresolve backends to other implementations. This backend blocks until the getaddrinfo() function exits.

netresolve --backends getaddrinfo --node www.sourceware.org

We also support glibc nsswitch modules through the nss backend. It finds the nsswitch dynamic module by name, loads the supported API functions and runs the most suitable one. The algorithm used in netresolve was inspired by glibc getaddrinfo() and glibc nscd code. This backend blocks until the nsswitch module function exits.

netresolve --backends nss:files --node localhost

netresolve --backends nss:dns --node www.sourceware.org

You can also pass the nsswitch module by absolute or relative path.

netresolve --backends nss:/usr/lib64/libnss_files.so

Or you can select the specific nsswitch API to be used.

# call `_nss_files_gethostbyname_r`
netresolve --backends nss:files:gethostbyname --node localhost

# call `_nss_files_gethostbyname2_r`
netresolve --backends nss:files:gethostbyname2 --node localhost

# call `_nss_files_gethostbyname3_r`
netresolve --backends nss:files:gethostbyname3 --node localhost

# call `_nss_files_gethostbyname4_r`
netresolve --backends nss:files:gethostbyname4 --node localhost

You can use the nss plugin to test systemd-resolved via libnss_resolve.so.

netresolve --backends nss:resolve --node localhost

We have experimental support for new nsswitch API based on a variant of getaddrinfo() proposed by Alexandre Oliva. This proposed API would consolidate the nsswitch APIs and overcome some of the limitations describe at the beginning of this document.

Auxiliary backends

There's an experimental backend to call a script to perform name resolution and feed it with the request serialized in a couple of text lines and read the result from it.

netresolve --backends exec:/path/to/my/script --node localhost

That one can be used for interactive testing as well:

netresolve --backends exec:socat:-:/dev/tty --node www.example.com

Note: This backend is untested and maybe not even functional.

Writing a custom backend

When a name resolution request is ready, netresolve calls one of the setup functions depending on the query type. If any of the setup functions is missing, netresolve assumes that the backend doesn't support the respective query type at all.

setup_forward(context, settings);

setup_reverse(context, settings);

setup_dns(context, settings);

The setup function initializes the query using input data from netresolve_backend_get_*() functions. It creates a state object using netresolve_backend_new_priv() (if necessary) to share its data with the other backend API functions. Finally it does one of the following:

  1. Add any data items using netresolve_backend_add_*() functions and call netresolve_backend_finished() to signal immediate success.
  2. Call netresolve_backend_failed() to signal immediate failure.
  3. Add one or more watchers using netresolve_backend_watch_fd() and netresolve_backend_watch_timeout().

In the third case, a dispath function is called by netresolve. It is not needed in the other two cases.

dispatch(context, settings);

The dispatch function retrieves the state object using netresolve_backend_get_priv(). Then it does one of the following:

  1. Add any data items and call netresolve_backend_finished() to signal success.
  2. Call netresolve_backend_failed() to signal failure.
  3. Leaves one or more watchers active.

When success or failure was reported by the plugin, or when the query has been cancelled, the cleanup function is called.

cleanup(context, settings);

The cleanup function also retrieves the state object, removes remaining watchers using netresolve_backend_watch_fd() and/or netresolve_backend_remove_timeout(), closes open file descriptors, returns allocated memory (except the context object itself which is freed by netresolve itself).

Calls to the above functions in a single backend are serialized, calling a backend API function doesn't cause any side effects for the backend.

API/ABI stability

The library is still considered experimental. The functions in netresolve.h are getting stable very soon.

Known bugs

The library doesn't currently sort paths according to their priority and/or RFC rules. The c-ares library blocks when /etc/resolv.conf is empty instead of quitting immediately, which in turn breaks tests when offline. The DNS backend doesn't support search domains. For more information, see the TODO file.

Acknowledgements and inspiration

Related projects

glibc

One way to look at netresolve is to see it as a testbed for future glibc improvements. It's written with testing and debugging in mind and prototyping new ideas in netresolve is very easy. Another way is to see it as a more flexible alternative to glibc host and service name resolution functionality. Either way glibc and POSIX were the main inspirations for this project.

c-ares

They provide ares_fds() and ares_timeout() to update the current set of file descriptors and the associated timeout. The netresolve callback API improves greatly on that. The application submits the watch_fd() callback.

sssd

They provide a backend-based caching service for user/group name resolution, authentication and related stuff. As netresolve doesn't really need to cache its results nor does it need to perform authentication services, it provides just a shared library, not a long-running daemon. Also, for DNS resolution they currently use c-ares.

libevent, squid, etc...

According to the website, libevent is using internal asynchronous DNS as well. The same applies to squid. Apparently the operating system's resolution API is not sufficient for many tools.

systemd-resolved

To my knowledge, name resolution related systemd features were announced after I published netresolve. The relation to that project is yet to be seen.

License

Copyright (c) 2013 Pavel Šimerda, Red Hat, Inc. (psimerda at redhat.com) and others All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.