[ovs-dev] DNS: Add basic support for asynchronous DNS resolving

Message ID 1512396220-20636-1-git-send-email-pkusunyifeng@gmail.com
State Changes Requested
Headers show
Series
  • [ovs-dev] DNS: Add basic support for asynchronous DNS resolving
Related show

Commit Message

Yifeng Sun Dec. 4, 2017, 2:03 p.m.
This patch is a simple implementation for the proposal discussed in
https://mail.openvswitch.org/pipermail/ovs-dev/2017-August/337038.html and
https://mail.openvswitch.org/pipermail/ovs-dev/2017-October/340013.html.

It enables ovs-vswitchd to use DNS names when specifying OpenFlow and
OVSDB remotes.

Below are some of the features and limitations of this patch:
	The resolving is asynchornous, so it doesn't block the main loop;
	Both IPv4 and IPv6 are supported;
	The resolving API is thread-safe;
	Depends on the unbound library;
	When multiple ip addresses are returned, only the first one is used;
	/etc/nsswitch.conf isn't respected as unbound library doesn't look at it;
	Depends on caller to retry later to use the resolved results.

Signed-off-by: Yifeng Sun <pkusunyifeng@gmail.com>
---
 configure.ac      |   1 +
 lib/automake.mk   |   6 ++
 lib/dns-resolve.c | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/dns-resolve.h |  32 +++++++
 lib/socket-util.c |  57 +++++++++++--
 m4/openvswitch.m4 |  10 +++
 6 files changed, 341 insertions(+), 8 deletions(-)
 create mode 100644 lib/dns-resolve.c
 create mode 100644 lib/dns-resolve.h

Comments

Ben Pfaff Dec. 13, 2017, 7:02 p.m. | #1
On Mon, Dec 04, 2017 at 06:03:40AM -0800, Yifeng Sun wrote:
> This patch is a simple implementation for the proposal discussed in
> https://mail.openvswitch.org/pipermail/ovs-dev/2017-August/337038.html and
> https://mail.openvswitch.org/pipermail/ovs-dev/2017-October/340013.html.
> 
> It enables ovs-vswitchd to use DNS names when specifying OpenFlow and
> OVSDB remotes.
> 
> Below are some of the features and limitations of this patch:
> 	The resolving is asynchornous, so it doesn't block the main loop;
> 	Both IPv4 and IPv6 are supported;
> 	The resolving API is thread-safe;
> 	Depends on the unbound library;
> 	When multiple ip addresses are returned, only the first one is used;
> 	/etc/nsswitch.conf isn't respected as unbound library doesn't look at it;
> 	Depends on caller to retry later to use the resolved results.
> 
> Signed-off-by: Yifeng Sun <pkusunyifeng@gmail.com>

Thanks a lot!  DNS support will be a great feature for our users.

I have some comments.

Usually, OVS functions use one of two calling conventions for reporting
errors.  coding-style.rst describes them this way:

    For functions that return a success or failure indication, prefer
    one of the following return value conventions:

    - An ``int`` where 0 indicates success and a positive errno value
      indicates a reason for failure.

    - A ``bool`` where true indicates success and false indicates
      failure.

The convention used here for dns_resolve() is different.  I'd prefer to
see it adopt one of the above styles.

Usually, we use a .h file for high-level documentation of a particular
code module, and then document particular functions in the .c file.
Since this module has very few public functions, this convention is not
as important, but I would still like to see it followed.

Usually, we use OVS_LIKELY and OVS_UNLIKELY only in
performance-sensitive inner loops.  I don't think that any of the DNS
code qualifies.

I am concerned about what could happen if the unbound library cannot be
initialized.  In that case, I think that every call to dns_resolve()
will try to initialize it again.  That is likely to log error messages
at a high rate.  I suggest either rate-limiting the log messages (with
VLOG_ERR_RL, etc.) or changing the initialization code so that it only
tries to initialize once and then permanently fails.  My guess is that
the latter is a better choice in this case.

