[SSSD] [PATCH] RESOLV: Add an internal function to read TTL from a DNS packet

Jakub Hrozek jhrozek at redhat.com
Wed Jan 21 16:30:52 UTC 2015


On Thu, Jan 15, 2015 at 12:00:31PM +0100, Pavel Březina wrote:
> On 01/14/2015 04:21 PM, Jakub Hrozek wrote:
> >On Mon, Dec 15, 2014 at 03:15:37AM +0100, Jakub Hrozek wrote:
> >>Hi,
> >>
> >>The attached patch is a self-contained change related to
> >>https://fedorahosted.org/sssd/ticket/1884. I also wrote code that uses
> >>the TTL values in the failover code:
> >>     https://fedorapeople.org/cgit/jhrozek/public_git/sssd.git/log/?h=srvttl
> >>
> >>But when creating the unit tests I stumbled upon:
> >>     https://fedorahosted.org/sssd/ticket/2533
> >>and especially:
> >>     https://fedorahosted.org/sssd/ticket/2532
> >>
> >>Until these two are fixed, I don't think we can merge the TTL patch.
> >>Feel free to review the changes in my git branch, though, I don't think
> >>they'd change.
> >
> >I changed the function itself to return a separate status as return
> >value and fill a pointer with the TTL.
> >
> >Still waiting for CI to finish, but the amended patch is attached.
> 
> >static bool
> >resolv_get_ttl(const unsigned char *abuf, const int alen, uint32_t *_ttl)
> >{
> >    const unsigned char *aptr;
> >    int ret;
> >    char *name = NULL;
> >    long len;
> >    uint32_t ttl = 0;
> >    uint32_t rr_ttl;
> >    unsigned int rr_len;
> >    unsigned int ancount;
> >    unsigned int i;
> >
> >    /* Read the number of RRs and then skip past the header */
> >    if (alen < HFIXEDSZ) {
> >        return false;
> >    }
> >
> >    ancount = DNS_HEADER_ANCOUNT(abuf);
> >    if (ancount == 0) {
> >        return false;
> >    }
> >
> >    aptr = abuf + NS_HFIXEDSZ;
> 
> Why do you use both HFIXEDSZ and NS_HFIXEDSZ? What is the difference?

The version without prefix is obsolete, we should use NS_*. It was a
copy-n-paste error from c-ares parsing function.

> 
> >
> >    /* We only care about len from the question data,
> >     * so that we can move past hostname */
> >    ret = ares_expand_name(aptr, abuf, alen, &name, &len);
> >    ares_free_string(name);
> >    if (ret != ARES_SUCCESS) {
> >        return false;
> >    }
> >
> >    /* Skip past the question */
> >    if (aptr + len + NS_QFIXEDSZ > abuf + alen) {
> >        ares_free_string(name);
> 
> double free of name

Good catch!

> 
> >        return false;
> >    }
> >    aptr += len + NS_QFIXEDSZ;
> 
> It is not necessary to do the addition twice:
> 
> aptr += len + NS_QFIXEDSZ;
> if (aptr > abuf + alen) {
>     return false;

I guess, as long as we don't access the pointer..

> }
> 
> >
> >    /* Examine each RR in turn and read the lowest TTL */
> >    for (i = 0; i < ancount; i++) {
> >        /* Decode the RR up to the data field. */
> >        ret = ares_expand_name(aptr, abuf, alen, &name, &len);
> >        ares_free_string(name);
> >        if (ret != ARES_SUCCESS) {
> >            return false;
> >        }
> >
> >        aptr += len;
> >        if (aptr + NS_RRFIXEDSZ > abuf + alen) {
> >            return false;
> >        }
> >
> >        rr_len = DNS_RR_LEN(aptr);
> >        rr_ttl = DNS_RR_TTL(aptr);
> >        if (aptr + rr_len > abuf + alen) {
> >            return false;
> >        }
> >        aptr += NS_RRFIXEDSZ + rr_len;
> >
> >        if (ttl > 0) {
> >            ttl = MIN(ttl, rr_ttl);
> >        } else {
> >            ttl = rr_ttl; /* special-case for first TTL */
> >        }
> >    }
> >
> >    *_ttl = ttl;
> >    return true;
> >}
> 
> It looks good otherwise. I got the following compilation error though:
> 
>   CC     src/tests/cmocka/ifp_tests-common_mock_resp.o
> In file included from /home/pbrezina/workspace/sssd/src/tests/common.h:31:0,
>                  from
> /home/pbrezina/workspace/sssd/src/tests/cmocka/common_mock.h:44,
>                  from
> /home/pbrezina/workspace/sssd/src/tests/cmocka/test_resolv_fake.c:35:
> /home/pbrezina/workspace/sssd/src/providers/ldap/sdap.h:507:33: error:
> function declaration isn’t a prototype [-Werror=strict-prototypes]

