@@ -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([
@@ -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
new file mode 100644
@@ -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);
+}
new file mode 100644
@@ -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 */
@@ -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);
@@ -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])])
@@ -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;
}
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