From d8de15bfbc1ed16d03fb3c445180896717bf5213 Mon Sep 17 00:00:00 2001 From: Ricardo Dias Date: Fri, 12 Feb 2016 16:02:48 +0000 Subject: [PATCH] Created a new class to handle DNS resolve functions. Signed-off-by: Ricardo Dias --- src/CMakeLists.txt | 4 +- src/common/Makefile.am | 7 +- src/common/dns_resolve.cc | 389 ++++++++++++++++++++++++++++++++++++++ src/common/dns_resolve.h | 160 ++++++++++++++++ 4 files changed, 556 insertions(+), 4 deletions(-) create mode 100644 src/common/dns_resolve.cc create mode 100644 src/common/dns_resolve.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c1a94f97dde99..f24814bffa9cb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -426,6 +426,7 @@ set(libcommon_files osdc/Objecter.cc common/Graylog.cc common/fs_types.cc + common/dns_resolve.cc ${arch_files} ${auth_files} $ @@ -478,7 +479,7 @@ endif(${HAVE_ARMV8_CRC}) add_library(common_utf8 STATIC common/utf8.c) -target_link_libraries(common json_spirit common_utf8 erasure_code rt uuid ${CRYPTO_LIBS} ${Boost_LIBRARIES} ${BLKID_LIBRARIES} ${EXECINFO_LIBRARIES}) +target_link_libraries(common json_spirit common_utf8 erasure_code rt uuid resolv ${CRYPTO_LIBS} ${Boost_LIBRARIES} ${BLKID_LIBRARIES} ${EXECINFO_LIBRARIES}) if(${WITH_LTTNG}) add_subdirectory(tracing) @@ -673,7 +674,6 @@ if(${WITH_RADOSGW}) curl expat fcgi - resolv ) endif(${WITH_RADOSGW}) if(${WITH_RBD}) diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 8b30eeadec17d..7e3db7adab01d 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -77,7 +77,8 @@ libcommon_internal_la_SOURCES = \ common/TracepointProvider.cc \ common/PluginRegistry.cc \ common/scrub_types.cc \ - common/blkdev.cc + common/blkdev.cc \ + common/dns_resolve.cc common/PluginRegistry.cc: ./ceph_ver.h @@ -167,6 +168,7 @@ LIBCOMMON_DEPS += \ $(LIBMSG) $(LIBAUTH) \ $(LIBCRUSH) $(LIBJSON_SPIRIT) $(LIBLOG) $(LIBARCH) \ $(BOOST_RANDOM_LIBS) \ + $(RESOLV_LIBS) \ -luuid if LINUX @@ -282,7 +284,8 @@ noinst_HEADERS += \ common/ceph_timer.h \ common/align.h \ common/mutex_debug.h \ - common/shunique_lock.h + common/shunique_lock.h \ + common/dns_resolve.h if ENABLE_XIO noinst_HEADERS += \ diff --git a/src/common/dns_resolve.cc b/src/common/dns_resolve.cc new file mode 100644 index 0000000000000..741edd1596542 --- /dev/null +++ b/src/common/dns_resolve.cc @@ -0,0 +1,389 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2016 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ +#include "dns_resolve.h" + +#include +#include +#include +#include +#include + +#include "acconfig.h" +#include "common/debug.h" +#include "msg/msg_types.h" + + +#define dout_subsys ceph_subsys_ + +using namespace std; + +namespace ceph { + +#ifdef HAVE_RES_NQUERY + +int ResolvHWrapper::res_nquery(res_state s, const char *hostname, int cls, + int type, u_char *buf, int bufsz) { + return ::res_nquery(s, hostname, cls, type, buf, bufsz); +} + +int ResolvHWrapper::res_nsearch(res_state s, const char *hostname, int cls, + int type, u_char *buf, int bufsz) { + return ::res_nsearch(s, hostname, cls, type, buf, bufsz); +} + +#else + +int ResolvHWrapper::res_query(const char *hostname, int cls, + int type, u_char *buf, int bufsz) { + return ::res_query(hostname, cls, type, buf, bufsz); +} + +int ResolvHWrapper::res_search(const char *hostname, int cls, + int type, u_char *buf, int bufsz) { + return ::res_search(hostname, cls, type, buf, bufsz); +} + +#endif + +DNSResolver::~DNSResolver() +{ +#ifdef HAVE_RES_NQUERY + list::iterator iter; + for (iter = states.begin(); iter != states.end(); ++iter) { + struct __res_state *s = *iter; + delete s; + } +#endif + delete resolv_h; +} + +#ifdef HAVE_RES_NQUERY +int DNSResolver::get_state(CephContext *cct, res_state *ps) +{ + lock.Lock(); + if (!states.empty()) { + res_state s = states.front(); + states.pop_front(); + lock.Unlock(); + *ps = s; + return 0; + } + lock.Unlock(); + struct __res_state *s = new struct __res_state; + s->options = 0; + if (res_ninit(s) < 0) { + delete s; + lderr(cct) << "ERROR: failed to call res_ninit()" << dendl; + return -EINVAL; + } + *ps = s; + return 0; +} + +void DNSResolver::put_state(res_state s) +{ + Mutex::Locker l(lock); + states.push_back(s); +} +#endif + +int DNSResolver::resolve_cname(CephContext *cct, const string& hostname, + string *cname, bool *found) +{ + *found = false; + +#ifdef HAVE_RES_NQUERY + res_state res; + int r = get_state(cct, &res); + if (r < 0) { + return r; + } +#endif + + int ret; + +#define LARGE_ENOUGH_DNS_BUFSIZE 1024 + unsigned char buf[LARGE_ENOUGH_DNS_BUFSIZE]; + +#define MAX_FQDN_SIZE 255 + char host[MAX_FQDN_SIZE + 1]; + const char *origname = hostname.c_str(); + unsigned char *pt, *answer; + unsigned char *answend; + int len; + +#ifdef HAVE_RES_NQUERY + len = resolv_h->res_nquery(res, origname, ns_c_in, ns_t_cname, buf, sizeof(buf)); +#else + { +# ifndef HAVE_THREAD_SAFE_RES_QUERY + Mutex::Locker l(lock); +# endif + len = resolv_h->res_query(origname, ns_c_in, ns_t_cname, buf, sizeof(buf)); + } +#endif + if (len < 0) { + lderr(cct) << "res_query() failed" << dendl; + ret = 0; + goto done; + } + + answer = buf; + pt = answer + NS_HFIXEDSZ; + answend = answer + len; + + /* read query */ + if ((len = dn_expand(answer, answend, pt, host, sizeof(host))) < 0) { + lderr(cct) << "ERROR: dn_expand() failed" << dendl; + ret = -EINVAL; + goto done; + } + pt += len; + + if (pt + 4 > answend) { + lderr(cct) << "ERROR: bad reply" << dendl; + ret = -EIO; + goto done; + } + + int type; + NS_GET16(type, pt); + + if (type != ns_t_cname) { + lderr(cct) << "ERROR: failed response type: type=" << type << + " (was expecting " << ns_t_cname << ")" << dendl; + ret = -EIO; + goto done; + } + + pt += NS_INT16SZ; /* class */ + + /* read answer */ + if ((len = dn_expand(answer, answend, pt, host, sizeof(host))) < 0) { + ret = 0; + goto done; + } + pt += len; + ldout(cct, 20) << "name=" << host << dendl; + + if (pt + 10 > answend) { + lderr(cct) << "ERROR: bad reply" << dendl; + ret = -EIO; + goto done; + } + + NS_GET16(type, pt); + pt += NS_INT16SZ; /* class */ + pt += NS_INT32SZ; /* ttl */ + pt += NS_INT16SZ; /* size */ + + if ((len = dn_expand(answer, answend, pt, host, sizeof(host))) < 0) { + ret = 0; + goto done; + } + ldout(cct, 20) << "cname host=" << host << dendl; + *cname = host; + + *found = true; + ret = 0; +done: +#ifdef HAVE_RES_NQUERY + put_state(res); +#endif + return ret; +} + + +int DNSResolver::resolve_ip_addr(CephContext *cct, const string& hostname, + entity_addr_t *addr) { + +#ifdef HAVE_RES_NQUERY + res_state res; + int r = get_state(cct, &res); + if (r < 0) { + return r; + } + return this->resolve_ip_addr(cct, &res, hostname, addr); +#else + return this->resolve_ip_addr(cct, NULL, hostname, addr); +#endif + +} + +int DNSResolver::resolve_ip_addr(CephContext *cct, res_state *res, const string& hostname, + entity_addr_t *addr) { + + u_char nsbuf[NS_PACKETSZ]; + int len; + + +#ifdef HAVE_RES_NQUERY + len = resolv_h->res_nquery(*res, hostname.c_str(), ns_c_in, ns_t_a, nsbuf, sizeof(nsbuf)); +#else + { +# ifndef HAVE_THREAD_SAFE_RES_QUERY + Mutex::Locker l(lock); +# endif + len = resolv_h->res_query(hostname.c_str(), ns_c_in, ns_t_a, nsbuf, sizeof(nsbuf)); + } +#endif + if (len < 0) { + lderr(cct) << "res_query() failed" << dendl; + return len; + } + else if (len == 0) { + ldout(cct, 20) << "no address found for hostname " << hostname << dendl; + return -1; + } + + ns_msg handle; + ns_initparse(nsbuf, len, &handle); + + if (ns_msg_count(handle, ns_s_an) == 0) { + ldout(cct, 20) << "no address found for hostname " << hostname << dendl; + return -1; + } + + ns_rr rr; + int r; + if ((r = ns_parserr(&handle, ns_s_an, 0, &rr)) < 0) { + lderr(cct) << "error while parsing DNS record" << dendl; + return r; + } + + char addr_buf[64]; + memset(addr_buf, 0, sizeof(addr_buf)); + inet_ntop(AF_INET, ns_rr_rdata(rr), addr_buf, sizeof(addr_buf)); + if (!addr->parse(addr_buf)) { + lderr(cct) << "failed to parse address '" << (const char *)ns_rr_rdata(rr) + << "'" << dendl; + return -1; + } + + return 0; +} + +int DNSResolver::resolve_srv_hosts(CephContext *cct, const string& service_name, + const SRV_Protocol trans_protocol, map *srv_hosts) { + return this->resolve_srv_hosts(cct, service_name, trans_protocol, "", srv_hosts); +} + +int DNSResolver::resolve_srv_hosts(CephContext *cct, const string& service_name, + const SRV_Protocol trans_protocol, const string& domain, + map *srv_hosts) { + +#ifdef HAVE_RES_NQUERY + res_state res; + int r = get_state(cct, &res); + if (r < 0) { + return r; + } +#endif + + int ret; + u_char nsbuf[NS_PACKETSZ]; + int num_hosts; + + string proto_str = srv_protocol_to_str(trans_protocol); + string query_str = "_"+service_name+"._"+proto_str+(domain.empty() ? "" + : "."+domain); + int len; + +#ifdef HAVE_RES_NQUERY + len = resolv_h->res_nsearch(res, query_str.c_str(), ns_c_in, ns_t_srv, nsbuf, + sizeof(nsbuf)); +#else + { +# ifndef HAVE_THREAD_SAFE_RES_QUERY + Mutex::Locker l(lock); +# endif + len = resolv_h->res_search(query_str.c_str(), ns_c_in, ns_t_srv, nsbuf, + sizeof(nsbuf)); + } +#endif + if (len < 0) { + lderr(cct) << "res_search() failed" << dendl; + ret = len; + goto done; + } + else if (len == 0) { + ldout(cct, 20) << "No hosts found for service " << query_str << dendl; + ret = 0; + goto done; + } + + ns_msg handle; + + ns_initparse(nsbuf, len, &handle); + + num_hosts = ns_msg_count (handle, ns_s_an); + if (num_hosts == 0) { + ldout(cct, 20) << "No hosts found for service " << query_str << dendl; + ret = 0; + goto done; + } + + ns_rr rr; + char full_target[NS_MAXDNAME]; + + for (int i = 0; i < num_hosts; i++) { + int r; + if ((r = ns_parserr(&handle, ns_s_an, i, &rr)) < 0) { + lderr(cct) << "Error while parsing DNS record" << dendl; + ret = r; + goto done; + } + + string full_srv_name = ns_rr_name(rr); + string protocol = "_" + proto_str; + string srv_domain = full_srv_name.substr(full_srv_name.find(protocol) + + protocol.length()); + + const u_char *p = ns_rr_rdata(rr); + p += NS_INT16SZ; // priority + p += NS_INT16SZ; // weight + + int port; + NS_GET16(port, p); + + memset(full_target, 0, sizeof(full_target)); + ns_name_ntop(p, full_target, NS_MAXDNAME); + + entity_addr_t addr; +#ifdef HAVE_RES_NQUERY + r = this->resolve_ip_addr(cct, &res, full_target, &addr); +#else + r = this->resolve_ip_addr(cct, NULL, full_target, &addr); +#endif + + if (r == 0) { + addr.set_port(port); + string target = full_target; + assert(target.find(srv_domain) != target.npos); + target = target.substr(0, target.find(srv_domain)); + (*srv_hosts)[target] = addr; + } + + } + + ret = 0; +done: +#ifdef HAVE_RES_NQUERY + put_state(res); +#endif + return ret; +} + +} + diff --git a/src/common/dns_resolve.h b/src/common/dns_resolve.h new file mode 100644 index 0000000000000..074564ddbb1af --- /dev/null +++ b/src/common/dns_resolve.h @@ -0,0 +1,160 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2016 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ +#ifndef CEPH_DNS_RESOLVE_H +#define CEPH_DNS_RESOLVE_H + +#include +#include +#include + +#include "common/Mutex.h" +#include "common/ceph_context.h" + +struct entity_addr_t; + +namespace ceph { + +/** + * this class is used to facilitate the testing of + * resolv.h functions. + */ +class ResolvHWrapper { + public: + virtual ~ResolvHWrapper() {} + +#ifdef HAVE_RES_NQUERY + virtual int res_nquery(res_state s, const char *hostname, int cls, int type, + u_char *buf, int bufsz); + + virtual int res_nsearch(res_state s, const char *hostname, int cls, int type, + u_char *buf, int bufsz); +#else + virtual int res_query(const char *hostname, int cls, int type, + u_char *buf, int bufsz); + + virtual int res_search(const char *hostname, int cls, int type, + u_char *buf, int bufsz); +#endif + +}; + + +/** + * @class DNSResolver + * + * This is a singleton class that exposes the functionality of DNS querying. + */ +class DNSResolver { + + public: + // singleton declaration + static DNSResolver *get_instance() + { + static DNSResolver instance; + return &instance; + } + DNSResolver(DNSResolver const&) = delete; + void operator=(DNSResolver const&) = delete; + + // this function is used by the unit test + static DNSResolver *get_instance(ResolvHWrapper *resolv_wrapper) { + DNSResolver *resolv = DNSResolver::get_instance(); + delete resolv->resolv_h; + resolv->resolv_h = resolv_wrapper; + return resolv; + } + + enum class SRV_Protocol { + TCP, UDP + }; + + + int resolve_cname(CephContext *cct, const std::string& hostname, + std::string *cname, bool *found); + + /** + * Resolves the address given a hostname. + * + * @param hostname the hostname to resolved + * @param[out] addr the hostname's address + * @returns 0 on success, negative error code on failure + */ + int resolve_ip_addr(CephContext *cct, const std::string& hostname, + entity_addr_t *addr); + + /** + * Returns the list of hostnames and addresses that provide a given + * service configured as DNS SRV records. + * + * @param service_name the service name + * @param trans_protocol the IP protocol used by the service (TCP or UDP) + * @param[out] srv_hosts the hostname to address map of available hosts + * providing the service. If no host exists the map is not + * changed. + * @returns 0 on success, negative error code on failure + */ + int resolve_srv_hosts(CephContext *cct, const std::string& service_name, + const SRV_Protocol trans_protocol, std::map *srv_hosts); + + /** + * Returns the list of hostnames and addresses that provide a given + * service configured as DNS SRV records. + * + * @param service_name the service name + * @param trans_protocol the IP protocol used by the service (TCP or UDP) + * @param domain the domain of the service + * @param[out] srv_hosts the hostname to address map of available hosts + * providing the service. If no host exists the map is not + * changed. + * @returns 0 on success, negative error code on failure + */ + int resolve_srv_hosts(CephContext *cct, const std::string& service_name, + const SRV_Protocol trans_protocol, const std::string& domain, + std::map *srv_hosts); + + private: + DNSResolver() : lock("DNSResolver") { resolv_h = new ResolvHWrapper(); } + ~DNSResolver(); + + Mutex lock; + ResolvHWrapper *resolv_h; +#ifdef HAVE_RES_NQUERY + std::list states; + + int get_state(CephContext *cct, res_state *ps); + void put_state(res_state s); +#endif + + /* this private function allows to reuse the res_state structure used + * by other function of this class + */ + int resolve_ip_addr(CephContext *cct, res_state *res, + const std::string& hostname, entity_addr_t *addr); + + std::string srv_protocol_to_str(SRV_Protocol proto) { + switch (proto) { + case SRV_Protocol::TCP: + return "tcp"; + case SRV_Protocol::UDP: + return "udp"; + } + return ""; + } + +}; + +} + +#endif + -- 2.39.5