]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
Created a new class to handle DNS resolve functions.
authorRicardo Dias <rdias@suse.com>
Fri, 12 Feb 2016 16:02:48 +0000 (16:02 +0000)
committerRicardo Dias <rdias@suse.com>
Mon, 30 May 2016 08:01:54 +0000 (09:01 +0100)
Signed-off-by: Ricardo Dias <rdias@suse.com>
src/CMakeLists.txt
src/common/Makefile.am
src/common/dns_resolve.cc [new file with mode: 0644]
src/common/dns_resolve.h [new file with mode: 0644]

index c1a94f97dde9934e5d59987199cb5054395a737d..f24814bffa9cb94dbc25256f14ae3f33e621a463 100644 (file)
@@ -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}
   $<TARGET_OBJECTS:compressor_objs>
@@ -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})
index 8b30eeadec17d05ed305e35266a9aa3c2b521e6d..7e3db7adab01d9b377f19de788ca515085a94ce5 100644 (file)
@@ -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 (file)
index 0000000..741edd1
--- /dev/null
@@ -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 <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <arpa/inet.h>
+#include <resolv.h>
+
+#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<res_state>::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<string, entity_addr_t> *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<string, entity_addr_t> *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 (file)
index 0000000..074564d
--- /dev/null
@@ -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 <string>
+#include <sys/types.h>
+#include <resolv.h>
+
+#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<std::string, entity_addr_t> *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<std::string, entity_addr_t> *srv_hosts);
+
+  private:
+    DNSResolver() : lock("DNSResolver") { resolv_h = new ResolvHWrapper(); }
+    ~DNSResolver();
+
+    Mutex lock;
+    ResolvHWrapper *resolv_h;
+#ifdef HAVE_RES_NQUERY
+    std::list<res_state> 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
+