udns(3)

๐Ÿ“ฆ
version 0.6 ยท 4 versions
, in alpine 3.22, alpine 3.23, alpine 3.24, debian trixie, epel 10, fedora 43, fedora 44, nixos 26.05, nixpkgs unstable, opensuse-leap 16.0, ubuntu questing, ubuntu resolute, ubuntu stonking

NAME ๐Ÿ’–

okay bestie, say hi to udns(3) โ€” the stub DNS resolver library. yes, that's the whole vibe: it resolves DNS for you without you having to hand-roll the whole packet situation. ๐Ÿฆ‹

SYNOPSYS โœจ

yes, the section is literally spelled SYNOPSYS in the original, and we're keeping it exactly like that, periodt ๐Ÿ’…
       #include <udns.h>
       struct dns_ctx;
       struct dns_query;
       extern struct dns_ctx dns_defctx;
       struct dns_ctx *ctx;
       typedef void dns_query_fn(ctx, void *result, void *data);
       typedef int
       dns_parse_fn(const unsigned char *qnd,
              const unsigned char *pkt,
              const unsigned char *cur,
              const unsigned char *end,
              void **resultp);

       cc ... -ludns

DESCRIPTION ๐Ÿ’…

okay so udns is a DNS library that implements thread-safe stub DNS resolver functionality. you can use it the traditional, synchronous way, or asynchronously with an application-supplied event loop. both? yes. iconic. ๐Ÿ’…

so here's the tea: DNS itself works with both TCP and UDP, and the normal move is a UDP query first, then if the result doesn't fit in the UDP buffer โ€” that's 512 bytes max for the original DNS protocol โ€” it retries the query over TCP. but udns? it uses UDP only, no TCP retry arc. instead it uses EDNS0 (RFC2671) extensions, which allow larger UDP buffers. slay.

the library uses a single UDP socket to perform all operations, even when it's asking multiple nameservers. this makes it super simple to use in asynchronous event-loop applications: your app should add only that single socket to the set of filedescriptors it monitors for I/O. one socket, whole resolver, no cap. ๐Ÿซถ

the library uses two main objects: a resolver context of type struct dns_ctx, and a query structure of type struct dns_query. both are opaque for an application. the resolver context holds global information about the resolver, such as the list of nameservers to use, the list of active requests, and the like. query objects hold information about a single DNS query in progress, and they are allocated/processed/freed by the library. a pointer to a query structure may be treated as an identifier of an in-progress query, and may be used to cancel the asynchronous query or to wait for it to complete.

the asynchronous interface works as follows. an application initializes a resolver context, submits any number of queries for it using one of the supplied dns_submit_XXX() routines โ€” each one returns the query identifier as a pointer to query structure โ€” waits for input on the UDP socket used by the library, and gives some control to the library by calling dns_ioevent() and dns_timeouts() routines when appropriate. the library performs all necessary processing and executes the application-supplied callback routine when a query completes (either successfully or not), giving it the result if any, a pointer to the resolver context (from which completion status may be obtained), and the data pointer supplied by an application when the query has been submitted. when submitting a query, an application requests how to handle the reply โ€” either return the raw DNS reply packet for its own low-level processing, or it may provide an address of a parsing routine of type dns_parse_fn to perform conversion of on-wire format into an easy-to-use data structure. the library provides parsing routines for several commonly used resource record types, as well as a type-safe higher-level interface that requests parsing automatically. the I/O monitoring and timeout handling may be either traditional select() or poll() based, or any callback-driven technique may be used. options, options. ๐Ÿ’…

additionally, the library provides a traditional synchronous interface, which may be intermixed with asynchronous calls. during synchronous query processing, other asynchronous queries for the same resolver context continue to be processed as usual. an application uses one of the numerous dns_resolve_XXX() routines provided by the library to perform a query. as with the asynchronous interface, an application may either request to return the raw DNS packet or a type-specific data structure by providing the parsing routine to handle the reply. every routine from the dns_resolve_XXX() series returns a pointer to result or NULL in case of any error. query completion status (or length of the raw DNS packet) is available from the resolver context using the dns_status() routine, the same way as for the asynchronous interface. yes, you can mix sync and async, it's giving multitasking main character energy. ๐Ÿ”ฅ

internally, the library uses the on-wire format of domain names, referred to as DN format in this manual page. this is a series of domain labels with a preceding length byte, terminated by a zero-length label which is an integral part of the DN format. there are several routines provided to convert from traditional asciiz string to DN and back. the higher-level type-specific query interface hides the DN format from an application. so you don't have to stare at length bytes unless you want to. ๐Ÿ’–

COMMON DEFINITIONS ๐ŸŽ€

every DNS Resource Record (RR) has a type and a class. the library defines several integer constants, DNS_C_XXX and DNS_T_XXX, to use as symbolic names for RR classes and types, such as DNS_C_IN for Internet class, DNS_T_A for IPv4 address record type, and so on. see the udns.h header file for the complete list of all such constants. iykyk.

the following constants are defined in the udns.h header file:

  • DNS_MAXDN (255 bytes) โ€” maximum length of the domain name in internal (on-wire) DN format. bestie, that's the DN-format limit, respect it. ๐Ÿ’…
  • DNS_MAXLABEL (63 bytes) โ€” maximum length of a single label in DN format. one label, one tiny queen. ๐Ÿ‘‘
  • DNS_MAXNAME (1024 bytes) โ€” maximum length of asciiz format of a domain name. yes, asciiz gets way more room. ๐ŸŒธ
  • DNS_HSIZE (12 bytes) โ€” size of the header in a DNS packet. it's giving fixed-size opening act. โœจ
  • DNS_PORT (53) โ€” default port to use when contacting a DNS server. the classic. ๐ŸŽ€
  • DNS_MAXSERV (6 servers) โ€” maximum number of DNS servers to use. six is the squad cap. ๐Ÿซถ
  • DNS_MAXPACKET (512 bytes) โ€” maximum length of DNS UDP packet as specified by the original DNS protocol. no EDNS0 glow-up here. ๐Ÿ’€
  • DNS_EDNS0PACKET (4096 bytes) โ€” default length of DNS UDP packet (with EDNS0 extensions) the library uses. note that recursive nameservers usually resides near the client asking them to resolve names, e.g. on the same LAN segment or even on the same host, so UDP packet fragmentation isn't a problem in most cases. note also that the size of actual packets will be as many bytes as actual reply size requires, which is smaller than this value in almost all cases. so it's a ceiling, not a flex. ๐ŸŒˆ

additionally, several constants are defined to simplify work with raw DNS packets, such as DNS response codes (DNS_R_XXX), DNS header layout (DNS_H_XXX) and others. again, see udns.h for the complete list. library error codes (DNS_E_XXX) are described later in this manual page. ๐Ÿ’œ

