// -*- 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 #include "include/scope_guard.h" #include "dns_resolve.h" #include "common/debug.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; } auto put_state = make_scope_guard([res, this] { this->put_state(res); }); #endif #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; return 0; } 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; return -EINVAL; } pt += len; if (pt + 4 > answend) { lderr(cct) << "ERROR: bad reply" << dendl; return -EIO; } 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; return -EIO; } pt += NS_INT16SZ; /* class */ /* read answer */ if ((len = dn_expand(answer, answend, pt, host, sizeof(host))) < 0) { return 0; } pt += len; ldout(cct, 20) << "name=" << host << dendl; if (pt + 10 > answend) { lderr(cct) << "ERROR: bad reply" << dendl; return -EIO; } 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) { return 0; } ldout(cct, 20) << "cname host=" << host << dendl; *cname = host; *found = true; return 0; } 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; } auto put_state = make_scope_guard([res, this] { this->put_state(res); }); #endif 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) << "failed for service " << query_str << dendl; return len; } else if (len == 0) { ldout(cct, 20) << "No hosts found for service " << query_str << dendl; return 0; } 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; return 0; } 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; return r; } 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()); auto rdata = ns_rr_rdata(rr); uint16_t priority = ns_get16(rdata); rdata += NS_INT16SZ; rdata += NS_INT16SZ; // weight uint16_t port = ns_get16(rdata); rdata += NS_INT16SZ; memset(full_target, 0, sizeof(full_target)); ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), rdata, full_target, sizeof(full_target)); 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; auto end = target.find(srv_domain); if (end == target.npos) { lderr(cct) << "resolved target not in search domain: " << target << " / " << srv_domain << dendl; return -EINVAL; } target = target.substr(0, end); (*srv_hosts)[target] = {priority, addr}; } } return 0; } }