Message ID | 1512396220-20636-1-git-send-email-pkusunyifeng@gmail.com |
---|---|
State | Changes Requested |
Headers | show |
Series | [ovs-dev] DNS: Add basic support for asynchronous DNS resolving | expand |
On Mon, Dec 04, 2017 at 06:03:40AM -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. > > Signed-off-by: Yifeng Sun <pkusunyifeng@gmail.com> Thanks a lot! DNS support will be a great feature for our users. I have some comments. Usually, OVS functions use one of two calling conventions for reporting errors. coding-style.rst describes them this way: For functions that return a success or failure indication, prefer one of the following return value conventions: - An ``int`` where 0 indicates success and a positive errno value indicates a reason for failure. - A ``bool`` where true indicates success and false indicates failure. The convention used here for dns_resolve() is different. I'd prefer to see it adopt one of the above styles. Usually, we use a .h file for high-level documentation of a particular code module, and then document particular functions in the .c file. Since this module has very few public functions, this convention is not as important, but I would still like to see it followed. Usually, we use OVS_LIKELY and OVS_UNLIKELY only in performance-sensitive inner loops. I don't think that any of the DNS code qualifies. I am concerned about what could happen if the unbound library cannot be initialized. In that case, I think that every call to dns_resolve() will try to initialize it again. That is likely to log error messages at a high rate. I suggest either rate-limiting the log messages (with VLOG_ERR_RL, etc.) or changing the initialization code so that it only tries to initialize once and then permanently fails. My guess is that the latter is a better choice in this case. Probably, ub_process() error logging should also be rate-limited. It is also worth thinking about resolve_callback__()'s logging, to figure out whether it is likely to be too voluminous in practice. I think that resolve_find_or_new__() could be made very slightly simpler by using shash_find_data(). xcalloc(1, x) can be written xzalloc(x). In some of the dns_resolve() error cases, *addr is not set to NULL. It's a better user interface if *addr is always set to NULL on error. These days, we tend to try to declare and initialize variables at the same point, so in dns_resolve() I would move the declarations of 'req' and 'retval' down to their points of first use. coding-style.rst says: Do parenthesize a subexpression that must be split across more than one line, e.g.:: *idxp = ((l1_idx << PORT_ARRAY_L1_SHIFT) | (l2_idx << PORT_ARRAY_L2_SHIFT) | (l3_idx << PORT_ARRAY_L3_SHIFT)); so that: return req != NULL && req->state == RESOLVE_GOOD && !resolve_check_expire__(req); should become: return (req != NULL && req->state == RESOLVE_GOOD && !resolve_check_expire__(req)); resolve_set_time__() seems simple enough to me that the code would be clearer if it were just written inline instead of as a function. I am not sure why the 'addr' member of struct resolve_request is marked const. I do not understand the benefit in this case. I see better why the 'name' member of struct resolve_request is marked const. It is because it duplicates the name in the shash_node and should not be separately freed. But I think that it would be a cleaner design in this case to use an hmap directly, by adding an hmap_node member to struct resolve_request and then changing all_reqs__ from struct shash to struct hmap. I do not understand why all_reqs exists. It seems that, each time it is used, one could write &all_reqs__ instead. Initialization in dns_resolve() and destruction in dns_resolve_destroy() seems asymmetric in at least one way: dns_resolve_destroy() destroys all_reqs, but dns_resolve_init__() does not initialize all_reqs. I believe that this means that calling dns_resolve_destroy(), then resolving an address, will cause a segfault. It is better to avoid that potential problem. In resolve_callback__(), the cast here is not needed: struct resolve_request *req; req = (struct resolve_request *)data; I would write it as: struct resolve_request *req = data; Similarly, in resolve_async__(), this cast is not needed either: qtype, ns_c_in, (void *)req, Actually, common OVS style in a case like this is to give the parameter a _ suffix, like this: static void resolve_callback__(void *req_, int err, struct ub_result *result) OVS_REQUIRES(dns_mutex__) { struct resolve_request *req = req_; Thanks, Ben.
Thanks a lot Ben for your review. I will apply your advice in v2. Yifeng On Wed, Dec 13, 2017 at 11:02 AM, Ben Pfaff <blp@ovn.org> wrote: > On Mon, Dec 04, 2017 at 06:03:40AM -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. > > > > Signed-off-by: Yifeng Sun <pkusunyifeng@gmail.com> > > Thanks a lot! DNS support will be a great feature for our users. > > I have some comments. > > Usually, OVS functions use one of two calling conventions for reporting > errors. coding-style.rst describes them this way: > > For functions that return a success or failure indication, prefer > one of the following return value conventions: > > - An ``int`` where 0 indicates success and a positive errno value > indicates a reason for failure. > > - A ``bool`` where true indicates success and false indicates > failure. > > The convention used here for dns_resolve() is different. I'd prefer to > see it adopt one of the above styles. > > Usually, we use a .h file for high-level documentation of a particular > code module, and then document particular functions in the .c file. > Since this module has very few public functions, this convention is not > as important, but I would still like to see it followed. > > Usually, we use OVS_LIKELY and OVS_UNLIKELY only in > performance-sensitive inner loops. I don't think that any of the DNS > code qualifies. > > I am concerned about what could happen if the unbound library cannot be > initialized. In that case, I think that every call to dns_resolve() > will try to initialize it again. That is likely to log error messages > at a high rate. I suggest either rate-limiting the log messages (with > VLOG_ERR_RL, etc.) or changing the initialization code so that it only > tries to initialize once and then permanently fails. My guess is that > the latter is a better choice in this case. > > Probably, ub_process() error logging should also be rate-limited. It is > also worth thinking about resolve_callback__()'s logging, to figure out > whether it is likely to be too voluminous in practice. > > I think that resolve_find_or_new__() could be made very slightly simpler > by using shash_find_data(). > > xcalloc(1, x) can be written xzalloc(x). > > In some of the dns_resolve() error cases, *addr is not set to NULL. > It's a better user interface if *addr is always set to NULL on error. > > These days, we tend to try to declare and initialize variables at the > same point, so in dns_resolve() I would move the declarations of 'req' > and 'retval' down to their points of first use. > > coding-style.rst says: > > Do parenthesize a subexpression that must be split across more than > one line, e.g.:: > > *idxp = ((l1_idx << PORT_ARRAY_L1_SHIFT) | > (l2_idx << PORT_ARRAY_L2_SHIFT) | > (l3_idx << PORT_ARRAY_L3_SHIFT)); > so that: > return req != NULL > && req->state == RESOLVE_GOOD > && !resolve_check_expire__(req); > should become: > return (req != NULL > && req->state == RESOLVE_GOOD > && !resolve_check_expire__(req)); > > resolve_set_time__() seems simple enough to me that the code would be > clearer if it were just written inline instead of as a function. > > I am not sure why the 'addr' member of struct resolve_request is marked > const. I do not understand the benefit in this case. > > I see better why the 'name' member of struct resolve_request is marked > const. It is because it duplicates the name in the shash_node and > should not be separately freed. But I think that it would be a cleaner > design in this case to use an hmap directly, by adding an hmap_node > member to struct resolve_request and then changing all_reqs__ from > struct shash to struct hmap. > > I do not understand why all_reqs exists. It seems that, each time it is > used, one could write &all_reqs__ instead. > > Initialization in dns_resolve() and destruction in dns_resolve_destroy() > seems asymmetric in at least one way: dns_resolve_destroy() destroys > all_reqs, but dns_resolve_init__() does not initialize all_reqs. I > believe that this means that calling dns_resolve_destroy(), then > resolving an address, will cause a segfault. It is better to avoid that > potential problem. > > In resolve_callback__(), the cast here is not needed: > struct resolve_request *req; > > req = (struct resolve_request *)data; > I would write it as: > struct resolve_request *req = data; > > Similarly, in resolve_async__(), this cast is not needed either: > qtype, ns_c_in, (void *)req, > > Actually, common OVS style in a case like this is to give the parameter > a _ suffix, like this: > > static void > resolve_callback__(void *req_, int err, struct ub_result *result) > OVS_REQUIRES(dns_mutex__) > { > struct resolve_request *req = req_; > > Thanks, > > Ben. >
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..762a10b0d28b --- /dev/null +++ b/lib/dns-resolve.c @@ -0,0 +1,243 @@ +/* + * 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 <arpa/inet.h> +#include <arpa/nameser.h> +#include <errno.h> +#include <string.h> +#include "dns-resolve.h" +#include "timeval.h" +#include "unbound.h" +#include "openvswitch/shash.h" +#include "openvswitch/vlog.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 shash all_reqs__ = SHASH_INITIALIZER(&all_reqs__); +static struct shash *const all_reqs = &all_reqs__; +static struct ub_ctx *ub_ctx__; + +enum resolve_state { + RESOLVE_INVALID, + RESOLVE_PENDING, + RESOLVE_GOOD, + RESOLVE_ERROR +}; + +struct resolve_request { + const char *name; /* the domain name to be resolved */ + const 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 void +resolve_callback__(void *data, int err, struct ub_result *result); + +static struct ub_ctx * +dns_resolve_init__(void) +{ + struct ub_ctx *ub_ctx; + int retval; + + ub_ctx = ub_ctx_create(); + if (!ub_ctx) { + VLOG_ERR("Failed to create unbound context"); + return NULL; + } + +#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 + + /* Handle '/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); + return ub_ctx; +} + +static struct resolve_request * +resolve_find_or_new__(const char *name) + OVS_REQUIRES(dns_mutex__) +{ + struct shash_node *node; + + node = shash_find(all_reqs, name); + if (node != NULL) { + return node->data; + } else { + struct resolve_request *req; + req = xcalloc(1, sizeof *req); + req->name = xstrdup(name); + req->state = RESOLVE_INVALID; + shash_add_nocopy(all_reqs, CONST_CAST(char *, req->name), req); + 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 void +resolve_set_time__(struct resolve_request *req) + OVS_REQUIRES(dns_mutex__) +{ + req->time = time_now(); +} + +static int +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, (void *)req, + resolve_callback__, NULL); + if (retval != 0) { + req->state = RESOLVE_ERROR; + return -1; + } else { + req->state = RESOLVE_PENDING; + return 0; + } + } + return -1; +} + +static void +resolve_callback__(void *data, int err, struct ub_result *result) + OVS_REQUIRES(dns_mutex__) +{ + struct resolve_request *req; + + req = (struct resolve_request *)data; + + if (err || (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(CONST_CAST(char *, req->addr)); + + req->ub_result = result; + req->addr = xstrdup(buffer); + req->state = RESOLVE_GOOD; + VLOG_INFO("%s: resolved to %s", req->name, req->addr); + return; + +bad: + VLOG_ERR("%s: failed to resolve", req->name); + ub_resolve_free(result); +} + +int +dns_resolve(const char *name, char **addr) + OVS_EXCLUDED(dns_mutex__) +{ + struct resolve_request *req; + int retval; + int err = -1; + + ovs_mutex_lock(&dns_mutex__); + + if (OVS_UNLIKELY(ub_ctx__ == NULL)) { + ub_ctx__ = dns_resolve_init__(); + if (ub_ctx__ == NULL) { + goto unlock; + } + } + + retval = ub_process(ub_ctx__); + if (retval) { + VLOG_ERR("%s: resolve error: %s", name, ub_strerror(retval)); + goto unlock; + } + + *addr = NULL; + + req = resolve_find_or_new__(name); + if (resolve_check_valid__(req)) { + *addr = xstrdup(req->addr); + err = 0; + } else if (req->state != RESOLVE_PENDING) { + resolve_set_time__(req); + err = resolve_async__(req, ns_t_a); + } +unlock: + ovs_mutex_unlock(&dns_mutex__); + return err; +} + +void +dns_resolve_destroy(void) +{ + if (ub_ctx__) { + /* Outstanding requests will be killed. */ + ub_ctx_delete(ub_ctx__); + ub_ctx__ = NULL; + + struct shash_node *node; + SHASH_FOR_EACH (node, all_reqs) { + struct resolve_request *req; + req = (struct resolve_request *)node->data; + ub_resolve_free(req->ub_result); + free(CONST_CAST(char *, req->addr)); + free(CONST_CAST(char *, req->name)); + free(req); + } + shash_destroy(all_reqs); + } +} diff --git a/lib/dns-resolve.h b/lib/dns-resolve.h new file mode 100644 index 000000000000..1619ff6434af --- /dev/null +++ b/lib/dns-resolve.h @@ -0,0 +1,32 @@ +/* + * 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 + +/* Returns 0 on success. If something goes wrong, returns -1 and the detailed + * information can be found in logs. When 0 is returned but *addr is NULL, it + * means that the resolving is in progress and the caller should recall later. + * + * This function is thread-safe. + * + * The caller is responsible for freeing the returned '*addr'. + */ +int dns_resolve(const char *name, char **addr); + +void dns_resolve_destroy(void); + +#endif diff --git a/lib/socket-util.c b/lib/socket-util.c index 055593d3572e..47164e92f6a7 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,35 @@ 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; + int err; + + err = dns_resolve(host_s, &tmp_host_s); + if (!err && tmp_host_s) { + 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 +426,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 +439,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 +448,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 +493,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 +618,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])])
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. Signed-off-by: Yifeng Sun <pkusunyifeng@gmail.com> --- configure.ac | 1 + lib/automake.mk | 6 ++ lib/dns-resolve.c | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/dns-resolve.h | 32 +++++++ lib/socket-util.c | 57 +++++++++++-- m4/openvswitch.m4 | 10 +++ 6 files changed, 341 insertions(+), 8 deletions(-) create mode 100644 lib/dns-resolve.c create mode 100644 lib/dns-resolve.h