โœจ RESOLVER CONTEXT โœจ

okay bestie, so a resolver context is literally a struct dns_ctx, and itโ€™s opaque to your app โ€” like you donโ€™t peek inside, you just vibe with it. ๐Ÿซถ Several routines provided by the library initialize, copy, and free resolver contexts. Most other high-level routines in this library expect a pointer to a resolver context, ctx, as the first argument. Thereโ€™s a default resolver context available, named dns_defctx. When the context pointer ctx passed to a routine is NULL, dns_defctx is used. Several resolver contexts may be active at the same time โ€” for example, when an application is multi-threaded and each thread uses a resolver. ๐Ÿ‘‘

So to actually use the library, your app should initialize and open one or more resolver context objects. These are two separate actions, performed by dns_init() (or dns_reset()), and dns_open(). Between those two calls, an application is free to perform additional initialisation, such as setting custom nameservers, options, or domain search lists. Optionally, in case no additional custom initialisation is required, dns_init() may open the context if the do_open argument (see below) is non-zero. โœจ

When initializing a resolver context, the library uses information from system file /etc/resolv.conf (see resolv.conf(5)), consults environment variables $LOCALDOMAIN, $NSCACHEIP, $NAMESERVERS, and $RES_OPTIONS, and the local host name to obtain the list of local nameservers, the domain name search list, and various resolver options. ๐ŸŒˆ

The following routines to initialize resolver context are available:

void dns_reset(ctx)
int dns_init(ctx, int do_open)

dns_reset() resets a given resolver context to default values, preparing it to be opened by dns_open(). It is ok to call this routine against an opened and active context โ€” all active queries will be dropped, sockets will be closed, and so on. This routine does not initialize any parameters from system configuration files; use dns_init() for this. Thereโ€™s no error return โ€” the operation always succeeds. dns_init() does everything dns_reset() does, plus initializes various parameters of the context according to system configuration and process environment variables. If do_open is non-zero, dns_init() calls dns_open(), so that the whole library initialisation is performed in a single step. ๐Ÿ’…

struct dns_ctx *dns_new(struct dns_ctx *copy)
void dns_free(ctx)

dns_new() allocates a new resolver context and copies all parameters for a given resolver context copy, or the default context if copy is NULL, and returns a pointer to the newly allocated context. The context being copied should be initialized. dns_new() may fail if thereโ€™s no memory available to make a copy of copy, in which case the routine will return a NULL pointer. dns_free() is used to close the associated socket and free resolver context resources and cancelling (abandoning) all active queries associated with it. Itโ€™s an error to free dns_defctx; only dynamically allocated contexts returned by dns_new() are allowed to be freed by dns_free(). ๐Ÿ˜ญ

int dns_add_serv(ctx, const char *servaddr)
int dns_add_serv_s(ctx, const struct sockaddr *sa)
int dns_add_srch(ctx, const char *srch)

Add an element to the list of nameservers (dns_add_serv(), as an asciiz-string servaddr with an IP address of the nameserver, and dns_add_serv_s(), as an initialized socket address sa), or the search list (dns_add_srch(), as a pointer to a domain name) for the given context ctx. If the last argument is a NULL pointer, the corresponding list (search or nameserver) is reset instead. Upon successful completion, each routine returns the new number of elements in the list in question. On error, a negative value is returned and global variable errno is set appropriately. It is an error to call any of these functions if the context is opened (after dns_open() or dns_init() with a non-zero argument). ๐Ÿซถ

int dns_set_opts(ctx, const char *opts)

Set resolver context options from the opts string, in the same way as processing the options statement in resolv.conf and the $RES_OPTIONS environment variable. Return the number of unrecognized/invalid options found (all recognized and valid options get processed). โœจ

void dns_set_opt(ctx, int opt, val)
TODO โ€” the flags argument is a bitmask with the following bits defined:
  • DNS_NOSRCH โ€” do not perform domain name search in search list.
  • DNS_NORD โ€” do not request recursion when performing queries (i.e. donโ€™t set RD flag in queries).
  • DNS_AAONLY โ€” request authoritative answers only (i.e. set AA flag in queries).
int dns_open(ctx)
int dns_sock(const ctx)
void dns_close(ctx)

dns_open() opens the UDP socket used for queries if not already open, and returns the associated file descriptor (or a negative value in case of error). Before any query can be submitted, the context should be opened using this routine. And before opening, the context should be initialized. dns_sock() returns the UDP socket if open, or -1 if not. dns_close() closes the UDP socket if it was open, and drops all active queries if any. ๐Ÿ”ฅ

int dns_active(const ctx)

Return the number of active queries queued for the given context ctx, or zero if none. ๐Ÿ‘‘

int dns_status(const ctx)

Return status code from the last operation. When using the synchronous interface, this is the query completion status of the last query. With the asynchronous interface, from within the callback routine, this is the query completion status of the query for which the callback is being called. When query submission fails, this is the error code indicating the failure reason. All error codes are negative and are represented by DNS_E_XXX constants described below. ๐Ÿ’€

void dns_ioevent(ctx, time_t now)

This routine may be called by an application to process I/O events on the UDP socket used by the library, as returned by dns_sock(). The routine tries to receive incoming UDP datagrams from the socket and process them. The socket is set up to be non-blocking, so it is safe to call the routine even if thereโ€™s no data to read. The routine will process as many datagrams as are queued for the socket, so it is safe to use it with either a level-triggered or edge-triggered I/O monitoring model. The now argument is either a current time as returned by time(), or 0, in which case the routine will obtain the current time by its own. ๐Ÿฆ‹

int dns_timeouts(ctx, int maxwait, time_t now)

Process any pending timeouts and return the number of seconds from the current time (now if it is not 0) to the time when the library wants the application to pass it control to process more queued requests. In case when there are no requests pending, this time is -1. The routine will not request a time larger than maxwait seconds if it is greater than or equal to zero. If now is 0, the routine will obtain the current time by its own; when it is not 0, it should contain the current time as returned by time(). ๐ŸŒธ

typedef void dns_utm_fn(ctx, int timeout, void *data)
void dns_set_tmcbck(ctx, dns_utm_fn *utmfn, void *data)

An application may use a custom callback-based I/O multiplexing mechanism. Usually such a mechanism has a concept of a timer, and an ability to register a timer event in the form of a callback routine which will be executed after a certain amount of time. In order to use such an event mechanism, udns provides an ability to register and de-register timer events necessary for internal processing using whatever event mechanism an application uses. For this to work, it is possible to associate a pointer to a routine that will perform the necessary work for (de)registering timer events with a given resolver context, and udns will call that routine at appropriate times. The prototype of such a routine is shown by the dns_utm_fn typedef above. Libudns associates a single timer with a resolver context. The user-supplied utmfn routine will be called by the library with the following arguments:

  • ctx == NULL โ€” delete user timer, at context free time or when an application changes the user timer request routine using dns_set_tmcbck();
  • ctx != NULL, timeout < 0 โ€” donโ€™t fire timer anymore, when there are no active requests;
  • ctx != NULL, timeout == 0 โ€” fire timer at the next possibility, but not immediately;
  • ctx != NULL, timeout > 0 โ€” fire timer after timeout seconds after now.

