diff mbox series

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

Message ID 1513688922-9560-1-git-send-email-pkusunyifeng@gmail.com
State Changes Requested
Headers show
Series [ovs-dev,PATCHv3] DNS: Add basic support for asynchronous DNS resolving | expand

Commit Message

Yifeng Sun Dec. 19, 2017, 1:08 p.m. UTC
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.

v1 -> v2: refactored and improved code based on reviewer's comments.
v2 -> v3: add commit message.

Signed-off-by: Yifeng Sun <pkusunyifeng@gmail.com>
---
 configure.ac            |   1 +
 lib/automake.mk         |   6 ++
 lib/dns-resolve.c       | 244 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/dns-resolve.h       |  26 ++++++
 lib/socket-util.c       |  56 +++++++++--
 m4/openvswitch.m4       |  10 ++
 vswitchd/ovs-vswitchd.c |   9 ++
 7 files changed, 344 insertions(+), 8 deletions(-)
 create mode 100644 lib/dns-resolve.c
 create mode 100644 lib/dns-resolve.h

Comments

Ben Pfaff Dec. 20, 2017, 5:32 p.m. UTC | #1
On Tue, Dec 19, 2017 at 05:08:42AM -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.
> 
> v1 -> v2: refactored and improved code based on reviewer's comments.
> v2 -> v3: add commit message.
> 
> Signed-off-by: Yifeng Sun <pkusunyifeng@gmail.com>

Thanks for v3!  I think that many users are looking forward to DNS
support.

Thanks for including a change log (e.g. v1->v2, ...).  However, the
change log should go after the --- line in the email; otherwise, "git
am" puts it into the commit message (and it should not be in there).
You can do that by putting --- into your commit message, or by adding
the change log manually within the editor invoked by "git send-email
--annotate".

This commit should update the documentation.  I imagine that in several
places the documentation says that only IP addresses are supported, and
that should now be fixed.

This commit should add a NEWS entry.

This commit should update the build instructions to describe the new
optional dependency.

This commit should add the proper dependencies to the Debian, RHEL, and
Fedora packaging.

The copyright notices in the new files look wrong to me.  This code is
new this year and as far as I know it is not derived from any older
code.  If so, then that means that the copyright year should be 2017 and
not any other years.

The dns-resolve code uses a strategy of making the client understand at
compile time whether the resolver is available, using an #ifdef.  This
is usually not the strategy that we use in Open vSwitch, because it
means that the knowledge whether a feature is available has to be spread
throughout the code.  Instead, we try to centralize it in a single
location to the extent practical, by writing appropriate stubs that do
nothing or return an error when the feature is not available.  You can
see a few examples of this strategy in lib/async-append*.[ch],
lib/route-table*.[ch], and lib/if-notifier*.[ch].  We usually find that
this makes code easier to understand and maintain.

Please consider the un-ratelimited log messages that this commit adds.
I think that some of them could flood the logs if they triggered.  If
so, please ratelimit them.

Thanks,

Ben.
Yifeng Sun Dec. 20, 2017, 6:04 p.m. UTC | #2
Hi Ben,

Thank you very much for the code review, I will fix and come up with v4
soon.

Best,
Yifeng

On Wed, Dec 20, 2017 at 9:32 AM, Ben Pfaff <blp@ovn.org> wrote:

