more system calls for sanitize
[ghsmtp.git] / DNS.cpp
blobba426e32e8d6e4ed7f99389be610189c8537e93e
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");
22 DEFINE_bool(random_dns_servers, false, "Pick starting DNS server at random");
24 namespace Config {
25 // The default timeout in glibc is 5 seconds. My setup with unbound
26 // in front of stubby with DNSSEC checking and all that seems to work
27 // better with just a little more time.
29 auto constexpr read_timeout{std::chrono::seconds(7)};
31 enum class sock_type : bool { stream, dgram };
33 struct nameserver {
34 char const* host; // name used to match cert
35 char const* addr;
36 char const* port;
37 sock_type typ;
40 constexpr nameserver nameservers[]{
42 "localhost",
43 "127.0.0.1",
44 "domain",
45 sock_type::stream,
49 "localhost",
50 "::1",
51 "domain",
52 sock_type::stream,
55 "one.one.one.one",
56 "1.1.1.1",
57 "domain-s",
58 sock_type::stream,
61 "one.one.one.one",
62 "1.1.1.1",
63 "domain",
64 sock_type::dgram,
67 "dns.google",
68 "8.8.8.8",
69 "domain",
70 sock_type::dgram,
73 "dns.google",
74 "8.8.4.4",
75 "domain",
76 sock_type::dgram,
80 "dns.google",
81 "2001:4860:4860::8888",
82 "domain",
83 sock_type::dgram,
86 "dns.google",
87 "2001:4860:4860::8844",
88 "domain",
89 sock_type::dgram,
92 "1dot1dot1dot1.cloudflare-dns.com",
93 "1.0.0.1",
94 "domain-s",
95 sock_type::stream,
98 "1dot1dot1dot1.cloudflare-dns.com",
99 "2606:4700:4700::1111",
100 "domain-s",
101 sock_type::stream,
104 "1dot1dot1dot1.cloudflare-dns.com",
105 "2606:4700:4700::1001",
106 "domain-s",
107 sock_type::stream,
110 "dns9.quad9.net",
111 "9.9.9.9",
112 "domain-s",
113 sock_type::stream,
116 "dns10.quad9.net",
117 "9.9.9.10",
118 "domain-s",
119 sock_type::stream,
122 "dns10.quad9.net",
123 "149.112.112.10",
124 "domain-s",
125 sock_type::stream,
128 "dns10.quad9.net",
129 "2620:fe::10",
130 "domain-s",
131 sock_type::stream,
135 } // namespace Config
137 template <typename T, std::size_t N>
138 constexpr std::size_t countof(T const (&)[N]) noexcept
140 return N;
143 namespace DNS {
145 Resolver::Resolver(fs::path config_path)
147 auto tries = countof(Config::nameservers);
149 if (FLAGS_random_dns_servers) {
150 ns_ = std::experimental::randint(
151 0, static_cast<int>(countof(Config::nameservers) - 1));
153 else {
154 ns_ = static_cast<int>(countof(Config::nameservers) - 1);
157 while (tries--) {
159 // try the next one, with wrap
160 if (++ns_ == countof(Config::nameservers))
161 ns_ = 0;
163 auto const& nameserver = Config::nameservers[ns_];
164 auto typ = (nameserver.typ == Config::sock_type::stream) ? SOCK_STREAM
165 : SOCK_DGRAM;
167 uint16_t port =
168 osutil::get_port(nameserver.port, (typ == SOCK_STREAM) ? "tcp" : "udp");
169 ns_fd_ = -1;
171 if (IP4::is_address(nameserver.addr)) {
172 ns_fd_ = socket(AF_INET, typ, 0);
173 PCHECK(ns_fd_ >= 0) << "socket() failed";
175 auto in4{sockaddr_in{}};
176 in4.sin_family = AF_INET;
177 in4.sin_port = htons(port);
178 CHECK_EQ(inet_pton(AF_INET, nameserver.addr,
179 reinterpret_cast<void*>(&in4.sin_addr)),
181 if (connect(ns_fd_, reinterpret_cast<const sockaddr*>(&in4),
182 sizeof(in4))) {
183 PLOG(INFO) << "connect failed " << nameserver.host << '['
184 << nameserver.addr << "]:" << nameserver.port;
185 close(ns_fd_);
186 ns_fd_ = -1;
187 continue;
190 else if (IP6::is_address(nameserver.addr)) {
191 ns_fd_ = socket(AF_INET6, typ, 0);
192 PCHECK(ns_fd_ >= 0) << "socket() failed";
194 auto in6{sockaddr_in6{}};
195 in6.sin6_family = AF_INET6;
196 in6.sin6_port = htons(port);
197 CHECK_EQ(inet_pton(AF_INET6, nameserver.addr,
198 reinterpret_cast<void*>(&in6.sin6_addr)),
200 if (connect(ns_fd_, reinterpret_cast<const sockaddr*>(&in6),
201 sizeof(in6))) {
202 PLOG(INFO) << "connect failed " << nameserver.host << '['
203 << nameserver.addr << "]:" << nameserver.port;
204 close(ns_fd_);
205 ns_fd_ = -1;
206 continue;
210 POSIX::set_nonblocking(ns_fd_);
212 if (nameserver.typ == Config::sock_type::stream) {
213 ns_sock_ = std::make_unique<Sock>(ns_fd_, ns_fd_);
214 if (FLAGS_log_dns_data) {
215 ns_sock_->log_data_on();
217 else {
218 ns_sock_->log_data_off();
221 if (port != 53) {
222 DNS::RR_collection tlsa_rrs; // empty FIXME!
223 ns_sock_->starttls_client(config_path, nullptr, nameserver.host,
224 tlsa_rrs, false);
225 if (ns_sock_->verified()) {
226 ns_fd_ = -1;
227 return;
229 close(ns_fd_);
230 ns_fd_ = -1;
231 continue;
233 ns_fd_ = -1;
236 return;
239 LOG(FATAL) << "no nameservers left to try";
242 message Resolver::xchg(message const& q)
244 if (Config::nameservers[ns_].typ == Config::sock_type::stream) {
245 CHECK_EQ(ns_fd_, -1);
247 uint16_t sz = htons(std::size(q));
249 ns_sock_->out().write(reinterpret_cast<char const*>(&sz), sizeof sz);
250 ns_sock_->out().write(reinterpret_cast<char const*>(begin(q)), size(q));
251 ns_sock_->out().flush();
253 sz = 0;
254 ns_sock_->in().read(reinterpret_cast<char*>(&sz), sizeof sz);
255 sz = ntohs(sz);
257 DNS::message::container_t bfr(sz);
258 ns_sock_->in().read(reinterpret_cast<char*>(bfr.data()), sz);
259 CHECK_EQ(ns_sock_->in().gcount(), std::streamsize(sz));
261 if (!ns_sock_->in()) {
262 LOG(WARNING) << "Resolver::xchg was able to read only "
263 << ns_sock_->in().gcount() << " octets";
266 return message{std::move(bfr)};
269 CHECK(Config::nameservers[ns_].typ == Config::sock_type::dgram);
270 CHECK_GE(ns_fd_, 0);
272 CHECK_EQ(send(ns_fd_, std::begin(q), std::size(q), 0), std::size(q));
274 DNS::message::container_t bfr(Config::max_udp_sz);
276 auto constexpr hook{[]() {}};
277 auto t_o{false};
278 auto const a_buf = reinterpret_cast<char*>(bfr.data());
279 auto const a_buflen = POSIX::read(ns_fd_, a_buf, int(Config::max_udp_sz),
280 hook, Config::read_timeout, t_o);
282 if (a_buflen < 0) {
283 LOG(WARNING) << "DNS read failed";
284 return message{0};
287 if (t_o) {
288 LOG(WARNING) << "DNS read timed out";
289 return message{0};
292 bfr.resize(a_buflen);
293 bfr.shrink_to_fit();
295 return message{std::move(bfr)};
298 RR_collection Resolver::get_records(RR_type typ, char const* name)
300 Query q(*this, typ, name);
301 return q.get_records();
304 std::vector<std::string> Resolver::get_strings(RR_type typ, char const* name)
306 Query q(*this, typ, name);
307 return q.get_strings();
310 bool Query::xchg_(Resolver& res, uint16_t id)
312 auto tries = 3;
314 while (tries) {
316 a_ = res.xchg(q_);
318 if (!size(a_)) {
319 bogus_or_indeterminate_ = true;
320 LOG(WARNING) << "no reply from nameserver";
321 return false;
324 if (size(a_) < min_message_sz()) {
325 bogus_or_indeterminate_ = true;
326 LOG(WARNING) << "packet too small";
327 return false;
330 if (a_.id() == id)
331 break;
333 LOG(WARNING) << "packet out of order; ids don't match, got " << a_.id()
334 << " expecting " << id;
335 --tries;
338 if (tries)
339 return true;
341 bogus_or_indeterminate_ = true;
342 LOG(WARNING) << "no tries left, giving up";
344 return false;
347 Query::Query(Resolver& res, RR_type type, char const* name)
348 : type_(type)
350 static_assert(std::numeric_limits<uint16_t>::min() == 0);
351 static_assert(std::numeric_limits<uint16_t>::max() == 65535);
353 uint16_t id =
354 std::experimental::randint(std::numeric_limits<uint16_t>::min(),
355 std::numeric_limits<uint16_t>::max());
357 uint16_t cls = ns_c_in;
359 q_ = create_question(name, type, cls, id);
361 if (!xchg_(res, id))
362 return;
364 if (size(a_) < min_message_sz()) {
365 bogus_or_indeterminate_ = true;
366 LOG(INFO) << "bad (or no) reply for " << name << '/' << type;
367 return;
370 check_answer(nx_domain_, bogus_or_indeterminate_, rcode_, extended_rcode_,
371 truncation_, authentic_data_, has_record_, q_, a_, type, name);
373 if (truncation_) {
374 // if UDP, retry with TCP
375 bogus_or_indeterminate_ = true;
376 LOG(INFO) << "truncated answer for " << name << '/' << type;
380 RR_collection Query::get_records()
382 if (bogus_or_indeterminate_)
383 return RR_collection{};
385 return DNS::get_records(a_, bogus_or_indeterminate_);
388 std::vector<std::string> Query::get_strings()
390 std::vector<std::string> ret;
392 auto const rr_set = get_records();
394 for (auto rr : rr_set) {
395 std::visit(
396 [&ret, type = type_](auto const& r) {
397 if (type == r.rr_type()) {
398 auto const s = r.as_str();
399 if (s)
400 ret.push_back(*s);
403 rr);
406 return ret;
409 } // namespace DNS