Probably, ub_process() error logging should also be rate-limited.  It is
also worth thinking about resolve_callback__()'s logging, to figure out
whether it is likely to be too voluminous in practice.

I think that resolve_find_or_new__() could be made very slightly simpler
by using shash_find_data().

xcalloc(1, x) can be written xzalloc(x).

In some of the dns_resolve() error cases, *addr is not set to NULL.
It's a better user interface if *addr is always set to NULL on error.

These days, we tend to try to declare and initialize variables at the
same point, so in dns_resolve() I would move the declarations of 'req'
and 'retval' down to their points of first use.

coding-style.rst says:

    Do parenthesize a subexpression that must be split across more than
    one line, e.g.::

        *idxp = ((l1_idx << PORT_ARRAY_L1_SHIFT) |
                 (l2_idx << PORT_ARRAY_L2_SHIFT) |
                 (l3_idx << PORT_ARRAY_L3_SHIFT));
so that:
    return req != NULL
        && req->state == RESOLVE_GOOD
        && !resolve_check_expire__(req);
should become:
    return (req != NULL
            && req->state == RESOLVE_GOOD
            && !resolve_check_expire__(req));

resolve_set_time__() seems simple enough to me that the code would be
clearer if it were just written inline instead of as a function.

I am not sure why the 'addr' member of struct resolve_request is marked
const.  I do not understand the benefit in this case.

I see better why the 'name' member of struct resolve_request is marked
const.  It is because it duplicates the name in the shash_node and
should not be separately freed.  But I think that it would be a cleaner
design in this case to use an hmap directly, by adding an hmap_node
member to struct resolve_request and then changing all_reqs__ from
struct shash to struct hmap.

I do not understand why all_reqs exists.  It seems that, each time it is
used, one could write &all_reqs__ instead.

Initialization in dns_resolve() and destruction in dns_resolve_destroy()
seems asymmetric in at least one way: dns_resolve_destroy() destroys
all_reqs, but dns_resolve_init__() does not initialize all_reqs.  I
believe that this means that calling dns_resolve_destroy(), then
resolving an address, will cause a segfault.  It is better to avoid that
potential problem.

In resolve_callback__(), the cast here is not needed:
    struct resolve_request *req;

    req = (struct resolve_request *)data;
I would write it as:
    struct resolve_request *req = data;

Similarly, in resolve_async__(), this cast is not needed either:
                                  qtype, ns_c_in, (void *)req,

Actually, common OVS style in a case like this is to give the parameter
a _ suffix, like this:

