more test cases
[ghsmtp.git] / DNS.cpp
blob3d63c9ec1492138295f30c1bdd05adc169e9f590
1 #include "DNS.hpp"
3 #include "DNS-iostream.hpp"
4 #include "IP4.hpp"
5 #include "IP6.hpp"
6 #include "Sock.hpp"
8 #include <atomic>
9 #include <limits>
10 #include <memory>
11 #include <tuple>
13 #include <arpa/nameser.h>
15 #include <experimental/random>
17 #include <glog/logging.h>
19 #include "osutil.hpp"
21 DEFINE_bool(log_dns_data, false, "log all DNS TCP protocol data");
23 namespace Config {
24 // The default timeout in glibc is 5 seconds. My setup with unbound
25 // in front of stubby with DNSSEC checking and all that seems to work
26 // better with just a little more time.
28 auto constexpr read_timeout{std::chrono::seconds(7)};
30 enum class sock_type : bool { stream, dgram };
32 struct nameserver {
33 char const* host; // name used to match cert
34 char const* addr;
35 char const* port;
36 sock_type typ;
39 constexpr nameserver nameservers[]{
41 "localhost",
42 "127.0.0.1",
43 "domain",
44 sock_type::stream,
47 "localhost",
48 "::1",
49 "domain",
50 sock_type::stream,
54 "one.one.one.one",
55 "1.1.1.1",
56 "domain",
57 sock_type::dgram,
60 "dns.google",
61 "8.8.8.8",
62 "domain",
63 sock_type::dgram,
66 "dns.google",
67 "8.8.4.4",
68 "domain",
69 sock_type::dgram,
73 "dns.google",
74 "2001:4860:4860::8888",
75 "domain",
76 sock_type::dgram,
79 "dns.google",
80 "2001:4860:4860::8844",
81 "domain",
82 sock_type::dgram,
85 "1dot1dot1dot1.cloudflare-dns.com",
86 "1.0.0.1",
87 "domain-s",
88 sock_type::stream,
91 "1dot1dot1dot1.cloudflare-dns.com",
92 "2606:4700:4700::1111",
93 "domain-s",
94 sock_type::stream,
97 "1dot1dot1dot1.cloudflare-dns.com",
98 "2606:4700:4700::1001",
99 "domain-s",
100 sock_type::stream,
103 "dns9.quad9.net",
104 "9.9.9.9",
105 "domain-s",
106 sock_type::stream,
109 "dns10.quad9.net",
110 "9.9.9.10",
111 "domain-s",
112 sock_type::stream,
115 "dns10.quad9.net",
116 "149.112.112.10",
117 "domain-s",
118 sock_type::stream,
121 "dns10.quad9.net",
122 "2620:fe::10",
123 "domain-s",
124 sock_type::stream,
128 } // namespace Config
130 template <typename T, std::size_t N>
131 constexpr std::size_t countof(T const (&)[N]) noexcept
133 return N;
136 namespace DNS {
138 Resolver::Resolver(fs::path config_path)
140 auto tries = countof(Config::nameservers);
142 ns_ = std::experimental::randint(
143 0, static_cast<int>(countof(Config::nameservers) - 1));
145 while (tries--) {
147 // try the next one, with wrap
148 if (++ns_ == countof(Config::nameservers))
149 ns_ = 0;
151 auto const& nameserver = Config::nameservers[ns_];
153 auto typ = (nameserver.typ == Config::sock_type::stream) ? SOCK_STREAM
154 : SOCK_DGRAM;
155 uint16_t port =
156 osutil::get_port(nameserver.port, (typ == SOCK_STREAM) ? "tcp" : "udp");
157 ns_fd_ = -1;
159 if (IP4::is_address(nameserver.addr)) {
160 ns_fd_ = socket(AF_INET, typ, 0);
161 PCHECK(ns_fd_ >= 0) << "socket() failed";
163 auto in4{sockaddr_in{}};
164 in4.sin_family = AF_INET;
165 in4.sin_port = htons(port);
166 CHECK_EQ(inet_pton(AF_INET, nameserver.addr,
167 reinterpret_cast<void*>(&in4.sin_addr)),
169 if (connect(ns_fd_, reinterpret_cast<const sockaddr*>(&in4),
170 sizeof(in4))) {
171 PLOG(INFO) << "connect failed " << nameserver.host << '['
172 << nameserver.addr << "]:" << nameserver.port;
173 close(ns_fd_);
174 ns_fd_ = -1;
175 continue;
178 else if (IP6::is_address(nameserver.addr)) {
179 ns_fd_ = socket(AF_INET6, typ, 0);
180 PCHECK(ns_fd_ >= 0) << "socket() failed";
182 auto in6{sockaddr_in6{}};
183 in6.sin6_family = AF_INET6;
184 in6.sin6_port = htons(port);
185 CHECK_EQ(inet_pton(AF_INET6, nameserver.addr,
186 reinterpret_cast<void*>(&in6.sin6_addr)),
188 if (connect(ns_fd_, reinterpret_cast<const sockaddr*>(&in6),
189 sizeof(in6))) {
190 PLOG(INFO) << "connect failed " << nameserver.host << '['
191 << nameserver.addr << "]:" << nameserver.port;
192 close(ns_fd_);
193 ns_fd_ = -1;
194 continue;
198 POSIX::set_nonblocking(ns_fd_);
200 if (nameserver.typ == Config::sock_type::stream) {
201 ns_sock_ = std::make_unique<Sock>(ns_fd_, ns_fd_);
202 if (FLAGS_log_dns_data) {
203 ns_sock_->log_data_on();
205 else {
206 ns_sock_->log_data_off();
209 if (port != 53) {
210 DNS::RR_collection tlsa_rrs; // empty FIXME!
211 ns_sock_->starttls_client(config_path, nullptr, nameserver.host,
212 tlsa_rrs, false);
213 if (ns_sock_->verified()) {
214 ns_fd_ = -1;
215 return;
217 close(ns_fd_);
218 ns_fd_ = -1;
219 continue;
221 ns_fd_ = -1;
224 return;
227 LOG(FATAL) << "no nameservers left to try";
230 message Resolver::xchg(message const& q)
232 if (Config::nameservers[ns_].typ == Config::sock_type::stream) {
233 CHECK_EQ(ns_fd_, -1);
235 uint16_t sz = htons(std::size(q));
237 ns_sock_->out().write(reinterpret_cast<char const*>(&sz), sizeof sz);
238 ns_sock_->out().write(reinterpret_cast<char const*>(begin(q)), size(q));
239 ns_sock_->out().flush();
241 sz = 0;
242 ns_sock_->in().read(reinterpret_cast<char*>(&sz), sizeof sz);
243 sz = ntohs(sz);
245 DNS::message::container_t bfr(sz);
246 ns_sock_->in().read(reinterpret_cast<char*>(bfr.data()), sz);
247 CHECK_EQ(ns_sock_->in().gcount(), std::streamsize(sz));
249 if (!ns_sock_->in()) {
250 LOG(WARNING) << "Resolver::xchg was able to read only "
251 << ns_sock_->in().gcount() << " octets";
254 return message{std::move(bfr)};
257 CHECK(Config::nameservers[ns_].typ == Config::sock_type::dgram);
258 CHECK_GE(ns_fd_, 0);
260 CHECK_EQ(send(ns_fd_, std::begin(q), std::size(q), 0), std::size(q));
262 DNS::message::container_t bfr(Config::max_udp_sz);
264 auto constexpr hook{[]() {}};
265 auto t_o{false};
266 auto const a_buf = reinterpret_cast<char*>(bfr.data());
267 auto const a_buflen = POSIX::read(ns_fd_, a_buf, int(Config::max_udp_sz),
268 hook, Config::read_timeout, t_o);
270 if (a_buflen < 0) {
271 LOG(WARNING) << "DNS read failed";
272 return message{0};
275 if (t_o) {
276 LOG(WARNING) << "DNS read timed out";
277 return message{0};
280 bfr.resize(a_buflen);
281 bfr.shrink_to_fit();
283 return message{std::move(bfr)};
286 RR_collection Resolver::get_records(RR_type typ, char const* name)
288 Query q(*this, typ, name);
289 return q.get_records();
292 std::vector<std::string> Resolver::get_strings(RR_type typ, char const* name)
294 Query q(*this, typ, name);
295 return q.get_strings();
298 bool Query::xchg_(Resolver& res, uint16_t id)
300 auto tries = 3;
302 while (tries) {
304 a_ = res.xchg(q_);
306 if (!size(a_)) {
307 bogus_or_indeterminate_ = true;
308 LOG(WARNING) << "no reply from nameserver";
309 return false;
312 if (size(a_) < min_message_sz()) {
313 bogus_or_indeterminate_ = true;
314 LOG(WARNING) << "packet too small";
315 return false;
318 if (a_.id() == id)
319 break;
321 LOG(WARNING) << "packet out of order; ids don't match, got " << a_.id()
322 << " expecting " << id;
323 --tries;
326 if (tries)
327 return true;
329 bogus_or_indeterminate_ = true;
330 LOG(WARNING) << "no tries left, giving up";
332 return false;
335 Query::Query(Resolver& res, RR_type type, char const* name)
336 : type_(type)
338 static_assert(std::numeric_limits<uint16_t>::min() == 0);
339 static_assert(std::numeric_limits<uint16_t>::max() == 65535);
341 uint16_t id =
342 std::experimental::randint(std::numeric_limits<uint16_t>::min(),
343 std::numeric_limits<uint16_t>::max());
345 uint16_t cls = ns_c_in;
347 q_ = create_question(name, type, cls, id);
349 if (!xchg_(res, id))
350 return;
352 if (size(a_) < min_message_sz()) {
353 bogus_or_indeterminate_ = true;
354 LOG(INFO) << "bad (or no) reply for " << name << '/' << type;
355 return;
358 check_answer(nx_domain_, bogus_or_indeterminate_, rcode_, extended_rcode_,
359 truncation_, authentic_data_, has_record_, q_, a_, type, name);
361 if (truncation_) {
362 // if UDP, retry with TCP
363 bogus_or_indeterminate_ = true;
364 LOG(INFO) << "truncated answer for " << name << '/' << type;
368 RR_collection Query::get_records()
370 if (bogus_or_indeterminate_)
371 return RR_collection{};
373 return DNS::get_records(a_, bogus_or_indeterminate_);
376 std::vector<std::string> Query::get_strings()
378 std::vector<std::string> ret;
380 auto const rr_set = get_records();
382 for (auto rr : rr_set) {
383 std::visit(
384 [&ret, type = type_](auto const& r) {
385 if (type == r.rr_type()) {
386 auto const s = r.as_str();
387 if (s)
388 ret.push_back(*s);
391 rr);
394 return ret;
397 } // namespace DNS