/**
* Copyright (C) 2017 MongoDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#ifndef MONGO_UTIL_DNS_QUERY_PLATFORM_INCLUDE_WHITELIST
#error Do not include the DNS Query platform implementation headers. Please use "mongo/util/dns_query.h" instead.
#endif
// DNS Headers for POSIX/libresolv have to be included in a specific order
// clang-format off
#include
#include
#include
#include
// clang-format on
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace mongo {
namespace dns {
// The anonymous namespace is safe, in this header, as it is not really a header. It is only used
// in the `dns_query.cpp` TU.
namespace {
using std::begin;
using std::end;
using namespace std::literals::string_literals;
const std::size_t kMaxExpectedDNSResponseSize = 65536;
const std::size_t kMaxSRVHostNameSize = 8192;
enum class DNSQueryClass {
kInternet = ns_c_in,
};
enum class DNSQueryType {
kSRV = ns_t_srv,
kTXT = ns_t_txt,
kAddress = ns_t_a,
};
/**
* A `ResourceRecord` represents a single DNS entry as parsed by the resolver API.
* It can be viewed as one of various record types, using the member functions.
* It roughly corresponds to the DNS RR data structure
*/
class ResourceRecord {
public:
explicit ResourceRecord() = default;
explicit ResourceRecord(std::string initialService, ns_msg& ns_answer, const int initialPos)
: _service(std::move(initialService)),
_answerStart(ns_msg_base(ns_answer)),
_answerEnd(ns_msg_end(ns_answer)),
_pos(initialPos) {
if (ns_parserr(&ns_answer, ns_s_an, initialPos, &this->_resource_record))
this->_badRecord();
}
/**
* View this record as a DNS TXT record.
*/
std::vector txtEntry() const {
const auto data = this->_rawData();
if (data.empty()) {
uasserted(ErrorCodes::DNSProtocolError, "DNS TXT Record is not correctly sized");
}
const std::size_t amount = data.front();
const auto first = begin(data) + 1;
std::vector rv;
if (data.size() - 1 < amount) {
uasserted(ErrorCodes::DNSProtocolError, "DNS TXT Record is not correctly sized");
}
rv.emplace_back(first, first + amount);
return rv;
}
/**
* View this record as a DNS A record.
*/
std::string addressEntry() const {
std::string rv;
auto data = _rawData();
if (data.size() != 4) {
uasserted(ErrorCodes::DNSProtocolError, "DNS A Record is not correctly sized");
}
for (const std::uint8_t& ch : data) {
std::ostringstream oss;
oss << int(ch);
rv += oss.str() + ".";
}
rv.pop_back();
return rv;
}
/**
* View this record as a DNS SRV record.
*/
SRVHostEntry srvHostEntry() const {
const std::size_t kPortOffsetInPacket = 4;
const std::uint8_t* const data = ns_rr_rdata(this->_resource_record);
if (data < this->_answerStart ||
data + kPortOffsetInPacket + sizeof(std::uint16_t) > this->_answerEnd) {
std::ostringstream oss;
oss << "Invalid record " << this->_pos << " of SRV answer for \"" << this->_service
<< "\": Incorrect result size";
uasserted(ErrorCodes::DNSProtocolError, oss.str());
}
const std::uint16_t port = [data] {
std::uint16_t tmp;
memcpy(&tmp, data + kPortOffsetInPacket, sizeof(tmp));
return ntohs(tmp);
}();
// The '@' is an impermissible character in a host name, so we populate the string we'll
// return with it, such that a failure in string manipulation or corrupted dns packets will
// cause an illegal hostname.
std::string name(kMaxSRVHostNameSize, '@');
const auto size = dn_expand(this->_answerStart,
this->_answerEnd,
data + kPortOffsetInPacket + sizeof(port),
&name[0],
name.size());
if (size < 1)
this->_badRecord();
// Trim the expanded name
name.resize(name.find('\0'));
name += '.';
// return by copy is equivalent to a `shrink_to_fit` and `move`.
return {name, port};
}
private:
void _badRecord() const {
std::ostringstream oss;
oss << "Invalid record " << this->_pos << " of DNS answer for \"" << this->_service
<< "\": \"" << strerror(errno) << "\"";
uasserted(ErrorCodes::DNSProtocolError, oss.str());
};
std::vector _rawData() const {
const std::uint8_t* const data = ns_rr_rdata(this->_resource_record);
const std::size_t length = ns_rr_rdlen(this->_resource_record);
return {data, data + length};
}
std::string _service;
ns_rr _resource_record;
const std::uint8_t* _answerStart;
const std::uint8_t* _answerEnd;
int _pos;
};
/**
* The `DNSResponse` class represents a response to a DNS query.
* It has STL-compatible iterators to view individual DNS Resource Records within a response.
*/
class DNSResponse {
public:
explicit DNSResponse(std::string initialService, std::vector initialData)
: _service(std::move(initialService)), _data(std::move(initialData)) {
if (ns_initparse(this->_data.data(), this->_data.size(), &this->_ns_answer)) {
std::ostringstream oss;
oss << "Invalid SRV answer for \"" << this->_service << "\"";
uasserted(ErrorCodes::DNSProtocolError, oss.str());
}
this->_nRecords = ns_msg_count(this->_ns_answer, ns_s_an);
if (!this->_nRecords) {
std::ostringstream oss;
oss << "No SRV records for \"" << this->_service << "\"";
uasserted(ErrorCodes::DNSProtocolError, oss.str());
}
}
class iterator {
public:
auto makeRelopsLens() const {
return std::tie(this->_response, this->_pos);
}
inline friend bool operator==(const iterator& lhs, const iterator& rhs) {
return lhs.makeRelopsLens() == rhs.makeRelopsLens();
}
inline friend bool operator<(const iterator& lhs, const iterator& rhs) {
return lhs.makeRelopsLens() < rhs.makeRelopsLens();
}
inline friend bool operator!=(const iterator& lhs, const iterator& rhs) {
return !(lhs == rhs);
}
const ResourceRecord& operator*() {
this->_populate();
return this->_record;
}
const ResourceRecord* operator->() {
this->_populate();
return &this->_record;
}
iterator& operator++() {
this->_advance();
return *this;
}
iterator operator++(int) {
iterator tmp = *this;
this->_advance();
return tmp;
}
private:
friend DNSResponse;
explicit iterator(DNSResponse* const r)
: _response(r), _record(this->_response->_service, this->_response->_ns_answer, 0) {}
explicit iterator(DNSResponse* const initialResponse, const int initialPos)
: _response(initialResponse), _pos(initialPos) {}
void _populate() {
if (this->_ready) {
return;
}
this->_record =
ResourceRecord(this->_response->_service, this->_response->_ns_answer, this->_pos);
this->_ready = true;
}
void _advance() {
++this->_pos;
this->_ready = false;
}
DNSResponse* _response;
int _pos = 0;
ResourceRecord _record;
bool _ready = false;
};
auto begin() {
return iterator(this);
}
auto end() {
return iterator(this, this->_nRecords);
}
std::size_t size() const {
return this->_nRecords;
}
private:
std::string _service;
std::vector _data;
ns_msg _ns_answer;
std::size_t _nRecords;
};
/**
* The `DNSQueryState` object represents the state of a DNS query interface, on Unix-like systems.
*/
class DNSQueryState : boost::noncopyable {
public:
std::vector raw_lookup(const std::string& service,
const DNSQueryClass class_,
const DNSQueryType type) {
std::vector result(kMaxExpectedDNSResponseSize);
const int size = res_nsearch(
&_state, service.c_str(), int(class_), int(type), &result[0], result.size());
if (size < 0) {
std::ostringstream oss;
oss << "Failed to look up service \"" << service << "\": " << strerror(errno);
uasserted(ErrorCodes::DNSHostNotFound, oss.str());
}
result.resize(size);
return result;
}
DNSResponse lookup(const std::string& service,
const DNSQueryClass class_,
const DNSQueryType type) {
return DNSResponse(service, raw_lookup(service, class_, type));
}
public:
~DNSQueryState() {
res_nclose(&_state);
}
DNSQueryState() : _state() {
res_ninit(&_state);
}
private:
struct __res_state _state;
};
} // namespace
} // namespace dns
} // namespace mongo