static void
resolve_callback__(void *req_, int err, struct ub_result *result)
    OVS_REQUIRES(dns_mutex__)
{
    struct resolve_request *req = req_;

Thanks,

Ben.
Yifeng Sun Dec. 13, 2017, 7:32 p.m. | #2
Thanks a lot Ben for your review. I will apply your advice in v2.

Yifeng

On Wed, Dec 13, 2017 at 11:02 AM, Ben Pfaff <blp@ovn.org> wrote:

> On Mon, Dec 04, 2017 at 06:03:40AM -0800, Yifeng Sun wrote:
> > This patch is a simple implementation for the proposal discussed in
> > https://mail.openvswitch.org/pipermail/ovs-dev/2017-August/337038.html
> and
> > https://mail.openvswitch.org/pipermail/ovs-dev/2017-October/340013.html.
> >
> > It enables ovs-vswitchd to use DNS names when specifying OpenFlow and
> > OVSDB remotes.
> >
> > Below are some of the features and limitations of this patch:
> >       The resolving is asynchornous, so it doesn't block the main loop;
> >       Both IPv4 and IPv6 are supported;
> >       The resolving API is thread-safe;
> >       Depends on the unbound library;
> >       When multiple ip addresses are returned, only the first one is
> used;
> >       /etc/nsswitch.conf isn't respected as unbound library doesn't look
> at it;
> >       Depends on caller to retry later to use the resolved results.
> >
> > Signed-off-by: Yifeng Sun <pkusunyifeng@gmail.com>
>
> Thanks a lot!  DNS support will be a great feature for our users.
>
> I have some comments.
>
> Usually, OVS functions use one of two calling conventions for reporting
> errors.  coding-style.rst describes them this way:
>
>     For functions that return a success or failure indication, prefer
>     one of the following return value conventions:
>
>     - An ``int`` where 0 indicates success and a positive errno value
>       indicates a reason for failure.
>
>     - A ``bool`` where true indicates success and false indicates
>       failure.
>
> The convention used here for dns_resolve() is different.  I'd prefer to
> see it adopt one of the above styles.
>
> Usually, we use a .h file for high-level documentation of a particular
> code module, and then document particular functions in the .c file.
> Since this module has very few public functions, this convention is not
> as important, but I would still like to see it followed.
>
> Usually, we use OVS_LIKELY and OVS_UNLIKELY only in
> performance-sensitive inner loops.  I don't think that any of the DNS
> code qualifies.
>
> I am concerned about what could happen if the unbound library cannot be
> initialized.  In that case, I think that every call to dns_resolve()
> will try to initialize it again.  That is likely to log error messages
> at a high rate.  I suggest either rate-limiting the log messages (with
> VLOG_ERR_RL, etc.) or changing the initialization code so that it only
> tries to initialize once and then permanently fails.  My guess is that
> the latter is a better choice in this case.
>
> Probably, ub_process() error logging should also be rate-limited.  It is
> also worth thinking about resolve_callback__()'s logging, to figure out
> whether it is likely to be too voluminous in practice.
>
> I think that resolve_find_or_new__() could be made very slightly simpler
> by using shash_find_data().
>
> xcalloc(1, x) can be written xzalloc(x).
>
> In some of the dns_resolve() error cases, *addr is not set to NULL.
> It's a better user interface if *addr is always set to NULL on error.
>
> These days, we tend to try to declare and initialize variables at the
> same point, so in dns_resolve() I would move the declarations of 'req'
> and 'retval' down to their points of first use.
>
> coding-style.rst says:
>
>     Do parenthesize a subexpression that must be split across more than
>     one line, e.g.::
>
>         *idxp = ((l1_idx << PORT_ARRAY_L1_SHIFT) |
>                  (l2_idx << PORT_ARRAY_L2_SHIFT) |
>                  (l3_idx << PORT_ARRAY_L3_SHIFT));
> so that:
>     return req != NULL
>         && req->state == RESOLVE_GOOD
>         && !resolve_check_expire__(req);
> should become:
>     return (req != NULL
>             && req->state == RESOLVE_GOOD
>             && !resolve_check_expire__(req));
>
> resolve_set_time__() seems simple enough to me that the code would be
> clearer if it were just written inline instead of as a function.
>
> I am not sure why the 'addr' member of struct resolve_request is marked
> const.  I do not understand the benefit in this case.
>
> I see better why the 'name' member of struct resolve_request is marked
> const.  It is because it duplicates the name in the shash_node and
> should not be separately freed.  But I think that it would be a cleaner
> design in this case to use an hmap directly, by adding an hmap_node
> member to struct resolve_request and then changing all_reqs__ from
> struct shash to struct hmap.
>
> I do not understand why all_reqs exists.  It seems that, each time it is
> used, one could write &all_reqs__ instead.
>
> Initialization in dns_resolve() and destruction in dns_resolve_destroy()
> seems asymmetric in at least one way: dns_resolve_destroy() destroys
> all_reqs, but dns_resolve_init__() does not initialize all_reqs.  I
> believe that this means that calling dns_resolve_destroy(), then
> resolving an address, will cause a segfault.  It is better to avoid that
> potential problem.
>
> In resolve_callback__(), the cast here is not needed:
>     struct resolve_request *req;
>
>     req = (struct resolve_request *)data;
> I would write it as:
>     struct resolve_request *req = data;
>
> Similarly, in resolve_async__(), this cast is not needed either:
>                                   qtype, ns_c_in, (void *)req,
>
> Actually, common OVS style in a case like this is to give the parameter
> a _ suffix, like this:
>
> static void
> resolve_callback__(void *req_, int err, struct ub_result *result)
>     OVS_REQUIRES(dns_mutex__)
> {
>     struct resolve_request *req = req_;
>
> Thanks,
>
> Ben.
>

Patch

diff --git a/configure.ac b/configure.ac
index 6a8113a5c58c..6a6ff57f1577 100644
--- a/configure.ac
+++ b/configure.ac
@@ -133,6 +133,7 @@  OVS_CHECK_LINUX_HOST
 OVS_LIBTOOL_VERSIONS
 OVS_CHECK_CXX
 AX_FUNC_POSIX_MEMALIGN
+OVS_CHECK_UNBOUND
 
 OVS_CHECK_INCLUDE_NEXT([stdio.h string.h])
 AC_CONFIG_FILES([
diff --git a/lib/automake.mk b/lib/automake.mk
index effe5b5c2940..1e86f5c409f5 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -438,6 +438,12 @@  else
 lib_libopenvswitch_la_SOURCES += lib/stream-nossl.c
 endif
 
+if HAVE_UNBOUND
+lib_libopenvswitch_la_SOURCES += \
+	lib/dns-resolve.h \
+	lib/dns-resolve.c
+endif
+
 pkgconfig_DATA += \
 	lib/libopenvswitch.pc \
 	lib/libsflow.pc
diff --git a/lib/dns-resolve.c b/lib/dns-resolve.c
new file mode 100644
index 000000000000..762a10b0d28b
--- /dev/null
+++ b/lib/dns-resolve.c
@@ -0,0 +1,243 @@ 
+/*
+ * Copyright (c) 2008, 2009, 2010, 2012, 2013, 2015, 2016, 2017 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <errno.h>
+#include <string.h>
+#include "dns-resolve.h"
+#include "timeval.h"
+#include "unbound.h"
+#include "openvswitch/shash.h"
+#include "openvswitch/vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(dns_resolve);
+
+/* Guard all_reqs and resolve_state of each request. */
+static struct ovs_mutex dns_mutex__ = OVS_MUTEX_INITIALIZER;
+static struct shash all_reqs__ = SHASH_INITIALIZER(&all_reqs__);
+static struct shash *const all_reqs = &all_reqs__;
+static struct ub_ctx *ub_ctx__;
+
+enum resolve_state {
+    RESOLVE_INVALID,
+    RESOLVE_PENDING,
+    RESOLVE_GOOD,
+    RESOLVE_ERROR
+};
+
+struct resolve_request {
+    const char *name;               /* the domain name to be resolved */
+    const char *addr;               /* the resolved ip address */
+    enum resolve_state state;       /* state of this request */
+    time_t time;                    /* resolving time */
+    struct ub_result *ub_result;    /* the stored unbound result */
+};
+
+static void
+resolve_callback__(void *data, int err, struct ub_result *result);
+
+static struct ub_ctx *
+dns_resolve_init__(void)
+{
+    struct ub_ctx *ub_ctx;
+    int retval;
+
+    ub_ctx = ub_ctx_create();
+    if (!ub_ctx) {
+        VLOG_ERR("Failed to create unbound context");
+        return NULL;
+    }
+
+#ifdef __linux__
+    retval = ub_ctx_resolvconf(ub_ctx, "/etc/resolv.conf");
+    if (retval != 0) {
+        VLOG_WARN("Failed to read /etc/resolv.conf: %s", ub_strerror(retval));
+    }
+#endif
+
+    /* Handle '/etc/hosts' on Linux and 'WINDIR/etc/hosts' on Windows. */
+    retval = ub_ctx_hosts(ub_ctx, NULL);
+    if (retval != 0) {
+        VLOG_WARN("Failed to read etc/hosts: %s", ub_strerror(retval));
+    }
+
+    ub_ctx_async(ub_ctx, true);
+    return ub_ctx;
+}
+
+static struct resolve_request *
+resolve_find_or_new__(const char *name)
+    OVS_REQUIRES(dns_mutex__)
+{
+    struct shash_node *node;
+
+    node = shash_find(all_reqs, name);
+    if (node != NULL) {
+        return node->data;
+    } else {
+        struct resolve_request *req;
+        req = xcalloc(1, sizeof *req);
+        req->name = xstrdup(name);
+        req->state = RESOLVE_INVALID;
+        shash_add_nocopy(all_reqs, CONST_CAST(char *, req->name), req);
+        return req;
+    }
+}
+
+static bool
+resolve_check_expire__(struct resolve_request *req)
+    OVS_REQUIRES(dns_mutex__)
+{
+    return time_now() > req->time + req->ub_result->ttl;
+}
+
+static bool
+resolve_check_valid__(struct resolve_request *req)
+    OVS_REQUIRES(dns_mutex__)
+{
+    return req != NULL
+        && req->state == RESOLVE_GOOD
+        && !resolve_check_expire__(req);
+}
+
+static void
+resolve_set_time__(struct resolve_request *req)
+    OVS_REQUIRES(dns_mutex__)
+{
+    req->time = time_now();
+}
+
+static int
+resolve_async__(struct resolve_request *req, int qtype)
+    OVS_REQUIRES(dns_mutex__)
+{
+    if (qtype == ns_t_a || qtype == ns_t_aaaa) {
+        int retval;
+        retval = ub_resolve_async(ub_ctx__, req->name,
+                                  qtype, ns_c_in, (void *)req,
+                                  resolve_callback__, NULL);
+        if (retval != 0) {
+            req->state = RESOLVE_ERROR;
+            return -1;
+        } else {
+            req->state = RESOLVE_PENDING;
+            return 0;
+        }
+    }
+    return -1;
+}
+
+static void
+resolve_callback__(void *data, int err, struct ub_result *result)
+    OVS_REQUIRES(dns_mutex__)
+{
+    struct resolve_request *req;
+
+    req = (struct resolve_request *)data;
+
+    if (err || (result->qtype == ns_t_aaaa && !result->havedata)) {
+        req->state = RESOLVE_ERROR;
+        goto bad;
+    }
+
+    /* IPv4 address is empty, try IPv6. */
+    if (result->qtype == ns_t_a && !result->havedata) {
+        ub_resolve_free(result);
+        resolve_async__(req, ns_t_aaaa);
+        return;
+    }
+
+    char buffer[INET6_ADDRSTRLEN];
+    int af = result->qtype == ns_t_a ? AF_INET : AF_INET6;
+    if (!inet_ntop(af, result->data[0], buffer, sizeof(buffer))) {
+        req->state = RESOLVE_ERROR;
+        goto bad;
+    }
+
+    ub_resolve_free(req->ub_result);
+    free(CONST_CAST(char *, req->addr));
+
+    req->ub_result = result;
+    req->addr = xstrdup(buffer);
+    req->state = RESOLVE_GOOD;
+    VLOG_INFO("%s: resolved to %s", req->name, req->addr);
+    return;
+
+bad:
+    VLOG_ERR("%s: failed to resolve", req->name);
+    ub_resolve_free(result);
+}
+
+int
+dns_resolve(const char *name, char **addr)
+    OVS_EXCLUDED(dns_mutex__)
+{
+    struct resolve_request *req;
+    int retval;
+    int err = -1;
+
+    ovs_mutex_lock(&dns_mutex__);
+
+    if (OVS_UNLIKELY(ub_ctx__ == NULL)) {
+        ub_ctx__ = dns_resolve_init__();
+        if (ub_ctx__ == NULL) {
+            goto unlock;
+        }
+    }
+
+    retval = ub_process(ub_ctx__);
+    if (retval) {
+        VLOG_ERR("%s: resolve error: %s", name, ub_strerror(retval));
+        goto unlock;
+    }
+
+    *addr = NULL;
+
+    req = resolve_find_or_new__(name);
+    if (resolve_check_valid__(req)) {
+        *addr = xstrdup(req->addr);
+        err = 0;
+    } else if (req->state != RESOLVE_PENDING) {
+        resolve_set_time__(req);
+        err = resolve_async__(req, ns_t_a);
+    }
+unlock:
+    ovs_mutex_unlock(&dns_mutex__);
+    return err;
+}
+
+void
+dns_resolve_destroy(void)
+{
+    if (ub_ctx__) {
+        /* Outstanding requests will be killed. */
+        ub_ctx_delete(ub_ctx__);
+        ub_ctx__ = NULL;
+
+        struct shash_node *node;
+        SHASH_FOR_EACH (node, all_reqs) {
+            struct resolve_request *req;
+            req = (struct resolve_request *)node->data;
+            ub_resolve_free(req->ub_result);
+            free(CONST_CAST(char *, req->addr));
+            free(CONST_CAST(char *, req->name));
+            free(req);
+        }
+        shash_destroy(all_reqs);
+    }
+}
diff --git a/lib/dns-resolve.h b/lib/dns-resolve.h
new file mode 100644
index 000000000000..1619ff6434af
--- /dev/null
+++ b/lib/dns-resolve.h
@@ -0,0 +1,32 @@ 
+/*
+ * Copyright (c) 2008, 2009, 2010, 2012, 2013, 2015, 2016, 2017 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef DNS_RESOLVE_H
+#define DNS_RESOLVE_H 1
+
+/* Returns 0 on success. If something goes wrong, returns -1 and the detailed
+ * information can be found in logs. When 0 is returned but *addr is NULL, it
+ * means that the resolving is in progress and the caller should recall later.
+ *
+ * This function is thread-safe.
+ *
+ * The caller is responsible for freeing the returned '*addr'.
+ */
+int dns_resolve(const char *name, char **addr);
+
+void dns_resolve_destroy(void);
+
+#endif
diff --git a/lib/socket-util.c b/lib/socket-util.c
index 055593d3572e..47164e92f6a7 100644
--- a/lib/socket-util.c
+++ b/lib/socket-util.c
@@ -46,11 +46,20 @@ 
 #include "netlink-protocol.h"
 #include "netlink-socket.h"
 #endif
+#ifdef HAVE_UNBOUND
+#include "dns-resolve.h"
+#endif
 
 VLOG_DEFINE_THIS_MODULE(socket_util);
 
 static int getsockopt_int(int fd, int level, int option, const char *optname,
                           int *valuep);
+static bool
+parse_sockaddr_components(struct sockaddr_storage *ss,
+                          char *host_s,
+                          const char *port_s, uint16_t default_port,
+                          const char *s,
+                          bool resolve_host);
 
 /* Sets 'fd' to non-blocking mode.  Returns 0 if successful, otherwise a
  * positive errno value. */
@@ -365,10 +374,35 @@  inet_parse_token(char **pp)
 }
 
 static bool
+parse_sockaddr_components_dns(struct sockaddr_storage *ss OVS_UNUSED,
+                              char *host_s,
+                              const char *port_s OVS_UNUSED,
+                              uint16_t default_port OVS_UNUSED,
+                              const char *s OVS_UNUSED)
+{
+    bool ret = false;
+#ifdef HAVE_UNBOUND
+    char *tmp_host_s;
+    int err;
+
+    err = dns_resolve(host_s, &tmp_host_s);
+    if (!err && tmp_host_s) {
+        ret = parse_sockaddr_components(ss, tmp_host_s, port_s,
+                                         default_port, s, false);
+        free(tmp_host_s);
+    }
+#else
+    VLOG_WARN("can't resolve \"%s\" without unbound library", host_s);
+#endif
+    return ret;
+}
+
+static bool
 parse_sockaddr_components(struct sockaddr_storage *ss,
                           char *host_s,
                           const char *port_s, uint16_t default_port,
-                          const char *s)
+                          const char *s,
+                          bool resolve_host)
 {
     struct sockaddr_in *sin = ALIGNED_CAST(struct sockaddr_in *, ss);
     int port;
@@ -392,8 +426,7 @@  parse_sockaddr_components(struct sockaddr_storage *ss,
         sin6->sin6_family = AF_INET6;
         sin6->sin6_port = htons(port);
         if (!addr || !*addr || !ipv6_parse(addr, &sin6->sin6_addr)) {
-            VLOG_ERR("%s: bad IPv6 address \"%s\"", s, addr ? addr : "");
-            goto exit;
+            goto resolve;
         }
 
 #ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
@@ -406,7 +439,7 @@  parse_sockaddr_components(struct sockaddr_storage *ss,
                 if (!sin6->sin6_scope_id) {
                     VLOG_ERR("%s: bad IPv6 scope \"%s\" (%s)",
                              s, scope, ovs_strerror(errno));
-                    goto exit;
+                    goto resolve;
                 }
             }
         }
@@ -415,13 +448,19 @@  parse_sockaddr_components(struct sockaddr_storage *ss,
         sin->sin_family = AF_INET;
         sin->sin_port = htons(port);
         if (host_s && !ip_parse(host_s, &sin->sin_addr.s_addr)) {
-            VLOG_ERR("%s: bad IPv4 address \"%s\"", s, host_s);
-            goto exit;
+            goto resolve;
         }
     }
 
     return true;
 
+resolve:
+    if (resolve_host && parse_sockaddr_components_dns(ss, host_s, port_s,
+                                                      default_port, s)) {
+        return true;
+    } else if (!resolve_host) {
+        VLOG_ERR("%s: bad IP address \"%s\"", s, host_s);
+    }
 exit:
     memset(ss, 0, sizeof *ss);
     return false;
@@ -454,7 +493,8 @@  inet_parse_active(const char *target_, uint16_t default_port,
         VLOG_ERR("%s: port must be specified", target_);
         ok = false;
     } else {
-        ok = parse_sockaddr_components(ss, host, port, default_port, target_);
+        ok = parse_sockaddr_components(ss, host, port, default_port,
+                                       target_, true);
     }
     if (!ok) {
         memset(ss, 0, sizeof *ss);
@@ -578,7 +618,8 @@  inet_parse_passive(const char *target_, int default_port,
         VLOG_ERR("%s: port must be specified", target_);
         ok = false;
     } else {
-        ok = parse_sockaddr_components(ss, host, port, default_port, target_);
+        ok = parse_sockaddr_components(ss, host, port, default_port,
+                                       target_, true);
     }
     if (!ok) {
         memset(ss, 0, sizeof *ss);
diff --git a/m4/openvswitch.m4 b/m4/openvswitch.m4
index de4d66ccb2db..312a0a49dd50 100644
--- a/m4/openvswitch.m4
+++ b/m4/openvswitch.m4
@@ -643,3 +643,13 @@  AC_DEFUN([OVS_CHECK_CXX],
      enable_cxx=false
    fi
    AM_CONDITIONAL([HAVE_CXX], [$enable_cxx])])
+
+dnl Checks for unbound library.
+AC_DEFUN([OVS_CHECK_UNBOUND],
+  [AC_CHECK_LIB(unbound, ub_ctx_create, [HAVE_UNBOUND=yes])
+   if test "$HAVE_UNBOUND" = yes; then
+     AC_DEFINE([HAVE_UNBOUND], [1], [Define to 1 if unbound is detected.])
+     LIBS="$LIBS -lunbound"
+   fi
+   AM_CONDITIONAL([HAVE_UNBOUND], [test "$HAVE_UNBOUND" = yes])
+   AC_SUBST([HAVE_UNBOUND])])