This one was fun to track down. So because in the tests we use both the system
resolver and indirectly the sdap.h include file is present, the function
declaration resolved to:

errno_t sdap_parse_deref(TALLOC_CTX *mem_ctx,
                         struct sdap_attr_map_info *minfo,
                         size_t num_maps,
                         LDAPDerefRes *dref,
                         struct sdap_deref_attrs ***(*__res_state())); 

I think the simplest solution is to rename the _res parameter into something else..
-------------- next part --------------
>From e122715014dd1fb4285ba135ed6d4c95778aad9c Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Wed, 21 Jan 2015 17:24:52 +0100
Subject: [PATCH 1/2] LDAP: Rename the _res output parameter to avoid clashing
 with libresolv in tests

---
 src/providers/ldap/sdap.c | 6 +++---
 src/providers/ldap/sdap.h | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c
index e428d5f4b112fe81c6f04ee1746fba0aa2bc10e5..c401e7dc86ded8b3685862c6528034b5cc55068d 100644
--- a/src/providers/ldap/sdap.c
+++ b/src/providers/ldap/sdap.c
@@ -556,7 +556,7 @@ errno_t sdap_parse_deref(TALLOC_CTX *mem_ctx,
                          struct sdap_attr_map_info *minfo,
                          size_t num_maps,
                          LDAPDerefRes *dref,
-                         struct sdap_deref_attrs ***_res)
+                         struct sdap_deref_attrs ***_deref_res)
 {
     TALLOC_CTX *tmp_ctx;
     LDAPDerefVal *dval;
@@ -604,7 +604,7 @@ errno_t sdap_parse_deref(TALLOC_CTX *mem_ctx,
         DEBUG(SSSDBG_FUNC_DATA,
               "Dereferenced entry [%s] has no attributes, skipping\n",
               orig_dn);
-        *_res = NULL;
+        *_deref_res = NULL;
         ret = EOK;
         goto done;
     }
@@ -709,7 +709,7 @@ errno_t sdap_parse_deref(TALLOC_CTX *mem_ctx,
     }
 
 
-    *_res = talloc_steal(mem_ctx, res);
+    *_deref_res = talloc_steal(mem_ctx, res);
     ret = EOK;
 done:
     talloc_zfree(tmp_ctx);
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index 921051b41a911a2d1117672a8e9c2697b679f24e..d7f6c646e59a8af8347e39afb644d8ea40b9a4c3 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -504,7 +504,7 @@ errno_t sdap_parse_deref(TALLOC_CTX *mem_ctx,
                          struct sdap_attr_map_info *minfo,
                          size_t num_maps,
                          LDAPDerefRes *dref,
-                         struct sdap_deref_attrs ***_res);
+                         struct sdap_deref_attrs ***_deref_res);
 
 errno_t setup_tls_config(struct dp_option *basic_opts);
 
-- 
2.1.0

-------------- next part --------------
>From fc1cadc30c43477453ad633e79d42d895bfeb721 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Fri, 12 Dec 2014 17:10:25 +0100
Subject: [PATCH 2/2] RESOLV: Add an internal function to read TTL from a DNS
 packet

Related:
https://fedorahosted.org/sssd/ticket/1884

Adds an internal resolver function that reads the TTL for SRV records as
specified by RFC-2181. Several internal c-ares definitions are used
until c-ares contains a function that exposes all this information via a
parsing function.
---
 Makefile.am                         |  27 +++
 configure.ac                        |   1 +
 src/external/libresolv.m4           |  12 ++
 src/providers/fail_over_srv.c       |   2 +-
 src/resolv/async_resolv.c           | 113 ++++++++++-
 src/resolv/async_resolv.h           |   4 +-
 src/resolv/async_resolv_utils.c     |   9 +-
 src/tests/cmocka/test_resolv_fake.c | 374 ++++++++++++++++++++++++++++++++++++
 src/tests/resolv-tests.c            |   2 +-
 9 files changed, 539 insertions(+), 5 deletions(-)
 create mode 100644 src/external/libresolv.m4
 create mode 100644 src/tests/cmocka/test_resolv_fake.c