The data argument passed to the routine will be the same as passed to dns_set_tmcbck(). ๐ŸŽ€

When a timer expires, an application should call dns_timeouts() routine (see below). Non-callback timer usage is provided too. ๐ŸŒŸ

XXXX TODO: some more resolver context routines, like dns_set_dbgfn() etc.

๐Ÿ’ซ QUERY INTERFACE ๐Ÿ’ซ

Okay bestie, so there are literally two ways to do a DNS query, and you gotta pick your fighter. ๐ŸฅŠ

  • The synchronous way โ€” old-school, no cap. udns does every single thing that needs doing, and only hands control back to your app once the query is done done.
  • The asynchronous way โ€” the moment. Your app submits one or more queries to the library using a given resolver context, then chills and waits for completion by watching the filedescriptor the library uses and calling library routines to process input on that filedescriptor. ๐Ÿ‘€

Async mode runs on callback routines: you hand over the address of a routine to run when the query finishes, plus a data pointer that gets passed along to that callback. It's giving "text me when you're done". ๐Ÿ’Œ

Queries get submitted as a struct dns_query. For an async query, your app calls one of the dns_submit_XXX() routines and feeds it everything the callback will need, along with all the query parameters. When the query completes, the library calls your callback and hands it:

  • the resolver context (which holds the query completion status),
  • the dynamically allocated result (either a raw DNS packet, or โ€” if you asked for parsing by passing a non-NULL parse routine โ€” a ready-to-use type-specific structure โœจ),
  • and that data pointer you provided when you submitted.
Heads up babes: your app is the one responsible for freeing the result memory. No one else is doing that for you. ๐Ÿซถ

A generic query callback routine looks like this:

typedef void
dns_query_fn(ctx, void *result, void *data)

The type-specific query interface expects basically the same shape of callback routine โ€” the only difference is the type of the result argument, which will be a pointer to a specific data structure (the decoded reply) instead of that void pointer to raw DNS packet data.

The result parsing routine looks like this:

typedef int
dns_parse_fn(const unsigned char *qdn,
      const unsigned char *pkt,
      const unsigned char *cur,
      const unsigned char *end,
      void **resultp);

When the library calls it, the arguments break down like this: pkt points to the start of the packet received; end points past the end of the packet received; cur points past the query DN in the query section of the packet; and qdn points to the original query DN. The routine should allocate a single buffer to hold the result, parse the reply filling in that buffer, and return the buffer using the resultp argument. It returns 0 in case of error, or udns error code (DNS_E_XXX constants) in case of error.

Fr fr, by the time the library calls your parse routine, the packet is already verified to be a reply to the original query โ€” matching query DN, query class and query type. So you don't gotta do that part. ๐Ÿ’…

And the type-specific query interface? It supplies the necessary parsing routines automatically. Iconic behaviour, honestly. ๐Ÿ‘‘


๐Ÿ˜ญ When things go wrong (error statuses)

If there's an error, the query completion status returned by dns_status(ctx) will contain one of these:

  • positive value โ€” length of raw DNS packet if parsing is not requested.
  • 0 โ€” the query was successeful and the reply points to type-specific data structure.
  • DNS_E_TEMPFAIL โ€” temporary error, the resolver nameserver was not able to process our query or timed out. ๐Ÿ™ˆ
  • DNS_E_PROTOCOL โ€” protocol error, a nameserver returned malformed reply. ๐Ÿ’€
  • DNS_E_NXDOMAIN โ€” the domain name does not exist.
  • DNS_E_NODATA โ€” there is no data of requested type found.
  • DNS_E_NOMEM โ€” out of memory while processing request.
  • DNS_E_BADQUERY โ€” some aspect of the query (most common is the domain name in question) is invalid, and the library can't even start a query.

The library provides two series of routines using a similar interface โ€” one for asynchronous queries, another for synchronous queries. In each series there are two general low-level routines to submit (asynchronous interface) and resolve (synchronous interface) queries, plus several type-specific routines with more easy-to-use interfaces. ๐ŸŒธ

To submit an asynchronous query, use one of the dns_submit_XXX() routines. Each of them accepts query parameters, pointers to the callback routine and to the callback data, and an optional current time hint. And note โ€” type-specific dns_submit_XXX() routines expect a specific type of callback routine as well, one that accepts the reply as a pointer to the corresponding structure, not a void pointer. Every dns_submit_XXX() routine returns a pointer to an internal query structure of type struct dns_query, which you use as the identifier for that query. ๐ŸŽ€

To resolve a query synchronously, use one of the dns_resolve_XXX() routines. They accept the same query parameters (but not the callback pointers) as the corresponding dns_submit_XXX(), and return the query result โ€” which is the same thing that would've been passed to the callback routine in the asynchronous interface.

In either case, the result memory (if the query completed successefully) is dynamically allocated and should be freed by your application. And if the query failed for any reason, the result will be NULL, and the error status will be available from the dns_status(ctx) routine as shown above. ๐Ÿ“

struct dns_query *
dns_submit_dn(ctx,
     const unsigned char *dn, qcls, qtyp, flags,
     parse, cbck, data)
struct dns_query *
dns_submit_p(ctx,
     const char *name, qcls, qtyp, flags,
     parse, cbck, data)
   enum dns_class qcls;
   enum dns_type qtyp;
   int flags;
   dns_parse_fn *parse;
   dns_query_fn *cbck;
   void *data;

Submit a query for processing for the given resolver context ctx. The two routines differ only in the 3rd argument, which is the domain name either in DN format (dn) or as an asciiz string (name). The query will be performed for the given domain name, with type qtyp in class qcls, using the option bits in flags, using the RR parsing routine pointed to by parse if it's not-NULL, and upon completion the cbck function will be called with the data argument. ๐Ÿ’Œ

In case of successeful query submission, the routine returns a pointer to the internal query structure, which may be treated as an identifier of the query as used by the library, and may be used as an argument for the dns_cancel() routine. In case of error, NULL will be returned, and the context error status (available using the dns_status() routine) will be set to the corresponding error code โ€” which in this case may be DNS_E_BADQUERY if the name of dn is invalid, DNS_E_NOMEM if there's no memory available to allocate the query structure, or DNS_E_TEMPFAIL if an internal error occurred.

void *dns_resolve_dn(ctx,
     const unsigned char *dn, qcls, qtyp, flags, parse);
