summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorYifeng Sun <pkusunyifeng@gmail.com>2018-06-26 14:06:21 -0700
committerBen Pfaff <blp@ovn.org>2018-07-06 13:50:00 -0700
commit771680d96fb6f996987630cb85bb749478512268 (patch)
treee3fddb4c770f88e405fd82787933216fc5b1ac58 /lib
parentdef5b366a3626fddc87a449e8447f79a6957d55f (diff)
downloadopenvswitch-771680d96fb6f996987630cb85bb749478512268.tar.gz
DNS: Add basic support for asynchronous DNS resolving
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 and other utilities to use DNS names when specifying OpenFlow and OVSDB remotes. Below are some of the features and limitations of this patch: - Resolving is asynchornous in daemon context, avoiding blocking main loop; - Resolving is synchronous in general utility context; - 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; - For async-resolving, caller need to retry later; there is no callback. Signed-off-by: Yifeng Sun <pkusunyifeng@gmail.com> Signed-off-by: Ben Pfaff <blp@ovn.org>
Diffstat (limited to 'lib')
-rw-r--r--lib/automake.mk7
-rw-r--r--lib/dns-resolve-stub.c36
-rw-r--r--lib/dns-resolve.c310
-rw-r--r--lib/dns-resolve.h26
-rw-r--r--lib/socket-util.c48
-rw-r--r--lib/stream.c8
-rw-r--r--lib/vconn-active.man14
-rw-r--r--lib/vconn-passive.man10
-rw-r--r--lib/vconn.c8
9 files changed, 440 insertions, 27 deletions
diff --git a/lib/automake.mk b/lib/automake.mk
index 3bec3fd56..fb43aa141 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -465,6 +465,13 @@ else
lib_libopenvswitch_la_SOURCES += lib/stream-nossl.c
endif
+lib_libopenvswitch_la_SOURCES += lib/dns-resolve.h
+if HAVE_UNBOUND
+lib_libopenvswitch_la_SOURCES += lib/dns-resolve.c
+else
+lib_libopenvswitch_la_SOURCES += lib/dns-resolve-stub.c
+endif
+
pkgconfig_DATA += \
lib/libopenvswitch.pc \
lib/libsflow.pc
diff --git a/lib/dns-resolve-stub.c b/lib/dns-resolve-stub.c
new file mode 100644
index 000000000..edf8337be
--- /dev/null
+++ b/lib/dns-resolve-stub.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2017, 2018 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 "compiler.h"
+
+void
+dns_resolve_init(void)
+{
+}
+
+bool
+dns_resolve(const char *name OVS_UNUSED, char **addr)
+{
+ *addr = NULL;
+ return false;
+}
+
+void
+dns_resolve_destroy(void)
+{
+}
diff --git a/lib/dns-resolve.c b/lib/dns-resolve.c
new file mode 100644
index 000000000..f1f911296
--- /dev/null
+++ b/lib/dns-resolve.c
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2017, 2018 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 <sys/types.h>
+#include <netinet/in.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 bool thread_is_daemon;
+
+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__);
+static bool resolve_result_to_addr__(struct ub_result *result, char **addr);
+static bool dns_resolve_sync__(const char *name, char **addr);
+
+/* Pass a true 'is_daemon' if you don't want the DNS-resolving to block the
+ * running thread.
+ */
+void
+dns_resolve_init(bool is_daemon)
+{
+ ub_ctx__ = ub_ctx_create();
+ if (ub_ctx__ == NULL) {
+ VLOG_ERR_RL(&rl, "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_RL(&rl, "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_RL(&rl, "Failed to read etc/hosts: %s",
+ ub_strerror(retval));
+ }
+
+ ub_ctx_async(ub_ctx__, true);
+ hmap_init(&all_reqs__);
+ thread_is_daemon = is_daemon;
+}
+
+/* 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. If this function is called under
+ * daemon-context, the resolving will undergo asynchronously. Otherwise, a
+ * synchronouse resolving will take place.
+ *
+ * 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;
+
+ if (!thread_is_daemon) {
+ return dns_resolve_sync__(name, addr);
+ }
+
+ *addr = NULL;
+ ovs_mutex_lock(&dns_mutex__);
+
+ if (ub_ctx__ == NULL) {
+ goto unlock;
+ }
+
+ /* ub_process is inside lock as 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) {
+ 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;
+ VLOG_ERR_RL(&rl, "%s: failed to resolve", req->name);
+ return;
+ }
+
+ /* 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 *addr;
+ if (!resolve_result_to_addr__(result, &addr)) {
+ req->state = RESOLVE_ERROR;
+ VLOG_ERR_RL(&rl, "%s: failed to resolve", req->name);
+ return;
+ }
+
+ ub_resolve_free(req->ub_result);
+ free(req->addr);
+
+ req->ub_result = result;
+ req->addr = addr;
+ req->state = RESOLVE_GOOD;
+ req->time = time_now();
+}
+
+static bool
+resolve_result_to_addr__(struct ub_result *result, char **addr)
+{
+ int af = result->qtype == ns_t_a ? AF_INET : AF_INET6;
+ char buffer[INET6_ADDRSTRLEN];
+
+ /* XXX: only the first returned IP is used. */
+ if (inet_ntop(af, result->data[0], buffer, sizeof buffer)) {
+ *addr = xstrdup(buffer);
+ } else {
+ *addr = NULL;
+ }
+
+ return (*addr != NULL);
+}
+
+static bool
+dns_resolve_sync__(const char *name, char **addr)
+{
+ *addr = NULL;
+
+ if (ub_ctx__ == NULL) {
+ dns_resolve_init(false);
+ if (ub_ctx__ == NULL) {
+ return false;
+ }
+ }
+
+ struct ub_result *result;
+ int retval = ub_resolve(ub_ctx__, name, ns_t_a, ns_c_in, &result);
+ if (retval != 0) {
+ return false;
+ } else if (!result->havedata) {
+ ub_resolve_free(result);
+
+ retval = ub_resolve(ub_ctx__, name, ns_t_aaaa, ns_c_in, &result);
+ if (retval != 0) {
+ return false;
+ } else if (!result->havedata) {
+ ub_resolve_free(result);
+ return false;
+ }
+ }
+
+ bool success = resolve_result_to_addr__(result, addr);
+ ub_resolve_free(result);
+ return success;
+}
diff --git a/lib/dns-resolve.h b/lib/dns-resolve.h
new file mode 100644
index 000000000..9cfa366a5
--- /dev/null
+++ b/lib/dns-resolve.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2017, 2018 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(bool is_daemon);
+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 c960d62d7..504f4cd59 100644
--- a/lib/socket-util.c
+++ b/lib/socket-util.c
@@ -48,6 +48,7 @@
#include "netlink-protocol.h"
#include "netlink-socket.h"
#endif
+#include "dns-resolve.h"
VLOG_DEFINE_THIS_MODULE(socket_util);
@@ -56,6 +57,12 @@ static int getsockopt_int(int fd, int level, int option, const char *optname,
static struct sockaddr_in *sin_cast(const struct sockaddr *);
static struct sockaddr_in6 *sin6_cast(const struct sockaddr *);
static const struct sockaddr *sa_cast(const struct sockaddr_storage *);
+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. */
@@ -420,10 +427,30 @@ inet_parse_port_host_tokens(char *s, char **portp, char **hostp)
}
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)
+{
+ char *tmp_host_s;
+
+ dns_resolve(host_s, &tmp_host_s);
+ if (tmp_host_s != NULL) {
+ parse_sockaddr_components(ss, tmp_host_s, port_s,
+ default_port, s, false);
+ free(tmp_host_s);
+ return true;
+ }
+ return false;
+}
+
+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 = sin_cast(sa_cast(ss));
int port;
@@ -445,7 +472,6 @@ 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;
}
@@ -468,13 +494,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;
@@ -505,7 +537,8 @@ inet_parse_active(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);
@@ -625,7 +658,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);
@@ -747,7 +781,7 @@ inet_parse_address(const char *target_, struct sockaddr_storage *ss)
{
char *target = xstrdup(target_);
char *host = unbracket(target);
- bool ok = parse_sockaddr_components(ss, host, NULL, 0, target_);
+ bool ok = parse_sockaddr_components(ss, host, NULL, 0, target_, false);
if (!ok) {
memset(ss, 0, sizeof *ss);
}
diff --git a/lib/stream.c b/lib/stream.c
index 63c592344..4e15fe0c8 100644
--- a/lib/stream.c
+++ b/lib/stream.c
@@ -126,11 +126,11 @@ stream_usage(const char *name, bool active, bool passive,
printf("\n");
if (active) {
printf("Active %s connection methods:\n", name);
- printf(" tcp:IP:PORT "
- "PORT at remote IP\n");
+ printf(" tcp:HOST:PORT "
+ "PORT at remote HOST\n");
#ifdef HAVE_OPENSSL
- printf(" ssl:IP:PORT "
- "SSL PORT at remote IP\n");
+ printf(" ssl:HOST:PORT "
+ "SSL PORT at remote HOST\n");
#endif
printf(" unix:FILE "
"Unix domain socket named FILE\n");
diff --git a/lib/vconn-active.man b/lib/vconn-active.man
index 395879c8a..9438ac079 100644
--- a/lib/vconn-active.man
+++ b/lib/vconn-active.man
@@ -1,10 +1,10 @@
-.IP "\fBssl:\fIip\fR[\fB:\fIport\fR]"
-.IQ "\fBtcp:\fIip\fR[\fB:\fIport\fR]"
-The specified \fIport\fR on the host at the given \fIip\fR, which must
-be expressed as an IP address (not a DNS name) in IPv4 or IPv6 address
-format. Wrap IPv6 addresses in square brackets,
-e.g. \fBtcp:[::1]:6653\fR. On Linux, use \fB%\fIdevice\fR to
-designate a scope for IPv6 link-level addresses,
+.IP "\fBssl:\fIhost\fR[\fB:\fIport\fR]"
+.IQ "\fBtcp:\fIhost\fR[\fB:\fIport\fR]"
+The specified \fIport\fR on the given \fIhost\fR, which can
+be expressed either as a DNS name (if built with unbound library)
+or an IP address in IPv4 or IPv6 address format. Wrap IPv6 addresses
+in square brackets, e.g. \fBtcp:[::1]:6653\fR. On Linux, use
+\fB%\fIdevice\fR to designate a scope for IPv6 link-level addresses,
e.g. \fBtcp:[fe80::1234%eth0]:6653\fR. For \fBssl\fR, the
\fB\-\-private\-key\fR, \fB\-\-certificate\fR, and \fB\-\-ca\-cert\fR
options are mandatory.
diff --git a/lib/vconn-passive.man b/lib/vconn-passive.man
index 1ffa18397..d02e75f81 100644
--- a/lib/vconn-passive.man
+++ b/lib/vconn-passive.man
@@ -1,12 +1,12 @@
-.IP "\fBpssl:\fR[\fIport\fR][\fB:\fIip\fR]"
-.IQ "\fBptcp:\fR[\fIport\fR][\fB:\fIip\fR]"
+.IP "\fBpssl:\fR[\fIport\fR][\fB:\fIhost\fR]"
+.IQ "\fBptcp:\fR[\fIport\fR][\fB:\fIhost\fR]"
Listens for OpenFlow connections on \fIport\fR. The default
\fIport\fR is 6653. By default, connections are allowed from any IPv4
-address. Specify \fIip\fR as an IPv4 address or a bracketed IPv6
+address. Specify \fIhost\fR as an IPv4 address or a bracketed IPv6
address (e.g. \fBptcp:6653:[::1]\fR). On Linux, use \fB%\fIdevice\fR
to designate a scope for IPv6 link-level addresses,
-e.g. \fBptcp:6653:[fe80::1234%eth0]\fR. DNS names may
-not be used. For \fBpssl\fR, the
+e.g. \fBptcp:6653:[fe80::1234%eth0]\fR. DNS names can
+be used if built with unbound library. For \fBpssl\fR, the
\fB\-\-private\-key\fR,\fB\-\-certificate\fR, and \fB\-\-ca\-cert\fR
options are mandatory.
.IP
diff --git a/lib/vconn.c b/lib/vconn.c
index c311843b2..e95ecbfa7 100644
--- a/lib/vconn.c
+++ b/lib/vconn.c
@@ -139,11 +139,11 @@ vconn_usage(bool active, bool passive, bool bootstrap OVS_UNUSED)
printf("\n");
if (active) {
printf("Active OpenFlow connection methods:\n");
- printf(" tcp:IP[:PORT] "
- "PORT (default: %d) at remote IP\n", OFP_PORT);
+ printf(" tcp:HOST[:PORT] "
+ "PORT (default: %d) at remote HOST\n", OFP_PORT);
#ifdef HAVE_OPENSSL
- printf(" ssl:IP[:PORT] "
- "SSL PORT (default: %d) at remote IP\n", OFP_PORT);
+ printf(" ssl:HOST[:PORT] "
+ "SSL PORT (default: %d) at remote HOST\n", OFP_PORT);
#endif
printf(" unix:FILE Unix domain socket named FILE\n");
}