Group PJ_DNS_RESOLVER

group PJ_DNS_RESOLVER

This module manages the host/server resolution by performing asynchronous DNS queries and caching the results in the cache. It uses PJLIB-UTIL low-level DNS parsing functions (see DNS and Asynchronous DNS Resolver) and currently supports several types of DNS resource records such as A record (typical query with gethostbyname()) and SRV record.

Features

Asynchronous Query and Query Aggregation

The DNS queries are performed asychronously, with timeout setting configured on per resolver instance basis. Application can issue multiple asynchronous queries simultaneously. Subsequent queries to the same resource (name and DNS resource type) while existing query is still pending will be merged into one query, so that only one DNS request packet is issued.

Query Retransmission

Asynchronous query will be retransmitted if no response is received within the preconfigured time. Once maximum retransmission count is exceeded and no response is received, the query will time out and the callback will be called when error status.

Response Caching with TTL

The resolver instance caches the results returned by nameservers, to enhance the performance by minimizing the message round-trip to the server. The TTL of the cached resposne is calculated from minimum TTL value found across all resource record (RR) TTL in the response and further more it can be limited to some preconfigured maximum TTL in the resolver.

Response caching can be disabled by setting the maximum TTL value of the resolver to zero.

Parallel and Backup Name Servers

When the resolver is configured with multiple nameservers, initially the queries will be issued to multiple name servers simultaneously to probe which servers are not active. Once the probing stage is done, subsequent queries will be directed to only one ACTIVE server which provides the best response time.

Name servers are probed periodically to see which nameservers are active and which are down. This probing is done when a query is sent, thus no timer is needed to maintain this. Also probing will be done in parallel so that there would be no additional delay for the query.

Supported Resource Records

The low-level DNS parsing utility (see DNS and Asynchronous DNS Resolver) supports parsing of the following DNS resource records (RR):

  • DNS A record

  • DNS SRV record

  • DNS PTR record

  • DNS NS record

  • DNS CNAME record

For other types of record, application can parse the raw resource record data (rdata) from the parsed DNS packet (pj_dns_parsed_packet).

Using the Resolver

To use the resolver, application first creates the resolver instance by calling pj_dns_resolver_create(). If application already has its own timer and ioqueue instances, it can instruct the resolver to use these instances so that application does not need to poll the resolver periodically to process events. If application does not specify the timer and ioqueue instance for the resolver, an internal timer and ioqueue will be created by the resolver. And since the resolver does not create it’s own thread, application MUST poll the resolver periodically by calling pj_dns_resolver_handle_events() to allow events (network and timer) to be processed.

Next, application MUST configure the nameservers to be used by the resolver, by calling pj_dns_resolver_set_ns().

Application performs asynchronous query by submitting the query with pj_dns_resolver_start_query(). Once the query completes (either successfully or times out), the callback will be called.

Application can cancel a pending query by calling pj_dns_resolver_cancel_query().

Resolver must be destroyed by calling pj_dns_resolver_destroy() to release all resources back to the system.

Resolver Limitations

Current implementation mainly suffers from a growing memory problem, which mainly is caused by the response caching. Although there is only one cache entry per {query, name} combination, these cache entry will never get deleted since there is no timer is created to invalidate these entries. So the more unique names being queried by application, there more enties will be created in the response cache.

Note that a single response entry will occupy about 600-700 bytes of pool memory (the PJ_DNS_RESOLVER_RES_BUF_SIZE value plus internal structure).

Application can work around this problem by doing one of these:

  • disable caching by setting PJ_DNS_RESOLVER_MAX_TTL and PJ_DNS_RESOLVER_INVALID_TTL to zero.

  • periodically query pj_dns_resolver_get_cached_count() and destroy- recreate the resolver to recycle the memory used by the resolver.

Note that future improvement may solve this problem by introducing expiration timer to the cached entries.

Reference

The PJLIB-UTIL resolver was built from the information in the following standards:

Typedefs

typedef struct pj_dns_resolver pj_dns_resolver

Opaque data type for DNS resolver object.

typedef struct pj_dns_async_query pj_dns_async_query

Opaque data type for asynchronous DNS query object.

typedef void pj_dns_callback(void *user_data, pj_status_t status, pj_dns_parsed_packet *response)

Type of asynchronous callback which will be called when the asynchronous query completes.

Parameters
  • user_data – The user data set by application when creating the asynchronous query.

  • status – Status of the DNS resolution.

  • response – The response packet received from the server. This argument may be NULL when status is not PJ_SUCCESS.

Functions

void pj_dns_settings_default(pj_dns_settings *s)

Set default values to the DNS settings.

Parameters

s – The DNS settings to be initialized.

pj_status_t pj_dns_resolver_create(pj_pool_factory *pf, const char *name, unsigned options, pj_timer_heap_t *timer, pj_ioqueue_t *ioqueue, pj_dns_resolver **p_resolver)

Create DNS resolver instance. After the resolver is created, application MUST configure the nameservers with pj_dns_resolver_set_ns().

When creating the resolver, application may specify both timer heap and ioqueue instance, so that it doesn’t need to poll the resolver periodically.

Parameters
  • pf – Pool factory where the memory pool will be created from.

  • name – Optional resolver name to identify the instance in the log.

  • options – Optional options, must be zero for now.

  • timer – Optional timer heap instance to be used by the resolver. If timer heap is not specified, an internal timer will be created, and application would need to poll the resolver periodically.

  • ioqueue – Optional I/O Queue instance to be used by the resolver. If ioqueue is not specified, an internal one will be created, and application would need to poll the resolver periodically.

  • p_resolver – Pointer to receive the resolver instance.

Returns