void *dns_resolve_p(ctx,
     const char *name, qcls, qtyp, flags, parse)
   enum dns_class qcls;
   enum dns_type qtyp;
   int flags;
   dns_parse_fn *parse;

Synchronous interface, bestie. โœจ These routines perform all the steps necessary to resolve the given query and return the result. If there's no positive result for any reason, all the routines return NULL, and set the context error status (available using the dns_status() routine) to indicate the error code. If the query was successeful, the context status code will contain either the length of the raw DNS reply packet if the parse argument was NULL (in which case the return value is a pointer to the reply DNS packet), or 0 (in which case the return value is the result of the parse routine). And if the query was successeful (return value is not NULL), the memory returned was dynamically allocated by the library and should be free()d by the application after use. ๐Ÿงš

void *dns_resolve(ctx, struct dns_query *q)

Wait for the given query q โ€” as returned by one of the dns_submit_XXX() routines โ€” to complete, and return the result. The callback routine will not be called for this query. After completion, the query identifier q is not valid. Both dns_resolve_dn() and dns_resolve_p() are just wrappers around the corresponding submit routines and this dns_resolve() routine. ๐Ÿฆ‹

void dns_cancel(ctx, struct dns_query *q)

Cancel an active query q, without calling a callback routine. After completion, the query identifier q is not valid.


๐ŸŒˆ TYPE-SPECIFIC QUERIES ๐ŸŒˆ

On top of the generic low-level query interface, the library hooks you up with a set of routines to perform specific queries in a type-safe manner, plus parsers for several well-known resource record types. The library implements a high-level interface for A, AAAA, PTR, MX and TXT records, and DNSBL and RHSBL functionality. These routines return specific types as the result of a query, instead of raw DNS packets. Here's what's available, bestie. ๐Ÿ‘‘

struct dns_rr_null {
  char *dnsn_qname;     /* original query name */
  char *dnsn_cname;     /* canonical name */
  unsigned dnsn_ttl;    /* Time-To-Live (TTL) value */
  int dnsn_nrr;         /* number of records in the set */
};

That's the NULL RR set, used as the base for all other RR type structures. Every RR structure as used by the library has these four standard fields, exactly like in struct dns_rr_null. ๐Ÿ’–


๐Ÿ…ฐ๏ธ IN A Queries ๐Ÿ…ฐ๏ธ

struct dns_rr_a4 {       /* IN A RRset */
  char *dnsa4_qname;     /* original query name */
  char *dnsa4_cname;     /* canonical name */
  unsigned dnsa4_ttl;    /* Time-To-Live (TTL) value */
  int dnsa4_nrr;         /* number of addresses in the set */
  struct in_addr dnsa4_addr[]; /* array of addresses */
};
typedef void
  dns_query_a4_fn(ctx, struct dns_rr_a4 *result, data)
dns_parse_fn dns_parse_a4;
struct dns_query *
dns_submit_a4(ctx, const char *name, int flags,
   dns_query_a4_fn *cbck, data);
struct dns_rr_a4 *
dns_resolve_a4(ctx, const char *name, int flags);

