CRLF
[ghsmtp.git] / DNS.cpp
blob577935c58322044f270e66aa168b01414eaeb8a8
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 <glog/logging.h>
17 #include "osutil.hpp"
19 DEFINE_bool(log_dns_data, false, "log all DNS TCP protocol data");
20 DEFINE_bool(random_dns_servers, false, "Pick starting DNS server at random");
22 namespace Config {
23 // The default timeout in glibc is 5 seconds. My setup with unbound
24 // in front of stubby with DNSSEC checking and all that seems to work
25 // better with just a little more time.
27 auto constexpr read_timeout{std::chrono::seconds(7)};
29 enum class sock_type : bool { stream, dgram };
31 struct nameserver {
32 char const* host; // name used to match cert
33 char const* addr;
34 char const* port;
35 sock_type typ;
38 constexpr nameserver nameservers[]{
40 "localhost",
41 "127.0.0.1",
42 "domain",
43 sock_type::stream,
47 "localhost",
48 "::1",
49 "domain",
50 sock_type::stream,
53 "one.one.one.one",
54 "1.1.1.1",
55 "domain-s",
56 sock_type::stream,
59 "one.one.one.one",
60 "1.1.1.1",
61 "domain",
62 sock_type::dgram,
65 "dns.google",
66 "8.8.8.8",
67 "domain",
68 sock_type::dgram,
71 "dns.google",
72 "8.8.4.4",
73 "domain",
74 sock_type::dgram,
78 "dns.google",
79 "2001:4860:4860::8888",
80 "domain",
81 sock_type::dgram,
84 "dns.google",
85 "2001:4860:4860::8844",
86 "domain",
87 sock_type::dgram,
90 "1dot1dot1dot1.cloudflare-dns.com",
91 "1.0.0.1",
92 "domain-s",
93 sock_type::stream,
96 "1dot1dot1dot1.cloudflare-dns.com",
97 "2606:4700:4700::1111",
98 "domain-s",
99 sock_type::stream,
102 "1dot1dot1dot1.cloudflare-dns.com",
103 "2606:4700:4700::1001",
104 "domain-s",
105 sock_type::stream,
108 "dns9.quad9.net",
109 "9.9.9.9",
110 "domain-s",
111 sock_type::stream,
114 "dns10.quad9.net",
115 "9.9.9.10",
116 "domain-s",
117 sock_type::stream,
120 "dns10.quad9.net",
121 "149.112.112.10",
122 "domain-s",
123 sock_type::stream,
126 "dns10.quad9.net",
127 "2620:fe::10",
128 "domain-s",
129 sock_type::stream,
133 } // namespace Config
135 template <typename T, std::size_t N>
136 constexpr std::size_t countof(T const (&)[N]) noexcept
138 return N;
141 namespace DNS {
143 Resolver::Resolver(fs::path config_path)
145 auto tries = countof(Config::nameservers);
147 if (FLAGS_random_dns_servers) {
148 std::uniform_int_distribution<int> uniform_dist(
149 0, countof(Config::nameservers) - 1);
150 ns_ = uniform_dist(rng_);
152 else {
153 ns_ = static_cast<int>(countof(Config::nameservers) - 1);
156 while (tries--) {
158 // try the next one, with wrap
159 if (++ns_ == countof(Config::nameservers))
160 ns_ = 0;
162 auto const& nameserver = Config::nameservers[ns_];
163 auto typ = (nameserver.typ == Config::sock_type::stream) ? SOCK_STREAM
164 : SOCK_DGRAM;
166 uint16_t port =
167 osutil::get_port(nameserver.port, (typ == SOCK_STREAM) ? "tcp" : "udp");
168 ns_fd_ = -1;
170 if (IP4::is_address(nameserver.addr)) {
171 ns_fd_ = socket(AF_INET, typ, 0);
172 PCHECK(ns_fd_ >= 0) << "socket() failed";
174 auto in4{sockaddr_in{}};
175 in4.sin_family = AF_INET;
176 in4.sin_port = htons(port);
177 CHECK_EQ(inet_pton(AF_INET, nameserver.addr,
178 reinterpret_cast<void*>(&in4.sin_addr)),
180 if (connect(ns_fd_, reinterpret_cast<const sockaddr*>(&in4),
181 sizeof(in4))) {
182 PLOG(INFO) << "connect failed " << nameserver.host << '['
183 << nameserver.addr << "]:" << nameserver.port;
184 close(ns_fd_);
185 ns_fd_ = -1;
186 continue;
189 else if (IP6::is_address(nameserver.addr)) {
190 ns_fd_ = socket(AF_INET6, typ, 0);
191 PCHECK(ns_fd_ >= 0) << "socket() failed";
193 auto in6{sockaddr_in6{}};
194 in6.sin6_family = AF_INET6;
195 in6.sin6_port = htons(port);
196 CHECK_EQ(inet_pton(AF_INET6, nameserver.addr,
197 reinterpret_cast<void*>(&in6.sin6_addr)),
199 if (connect(ns_fd_, reinterpret_cast<const sockaddr*>(&in6),
200 sizeof(in6))) {
201 PLOG(INFO) << "connect failed " << nameserver.host << '['
202 << nameserver.addr << "]:" << nameserver.port;
203 close(ns_fd_);
204 ns_fd_ = -1;
205 continue;
209 POSIX::set_nonblocking(ns_fd_);
211 if (nameserver.typ == Config::sock_type::stream) {
212 ns_sock_ = std::make_unique<Sock>(ns_fd_, ns_fd_);
213 if (FLAGS_log_dns_data) {
214 ns_sock_->log_data_on();
216 else {
217 ns_sock_->log_data_off();
220 if (port != 53) {
221 DNS::RR_collection tlsa_rrs; // empty FIXME!
222 ns_sock_->starttls_client(config_path, nullptr, nameserver.host,
223 tlsa_rrs, false);
224 if (ns_sock_->verified()) {
225 ns_fd_ = -1;
226 return;
228 close(ns_fd_);
229 ns_fd_ = -1;
230 continue;
232 ns_fd_ = -1;
235 return;
238 LOG(FATAL) << "no nameservers left to try";
241 message Resolver::xchg(message const& q)
243 if (Config::nameservers[ns_].typ == Config::sock_type::stream) {
244 CHECK_EQ(ns_fd_, -1);
246 uint16_t sz = htons(std::size(q));
248 ns_sock_->out().write(reinterpret_cast<char const*>(&sz), sizeof sz);
249 ns_sock_->out().write(reinterpret_cast<char const*>(begin(q)), size(q));
250 ns_sock_->out().flush();
252 sz = 0;
253 ns_sock_->in().read(reinterpret_cast<char*>(&sz), sizeof sz);
254 sz = ntohs(sz);
256 DNS::message::container_t bfr(sz);
257 ns_sock_->in().read(reinterpret_cast<char*>(bfr.data()), sz);
258 CHECK_EQ(ns_sock_->in().gcount(), std::streamsize(sz));
260 if (!ns_sock_->in()) {
261 LOG(WARNING) << "Resolver::xchg was able to read only "
262 << ns_sock_->in().gcount() << " octets";
265 return message{std::move(bfr)};
268 CHECK(Config::nameservers[ns_].typ == Config::sock_type::dgram);
269 CHECK_GE(ns_fd_, 0);
271 CHECK_EQ(send(ns_fd_, std::begin(q), std::size(q), 0), std::size(q));
273 DNS::message::container_t bfr(Config::max_udp_sz);
275 auto constexpr hook{[]() {}};
276 auto t_o{false};
277 auto const a_buf = reinterpret_cast<char*>(bfr.data());
278 auto const a_buflen = POSIX::read(ns_fd_, a_buf, int(Config::max_udp_sz),
279 hook, Config::read_timeout, t_o);
281 if (a_buflen < 0) {
282 LOG(WARNING) << "DNS read failed";
283 return message{0};
286 if (t_o) {
287 LOG(WARNING) << "DNS read timed out";
288 return message{0};
291 bfr.resize(a_buflen);
292 bfr.shrink_to_fit();
294 return message{std::move(bfr)};
297 RR_collection Resolver::get_records(RR_type typ, char const* name)
299 Query q(*this, typ, name);
300 return q.get_records();
303 std::vector<std::string> Resolver::get_strings(RR_type typ, char const* name)
305 Query q(*this, typ, name);
306 return q.get_strings();
309 bool Query::xchg_(Resolver& res, uint16_t id)
311 auto tries = 3;
313 while (tries) {
315 a_ = res.xchg(q_);
317 if (!size(a_)) {
318 bogus_or_indeterminate_ = true;
319 LOG(WARNING) << "no reply from nameserver";
320 return false;
323 if (size(a_) < min_message_sz()) {
324 bogus_or_indeterminate_ = true;
325 LOG(WARNING) << "packet too small";
326 return false;
329 if (a_.id() == id)
330 break;
332 LOG(WARNING) << "packet out of order; ids don't match, got " << a_.id()
333 << " expecting " << id;
334 --tries;
337 if (tries)
338 return true;
340 bogus_or_indeterminate_ = true;
341 LOG(WARNING) << "no tries left, giving up";
343 return false;
346 Query::Query(Resolver& res, RR_type type, char const* name)
347 : type_(type)
349 uint16_t id = res.rnd_id();
350 uint16_t cls = ns_c_in;
352 q_ = create_question(name, type, cls, id);
354 if (!xchg_(res, id))
355 return;
357 if (size(a_) < min_message_sz()) {
358 bogus_or_indeterminate_ = true;
359 LOG(INFO) << "bad (or no) reply for " << name << '/' << type;
360 return;
363 check_answer(nx_domain_, bogus_or_indeterminate_, rcode_, extended_rcode_,
364 truncation_, authentic_data_, has_record_, q_, a_, type, name);
366 if (truncation_) {
367 // if UDP, retry with TCP
368 bogus_or_indeterminate_ = true;
369 LOG(INFO) << "truncated answer for " << name << '/' << type;
373 RR_collection Query::get_records()
375 if (bogus_or_indeterminate_)
376 return RR_collection{};
378 return DNS::get_records(a_, bogus_or_indeterminate_);
381 std::vector<std::string> Query::get_strings()
383 std::vector<std::string> ret;
385 auto const rr_set = get_records();
387 for (auto rr : rr_set) {
388 std::visit(
389 [&ret, type = type_](auto const& r) {
390 if (type == r.rr_type()) {
391 auto const s = r.as_str();
392 if (s)
393 ret.push_back(*s);
396 rr);
399 return ret;
402 } // namespace DNS