diff options
author | Yifeng Sun <pkusunyifeng@gmail.com> | 2018-06-26 14:06:21 -0700 |
---|---|---|
committer | Ben Pfaff <blp@ovn.org> | 2018-07-06 13:50:00 -0700 |
commit | 771680d96fb6f996987630cb85bb749478512268 (patch) | |
tree | e3fddb4c770f88e405fd82787933216fc5b1ac58 /lib | |
parent | def5b366a3626fddc87a449e8447f79a6957d55f (diff) | |
download | openvswitch-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.mk | 7 | ||||
-rw-r--r-- | lib/dns-resolve-stub.c | 36 | ||||
-rw-r--r-- | lib/dns-resolve.c | 310 | ||||
-rw-r--r-- | lib/dns-resolve.h | 26 | ||||
-rw-r--r-- | lib/socket-util.c | 48 | ||||
-rw-r--r-- | lib/stream.c | 8 | ||||
-rw-r--r-- | lib/vconn-active.man | 14 | ||||
-rw-r--r-- | lib/vconn-passive.man | 10 | ||||
-rw-r--r-- | lib/vconn.c | 8 |
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"); } |