3 #include "DNS-iostream.hpp"
13 #include <arpa/nameser.h>
15 #include <experimental/random>
17 #include <glog/logging.h>
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
};
31 char const* host
; // name used to match cert
37 constexpr nameserver nameservers
[]{
72 "2001:4860:4860::8888",
78 "2001:4860:4860::8844",
83 "1dot1dot1dot1.cloudflare-dns.com",
89 "1dot1dot1dot1.cloudflare-dns.com",
90 "2606:4700:4700::1111",
95 "1dot1dot1dot1.cloudflare-dns.com",
96 "2606:4700:4700::1001",
126 } // namespace Config
128 template <typename T
, std::size_t N
>
129 constexpr std::size_t countof(T
const (&)[N
]) noexcept
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));
145 // try the next one, with wrap
146 if (++ns_
== countof(Config::nameservers
))
149 auto const& nameserver
= Config::nameservers
[ns_
];
151 auto typ
= (nameserver
.typ
== Config::sock_type::stream
) ? SOCK_STREAM
153 uint16_t port
= osutil::get_port(nameserver
.port
,
154 (typ
== SOCK_STREAM
) ? "tcp" : "udp");
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
),
169 PLOG(INFO
) << "connect failed " << nameserver
.host
<< '['
170 << nameserver
.addr
<< "]:" << nameserver
.port
;
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
),
188 PLOG(INFO
) << "connect failed " << nameserver
.host
<< '['
189 << nameserver
.addr
<< "]:" << nameserver
.port
;
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_
);
202 DNS::RR_collection tlsa_rrs
; // empty FIXME!
203 ns_sock_
->starttls_client(config_path
, nullptr, nameserver
.host
,
205 if (ns_sock_
->verified()) {
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();
234 ns_sock_
->in().read(reinterpret_cast<char*>(&sz
), sizeof 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
);
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
{[]() {}};
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
);
263 LOG(WARNING
) << "DNS read failed";
268 LOG(WARNING
) << "DNS read timed out";
272 bfr
.resize(a_buflen
);
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
)
299 bogus_or_indeterminate_
= true;
300 LOG(WARNING
) << "no reply from nameserver";
304 if (size(a_
) < min_udp_sz()) {
305 bogus_or_indeterminate_
= true;
306 LOG(WARNING
) << "packet too small";
313 LOG(WARNING
) << "packet out of order; ids don't match, got " << a_
.id()
314 << " expecting " << id
;
321 bogus_or_indeterminate_
= true;
322 LOG(WARNING
) << "no tries left, giving up";
327 Query::Query(Resolver
& res
, RR_type type
, char const* name
)
330 static_assert(std::numeric_limits
<uint16_t>::min() == 0);
331 static_assert(std::numeric_limits
<uint16_t>::max() == 65535);
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
);
343 if (size(a_
) < min_udp_sz()) {
344 bogus_or_indeterminate_
= true;
345 LOG(INFO
) << "bad (or no) reply for " << name
<< '/' << type
;
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
) {
369 [&ret
, type
= type_
](auto const& r
) {
370 if (type
== r
.rr_type()) {
371 auto const s
= r
.as_str();