> On Tue, Dec 19, 2017 at 05:08:42AM -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.
> >
> > v1 -> v2: refactored and improved code based on reviewer's comments.
> > v2 -> v3: add commit message.
> >
> > Signed-off-by: Yifeng Sun <pkusunyifeng@gmail.com>
>
> Thanks for v3!  I think that many users are looking forward to DNS
> support.
>
> Thanks for including a change log (e.g. v1->v2, ...).  However, the
> change log should go after the --- line in the email; otherwise, "git
> am" puts it into the commit message (and it should not be in there).
> You can do that by putting --- into your commit message, or by adding
> the change log manually within the editor invoked by "git send-email
> --annotate".
>
> This commit should update the documentation.  I imagine that in several
> places the documentation says that only IP addresses are supported, and
> that should now be fixed.
>
> This commit should add a NEWS entry.
>
> This commit should update the build instructions to describe the new
> optional dependency.
>
> This commit should add the proper dependencies to the Debian, RHEL, and
> Fedora packaging.
>
> The copyright notices in the new files look wrong to me.  This code is
> new this year and as far as I know it is not derived from any older
> code.  If so, then that means that the copyright year should be 2017 and
> not any other years.
>
> The dns-resolve code uses a strategy of making the client understand at
> compile time whether the resolver is available, using an #ifdef.  This
> is usually not the strategy that we use in Open vSwitch, because it
> means that the knowledge whether a feature is available has to be spread
> throughout the code.  Instead, we try to centralize it in a single
> location to the extent practical, by writing appropriate stubs that do
> nothing or return an error when the feature is not available.  You can
> see a few examples of this strategy in lib/async-append*.[ch],
> lib/route-table*.[ch], and lib/if-notifier*.[ch].  We usually find that
> this makes code easier to understand and maintain.
>
> Please consider the un-ratelimited log messages that this commit adds.
> I think that some of them could flood the logs if they triggered.  If
> so, please ratelimit them.
>
> Thanks,
>
> Ben.
>
diff mbox series

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..401dc5c8f126
--- /dev/null
+++ b/lib/dns-resolve.c
@@ -0,0 +1,244 @@ 
+/*
+ * 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 "dns-resolve.h"
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <errno.h>
+#include <string.h>
+#include <unbound.h>
+#include "hash.h"
+#include "openvswitch/hmap.h"
+#include "openvswitch/vlog.h"
+#include "timeval.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 hmap all_reqs__;
+static struct ub_ctx *ub_ctx__;
+
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+
+enum resolve_state {
+    RESOLVE_INVALID,
+    RESOLVE_PENDING,
+    RESOLVE_GOOD,
+    RESOLVE_ERROR
+};
+
+struct resolve_request {
+    struct hmap_node hmap_node;     /* node for all_reqs__ */
+    char *name;                     /* the domain name to be resolved */
+    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 struct resolve_request *resolve_find_or_new__(const char *name)
+    OVS_REQUIRES(dns_mutex__);
+static bool resolve_check_expire__(struct resolve_request *req)
+    OVS_REQUIRES(dns_mutex__);
+static bool resolve_check_valid__(struct resolve_request *req)
+    OVS_REQUIRES(dns_mutex__);
+static bool resolve_async__(struct resolve_request *req, int qtype)
+    OVS_REQUIRES(dns_mutex__);
+static void resolve_callback__(void *req, int err, struct ub_result *)
+    OVS_REQUIRES(dns_mutex__);
+
+void
+dns_resolve_init(void)
+{
+    ub_ctx__ = ub_ctx_create();
+    if (ub_ctx__ == NULL) {
+        VLOG_ERR("Failed to create libunbound context, "
+        "so asynchronous DNS resolving is disabled.");
+        return;
+    }
+
+    int retval;
+#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
+
+    /* Handles '/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);
+    hmap_init(&all_reqs__);
+}
+
+/* Returns true on success. Otherwise, returns false and the error information
+ * can be found in logs. If there is no error information, then the resolving
+ * is in process and the caller should call again later. The value of '*addr'
+ * is always nullified if false is returned.
+ *
+ * This function is thread-safe.
+ *
+ * The caller is responsible for freeing the returned '*addr'.
+ */
+bool
+dns_resolve(const char *name, char **addr)
+    OVS_EXCLUDED(dns_mutex__)
+{
+    bool success = false;
+
+    *addr = NULL;
+    ovs_mutex_lock(&dns_mutex__);
+
+    /* ub_process is inside lock because it invokes resolve_callback__. */
+    int retval = ub_process(ub_ctx__);
+    if (retval != 0) {
+        VLOG_ERR_RL(&rl, "dns-resolve error: %s", ub_strerror(retval));
+        goto unlock;
+    }
+
+    struct resolve_request *req;
+    req = resolve_find_or_new__(name);
+    if (resolve_check_valid__(req)) {
+        *addr = xstrdup(req->addr);
+        success = true;
+    } else if (req->state != RESOLVE_PENDING) {
+        req->time = time_now();
+        success = resolve_async__(req, ns_t_a);
+    }
+unlock:
+    ovs_mutex_unlock(&dns_mutex__);
+    return success;
+}
+
+void
+dns_resolve_destroy(void)
+{
+    if (ub_ctx__ != NULL) {
+        /* Outstanding requests will be killed. */
+        ub_ctx_delete(ub_ctx__);
+        ub_ctx__ = NULL;
+
+        struct resolve_request *req;
+        HMAP_FOR_EACH (req, hmap_node, &all_reqs__) {
+            ub_resolve_free(req->ub_result);
+            free(req->addr);
+            free(req->name);
+            free(req);
+        }
+        hmap_destroy(&all_reqs__);
+    }
+}
+
+static struct resolve_request *
+resolve_find_or_new__(const char *name)
+    OVS_REQUIRES(dns_mutex__)
+{
+    struct resolve_request *req;
+
+    HMAP_FOR_EACH_IN_BUCKET (req, hmap_node, hash_string(name, 0),
+                            &all_reqs__) {
+        if (!strcmp(name, req->name)) {
+            return req;
+        }
+    }
+
+    req = xzalloc(sizeof *req);
+    req->name = xstrdup(name);
+    req->state = RESOLVE_INVALID;
+    hmap_insert(&all_reqs__, &req->hmap_node, hash_string(req->name, 0));
+    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 bool
+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, req,
+                                  resolve_callback__, NULL);
+        if (retval != 0) {
+            req->state = RESOLVE_ERROR;
+            return false;
+        } else {
+            req->state = RESOLVE_PENDING;
+            return true;
+        }
+    }
+    return false;
+}
+
+static void
+resolve_callback__(void *req_, int err, struct ub_result *result)
+    OVS_REQUIRES(dns_mutex__)
+{
+    struct resolve_request *req = req_;
+
+    if (err != 0 || (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(req->addr);
+
+    req->ub_result = result;
+    req->addr = xstrdup(buffer);
+    req->state = RESOLVE_GOOD;
+    VLOG_DBG("%s: resolved to %s", req->name, req->addr);
+    return;
+
+bad:
+    VLOG_ERR_RL(&rl, "%s: failed to resolve", req->name);
+    ub_resolve_free(result);
+}
diff --git a/lib/dns-resolve.h b/lib/dns-resolve.h
new file mode 100644
index 000000000000..95bc4e6bb9cb
--- /dev/null
+++ b/lib/dns-resolve.h
@@ -0,0 +1,26 @@ 
+/*
+ * 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
+
+#include <stdbool.h>
+
+void dns_resolve_init(void);
+bool dns_resolve(const char *name, char **addr);
+void dns_resolve_destroy(void);
+
+#endif /* dns-resolve.h */
diff --git a/lib/socket-util.c b/lib/socket-util.c
index 055593d3572e..d8812316b7ff 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,34 @@  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;
+
+    dns_resolve(host_s, &tmp_host_s);
+    if (tmp_host_s != NULL) {
+        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 +425,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 +438,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 +447,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 +492,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 +617,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])])
diff --git a/vswitchd/ovs-vswitchd.c b/vswitchd/ovs-vswitchd.c
index d5e07c0376cd..1c11111f30d5 100644
--- a/vswitchd/ovs-vswitchd.c
+++ b/vswitchd/ovs-vswitchd.c
@@ -48,6 +48,9 @@ 
 #include "openvswitch/vconn.h"
 #include "openvswitch/vlog.h"
 #include "lib/vswitch-idl.h"
+#ifdef HAVE_UNBOUND
+#include "lib/dns-resolve.h"
+#endif
 
 VLOG_DEFINE_THIS_MODULE(vswitchd);
 
@@ -77,6 +80,9 @@  main(int argc, char *argv[])
 
     set_program_name(argv[0]);
 
+#ifdef HAVE_UNBOUND
+    dns_resolve_init();
+#endif
     ovs_cmdl_proctitle_init(argc, argv);
     service_start(&argc, &argv);
     remote = parse_options(argc, argv, &unixctl_path);
@@ -135,6 +141,9 @@  main(int argc, char *argv[])
     bridge_exit(cleanup);
     unixctl_server_destroy(unixctl);
     service_stop();
+#ifdef HAVE_UNBOUND
+    dns_resolve_destroy();
+#endif
 
     return 0;
 }