diff --git a/Makefile.am b/Makefile.am
index ca8921bb132db6b7a57d9436a3219057d08b64c6..d1d750ba92f069206436a81f796784a15ca49aed 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -221,6 +221,10 @@ if HAVE_CMOCKA
         responder_cache_req-tests \
         $(NULL)
 
+if HAVE_LIBRESOLV
+non_interactive_cmocka_based_tests += test_resolv_fake
+endif   # HAVE_LIBRESOLV
+
 if BUILD_IFP
 non_interactive_cmocka_based_tests += ifp_tests
 endif   # BUILD_IFP
@@ -2205,6 +2209,29 @@ responder_cache_req_tests_LDADD = \
     libsss_test_common.la \
     $(NULL)
 
+if HAVE_LIBRESOLV
+test_resolv_fake_SOURCES = \
+    src/tests/cmocka/test_resolv_fake.c \
+    src/resolv/async_resolv.c \
+    $(NULL)
+test_resolv_fake_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(NULL)
+test_resolv_fake_LDFLAGS = \
+    -Wl,-wrap,ares_query \
+    $(NULL)
+test_resolv_fake_LDADD = \
+    $(CMOCKA_LIBS) \
+    $(POPT_LIBS) \
+    $(TALLOC_LIBS) \
+    $(CARES_LIBS) \
+    $(DHASH_LIBS) \
+    $(RESOLV_LIBS) \
+    libsss_debug.la \
+    libsss_test_common.la \
+    $(NULL)
+endif # HAVE_LIBRESOLV
+
 endif # HAVE_CMOCKA
 
 noinst_PROGRAMS = pam_test_client
diff --git a/configure.ac b/configure.ac
index e5ec204ad9671d15deb1830c60168e066a66f198..dd64df3d9a1072347677cd3f6d81edb8a2509a67 100644
--- a/configure.ac
+++ b/configure.ac
@@ -173,6 +173,7 @@ m4_include([src/external/sasl.m4])
 m4_include([src/external/configlib.m4])
 m4_include([src/external/libnfsidmap.m4])
 m4_include([src/external/cwrap.m4])
+m4_include([src/external/libresolv.m4])
 
 if test x$build_config_lib = xyes; then
     m4_include([src/external/libaugeas.m4])
diff --git a/src/external/libresolv.m4 b/src/external/libresolv.m4
new file mode 100644
index 0000000000000000000000000000000000000000..225cf2be8ddbdd9aea19bf8c716c5a956b07b97f
--- /dev/null
+++ b/src/external/libresolv.m4
@@ -0,0 +1,12 @@
+AC_SUBST(RESOLV_CFLAGS)
+AC_SUBST(RESOLV_LIBS)
+
+# Some unit tests require libresolv to fake DNS packets
+SSS_AC_EXPAND_LIB_DIR()
+AC_CHECK_LIB([resolv],
+             [ns_name_compress],
+             [RESOLV_LIBS="-L$sss_extra_libdir -lresolv"],
+             [AC_MSG_WARN([No libresolv detected, some tests will not run])],
+             [-L$sss_extra_libdir])
+
+AM_CONDITIONAL([HAVE_LIBRESOLV], [test x"$RESOLV_LIBS" != "x"])
diff --git a/src/providers/fail_over_srv.c b/src/providers/fail_over_srv.c
index 1b99f3150d49d23a137815586aba0d4def6c5c42..5c06d28767b8afbc4e573a7881e7855f35747460 100644
--- a/src/providers/fail_over_srv.c
+++ b/src/providers/fail_over_srv.c
@@ -83,7 +83,7 @@ static void fo_discover_srv_done(struct tevent_req *subreq)
     state = tevent_req_data(req, struct fo_discover_srv_state);
 
     ret = resolv_discover_srv_recv(state, subreq,
-                                   &reply_list, &state->dns_domain);
+                                   &reply_list, NULL, &state->dns_domain);
     talloc_zfree(subreq);
     if (ret == ENOENT) {
         ret = ERR_SRV_NOT_FOUND;
diff --git a/src/resolv/async_resolv.c b/src/resolv/async_resolv.c
index f93612440ec8f9ce393bede8316b512e46d577bc..85c4d99a4ef3c5071353f176c78caba115286fe4 100644
--- a/src/resolv/async_resolv.c
+++ b/src/resolv/async_resolv.c
@@ -62,7 +62,21 @@
 #endif
 
 #define DNS__16BIT(p)                   (((p)[0] << 8) | (p)[1])
+
+/*
+ * Macro DNS__32BIT reads a network long (32 bit) given in network
+ * byte order, and returns its value as an unsigned int. Copied
+ * from c-ares source code.
+ */
+#define DNS__32BIT(p)  ((unsigned int) \
+                         (((unsigned int)((unsigned char)(p)[0]) << 24U) | \
+                          ((unsigned int)((unsigned char)(p)[1]) << 16U) | \
+                          ((unsigned int)((unsigned char)(p)[2]) <<  8U) | \
+                          ((unsigned int)((unsigned char)(p)[3]))))
+
 #define DNS_HEADER_ANCOUNT(h)           DNS__16BIT((h) + 6)
