diff mbox series

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

Message ID 1513273361-17894-1-git-send-email-pkusunyifeng@gmail.com
State Superseded
Headers show
Series [ovs-dev,PATCHv2] DNS: Add basic support for asynchronous DNS resolving | expand

Commit Message

Yifeng Sun Dec. 14, 2017, 5:42 p.m. UTC
v1 -> v2: refactored and improved code based on reviewer's comments.

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
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;
 }