summaryrefslogtreecommitdiff
path: root/pjlib-util/include/pjlib-util/resolver.h
diff options
context:
space:
mode:
Diffstat (limited to 'pjlib-util/include/pjlib-util/resolver.h')
-rw-r--r--pjlib-util/include/pjlib-util/resolver.h520
1 files changed, 520 insertions, 0 deletions
diff --git a/pjlib-util/include/pjlib-util/resolver.h b/pjlib-util/include/pjlib-util/resolver.h
new file mode 100644
index 00000000..471b5717
--- /dev/null
+++ b/pjlib-util/include/pjlib-util/resolver.h
@@ -0,0 +1,520 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJLIB_UTIL_RESOLVER_H__
+#define __PJLIB_UTIL_RESOLVER_H__
+
+/**
+ * @file resolver.h
+ * @brief Asynchronous DNS resolver
+ */
+#include <pjlib-util/dns.h>
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * @defgroup PJ_DNS_RESOLVER Asynchronous DNS Server Resolution
+ * @ingroup PJLIB_UTIL
+ * @{
+ *
+ * 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 @ref PJ_DNS) and currently supports
+ * several types of DNS resource records such as A record (typical query with
+ * gethostbyname()) and SRV record.
+ *
+ * \section PJ_DNS_RESOLVER_FEATURES Features
+ *
+ * \subsection PJ_DNS_RESOLVER_FEATURES_ASYNC 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.
+ *
+ * \subsection PJ_DNS_RESOLVER_FEATURES_RETRANSMISSION 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.
+ *
+ * \subsection PJ_DNS_RESOLVER_FEATURES_CACHING 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.
+ *
+ * \subsection PJ_DNS_RESOLVER_FEATURES_PARALLEL 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.
+ *
+ *
+ * \subsection PJ_DNS_RESOLVER_FEATURES_REC Supported Resource Records
+ *
+ * The low-level DNS parsing utility (see @ref PJ_DNS) 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).
+ *
+ *
+ * \section PJ_DNS_RESOLVER_USING 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.
+ *
+ *
+ * \section PJ_DNS_RESOLVER_LIMITATIONS 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.
+ *
+ * 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.
+ *
+ *
+ * \section PJ_DNS_RESOLVER_REFERENCE Reference
+ *
+ * The PJLIB-UTIL resolver was built from the information in the following
+ * standards:
+ * - RFC 1035: "Domain names - implementation and specification"
+ * - RFC 2782: "A DNS RR for specifying the location of services (DNS SRV)"
+ */
+
+
+/*
+ * CONFIGURATIONS
+ */
+
+/**
+ * Maximum numbers of DNS nameservers that can be configured in resolver.
+ */
+#ifndef PJ_DNS_RESOLVER_MAX_NS
+# define PJ_DNS_RESOLVER_MAX_NS 16
+#endif
+
+
+/**
+ * Default retransmission delay, in miliseconds. The combination of
+ * retransmission delay and count determines the query timeout.
+ *
+ * Default: 2000 (2 seconds, according to RFC 1035)
+ */
+#ifndef PJ_DNS_RESOLVER_QUERY_RETRANSMIT_DELAY
+# define PJ_DNS_RESOLVER_QUERY_RETRANSMIT_DELAY 2000
+#endif
+
+
+/**
+ * Maximum number of transmissions before timeout is declared for
+ * the query.
+ *
+ * Default: 2
+ */
+#ifndef PJ_DNS_RESOLVER_QUERY_RETRANSMIT_COUNT
+# define PJ_DNS_RESOLVER_QUERY_RETRANSMIT_COUNT 5
+#endif
+
+
+/**
+ * Maximum life-time of DNS response in the resolver response cache,
+ * in seconds. If the value is zero, then DNS response caching will be
+ * disabled.
+ *
+ * Default is 300 seconds (5 minutes).
+ *
+ * @see PJ_DNS_RESOLVER_INVALID_TTL
+ */
+#ifndef PJ_DNS_RESOLVER_MAX_TTL
+# define PJ_DNS_RESOLVER_MAX_TTL (5*60)
+#endif
+
+/**
+ * The life-time of invalid DNS response in the resolver response cache.
+ * An invalid DNS response is a response without an answer. These
+ * responses can be put in the cache too to minimize message round-trip.
+ *
+ * Default: 0 (which means, invalid DNS response will not be cached).
+ *
+ * @see PJ_DNS_RESOLVER_MAX_TTL
+ */
+#ifndef PJ_DNS_RESOLVER_INVALID_TTL
+# define PJ_DNS_RESOLVER_INVALID_TTL 60
+#endif
+
+/**
+ * The interval on which nameservers which are known to be good to be
+ * probed again to determine whether they are still good. Note that
+ * this applies to both active nameserver (the one currently being used)
+ * and idle nameservers (good nameservers that are not currently selected).
+ * The probing to query the "goodness" of nameservers involves sending
+ * the same query to multiple servers, so it's probably not a good idea
+ * to send this probing too often.
+ *
+ * Default: 600 (ten minutes)
+ *
+ * @see PJ_DNS_RESOLVER_BAD_NS_TTL
+ */
+#ifndef PJ_DNS_RESOLVER_GOOD_NS_TTL
+# define PJ_DNS_RESOLVER_GOOD_NS_TTL (10*60)
+#endif
+
+/**
+ * The interval on which nameservers which known to be bad to be probed
+ * again to determine whether it is still bad.
+ *
+ * Default: 600 (ten minutes)
+ *
+ * @see PJ_DNS_RESOLVER_GOOD_NS_TTL
+ */
+#ifndef PJ_DNS_RESOLVER_BAD_NS_TTL
+# define PJ_DNS_RESOLVER_BAD_NS_TTL (1*60)
+#endif
+
+
+/**
+ * Maximum size of UDP packet. RFC 1035 states that maximum size of
+ * DNS packet carried over UDP is 512 bytes.
+ *
+ * Default: 512 byes
+ */
+#ifndef PJ_DNS_RESOLVER_MAX_UDP_SIZE
+# define PJ_DNS_RESOLVER_MAX_UDP_SIZE 512
+#endif
+
+
+/**
+ * Size of memory pool allocated for each individual DNS response cache.
+ * This value here should be more or less the same as maximum UDP packet
+ * size (PJ_DNS_RESOLVER_MAX_UDP_SIZE), since the DNS replicator function
+ * (#pj_dns_packet_dup()) is also capable of performing name compressions.
+ *
+ * Default: 512 (as a broad guidance, 400 is good for 4 SRV entries).
+ */
+#ifndef PJ_DNS_RESOLVER_RES_BUF_SIZE
+# define PJ_DNS_RESOLVER_RES_BUF_SIZE 512
+#endif
+
+
+
+/**
+ * Opaque data type for DNS resolver object.
+ */
+typedef struct pj_dns_resolver pj_dns_resolver;
+
+/**
+ * Opaque data type for asynchronous DNS query object.
+ */
+typedef struct pj_dns_async_query pj_dns_async_query;
+
+/**
+ * Type of asynchronous callback which will be called when the asynchronous
+ * query completes.
+ *
+ * @param user_data The user data set by application when creating the
+ * asynchronous query.
+ * @param status Status of the DNS resolution.
+ * @param response The response packet received from the server. This
+ * argument may be NULL when status is not PJ_SUCCESS.
+ */
+typedef void pj_dns_callback(void *user_data,
+ pj_status_t status,
+ pj_dns_parsed_packet *response);
+
+
+/**
+ * This structure describes resolver settings.
+ */
+typedef struct pj_dns_settings
+{
+ unsigned options; /**< Options flags. */
+ unsigned qretr_delay; /**< Query retransmit delay in msec. */
+ unsigned qretr_count; /**< Query maximum retransmission count. */
+ unsigned cache_max_ttl; /**< Maximum TTL for cached responses. If the
+ value is zero, caching is disabled. */
+} pj_dns_settings;
+
+
+/**
+ * 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.
+ *
+ * @param pf Pool factory where the memory pool will be created from.
+ * @param name Optional resolver name to identify the instance in
+ * the log.
+ * @param options Optional options, must be zero for now.
+ * @param 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.
+ * @param 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.
+ * @param p_resolver Pointer to receive the resolver instance.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code,
+ */
+PJ_DECL(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);
+
+
+/**
+ * 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.
+ *
+ * @param resolver The resolver instance.
+ * @param count Number of name servers in the array.
+ * @param servers Array of name server IP addresses or hostnames. If
+ * hostname is specified, the hostname must be resolvable
+ * with pj_gethostbyname().
+ * @param ports Optional array of ports. If this argument is NULL,
+ * the nameserver will use default port.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code,
+ */
+PJ_DECL(pj_status_t) pj_dns_resolver_set_ns(pj_dns_resolver *resolver,
+ unsigned count,
+ const pj_str_t servers[],
+ const pj_uint16_t ports[]);
+
+
+/**
+ * Get the resolver current settings.
+ *
+ * @param resolver The resolver instance.
+ * @param st Buffer to be filled up with resolver settings.
+ *
+ * @return The query timeout setting, in seconds.
+ */
+PJ_DECL(pj_status_t) pj_dns_resolver_get_settings(pj_dns_resolver *resolver,
+ 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.
+ *
+ * @param resolver The resolver instance.
+ * @param st The resolver settings.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code,
+ */
+PJ_DECL(pj_status_t) pj_dns_resolver_set_settings(pj_dns_resolver *resolver,
+ const pj_dns_settings *st);
+
+
+/**
+ * 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 \a timer or
+ * \a ioqueue argument in #pj_dns_resolver_create()).
+ *
+ * @param resolver The resolver instance.
+ * @param timeout Maximum time to wait for event occurence. If this
+ * argument is NULL, this function will wait forever
+ * until events occur.
+ */
+PJ_DECL(void) pj_dns_resolver_handle_events(pj_dns_resolver *resolver,
+ const pj_time_val *timeout);
+
+
+/**
+ * Destroy DNS resolver instance.
+ *
+ * @param resolver The resolver object to be destryed
+ * @param 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.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code,
+ */
+PJ_DECL(pj_status_t) pj_dns_resolver_destroy(pj_dns_resolver *resolver,
+ pj_bool_t notify);
+
+
+/**
+ * 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 \a 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 \a p_query
+ * argument is not NULL, the value will be set to NULL since no new query
+ * is started.
+ *
+ * @param resolver The resolver object.
+ * @param name The name to be resolved.
+ * @param type The type of resource (see #pj_dns_type constants).
+ * @param options Optional options, must be zero for now.
+ * @param cb Callback to be called when the query completes,
+ * either successfully or with failure.
+ * @param user_data Arbitrary user data to be associated with the query,
+ * and which will be given back in the callback.
+ * @param 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.
+ *
+ * @return PJ_SUCCESS if either an asynchronous query has been
+ * started successfully or response cache is available and
+ * the user callback has been called.
+ */
+PJ_DECL(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);
+
+/**
+ * Cancel a pending query.
+ *
+ * @param query The pending asynchronous query to be cancelled.
+ * @param notify If non-zero, the callback will be called with failure
+ * status to notify that the query has been cancelled.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code,
+ */
+PJ_DECL(pj_status_t) pj_dns_resolver_cancel_query(pj_dns_async_query *query,
+ pj_bool_t notify);
+
+
+/**
+ * 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.
+ *
+ * @param resolver The resolver instance.
+ * @param pkt DNS packet to be added to the DNS cache. If the packet
+ * matches existing entry, it will update the entry.
+ * @param 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.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_dns_resolver_add_entry(pj_dns_resolver *resolver,
+ const pj_dns_parsed_packet *pkt,
+ pj_bool_t set_ttl);
+
+/**
+ * Get the total number of response in the response cache.
+ *
+ * @param resolver The resolver instance.
+ *
+ * @return Current number of entries being stored in the response
+ * cache.
+ */
+PJ_DECL(unsigned) pj_dns_resolver_get_cached_count(pj_dns_resolver *resolver);
+
+
+/**
+ * Dump resolver state to the log.
+ *
+ * @param resolver The resolver instance.
+ * @param detail Will print detailed entries.
+ */
+PJ_DECL(void) pj_dns_resolver_dump(pj_dns_resolver *resolver,
+ pj_bool_t detail);
+
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+
+#endif /* __PJLIB_UTIL_RESOLVER_H__ */
+