+#define DNS_RR_LEN(r)                   DNS__16BIT((r) + 8)
+#define DNS_RR_TTL(r)                   DNS__32BIT((r) + 4)
 
 #define RESOLV_TIMEOUTMS  2000
 
@@ -1572,6 +1586,7 @@ struct getsrv_state {
 
     /* parsed data returned by ares */
     struct ares_srv_reply *reply_list;
+    uint32_t ttl;
     int status;
     int timeouts;
     int retrying;
@@ -1607,6 +1622,7 @@ resolv_getsrv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
     state->resolv_ctx = ctx;
     state->query = query;
     state->reply_list = NULL;
+    state->ttl = 0;
     state->status = 0;
     state->timeouts = 0;
     state->retrying = 0;
@@ -1624,6 +1640,92 @@ resolv_getsrv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
     return req;
 }
 
+/*
+ * Implemented based on http://tools.ietf.org/html/rfc2181#section-5
+ *
+ * Especially:
+ * 5.2. TTLs of RRs in an RRSet
+ *  Consequently the use of differing TTLs in an RRSet is hereby
+ *  deprecated, the TTLs of all RRs in an RRSet must be the same.
+ *  ...
+ *  Should an authoritative source send such a malformed RRSet, the
+ *  client should treat the RRs for all purposes as if all TTLs in the
+ *  RRSet had been set to the value of the lowest TTL in the RRSet.
+ *
+ *  On success, returns true and sets the TTL in the _ttl parameter. On
+ *  failure, returns false and _ttl is undefined.
+ */
+static bool
+resolv_get_ttl(const unsigned char *abuf, const int alen, uint32_t *_ttl)
+{
+    const unsigned char *aptr;
+    int ret;
+    char *name = NULL;
+    long len;
+    uint32_t ttl = 0;
+    uint32_t rr_ttl;
+    unsigned int rr_len;
+    unsigned int ancount;
+    unsigned int i;
+
+    /* Read the number of RRs and then skip past the header */
+    if (alen < NS_HFIXEDSZ) {
+        return false;
+    }
+
+    ancount = DNS_HEADER_ANCOUNT(abuf);
+    if (ancount == 0) {
+        return false;
+    }
+
+    aptr = abuf + NS_HFIXEDSZ;
+
+    /* We only care about len from the question data,
+     * so that we can move past hostname */
+    ret = ares_expand_name(aptr, abuf, alen, &name, &len);
+    ares_free_string(name);
+    if (ret != ARES_SUCCESS) {
+        return false;
+    }
+
+    /* Skip past the question */
+    aptr += len + NS_QFIXEDSZ;
+    if (aptr > abuf + alen) {
+        return false;
+    }
+
+    /* Examine each RR in turn and read the lowest TTL */
+    for (i = 0; i < ancount; i++) {
+        /* Decode the RR up to the data field. */
+        ret = ares_expand_name(aptr, abuf, alen, &name, &len);
+        ares_free_string(name);
+        if (ret != ARES_SUCCESS) {
+            return false;
+        }
+
+        aptr += len;
+        if (aptr + NS_RRFIXEDSZ > abuf + alen) {
+            return false;
+        }
+
+        rr_len = DNS_RR_LEN(aptr);
+        rr_ttl = DNS_RR_TTL(aptr);
+        if (aptr + rr_len > abuf + alen) {
+            return false;
+        }
+        aptr += NS_RRFIXEDSZ + rr_len;
+
+        if (ttl > 0) {
+            ttl = MIN(ttl, rr_ttl);
+        } else {
+            ttl = rr_ttl; /* special-case for first TTL */
+        }
+    }
+
+    *_ttl = ttl;
+    return true;
+}
+
 static void
 resolv_getsrv_done(void *arg, int status, int timeouts, unsigned char *abuf, int alen)
 {
@@ -1631,6 +1733,7 @@ resolv_getsrv_done(void *arg, int status, int timeouts, unsigned char *abuf, int
     struct tevent_req *req;
     struct getsrv_state *state;
     int ret;
+    bool ok;
     struct ares_srv_reply *reply_list;
 
     if (rreq->rwatch == NULL) {
@@ -1671,6 +1774,10 @@ resolv_getsrv_done(void *arg, int status, int timeouts, unsigned char *abuf, int
         goto fail;
     }
     state->reply_list = reply_list;
+    ok = resolv_get_ttl(abuf, alen, &state->ttl);
+    if (ok == false) {
+        state->ttl = RESOLV_DEFAULT_TTL;
+    }
 
     tevent_req_done(req);
     return;
@@ -1682,7 +1789,8 @@ fail:
 
 int
 resolv_getsrv_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, int *status,
-                   int *timeouts, struct ares_srv_reply **reply_list)
+                   int *timeouts, struct ares_srv_reply **reply_list,
+                   uint32_t *ttl)
 {
     struct getsrv_state *state = tevent_req_data(req, struct getsrv_state);
 
@@ -1692,6 +1800,9 @@ resolv_getsrv_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, int *status,
         *timeouts = state->timeouts;
     if (reply_list)
         *reply_list = talloc_steal(mem_ctx, state->reply_list);
+    if (ttl) {
+        *ttl = state->ttl;
+    }
 
     TEVENT_REQ_RETURN_ON_ERROR(req);
 
diff --git a/src/resolv/async_resolv.h b/src/resolv/async_resolv.h
index 919ba370b87304735bff279d1502386d2ac28735..9b08f12ae159d377b50b9ddfe9f71397a461635d 100644
--- a/src/resolv/async_resolv.h
+++ b/src/resolv/async_resolv.h
@@ -144,7 +144,8 @@ int resolv_getsrv_recv(TALLOC_CTX *mem_ctx,
                        struct tevent_req *req,
                        int *status,
                        int *timeouts,
-                       struct ares_srv_reply **reply_list);
+                       struct ares_srv_reply **reply_list,
+                       uint32_t *ttl);
 
 /* This is an implementation of section "Usage rules" of RFC 2782 */
 int
@@ -187,6 +188,7 @@ resolv_discover_srv_send(TALLOC_CTX *mem_ctx,
 errno_t resolv_discover_srv_recv(TALLOC_CTX *mem_ctx,
                                  struct tevent_req *req,
                                  struct ares_srv_reply **_reply_list,
+                                 uint32_t *_ttl,
                                  char **_dns_domain);
 
 #endif /* __ASYNC_RESOLV_H__ */
diff --git a/src/resolv/async_resolv_utils.c b/src/resolv/async_resolv_utils.c
index c5ae5f9d43734c997e6f4e3e396e1d367f80784a..250681717f9548e4e9e2d8ee2951218605140ed4 100644
--- a/src/resolv/async_resolv_utils.c
+++ b/src/resolv/async_resolv_utils.c
@@ -162,6 +162,7 @@ struct resolv_discover_srv_state {
     int domain_index;
 
     struct ares_srv_reply *reply_list;
+    uint32_t ttl;
 };
 
 static errno_t resolv_discover_srv_next_domain(struct tevent_req *req);
@@ -272,7 +273,8 @@ static void resolv_discover_srv_done(struct tevent_req *subreq)
     req = tevent_req_callback_data(subreq, struct tevent_req);
     state = tevent_req_data(req, struct resolv_discover_srv_state);
 
-    ret = resolv_getsrv_recv(state, subreq, &status, NULL, &state->reply_list);
+    ret = resolv_getsrv_recv(state, subreq, &status, NULL,
+                             &state->reply_list, &state->ttl);
     talloc_zfree(subreq);
     if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE, "SRV query failed [%d]: %s\n",
@@ -307,6 +309,7 @@ done:
 errno_t resolv_discover_srv_recv(TALLOC_CTX *mem_ctx,
                                  struct tevent_req *req,
                                  struct ares_srv_reply **_reply_list,
+                                 uint32_t *_ttl,
                                  char **_dns_domain)
 {
     struct resolv_discover_srv_state *state = NULL;
@@ -331,5 +334,9 @@ errno_t resolv_discover_srv_recv(TALLOC_CTX *mem_ctx,
         *_reply_list = talloc_steal(mem_ctx, state->reply_list);
     }
 
+    if (*_ttl) {
+        *_ttl = state->ttl;
+    }
+
     return EOK;
 }
diff --git a/src/tests/cmocka/test_resolv_fake.c b/src/tests/cmocka/test_resolv_fake.c
new file mode 100644
index 0000000000000000000000000000000000000000..6c201e7023c4138703a8057f6eb10c9210385499
--- /dev/null
+++ b/src/tests/cmocka/test_resolv_fake.c
@@ -0,0 +1,374 @@
+/*
+    Authors:
+        Jakub Hrozek <jhrozek at redhat.com>
+
+    Copyright (C) 2014 Red Hat
+
+    SSSD tests: Resolver tests using a fake resolver library
+
+    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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <talloc.h>
+#include <tevent.h>
+#include <errno.h>
+#include <popt.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include <resolv.h>
+
+#include "tests/cmocka/common_mock.h"
+#include "tests/cmocka/common_mock_resp.h"
+
+#define TEST_BUFSIZE         1024
+#define TEST_DEFAULT_TIMEOUT 5
+#define TEST_SRV_QUERY "_ldap._tcp.sssd.com"
+
+static TALLOC_CTX *global_mock_context = NULL;
+
+struct srv_rrdata {
+    uint16_t port;
+    uint16_t prio;
+    uint16_t weight;
+    uint32_t ttl;
+    const char *hostname;
+};
+
+static ssize_t dns_header(unsigned char **buf, size_t ancount)
+{
+    uint8_t *hb;
+    HEADER *h;
+
+    hb = *buf;
+    memset(hb, 0, NS_HFIXEDSZ);
+
+    h = (HEADER *) hb;
+    h->id = res_randomid();     /* random query ID */
+    h->qr = 1;                  /* response flag */
+    h->rd = 1;                  /* recursion desired */
+    h->ra = 1;                  /* resursion available */
+
+    h->qdcount = htons(1);          /* no. of questions */
+    h->ancount = htons(ancount);    /* no. of answers */
+    h->arcount = htons(0);          /* no. of add'tl records */
+
+    hb += NS_HFIXEDSZ;              /* move past the header */
+    *buf = hb;
+
+    return NS_HFIXEDSZ;
+}
+
+static ssize_t dns_question(const char *question,
+                            uint16_t type,
+                            uint8_t **question_ptr,
+                            size_t remaining)
+{
+    unsigned char *qb = *question_ptr;
+    int n;
+
+    n = ns_name_compress(question, qb, remaining, NULL, NULL);
+    assert_true(n > 0);
+
+    qb += n;
+    remaining -= n;
+
+    NS_PUT16(type, qb);
+    NS_PUT16(ns_c_in, qb);
+
+    *question_ptr = qb;
+    return n + 2 * sizeof(uint16_t);
+}
+
+static ssize_t add_rr_common(uint16_t type,
+                             uint32_t ttl,
+                             size_t rdata_size,
+                             const char *key,
+                             size_t remaining,
+                             uint8_t **rdata_ptr)
+{
+    uint8_t *rd = *rdata_ptr;
+    ssize_t written = 0;
+
+    written = ns_name_compress(key, rd, remaining, NULL, NULL);
+    assert_int_not_equal(written, -1);
+    rd += written;
+    remaining -= written;
+
+    assert_true(remaining > 3 * sizeof(uint16_t) + sizeof(uint32_t));
+    NS_PUT16(type, rd);
+    NS_PUT16(ns_c_in, rd);
+    NS_PUT32(ttl, rd);
+    NS_PUT16(rdata_size, rd);
+
+    assert_true(remaining > rdata_size);
+    *rdata_ptr = rd;
+    return written + 3 * sizeof(uint16_t) + sizeof(uint32_t) + rdata_size;
+}
+
+static ssize_t add_srv_rr(struct srv_rrdata *rr,
+                          const char *question,
+                          uint8_t *answer,
+                          size_t anslen)
+{
+    uint8_t *a = answer;
+    ssize_t resp_size;
+    size_t rdata_size;
+    unsigned char hostname_compressed[MAXDNAME];
+    ssize_t compressed_len;
+
+    rdata_size = 3 * sizeof(uint16_t);
+
+    /* Prepare the data to write */
+    compressed_len = ns_name_compress(rr->hostname,
+                                      hostname_compressed, MAXDNAME,
+                                      NULL, NULL);
+    assert_int_not_equal(compressed_len, -1);
+    rdata_size += compressed_len;
+
+    resp_size = add_rr_common(ns_t_srv, rr->ttl, rdata_size,
+                              question, anslen, &a);
+
+    NS_PUT16(rr->prio, a);
+    NS_PUT16(rr->weight, a);
+    NS_PUT16(rr->port, a);
+    memcpy(a, hostname_compressed, compressed_len);
+
+    return resp_size;
+}
+
+unsigned char *create_srv_buffer(TALLOC_CTX *mem_ctx,
+                                 const char *question,
+                                 struct srv_rrdata *rrs,
+                                 size_t n_rrs,
+                                 size_t *_buflen)
+{
+    unsigned char *buf;
+    unsigned char *buf_head;
+    ssize_t len;
+    ssize_t i;
+    ssize_t total = 0;
+
+    buf = talloc_zero_array(mem_ctx, unsigned char, TEST_BUFSIZE);
+    assert_non_null(buf);
+    buf_head = buf;
+
+    len = dns_header(&buf, n_rrs);
+    assert_true(len > 0);
+    total += len;
+
+    len = dns_question(question, ns_t_srv, &buf, TEST_BUFSIZE - total);
+    assert_true(len > 0);
+    total += len;
+
+    /* answer */
+    for (i = 0; i < n_rrs; i++) {
+        len = add_srv_rr(&rrs[i], question, buf, TEST_BUFSIZE - total);
+        assert_true(len > 0);
+        total += len;
+        buf += len;
+    }
+
+    *_buflen = total;
+    return buf_head;
+}
+
+struct fake_ares_query {
+    int status;
+    int timeouts;
+    unsigned char *abuf;
+    int alen;
+};
+
+void mock_ares_query(int status, int timeouts, unsigned char *abuf, int alen)
+{
+    will_return(__wrap_ares_query, status);
+    will_return(__wrap_ares_query, timeouts);
+    will_return(__wrap_ares_query, abuf);
+    will_return(__wrap_ares_query, alen);
+}
+
+void __wrap_ares_query(ares_channel channel, const char *name, int dnsclass,
+                       int type, ares_callback callback, void *arg)
+{
+    struct fake_ares_query query;
+
+    query.status = sss_mock_type(int);
+    query.timeouts = sss_mock_type(int);
+    query.abuf = sss_mock_ptr_type(unsigned char *);
+    query.alen = sss_mock_type(int);
+
+    callback(arg, query.status, query.timeouts, query.abuf, query.alen);
+}
+
+/* The unit test */
+struct resolv_fake_ctx {
+    struct resolv_ctx *resolv;
+    struct sss_test_ctx *ctx;
+};
+
+void test_resolv_fake_setup(void **state)
+{
+    struct resolv_fake_ctx *test_ctx;
+    int ret;
+
+    assert_true(leak_check_setup());
+    global_mock_context = talloc_new(global_talloc_context);
+    assert_non_null(global_mock_context);
+
+    test_ctx = talloc_zero(global_mock_context,
+                           struct resolv_fake_ctx);
+    assert_non_null(test_ctx);
+
+    test_ctx->ctx = create_ev_test_ctx(test_ctx);
+    assert_non_null(test_ctx->ctx);
+
+    ret = resolv_init(test_ctx, test_ctx->ctx->ev,
+                      TEST_DEFAULT_TIMEOUT, &test_ctx->resolv);
+    assert_int_equal(ret, EOK);
+
+    *state = test_ctx;
+}
+
+void test_resolv_fake_teardown(void **state)
+{
+    struct resolv_fake_ctx *test_ctx =
+        talloc_get_type(*state, struct resolv_fake_ctx);
+
+    talloc_free(test_ctx);
+    talloc_free(global_mock_context);
+    assert_true(leak_check_teardown());
+}
+
+void test_resolv_fake_srv_done(struct tevent_req *req)
+{
+    errno_t ret;
+    TALLOC_CTX *tmp_ctx;
+    int status;
+    uint32_t ttl;
+    struct ares_srv_reply *srv_replies = NULL;
+    struct resolv_fake_ctx *test_ctx =
+        tevent_req_callback_data(req, struct resolv_fake_ctx);
+
+    tmp_ctx = talloc_new(test_ctx);
+    assert_non_null(tmp_ctx);
+
+    ret = resolv_getsrv_recv(tmp_ctx, req, &status, NULL,
+                             &srv_replies, &ttl);
+    assert_int_equal(ret, EOK);
+
+    assert_non_null(srv_replies);
+    assert_int_equal(srv_replies->priority, 1);
+    assert_int_equal(srv_replies->weight, 40);
+    assert_int_equal(srv_replies->port, 389);
+    assert_string_equal(srv_replies->host, "ldap.sssd.com");
+
+    srv_replies = srv_replies->next;
+    assert_non_null(srv_replies);
+    assert_int_equal(srv_replies->priority, 1);
+    assert_int_equal(srv_replies->weight, 60);
+    assert_int_equal(srv_replies->port, 389);
+    assert_string_equal(srv_replies->host, "ldap2.sssd.com");
+
+    srv_replies = srv_replies->next;
+    assert_null(srv_replies);
+
+    assert_int_equal(ttl, 500);
+
+    talloc_free(tmp_ctx);
+    test_ctx->ctx->error = EOK;
+    test_ctx->ctx->done = true;
+}
+
+void test_resolv_fake_srv(void **state)
+{
+    int ret;
+    struct tevent_req *req;
+    struct resolv_fake_ctx *test_ctx =
+        talloc_get_type(*state, struct resolv_fake_ctx);
+
+    unsigned char *buf;
+    size_t buflen;
+
+    struct srv_rrdata rr[2];
+
+    rr[0].prio = 1;
+    rr[0].port = 389;
+    rr[0].weight = 40;
+    rr[0].ttl = 600;
+    rr[0].hostname = "ldap.sssd.com";
+
+    rr[1].prio = 1;
+    rr[1].port = 389;
+    rr[1].weight = 60;
+    rr[1].ttl = 500;
+    rr[1].hostname = "ldap2.sssd.com";
+
+    buf = create_srv_buffer(test_ctx, TEST_SRV_QUERY, rr, 2, &buflen);
+    assert_non_null(buf);
+    mock_ares_query(0, 0, buf, buflen);
+
+    req = resolv_getsrv_send(test_ctx, test_ctx->ctx->ev,
+                             test_ctx->resolv, TEST_SRV_QUERY);
+    assert_non_null(req);
+    tevent_req_set_callback(req, test_resolv_fake_srv_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->ctx);
+    assert_int_equal(ret, ERR_OK);
+}
+
+int main(int argc, const char *argv[])
+{
+    int rv;
+    poptContext pc;
+    int opt;
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        SSSD_DEBUG_OPTS
+        POPT_TABLEEND
+    };
+
+    const UnitTest tests[] = {
+        unit_test_setup_teardown(test_resolv_fake_srv,
+                                 test_resolv_fake_setup,
+                                 test_resolv_fake_teardown),
+    };
+
+    /* Set debug level to invalid value so we can deside if -d 0 was used. */
+    debug_level = SSSDBG_INVALID;
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while((opt = poptGetNextOpt(pc)) != -1) {
+        switch(opt) {
+        default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                    poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            return 1;
+        }
+    }
+    poptFreeContext(pc);
+
+    DEBUG_CLI_INIT(debug_level);
+
+    /* Even though normally the tests should clean up after themselves
+     * they might not after a failed run. Remove the old db to be sure */
+    tests_set_cwd();
+
+    rv = run_tests(tests);
+    return rv;
+}
diff --git a/src/tests/resolv-tests.c b/src/tests/resolv-tests.c
index 30d551f636b73605ab3daabf5bdddf2c5884b5a2..6c6783b622b63d3fdb7144d59e8107fe5d705251 100644
--- a/src/tests/resolv-tests.c
+++ b/src/tests/resolv-tests.c
@@ -501,7 +501,7 @@ static void test_internet(struct tevent_req *req)
         break;
     case TESTING_SRV:
         recv_status = resolv_getsrv_recv(tmp_ctx, req, &status, NULL,
-                                         &srv_replies);
+                                         &srv_replies, NULL);
         test_ctx->error = (srv_replies == NULL) ? ENOENT : EOK;
         for (srvptr = srv_replies; srvptr != NULL; srvptr = srvptr->next) {
             DEBUG(SSSDBG_OP_FAILURE,
-- 
2.1.0



More information about the sssd-devel mailing list