summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pjlib-util/include/pjlib-util/errno.h7
-rw-r--r--pjlib-util/include/pjlib-util/resolver.h40
-rw-r--r--pjlib-util/src/pjlib-util/errno.c1
-rw-r--r--pjlib-util/src/pjlib-util/resolver.c106
4 files changed, 151 insertions, 3 deletions
diff --git a/pjlib-util/include/pjlib-util/errno.h b/pjlib-util/include/pjlib-util/errno.h
index bff14194..8db99f6a 100644
--- a/pjlib-util/include/pjlib-util/errno.h
+++ b/pjlib-util/include/pjlib-util/errno.h
@@ -174,6 +174,13 @@
* No answer record in the DNS response.
*/
#define PJLIB_UTIL_EDNSNOANSWERREC (PJLIB_UTIL_ERRNO_START+47) /* 320047 */
+/**
+ * @hideinitializer
+ * Invalid DNS answer. This error is raised for example when the DNS
+ * answer does not have a query section, or the type of RR in the answer
+ * doesn't match the query.
+ */
+#define PJLIB_UTIL_EDNSINANSWER (PJLIB_UTIL_ERRNO_START+48) /* 320048 */
/* DNS ERRORS MAPPED FROM RCODE: */
diff --git a/pjlib-util/include/pjlib-util/resolver.h b/pjlib-util/include/pjlib-util/resolver.h
index f7fd9ede..2d33aa3b 100644
--- a/pjlib-util/include/pjlib-util/resolver.h
+++ b/pjlib-util/include/pjlib-util/resolver.h
@@ -197,6 +197,33 @@ typedef struct pj_dns_settings
unsigned bad_ns_ttl; /**< See #PJ_DNS_RESOLVER_BAD_NS_TTL */
} pj_dns_settings;
+
+/**
+ * This structure represents DNS A record, as the result of parsing
+ * DNS response packet using #pj_dns_parse_a_response().
+ */
+typedef struct pj_dns_a_record
+{
+ /** The target name being queried. */
+ pj_str_t name;
+
+ /** If target name corresponds to a CNAME entry, the alias contains
+ * the value of the CNAME entry, otherwise it will be empty.
+ */
+ pj_str_t alias;
+
+ /** Number of IP addresses. */
+ unsigned addr_count;
+
+ /** IP addresses of the host found in the response */
+ pj_in_addr addr[PJ_DNS_MAX_IP_IN_A_REC];
+
+ /** Internal buffer for hostname and alias. */
+ char buf_[128];
+
+} pj_dns_a_record;
+
+
/**
* Set default values to the DNS settings.
*
@@ -365,6 +392,19 @@ PJ_DECL(pj_status_t) pj_dns_resolver_start_query(pj_dns_resolver *resolver,
PJ_DECL(pj_status_t) pj_dns_resolver_cancel_query(pj_dns_async_query *query,
pj_bool_t notify);
+/**
+ * A utility function to parse a DNS response containing A records into
+ * DNS A record.
+ *
+ * @param pkt The DNS response packet.
+ * @param rec The structure to be initialized with the parsed
+ * DNS A record from the packet.
+ *
+ * @return PJ_SUCCESS if response can be parsed successfully.
+ */
+PJ_DECL(pj_status_t) pj_dns_parse_a_response(const pj_dns_parsed_packet *pkt,
+ pj_dns_a_record *rec);
+
/**
* Put the specified DNS packet into DNS cache. This function is mainly used
diff --git a/pjlib-util/src/pjlib-util/errno.c b/pjlib-util/src/pjlib-util/errno.c
index f09cd775..e4d8bce9 100644
--- a/pjlib-util/src/pjlib-util/errno.c
+++ b/pjlib-util/src/pjlib-util/errno.c
@@ -58,6 +58,7 @@ static const struct
PJ_BUILD_ERR( PJLIB_UTIL_EDNSNONS, "No nameserver is in DNS resolver"),
PJ_BUILD_ERR( PJLIB_UTIL_EDNSNOWORKINGNS, "No working DNS nameserver"),
PJ_BUILD_ERR( PJLIB_UTIL_EDNSNOANSWERREC, "No answer record in the DNS response"),
+ PJ_BUILD_ERR( PJLIB_UTIL_EDNSINANSWER, "Invalid DNS answer"),
PJ_BUILD_ERR( PJLIB_UTIL_EDNS_FORMERR, "DNS \"Format error\""),
PJ_BUILD_ERR( PJLIB_UTIL_EDNS_SERVFAIL, "DNS \"Server failure\""),
diff --git a/pjlib-util/src/pjlib-util/resolver.c b/pjlib-util/src/pjlib-util/resolver.c
index 6740bd31..0a0905ab 100644
--- a/pjlib-util/src/pjlib-util/resolver.c
+++ b/pjlib-util/src/pjlib-util/resolver.c
@@ -424,6 +424,7 @@ PJ_DEF(pj_status_t) pj_dns_resolver_set_ns( pj_dns_resolver *resolver,
pj_status_t status;
PJ_ASSERT_RETURN(resolver && count && servers, PJ_EINVAL);
+ PJ_ASSERT_RETURN(count < PJ_DNS_RESOLVER_MAX_NS, PJ_EINVAL);
pj_mutex_lock(resolver->mutex);
@@ -801,6 +802,103 @@ PJ_DEF(pj_status_t) pj_dns_resolver_cancel_query(pj_dns_async_query *query,
}
+/*
+ * DNS response containing A packet.
+ */
+PJ_DEF(pj_status_t) pj_dns_parse_a_response(const pj_dns_parsed_packet *pkt,
+ pj_dns_a_record *rec)
+{
+ pj_str_t hostname, alias, *res_name;
+ unsigned bufstart = 0;
+ unsigned bufleft = sizeof(rec->buf_);
+ unsigned i, ansidx;
+
+ PJ_ASSERT_RETURN(pkt && rec, PJ_EINVAL);
+
+ /* Init the record */
+ pj_bzero(rec, sizeof(pj_dns_a_record));
+
+ /* Return error if there's error in the packet. */
+ if (PJ_DNS_GET_RCODE(pkt->hdr.flags))
+ return PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_GET_RCODE(pkt->hdr.flags));
+
+ /* Return error if there's no query section */
+ if (pkt->hdr.qdcount == 0)
+ return PJLIB_UTIL_EDNSINANSWER;
+
+ /* Return error if there's no answer */
+ if (pkt->hdr.anscount == 0)
+ return PJLIB_UTIL_EDNSNOANSWERREC;
+
+ /* Get the hostname from the query. */
+ hostname = pkt->q[0].name;
+
+ /* Copy hostname to the record */
+ if (hostname.slen > (int)bufleft) {
+ return PJ_ENAMETOOLONG;
+ }
+
+ pj_memcpy(&rec->buf_[bufstart], hostname.ptr, hostname.slen);
+ rec->name.ptr = &rec->buf_[bufstart];
+ rec->name.slen = hostname.slen;
+
+ bufstart += hostname.slen;
+ bufleft -= hostname.slen;
+
+ /* Find the first RR which name matches the hostname */
+ for (ansidx=0; ansidx < pkt->hdr.anscount; ++ansidx) {
+ if (pj_stricmp(&pkt->ans[ansidx].name, &hostname)==0)
+ break;
+ }
+
+ if (ansidx == pkt->hdr.anscount)
+ return PJLIB_UTIL_EDNSNOANSWERREC;
+
+ /* If hostname is a CNAME, get the alias. */
+ if (pkt->ans[ansidx].type == PJ_DNS_TYPE_CNAME) {
+ alias = pkt->ans[ansidx].rdata.cname.name;
+ res_name = &alias;
+ } else if (pkt->ans[ansidx].type == PJ_DNS_TYPE_A) {
+ alias.ptr = NULL;
+ alias.slen = 0;
+ res_name = &hostname;
+ } else {
+ return PJLIB_UTIL_EDNSINANSWER;
+ }
+
+ /* Copy alias to the record, if present. */
+ if (alias.slen) {
+ if (alias.slen > (int)bufleft)
+ return PJ_ENAMETOOLONG;
+
+ pj_memcpy(&rec->buf_[bufstart], alias.ptr, alias.slen);
+ rec->alias.ptr = &rec->buf_[bufstart];
+ rec->alias.slen = alias.slen;
+
+ bufstart += alias.slen;
+ bufleft -= alias.slen;
+ }
+
+ /* Now scan the answer for all type A RRs where the name matches
+ * hostname or alias.
+ */
+ for (i=0; i<pkt->hdr.anscount; ++i) {
+ if (pkt->ans[i].type == PJ_DNS_TYPE_A &&
+ pj_stricmp(&pkt->ans[i].name, res_name)==0 &&
+ rec->addr_count < PJ_DNS_MAX_IP_IN_A_REC)
+ {
+ rec->addr[rec->addr_count].s_addr =
+ pkt->ans[i].rdata.a.ip_addr.s_addr;
+ ++rec->addr_count;
+ }
+ }
+
+ if (rec->addr_count == 0)
+ return PJLIB_UTIL_EDNSNOANSWERREC;
+
+ return PJ_SUCCESS;
+}
+
/* Set nameserver state */
static void set_nameserver_state(pj_dns_resolver *resolver,
@@ -1030,14 +1128,16 @@ static void update_res_cache(pj_dns_resolver *resolver,
}
/* Duplicate the packet.
- * We don't need to keep the query, NS, and AR sections from the packet,
- * so exclude from duplication.
+ * We don't need to keep the NS and AR sections from the packet,
+ * so exclude from duplication. We do need to keep the Query
+ * section since DNS A parser needs the query section to know
+ * the name being requested.
*/
res_pool = pj_pool_create_on_buf("respool", cache->buf, sizeof(cache->buf));
PJ_TRY {
cache->pkt = NULL;
pj_dns_packet_dup(res_pool, pkt,
- PJ_DNS_NO_QD | PJ_DNS_NO_NS | PJ_DNS_NO_AR,
+ PJ_DNS_NO_NS | PJ_DNS_NO_AR,
&cache->pkt);
}
PJ_CATCH_ANY {