PJ_SUCCESS on success, or the appropriate error code,

pj_status_t pj_dns_resolver_set_ns(pj_dns_resolver *resolver, unsigned count, const pj_str_t servers[], const pj_uint16_t ports[])

Update the name servers for the DNS resolver. The name servers MUST be configured before any resolution can be done. The order of nameservers specifies their priority; the first name server will be tried first before the next in the list.

Parameters
  • resolver – The resolver instance.

  • count – Number of name servers in the array.

  • servers – Array of name server IP addresses or hostnames. If hostname is specified, the hostname must be resolvable with pj_gethostbyname().

  • ports – Optional array of ports. If this argument is NULL, the nameserver will use default port.

Returns

PJ_SUCCESS on success, or the appropriate error code,

pj_status_t pj_dns_resolver_get_settings(pj_dns_resolver *resolver, pj_dns_settings *st)

Get the resolver current settings.

Parameters
  • resolver – The resolver instance.

  • st – Buffer to be filled up with resolver settings.

Returns

The query timeout setting, in seconds.

pj_status_t pj_dns_resolver_set_settings(pj_dns_resolver *resolver, const pj_dns_settings *st)

Modify the resolver settings. Application should initialize the settings by retrieving current settings first before applying new settings, to ensure that all fields are initialized properly.

Parameters
  • resolver – The resolver instance.

  • st – The resolver settings.

Returns

PJ_SUCCESS on success, or the appropriate error code,

void pj_dns_resolver_handle_events(pj_dns_resolver *resolver, const pj_time_val *timeout)

Poll for events from the resolver. This function MUST be called periodically when the resolver is using it’s own timer or ioqueue (in other words, when NULL is specified as either timer or ioqueue argument in pj_dns_resolver_create()).

Parameters
  • resolver – The resolver instance.

  • timeout – Maximum time to wait for event occurence. If this argument is NULL, this function will wait forever until events occur.

pj_status_t pj_dns_resolver_destroy(pj_dns_resolver *resolver, pj_bool_t notify)

Destroy DNS resolver instance.

Parameters
  • resolver – The resolver object to be destryed

  • notify – If non-zero, all pending asynchronous queries will be cancelled and its callback will be called. If FALSE, then no callback will be called.

Returns

PJ_SUCCESS on success, or the appropriate error code,

pj_status_t pj_dns_resolver_start_query(pj_dns_resolver *resolver, const pj_str_t *name, int type, unsigned options, pj_dns_callback *cb, void *user_data, pj_dns_async_query **p_query)

Create and start asynchronous DNS query for a single resource. Depending on whether response cache is available, this function will either start an asynchronous DNS query or call the callback immediately.

If response is not available in the cache, an asynchronous query will be started, and callback will be called at some time later when the query completes. If p_query argument is not NULL, it will be filled with the asynchronous query object.

If response is available in the cache, the callback will be called immediately before this function returns. In this case, if p_query argument is not NULL, the value will be set to NULL since no new query is started.

Parameters
  • resolver – The resolver object.

  • name – The name to be resolved.

  • type – The type of resource (see pj_dns_type constants).

  • options – Optional options, must be zero for now.

  • cb – Callback to be called when the query completes, either successfully or with failure.

  • user_data – Arbitrary user data to be associated with the query, and which will be given back in the callback.

  • p_query – Optional pointer to receive the query object, if one was started. If this pointer is specified, a NULL may be returned if response cache is available immediately.

Returns

PJ_SUCCESS if either an asynchronous query has been started successfully or response cache is available and the user callback has been called.

pj_status_t pj_dns_resolver_cancel_query(pj_dns_async_query *query, pj_bool_t notify)

Cancel a pending query.

Parameters
  • query – The pending asynchronous query to be cancelled.

  • notify – If non-zero, the callback will be called with failure status to notify that the query has been cancelled.

Returns

PJ_SUCCESS on success, or the appropriate error code,

pj_status_t pj_dns_parse_a_response(const pj_dns_parsed_packet *pkt, pj_dns_a_record *rec)

A utility function to parse a DNS response containing A records into DNS A record.

Parameters
  • pkt – The DNS response packet.

  • rec – The structure to be initialized with the parsed DNS A record from the packet.

Returns

PJ_SUCCESS if response can be parsed successfully.

pj_status_t pj_dns_resolver_add_entry(pj_dns_resolver *resolver, const pj_dns_parsed_packet *pkt, pj_bool_t set_ttl)

Put the specified DNS packet into DNS cache. This function is mainly used for testing the resolver, however it can also be used to inject entries into the resolver.

The packet MUST contain either answer section or query section so that it can be indexed.

Parameters
  • resolver – The resolver instance.

  • pkt – DNS packet to be added to the DNS cache. If the packet matches existing entry, it will update the entry.

  • set_ttl – If the value is PJ_FALSE, the entry will not expire (so use with care). Otherwise cache expiration will be calculated based on the TTL of the answeres.

Returns

PJ_SUCCESS on success, or the appropriate error code.

unsigned pj_dns_resolver_get_cached_count(pj_dns_resolver *resolver)

Get the total number of response in the response cache.

Parameters

resolver – The resolver instance.

Returns

Current number of entries being stored in the response cache.

void pj_dns_resolver_dump(pj_dns_resolver *resolver, pj_bool_t detail)

Dump resolver state to the log.

Parameters
  • resolver – The resolver instance.

  • detail – Will print detailed entries.

struct pj_dns_settings
#include <resolver.h>

This structure describes resolver settings.

struct pj_dns_a_record
#include <resolver.h>

This structure represents DNS A record, as the result of parsing DNS response packet using pj_dns_parse_a_response().