bogus string that are not nullptr
[ghsmtp.git] / DNS.cpp
blobcdf852a42719599d60584d0ab90ccf22ef9c7611
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 namespace Config {
22 // The default timeout in glibc is 5 seconds. My setup with unbound
23 // in front of stubby with DNSSEC checking and all that seems to work
24 // better with just a little more time.
26 auto constexpr read_timeout{std::chrono::seconds(7)};
28 enum class sock_type : bool { stream, dgram };
30 struct nameserver {
31 char const* host; // name used to match cert
32 char const* addr;
33 char const* port;
34 sock_type typ;
37 constexpr nameserver nameservers[]{
39 "localhost",
40 "127.0.0.1",
41 "domain",
42 sock_type::dgram,
45 "localhost",
46 "::1",
47 "domain",
48 sock_type::dgram,
52 "one.one.one.one",
53 "1.1.1.1",
54 "domain",
55 sock_type::dgram,
58 "dns.google",
59 "8.8.8.8",
60 "domain",
61 sock_type::dgram,
64 "dns.google",
65 "8.8.4.4",
66 "domain",
67 sock_type::dgram,
71 "dns.google",
72 "2001:4860:4860::8888",
73 "domain",
74 sock_type::dgram,
77 "dns.google",
78 "2001:4860:4860::8844",
79 "domain",
80 sock_type::dgram,
83 "1dot1dot1dot1.cloudflare-dns.com",
84 "1.0.0.1",
85 "domain-s",
86 sock_type::stream,
89 "1dot1dot1dot1.cloudflare-dns.com",
90 "2606:4700:4700::1111",
91 "domain-s",
92 sock_type::stream,
95 "1dot1dot1dot1.cloudflare-dns.com",
96 "2606:4700:4700::1001",
97 "domain-s",
98 sock_type::stream,
101 "dns9.quad9.net",
102 "9.9.9.9",
103 "domain-s",
104 sock_type::stream,
107 "dns10.quad9.net",
108 "9.9.9.10",
109 "domain-s",
110 sock_type::stream,
113 "dns10.quad9.net",
114 "149.112.112.10",
115 "domain-s",
116 sock_type::stream,
119 "dns10.quad9.net",
120 "2620:fe::10",
121 "domain-s",
122 sock_type::stream,
126 } // namespace Config
128 template <typename T, std::size_t N>
129 constexpr std::size_t countof(T const (&)[N]) noexcept
131 return N;
134 namespace DNS {
136 Resolver::Resolver(fs::path config_path)
138 auto tries = countof(Config::nameservers);
140 ns_ = std::experimental::randint(
141 0, static_cast<int>(countof(Config::nameservers) - 1));
143 while (tries--) {
145 // try the next one, with wrap
146 if (++ns_ == countof(Config::nameservers))
147 ns_ = 0;
149 auto const& nameserver = Config::nameservers[ns_];
151 auto typ = (nameserver.typ == Config::sock_type::stream) ? SOCK_STREAM
152 : SOCK_DGRAM;
153 uint16_t port = osutil::get_port(nameserver.port,
154 (typ == SOCK_STREAM) ? "tcp" : "udp");
155 ns_fd_ = -1;
157 if (IP4::is_address(nameserver.addr)) {
158 ns_fd_ = socket(AF_INET, typ, 0);
159 PCHECK(ns_fd_ >= 0) << "socket() failed";
161 auto in4{sockaddr_in{}};
162 in4.sin_family = AF_INET;
163 in4.sin_port = htons(port);
164 CHECK_EQ(inet_pton(AF_INET, nameserver.addr,
165 reinterpret_cast<void*>(&in4.sin_addr)),
167 if (connect(ns_fd_, reinterpret_cast<const sockaddr*>(&in4),
168 sizeof(in4))) {
169 PLOG(INFO) << "connect failed " << nameserver.host << '['
170 << nameserver.addr << "]:" << nameserver.port;
171 close(ns_fd_);
172 ns_fd_ = -1;
173 continue;
176 else if (IP6::is_address(nameserver.addr)) {
177 ns_fd_ = socket(AF_INET6, typ, 0);
178 PCHECK(ns_fd_ >= 0) << "socket() failed";
180 auto in6{sockaddr_in6{}};
181 in6.sin6_family = AF_INET6;
182 in6.sin6_port = htons(port);
183 CHECK_EQ(inet_pton(AF_INET6, nameserver.addr,
184 reinterpret_cast<void*>(&in6.sin6_addr)),
186 if (connect(ns_fd_, reinterpret_cast<const sockaddr*>(&in6),
187 sizeof(in6))) {
188 PLOG(INFO) << "connect failed " << nameserver.host << '['
189 << nameserver.addr << "]:" << nameserver.port;
190 close(ns_fd_);
191 ns_fd_ = -1;
192 continue;
196 POSIX::set_nonblocking(ns_fd_);
198 if (nameserver.typ == Config::sock_type::stream) {
199 ns_sock_ = std::make_unique<Sock>(ns_fd_, ns_fd_);
201 if (port != 53) {
202 DNS::RR_collection tlsa_rrs; // empty FIXME!
203 ns_sock_->starttls_client(config_path, nullptr, nameserver.host,
204 tlsa_rrs, false);
205 if (ns_sock_->verified()) {
206 ns_fd_ = -1;
207 return;
209 close(ns_fd_);
210 ns_fd_ = -1;
211 continue;
213 ns_fd_ = -1;
216 return;
219 LOG(FATAL) << "no nameservers left to try";
222 message Resolver::xchg(message const& q)
224 if (Config::nameservers[ns_].typ == Config::sock_type::stream) {
225 CHECK_EQ(ns_fd_, -1);
227 uint16_t sz = htons(std::size(q));
229 ns_sock_->out().write(reinterpret_cast<char const*>(&sz), sizeof sz);
230 ns_sock_->out().write(reinterpret_cast<char const*>(begin(q)), size(q));
231 ns_sock_->out().flush();
233 sz = 0;
234 ns_sock_->in().read(reinterpret_cast<char*>(&sz), sizeof sz);
235 sz = ntohs(sz);
237 DNS::message::container_t bfr(sz);
238 ns_sock_->in().read(reinterpret_cast<char*>(bfr.data()), sz);
239 CHECK_EQ(ns_sock_->in().gcount(), std::streamsize(sz));
241 if (!ns_sock_->in()) {
242 LOG(WARNING) << "Resolver::xchg was able to read only "
243 << ns_sock_->in().gcount() << " octets";
246 return message{std::move(bfr)};
249 CHECK(Config::nameservers[ns_].typ == Config::sock_type::dgram);
250 CHECK_GE(ns_fd_, 0);
252 CHECK_EQ(send(ns_fd_, std::begin(q), std::size(q), 0), std::size(q));
254 DNS::message::container_t bfr(Config::max_udp_sz);
256 auto constexpr hook{[]() {}};
257 auto t_o{false};
258 auto const a_buf = reinterpret_cast<char*>(bfr.data());
259 auto const a_buflen = POSIX::read(ns_fd_, a_buf, int(Config::max_udp_sz),
260 hook, Config::read_timeout, t_o);
262 if (a_buflen < 0) {
263 LOG(WARNING) << "DNS read failed";
264 return message{0};
267 if (t_o) {
268 LOG(WARNING) << "DNS read timed out";
269 return message{0};
272 bfr.resize(a_buflen);
273 bfr.shrink_to_fit();
275 return message{std::move(bfr)};
278 RR_collection Resolver::get_records(RR_type typ, char const* name)
280 Query q(*this, typ, name);
281 return q.get_records();
284 std::vector<std::string> Resolver::get_strings(RR_type typ, char const* name)
286 Query q(*this, typ, name);
287 return q.get_strings();
290 bool Query::xchg_(Resolver& res, uint16_t id)
292 auto tries = 3;
294 while (tries) {
296 a_ = res.xchg(q_);
298 if (!size(a_)) {
299 bogus_or_indeterminate_ = true;
300 LOG(WARNING) << "no reply from nameserver";
301 return false;
304 if (size(a_) < min_udp_sz()) {
305 bogus_or_indeterminate_ = true;
306 LOG(WARNING) << "packet too small";
307 return false;
310 if (a_.id() == id)
311 break;
313 LOG(WARNING) << "packet out of order; ids don't match, got " << a_.id()
314 << " expecting " << id;
315 --tries;
318 if (tries)
319 return true;
321 bogus_or_indeterminate_ = true;
322 LOG(WARNING) << "no tries left, giving up";
324 return false;
327 Query::Query(Resolver& res, RR_type type, char const* name)
328 : type_(type)
330 static_assert(std::numeric_limits<uint16_t>::min() == 0);
331 static_assert(std::numeric_limits<uint16_t>::max() == 65535);
333 uint16_t id
334 = std::experimental::randint(std::numeric_limits<uint16_t>::min(),
335 std::numeric_limits<uint16_t>::max());
337 uint16_t cls = ns_c_in;
338 q_ = create_question(name, type, cls, id);
340 if (!xchg_(res, id))
341 return;
343 if (size(a_) < min_udp_sz()) {
344 bogus_or_indeterminate_ = true;
345 LOG(INFO) << "bad (or no) reply for " << name << '/' << type;
346 return;
349 check_answer(nx_domain_, bogus_or_indeterminate_, rcode_, extended_rcode_,
350 authentic_data_, has_record_, q_, a_, type, name);
353 RR_collection Query::get_records()
355 if (bogus_or_indeterminate_)
356 return RR_collection{};
358 return DNS::get_records(a_, bogus_or_indeterminate_);
361 std::vector<std::string> Query::get_strings()
363 std::vector<std::string> ret;
365 auto const rr_set = get_records();
367 for (auto rr : rr_set) {
368 std::visit(
369 [&ret, type = type_](auto const& r) {
370 if (type == r.rr_type()) {
371 auto const s = r.as_str();
372 if (s)
373 ret.push_back(*s);
376 rr);
379 return ret;
382 } // namespace DNS