From patchwork Tue Dec 19 13:08:42 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yifeng Sun X-Patchwork-Id: 850988 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="KKxF/yi9"; 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 3z1QxT5NFpz9s72 for ; Wed, 20 Dec 2017 05:13:01 +1100 (AEDT) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id B5713C13; Tue, 19 Dec 2017 18:12:58 +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 0C45BAE7 for ; Tue, 19 Dec 2017 18:12:58 +0000 (UTC) X-Greylist: whitelisted by SQLgrey-1.7.6 Received: from mail-wm0-f54.google.com (mail-wm0-f54.google.com [74.125.82.54]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 577C287 for ; Tue, 19 Dec 2017 18:12:55 +0000 (UTC) Received: by mail-wm0-f54.google.com with SMTP id r78so5464928wme.5 for ; Tue, 19 Dec 2017 10:12:55 -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=twg/TqsDd4hZcG9mRu0CcDTOAjo0Vtdy7W7I92JNW8g=; b=KKxF/yi9tqYZKuN0tDDOM8ZDfFOzHFjzLyWsOZ0SYwVYRrpnOHzUUl6MOwnBbeHOhW pxgGmQxKMmOCyhE684cD6moRbLtLXa/k4BnTvbbAlWyLNknPE+pMzfon4bIbfz9b35+M 3awBMpNhqbZVE0XrCTGsUA1BIwz1dSRYCpvrbH8Ag9og+usQ+1EdhWmW4O6qPt79vDlO WCEcZ6NyZgbxKXKK43gq4AmRF4bFjkjH32tC/e21yUwM7j+e2ULsdStjvcmVy2JeGe3Z FfWvC+shxgYjSqydUcLl3MqwEbri65Up8TjPLGduMCLZOrMJ67DXoiSQxGppkQOQT2Iz JyoA== 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=twg/TqsDd4hZcG9mRu0CcDTOAjo0Vtdy7W7I92JNW8g=; b=l51rPXUvc4fDvWGq8lUpS9hg6pTIJlWBxw//YkGd16oqsbBSZNMQnwdsgWfuz8nYJj NAHVL7zFVSrDFOYxD+8sE7JS/1smUqYdkdYhP6z/V2WxMAcMz4psNfVi5o5ng3HBqrMa vllHzegRHnqW4Mvc1uVbeNL3mZIu+n9hDtqvhECVf9k39bGgMd0GlF/4wKRFYREi5SCs NQNCjuJzyKBXGIpT652sZU5ycSHwiQje6NOJdEOLblAQQf+82TFm1uXePINbxYgZfQL9 bOs0Qbq2bCfpr1+VOdI2emgLOeQtxZeCFb1jezlrpU0PhomZjMtH93vOHzxBLlHNYNLz YGOg== X-Gm-Message-State: AKGB3mI8pPk8yIbBlA/HMfK8yhyLTT+aHZPQMfTvqYtw6CGy9rcEmtEA rhxLYD31mv/4yi4g+WI2hOA= X-Google-Smtp-Source: ACJfBotY+gewUBJ95zsc7FF2WtwF4p674HfSopc83eaTbGpd/aRe/t795VK5NMFNZBTU6t7BzGq72w== X-Received: by 10.80.160.198 with SMTP id 64mr1780192edo.242.1513707173812; Tue, 19 Dec 2017 10:12:53 -0800 (PST) Received: from yfsovs.eng.vmware.com ([208.91.1.34]) by smtp.gmail.com with ESMTPSA id g3sm10806362edi.11.2017.12.19.10.12.51 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 19 Dec 2017 10:12:52 -0800 (PST) From: Yifeng Sun To: blp@ovn.org, dev@openvswitch.org Date: Tue, 19 Dec 2017 05:08:42 -0800 Message-Id: <1513688922-9560-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] [PATCHv3] 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. v1 -> v2: refactored and improved code based on reviewer's comments. v2 -> v3: add commit message. Signed-off-by: Yifeng Sun --- 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 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..401dc5c8f126 --- /dev/null +++ b/lib/dns-resolve.c @@ -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 +#include "dns-resolve.h" +#include +#include +#include +#include +#include +#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); +} diff --git a/lib/dns-resolve.h b/lib/dns-resolve.h new file mode 100644 index 000000000000..95bc4e6bb9cb --- /dev/null +++ b/lib/dns-resolve.h @@ -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 + +void dns_resolve_init(void); +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 055593d3572e..d8812316b7ff 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,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); 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])]) diff --git a/vswitchd/ovs-vswitchd.c b/vswitchd/ovs-vswitchd.c index d5e07c0376cd..1c11111f30d5 100644 --- a/vswitchd/ovs-vswitchd.c +++ b/vswitchd/ovs-vswitchd.c @@ -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; }