From patchwork Mon Dec 4 14:03:40 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yifeng Sun X-Patchwork-Id: 844376 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=openvswitch.org (client-ip=140.211.169.12; helo=mail.linuxfoundation.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="vMPGXW1j"; dkim-atps=neutral Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3yrDsy2d2mz9sNV for ; Tue, 5 Dec 2017 06:08:06 +1100 (AEDT) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 4C373CAD; Mon, 4 Dec 2017 19:08:04 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id B36AEC98 for ; Mon, 4 Dec 2017 19:08:03 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-pg0-f43.google.com (mail-pg0-f43.google.com [74.125.83.43]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 2864F3FB for ; Mon, 4 Dec 2017 19:08:01 +0000 (UTC) Received: by mail-pg0-f43.google.com with SMTP id g7so8855421pgs.0 for ; Mon, 04 Dec 2017 11:08:01 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id; bh=l5S0OVHS8aHxNJKHuhjnvFFiChgcQtRBp6g01/ZIfZI=; b=vMPGXW1joG87cVCmJN/0dJlQaK93cLMTYQXqu9ZKNVY7+EVvpkG4YIGescYhBEhqJv MqHzNvaH5F1Chy1VB30iOCsSATOoZ9eH/kOGMh57d1FkDwACJpesQUQafYDn8RtFpvhC Y9QU0rVhDpa6VP7xGaYG6Ik+2YHKYLvxEWBitEzO6ZBL0aH3N2i+BiT8R9b9X7NR3jcj NEPvfNHDDXtAB4DDyh3zVWoVlvE2bhVwCmCS3Af/5gg/WcXV/UoLWckU4AtLXzAVf1dk R2enjBCJhjuojN3W9tmRlvLrlqRq4jxz1kckNC4rrsqt/4ffEhWJZ3ptxq5BodcSCWpq VJFQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=l5S0OVHS8aHxNJKHuhjnvFFiChgcQtRBp6g01/ZIfZI=; b=R4+oK4bOAxqLv/wZe8rtkLkpPjECpit+IyBqpQiMNmVHRMUgXl7KSLCUQdd/qcwEjx 2ltDVhGUQJwsf/SbFNc6ybGJfG/h1ShloXMvC7kAHyEZo+3iV27/rONrkA18da0xNJEo x4xTDTBDldjYNwIcE9wvBZCufb1oX50zGNEzg0wXu78boLpbsG4zsnfWwG8xYRuOU8vd vSUKMpZSp2PyMxgaZxym/PqRjlD4t697IwwDq+J4jsB2ODfU+ZOeiXrMEIlcF1ut87Gp DAimMR+R4Cv3s2ARTU7F/AGu4/yaIbHm8WLI7T8xBVB82tMoy1cdrtDOjmoa5Q2KGmW4 wdBA== X-Gm-Message-State: AJaThX4JT17/RE09apXaC79TxtxNcnWWYDe5ZIPbIvbVA0agPJjJHBvp pyDA6eyhVjXYwCv4RYbxtYeeBQ== X-Google-Smtp-Source: AGs4zMZM6DSTpO9gDlPlcwTmGCiIxa1xmp/+vElFmpe35X+mqHo2nwXNa/RGDCYehJ9yIoeG+YK1kw== X-Received: by 10.99.182.2 with SMTP id j2mr14877641pgf.116.1512414480289; Mon, 04 Dec 2017 11:08:00 -0800 (PST) Received: from yfsovs.eng.vmware.com ([208.91.1.34]) by smtp.gmail.com with ESMTPSA id z78sm26752267pfk.115.2017.12.04.11.07.59 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 04 Dec 2017 11:07:59 -0800 (PST) From: Yifeng Sun To: dev@openvswitch.org Date: Mon, 4 Dec 2017 06:03:40 -0800 Message-Id: <1512396220-20636-1-git-send-email-pkusunyifeng@gmail.com> X-Mailer: git-send-email 2.7.4 X-Spam-Status: No, score=-0.4 required=5.0 tests=BAYES_00, DATE_IN_PAST_03_06, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, FREEMAIL_FROM, RCVD_IN_DNSWL_NONE autolearn=no version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Subject: [ovs-dev] [PATCH] DNS: Add basic support for asynchronous DNS resolving X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org 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 --- 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 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 +#include +#include +#include +#include +#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])])