The dns_rr_a4 structure holds the result of an IN A query, which is an array of IPv4 addresses. ๐Ÿ“ฎ The callback routine for IN A queries is expected to be of type dns_query_a4_fn, which expects a pointer to a dns_rr_a4 structure as the query result instead of a raw DNS packet. The dns_parse_a4() is used to convert a raw DNS reply packet into a dns_rr_a4 structure (it's used internally, and may be used directly too with the generic query interface).

The routines dns_submit_a4() and dns_resolve_a4() are used to perform A IN queries in a type-safe manner. The name parameter is the domain name in question, and flags is the query flags bitmask, with one bit, DNS_NOSRCH, of practical interest โ€” and if the name is absolute, that is, it ends up with a dot, the DNS_NOSRCH flag will be set automatically. ๐ŸŽ€

๐Ÿ’œ IN AAAA Queries ๐Ÿ’œ

struct dns_rr_a6 {       /* IN AAAA RRset */
  char *dnsa6_qname;     /* original query name */
  char *dnsa6_cname;     /* canonical name */
  unsigned dnsa6_ttl;    /* Time-To-Live (TTL) value */
  int dnsa6_nrr;         /* number of addresses in the set */
  struct in6_addr dnsa6_addr[]; /* array of addresses */
};
typedef void
  dns_query_a6_fn(ctx, struct dns_rr_a6 *result, data)
dns_parse_fn dns_parse_a6;
struct dns_query *
dns_submit_a6(ctx, const char *name, int flags,
   dns_query_a6_fn *cbck, data);
struct dns_rr_a6 *
dns_resolve_a6(ctx, const char *name, int flags);

okay so dns_rr_a6 is where the result of an IN AAAA query lives, and it's literally just an array of IPv6 addresses. it's giving "list of addresses, that's the whole personality". โœจ

your callback for IN AAAA queries has to be type dns_query_a6_fn, and the way that works is it receives a pointer to the dns_rr_a6 structure as the query result โ€” not a raw DNS packet. so much nicer bestie, no packet-parsing trauma. ๐Ÿซถ

dns_parse_a6() is the glow-up artist that turns a raw DNS reply packet into a dns_rr_a6 structure. it's used internally, but you can also call it yourself directly with the generic query interface. ๐Ÿ’…

dns_submit_a6() and dns_resolve_a6() are how you actually run AAAA IN queries in a type-safe manner.

the name parameter is the domain name in question, and flags is the query flags bitmask. exactly one bit is of practical interest here: DNS_NOSRCH. and heads up โ€” if the name is absolute, like it ends with a dot, the DNS_NOSRCH flag gets set automatically for you. iconic, zero effort. ๐Ÿ‘‘


๐ŸŒธ IN PTR Queries ๐ŸŒธ

struct dns_rr_ptr {       /* IN PTR RRset */
  char *dnsptr_qname;     /* original query name */
  char *dnsptr_cname;     /* canonical name */
  unsigned dnsptr_ttl;    /* Time-To-Live (TTL) value */
  int dnsptr_nrr;         /* number of domain name pointers */
  char *dnsptr_ptr[];     /* array of domain name pointers */
};
typedef void
  dns_query_ptr_fn(ctx, struct dns_rr_ptr *result, data)
dns_parse_fn dns_parse_ptr;
struct dns_query *
dns_submit_a4ptr(ctx, const struct in_addr *addr,
   dns_query_ptr_fn *cbck, data);
struct dns_rr_ptr *
dns_resolve_a4ptr(ctx, const struct in_addr *addr);
struct dns_query *
dns_submit_a6ptr(ctx, const struct in6_addr *addr,
   dns_query_ptr_fn *cbck, data);
struct dns_rr_ptr *
dns_resolve_a6ptr(ctx, const struct in6_addr *addr);

dns_rr_ptr holds the result of an IN PTR query, which is an array of domain name pointers for a given IPv4 or IPv6 address. reverse DNS tea, served fresh. ๐Ÿ“

callbacks for IN PTR queries need to be type dns_query_ptr_fn, and it gets a pointer to a dns_rr_ptr structure as the query result instead of a raw DNS packet. dns_parse_ptr() converts a raw DNS reply packet into a dns_rr_ptr structure โ€” used internally, and also fair game to call directly with the generic query interface.

dns_submit_a4ptr() and dns_resolve_a4ptr() do IN PTR queries for IPv4 addresses in a type-safe manner. dns_submit_a6ptr() and dns_resolve_a6ptr() do IN PTR queries for IPv6 addresses. two flavors, both slay. ๐Ÿฆ‹


๐Ÿ’– IN MX Queries ๐Ÿ’–

struct dns_mx {          /* single MX record */
  int priority;          /* priority value of this MX */
  char *name;            /* domain name of this MX */
};
struct dns_rr_mx {       /* IN MX RRset */
  char *dnsmx_qname;     /* original query name */
  char *dnsmx_cname;     /* canonical name */
  unsigned dnsmx_ttl;    /* Time-To-Live (TTL) value */
  int dnsmx_nrr;         /* number of mail exchangers in the set */
  struct dns_mx dnsmx_mx[]; /* array of mail exchangers */
};
typedef void
  dns_query_mx_fn(ctx, struct dns_rr_mx *result, data)
dns_parse_fn dns_parse_mx;
struct dns_query *
dns_submit_mx(ctx, const char *name, int flags,
   dns_query_mx_fn *cbck, data);
struct dns_rr_mx *
dns_resolve_mx(ctx, const char *name, int flags);

dns_rr_mx holds the result of an IN MX query โ€” an array of mail exchangers for a given domain. if your mail is gonna go anywhere, it goes here first. ๐Ÿ’Œ

callbacks for IN MX queries are type dns_query_mx_fn, and they get a pointer to a dns_rr_mx structure as the query result instead of a raw DNS packet. dns_parse_mx() converts a raw DNS reply packet into a dns_rr_mx structure (internally used, and you can call it directly too with the generic query interface).

dns_submit_mx() and dns_resolve_mx() perform IN MX queries in a type-safe manner.

name is the domain name in question, flags is the query flags bitmask, and again the one bit with actual relevance is DNS_NOSRCH โ€” and yeah, if the name is absolute (ends with a dot), that DNS_NOSRCH flag just sets itself automatically. auto-pilot behavior, we love to see it. โœจ


๐ŸŽ€ TXT Queries ๐ŸŽ€

struct dns_txt {          /* single TXT record */
  int len;                /* length of the text */
  unsigned char *txt;     /* pointer to the text */
};
struct dns_rr_txt {       /* TXT RRset */
  char *dnstxt_qname;     /* original query name */
  char *dnstxt_cname;     /* canonical name */
  unsigned dnstxt_ttl;    /* Time-To-Live (TTL) value */
  int dnstxt_nrr;         /* number of text records in the set */
  struct dns_txt dnstxt_txt[]; /* array of TXT records */
};
typedef void
  dns_query_txt_fn(ctx, struct dns_rr_txt *result, data)
dns_parse_fn dns_parse_txt;
struct dns_query *
dns_submit_txt(ctx, const char *name, enum dns_class qcls,
   int flags, dns_query_txt_fn *cbck, data);
struct dns_rr_txt *
dns_resolve_txt(ctx, const char *name,
             enum dns_class qcls, int flags);

dns_rr_txt holds the result of a TXT query โ€” an array of text records for a given domain name. yes, the vibes-as-DNS-records record type. ๐ŸŒˆ

callbacks for TXT queries are type dns_query_txt_fn, and they get a pointer to a dns_rr_txt structure as the query result instead of a raw DNS packet. dns_parse_txt() converts a raw DNS reply packet into a dns_rr_txt structure (it's used internally, and it may be used directly too with the generic query interface).

dns_submit_txt() and dns_resolve_txt() are used to perform IN MX queries in a type-safe manner.

name is the domain name in question, flags is the query flags bitmask, and the one bit of practical interest is DNS_NOSRCH (if the name is absolute โ€” i.e. it ends up with a dot โ€” the DNS_NOSRCH flag will be set automatically).

note that each TXT string is represented by struct dns_txt, while zero-terminated (and the len field of the structure does not include the terminator), may contain embedded null characters โ€” content of TXT records is not interpreted by the library in any way.

so like, the library does not read your TXT tea. it just hands it over. ๐Ÿ™ˆ


โญ SRV Queries โญ

struct dns_srv {          /* single SRV record */
  int priority;           /* priority of the record */
  int weight;             /* weight of the record */
  int port;               /* the port number to connect to */
  char *name;             /* target host name */
};
struct dns_rr_srv {       /* SRV RRset */
  char *dnssrv_qname;     /* original query name */
  char *dnssrv_cname;     /* canonical name */
  unsigned dnssrv_ttl;    /* Time-To-Live (TTL) value */
  int dnssrv_nrr;         /* number of text records in the set */
  struct dns_srv dnssrv_srv[]; /* array of SRV records */
};
typedef void
  dns_query_srv_fn(ctx, struct dns_rr_srv *result, data)
dns_parse_fn dns_parse_srv;
struct dns_query *
dns_submit_srv(ctx, const char *name, const char *service, const char *protocol,
   int flags, dns_query_txt_fn *cbck, data);
struct dns_rr_srv *
dns_resolve_srv(ctx, const char *name, const char *service, const char *protocol,
             int flags);

dns_rr_srv holds the result of an IN SRV (rfc2782) query โ€” an array of servers (together with their port numbers) that are performing operations for a given service using a given protocol on a target domain name. it's giving "who's on shift and where do I knock". ๐Ÿšช

callbacks for IN SRV queries are type dns_query_srv_fn, and they get a pointer to a dns_rr_srv structure as the query result instead of a raw DNS packet. dns_parse_srv() converts a raw DNS reply packet into a dns_rr_srv structure (used internally, and also usable directly with the generic query interface).

dns_submit_srv() and dns_resolve_srv() perform IN SRV queries in a type-safe manner.

name is the domain name in question. service and protocol specify the service and the protocol in question โ€” the library constructs the query DN according to rfc2782 rules โ€” and they may be NULL (in which case the library assumes the name parameter holds the complete SRV query). flags is the query flags bitmask, one bit of practical interest, DNS_NOSRCH, and if the name is absolute, that is, it ends up with a dot, the DNS_NOSRCH flag will be set automatically. ๐Ÿ’…


๐Ÿงš NAPTR Queries ๐Ÿงš

struct dns_naptr {        /* single NAPTR record */
  int order;              /* record order */
  int preference;         /* preference of this record */
  char *flags;            /* application-specific flags */
  char *service;          /* service parameter */
  char *regexp;           /* substitutional regular expression */
  char *replacement;      /* replacement string */
};
struct dns_rr_naptr {     /* NAPTR RRset */
  char *dnsnaptr_qname;   /* original query name */
  char *dnsnaptr_cname;   /* canonical name */
  unsigned dnsnaptr_ttl;  /* Time-To-Live (TTL) value */
  int dnsnaptr_nrr;       /* number of text records in the set */
  struct dns_naptr dnsnaptr_naptr[]; /* array of NAPTR records */
};
typedef void
  dns_query_naptr_fn(ctx, struct dns_rr_naptr *result, data)
dns_parse_fn dns_parse_naptr;
struct dns_query *
dns_submit_naptr(ctx, const char *name, int flags,
   dns_query_txt_fn *cbck, data);
struct dns_rr_naptr *
dns_resolve_naptr(ctx, const char *name, int flags);

dns_rr_naptr holds the result of an IN NAPTR (rfc3403) query. this is the one with order, preference, flags, service, regexp and replacement all in one record โ€” main character energy, honestly. ๐Ÿ‘‘

callbacks for IN NAPTR queries are type dns_query_naptr_fn, and they expect a pointer to a dns_rr_naptr structure as the query result instead of a raw DNS packet. dns_parse_naptr() converts a raw DNS reply packet into a dns_rr_naptr structure (used internally, and it may be used directly too with the generic query interface).

dns_submit_naptr() and dns_resolve_naptr() perform IN NAPTR queries in a type-safe manner.

name is the domain name in question, and flags is the query flags bitmask โ€” one bit of practical interest, DNS_NOSRCH, and if the name is absolute, meaning it ends up with a dot, the DNS_NOSRCH flag will be set automatically. periodt. ๐Ÿ’œ


okay so that's the whole type-specific query lineup, and next up we get into the rest of the resolver API โ€” stay with me bestie, it only gets more useful from here. ๐Ÿฆ‹

๐Ÿ’– DNSBL Interface ๐Ÿ’–

Okay bestie, listen. A DNS-based blocklist โ€” a DNSBL โ€” is everywhere these days, and the main character job it has is protecting mailservers from spammers. The library ships a whole DNSBL interface: a set of routines that run queries against DNSBLs for you. ๐ŸŽ€

These routines take an IP address (IPv4 and IPv6 are both supported, no cap) plus a base DNSBL zone as their query parameters, and they hand you back either a dns_rr_a4 or a dns_rr_txt structure. And heads up โ€” the IPv6 interface returns an IPv4 RRset. That's just how it is. ๐Ÿ™ˆ

struct dns_query *
dns_submit_a4dnsbl(ctx,
  const struct in_addr *addr, const char *dnsbl,
  dns_query_a4_fn *cbck, void *data);
struct dns_query *
dns_submit_a4dnsbl_txt(ctx,
  const struct in_addr *addr, const char *dnsbl,
  dns_query_txt_fn *cbck, void *data);
struct dns_query *
dns_submit_a6dnsbl(ctx,
  const struct in6_addr *addr, const char *dnsbl,
  dns_query_a4_fn *cbck, void *data);
struct dns_query *
dns_submit_a6dnsbl_txt(ctx,
  const struct in6_addr *addr, const char *dnsbl,
  dns_query_txt_fn *cbck, void *data);
struct dns_rr_a4 *dns_resolve_a4dnsbl(ctx,
  const struct in_addr *addr, const char *dnsbl)
struct dns_rr_txt *dns_resolve_a4dnsbl_txt(ctx,
  const struct in_addr *addr, const char *dnsbl)
struct dns_rr_a4 *dns_resolve_a6dnsbl(ctx,
  const struct in6_addr *addr, const char *dnsbl)
struct dns_rr_txt *dns_resolve_a6dnsbl_txt(ctx,
  const struct in6_addr *addr, const char *dnsbl)

So what do they do? They perform (submit or resolve) a DNSBL query for the given dnsbl domain and the IP addr in question, requesting either A or TXT records. Pick your fighter. โœจ


๐ŸŒธ RHSBL Interface ๐ŸŒธ

RHSBL is literally DNSBL's sibling โ€” same vibe, one plot twist: instead of an IP address, the parameter is a domain name. That's it. That's the difference. ๐Ÿฆ‹

struct dns_query *
dns_submit_rhsbl(ctx, const char *name, const char *rhsbl,
  dns_query_a4_fn *cbck, void *data);
struct dns_query *
dns_submit_rhsbl_txt(ctx, const char *name, const char *rhsbl,
  dns_query_txt_fn *cbck, void *data);
struct dns_rr_a4 *
dns_resolve_rhsbl(ctx, const char *name, const char *rhsbl);
struct dns_rr_txt *
dns_resolve_rhsbl_txt(ctx, const char *name, const char *rhsbl);

Perform (submit or resolve) a RHSBL query for the given rhsbl domain and name in question, requesting either A or TXT records. Same energy, different input. ๐Ÿ’…


๐ŸŒˆ LOW-LEVEL INTERFACE ๐ŸŒˆ

Okay so now we go DOWNSTAIRS. This is the basement level, where you're handling the raw stuff yourself. Buckle up, bestie, it's giving โœจengineeringโœจ. ๐Ÿ”ฅ

๐ŸŽ€ Domain Names (DNs) ๐ŸŽ€

A DN is a series of domain name labels, each one starting with a length byte, followed by an empty label (a label with zero length). That's the whole format, no cap.

Here are the routines you get to work with DNs. ๐Ÿซถ

  • unsigned dns_dnlen(const unsigned char *dn) โ€” returns the length of the domain name dn, including the terminating label.
  • unsigned dns_dnlabels(const unsigned char *dn) โ€” returns the number of non-zero labels in domain name dn.
  • unsigned dns_dnequal(dn1, dn2) with const unsigned char *dn1, *dn2; โ€” tests whether the two domain names, dn1 and dn2, are equal (case-insensitive). It returns the domain name length if they're equal, or 0 if they're not.
  • unsigned dns_dntodn(sdn, ddn, dnsiz) with const unsigned char *sdn; unsigned char *ddn; unsigned dnsiz; โ€” copies the source domain name sdn to the destination buffer ddn of size dnsiz. Returns the domain name length, or 0 if ddn is too small.
  • int dns_ptodn(name, namelen, dn, dnsiz, isabs) / int dns_sptodn(name, dn, dnsiz) with const char *name; unsigned namelen; unsigned char *dn; unsigned dnsiz; int *isabs; โ€” converts the asciiz name name of length namelen to DN format, placing the result into buffer dn of size dnsiz. Returns the length of the DN if successful, 0 if the dn buffer supplied is too small, or a negative value if name is invalid. If isabs is non-NULL and the conversion was successful, *isabs will be set to either 1 or 0 depending on whether name was absolute (i.e. ending with a dot) or not. The name length, namelen, may be zero, in which case strlen(name) will be used. The second form, dns_sptodn(), is a simplified form of dns_ptodn(), equivalent to dns_ptodn(name, 0, dn, dnlen, 0).
  • extern const unsigned char dns_inaddr_arpa_dn[]
  • int dns_a4todn(const struct in_addr *addr, const unsigned char *tdn, unsigned char *dn, unsigned dnsiz)
  • int dns_a4ptodn(const struct in_addr *addr, const char *tname, unsigned char *dn, unsigned dnsiz)
  • extern const unsigned char dns_ip6_arpa_dn[]
  • int dns_a6todn(const struct in6_addr *addr, const unsigned char *tdn, unsigned char *dn, unsigned dnsiz)
  • int dns_a6ptodn(const struct in6_addr *addr, const char *tname, unsigned char *dn, unsigned dnsiz)

These are several variants of routines to convert an IPv4 or IPv6 address addr into a reverseDNS-like domain name in DN format, storing the result in dn of size dnsiz. tdn (or tname) is the base zone name, like in-addr.arpa for IPv4 or in6.arpa for IPv6. If tdn (or tname) is NULL, dns_inaddr_arpa_dn (or dns_ip6_arpa_dn) will be used. The routines may be used to construct a DN for a DNSBL lookup, for example โ€” iykyk. ๐Ÿฆ‹ All routines return the length of the resulting DN on success, -1 if the resulting DN is invalid, or 0 if the dn buffer (dnsiz) is too small. To hold a standard rDNS DN, a buffer of size DNS_A4RSIZE (30 bytes) for an IPv4 address, or DNS_A6RSIZE (74 bytes) for an IPv6 address, is sufficient.

  • int dns_dntop(dn, name, namesiz) with const unsigned char *dn; const char *name; unsigned namesiz; โ€” converts domain name dn in DN format to an asciiz string, placing the result into name buffer of size namesiz. The maximum length of the asciiz representation of a domain name is DNS_MAXNAME (1024) bytes. The root domain is represented as an empty string. Returns the length of the resulting name (including the terminating character, i.e. strlen(name)+1) on success, 0 if the name buffer is too small, or a negative value if dn is invalid (that last case should never happen, since all routines in this library which produce domain names ensure the DNs generated are valid).
  • const char *dns_dntosp(const unsigned char *dn) โ€” converts domain name dn in DN format to an asciiz string using a static buffer. Returns the resulting asciiz string on success or NULL on failure. > Careful bestie: since this routine uses a static buffer, it is not thread-safe. Do not come for me when it breaks. ๐Ÿ’€
  • unsigned dns_dntop_size(const unsigned char *dn) โ€” returns the buffer size needed to convert the dn domain name in DN format to an asciiz string, for dns_dntop(). The routine returns either the size of buffer required, including the trailing zero byte, or 0 if dn is invalid.

๐Ÿงš Working with DNS Packets ๐Ÿงš

These next routines encode and decode DNS on-wire packets. Yes, this is the low-level interface. Yes, you're doing it by hand. Iconic behaviour honestly. โญ

DNS response codes (returned by the dns_rcode() routine) are defined as constants prefixed with DNS_R_. See the udns.h header file for the complete list. In particular, the constants DNS_R_NOERROR (0), DNS_R_SERVFAIL, and DNS_R_NXDOMAIN may be of interest to an application. ๐Ÿ‘‘

  • unsigned dns_get16(const unsigned char *p)
  • unsigned dns_get32(const unsigned char *p)

Helper routines โ€” convert a 16-bit or 32-bit integer in on-wire format, pointed to by p, to unsigned.

  • unsigned char *dns_put16(unsigned char *d, unsigned n)
  • unsigned char *dns_put32(unsigned char *d, unsigned n)

Helper routines โ€” convert the unsigned 16-bit or 32-bit integer n to on-wire format into the buffer pointed to by d, and return d+2 or d+4.

  • DNS_HSIZE (12) โ€” defines the size of the DNS header. The data section in the DNS packet immediately follows the header. In the header there are the query identifier (id), various flags and codes, and the number of resource records in the various data sections. See the udns.h header file for the complete list of DNS header definitions.
  • unsigned dns_qid(const unsigned char *pkt)
  • int dns_rd(const unsigned char *pkt)
  • int dns_tc(const unsigned char *pkt)
  • int dns_aa(const unsigned char *pkt)
  • int dns_qr(const unsigned char *pkt)
  • int dns_ra(const unsigned char *pkt)
  • unsigned dns_opcode(const unsigned char *pkt)
  • unsigned dns_rcode(const unsigned char *pkt)
  • unsigned dns_numqd(const unsigned char *pkt)
  • unsigned dns_numan(const unsigned char *pkt)
  • unsigned dns_numns(const unsigned char *pkt)
  • unsigned dns_numar(const unsigned char *pkt)
  • const unsigned char *dns_payload(const unsigned char *pkt)

These return the various parts from the DNS packet header pkt: the query identifier (qid), recursion desired (rd) flag, truncation occurred (tc) flag, authoritative answer (aa) flag, query response (qr) flag, recursion available (ra) flag, operation code (opcode), result code (rcode), number of entries in the question section (numqd), number of answers (numan), number of authority records (numns), number of additional records (numar), and the pointer to the packet data (payload). โœจ

  • int dns_getdn(pkt, curp, pkte, dn, dnsiz)
  • const unsigned char *dns_skipdn(cur, pkte) with const unsigned char *pkt, *pkte, **curp, *cur; unsigned char *dn; unsigned dnsiz;

dns_getdn() extracts a DN from DNS packet pkt โ€” which ends before pkte โ€” starting at position *curp, into the buffer pointed to by dn of size dnsiz. Upon successful completion, *curp will point to the next byte in the packet after the extracted domain name. It returns a positive number (the length of the DN in dn) upon successful completion, a negative value on error (when the packet contains invalid data), or zero if dnsiz is too small (the maximum length of a domain name is DNS_MAXDN). dns_skipdn() returns a pointer to the next byte in the DNS packet which ends up before pkte after a domain name which starts at the cur byte, or NULL if the packet is invalid. dns_skipdn() is more or less equivalent to what dns_getdn() does, except it does not actually extract the domain name in question, and it uses a simpler interface. ๐ŸŽ€

struct dns_rr {
  unsigned char dnsrr_dn[DNS_MAXDN]; /* the RR DN name */
  enum dns_class dnsrr_cls;          /* class of the RR */
  enum dns_type  dnsrr_typ;          /* type of the RR */
  unsigned dnsrr_ttl;                /* TTL value */
  unsigned dnsrr_dsz;                /* size of data in bytes */
  const unsigned char *dnsrr_dptr;   /* pointer to the first data byte */
  const unsigned char *dnsrr_dend;   /* next byte after RR */
};

The dns_rr structure is used to hold information about a single DNS Resource Record (RR) in an easy-to-use form. Slay. ๐Ÿ’œ

okay so here's the struct that's holding the whole parse-state vibe โ€” say hi to dns_parse ๐Ÿ‘‘

struct dns_parse {
  const unsigned char *dnsp_pkt; /* pointer to the packet being parsed */
  const unsigned char *dnsp_end; /* end of the packet pointer */
  const unsigned char *dnsp_cur; /* current packet position */
  const unsigned char *dnsp_ans; /* pointer to the answer section */
  int dnsp_rrl;                  /* number of RRs left */
  int dnsp_nrr;                  /* number of relevant RRs seen so far */
  unsigned dnsp_ttl;             /* TTL value so far */
  const unsigned char *dnsp_qdn; /* the domain of interest or NULL */
  enum dns_class dnsp_qcls;      /* class of interest or 0 for any */
  enum dns_type  dnsp_qtyp;      /* type of interest or 0 for any */
  unsigned char dnsp_dnbuf[DNS_MAXDN]; /* domain name buffer */
};

The dns_parse structure is used to parse a DNS reply packet. It holds information about the packet being parsed (the dnsp_pkt, dnsp_end and dnsp_cur fields), the number of RRs in the current section left to do, and the information about the specific RR we're looking for (the dnsp_qdn, dnsp_qcls and dnsp_qtyp fields). ๐Ÿ’…


โœจ dns_initparse() โœจ

int dns_initparse(struct dns_parse *p,
  const unsigned char *qdn,
  const unsigned char *pkt,
  const unsigned char *cur,
  const unsigned char *end)

This one initializes the RR parsing structure p. The arguments pkt, cur and end should describe the received packet: pkt is the start of the packet, end points to the next byte after the end of the packet, and cur points past the query DN in the query section (so, to the query class + type information). And qdn points to the query DN. These are literally the arguments passed to the dns_parse_fn() routine. no cap

dns_initparse() initializes the dnsp_pkt, dnsp_end and dnsp_qdn fields to the corresponding arguments, extracts and initializes the dnsp_qcls and dnsp_qtyp fields to the values found at the cur pointer, initializes the dnsp_cur and dnsp_ans fields to be cur+4 (so, to the start of the answer section), and initializes the dnsp_rrl field to be the number of entries in the answer section. dnsp_ttl will be set to the max TTL value, 0xffffffff, and dnsp_nrr to 0. ๐Ÿซถ


โœจ dns_nextrr() โœจ

int dns_nextrr(struct dns_parse *p, struct dns_rr *rr);

This searches for the next RR in the packet based on the criteria provided in the p structure, filling in the rr structure and advancing p->dnsp_cur to the next RR in the packet. RR selection is based on the dnsp_qdn, dnsp_qcls and dnsp_qtyp fields in the dns_parse structure. Any (or all) of the 3 fields may be 0, which means any actual value from the packet is acceptable. In case the field isn't 0 (or NULL for dnsp_qdn), only RRs with the corresponding characteristics are acceptable. periodt

Additionally, when dnsp_qdn is non-NULL, dns_nextrr() performs automatic CNAME expansion. The routine will return a positive value on success, 0 if it reached the end of the current section in the packet (p->dnsp_rrl is zero), or a negative value if the next RR can't be decoded (packet format is invalid). The routine updates p->dnsp_qdn automatically when this field is non-NULL and it encounters appropriate CNAME RRs (saving the CNAME target in p->dnsp_dnbuf), so after the end of the process, p->dnsp_qdn will point to the canonical name of the domain in question. The routine updates the p->dnsp_ttl value to be the minimum TTL of all RRs found. ๐Ÿ”ฅ


โœจ dns_rewind() โœจ

void dns_rewind(struct dns_parse *p, const unsigned char *qdn)

This routine "rewinds" the packet parse state structure to be at the same state as after a call to dns_initparse(), i.e. it repositions the parse structure p to the start of the answer section and initializes p->dnsp_rrl to the number of entries in the answer section. it's giving rewind button energy โช


โœจ dns_stdrr_size() โœจ

int dns_stdrr_size(const struct dns_parse *p);

Returns the size needed to hold standard RRset structure information, as shown in the dns_rr_null structure (for the query and canonical names). Used to calculate the amount of memory to allocate for the common part of type-specific RR structures in parsing routines. ๐Ÿงฎ


โœจ dns_stdrr_finish() โœจ

void *dns_stdrr_finish(struct dns_rr_null *ret, char *cp,
  const struct dns_parse *p);

Initializes standard RRset fields in the ret structure using the buffer pointed to by cp, which should have at least as many bytes as dns_stdrr_size(p) returned. Used to finalize the common part of type-specific RR structures in parsing routines. โœจ


See the library source for usage examples of all the above low-level routines, especially the source of the parsing routines. iykyk ๐Ÿ“š


โœจ Auxilary Routines โœจ

โœจ dns_pton() โœจ

int dns_pton(int af, const char *src, void *dst);

Provides functionality similar to the standard inet_pton() routine, to convert the printable representation of an IP address of family af (either AF_INET or AF_INET6) pointed to by src into binary form suitable for socket addresses and transmission over the network, in the buffer pointed to by dst. The destination buffer should be of size 4 for AF_INET family or 16 for AF_INET6. The return value is positive on success, 0 if src is not a valid text representation of an address of family af, or negative if the given address family is not supported. ๐ŸŒ

โœจ dns_ntop() โœจ

const char *dns_ntop(int af, const void *src,
    char *dst, int dstsize)

Provides functionality similar to the standard inet_ntop() routine, to convert the binary representation of an IP address of family af (either AF_INET or AF_INET6) pointed to by src (either 4 or 16 bytes) into printable form in the buffer pointed to by dst of size dstsize. The destination buffer should be at least of size 16 bytes for AF_INET family or 46 bytes for AF_INET6. The return value is either dst, or a NULL pointer if dstsize is too small to hold this address or if the given address family is not supported. ๐Ÿ’–


โœจ AUTHOR โœจ

The udns library has been written by Michael Tokarev, mjt+udns@tls.msk.ru.


โœจ VERSION โœจ

This manual page corresponds to udns version 0.6, released Jul-2024.


and that's the whole page bestie ๐Ÿ’… this one's from the Library Functions section, dated Jan 2014, and it's for udns(3). slay, go parse some packets responsibly ๐Ÿฆ‹๐ŸŒธ