Line data Source code
1 : #include "fd_adns.h"
2 : #include "fd_netdb.h"
3 : #include "fd_lookup.h"
4 : #include "../../util/log/fd_log.h"
5 : #include "../../util/io/fd_io.h"
6 :
7 : #include <errno.h>
8 : #include <unistd.h>
9 : #include <sys/epoll.h>
10 : #include <sys/socket.h>
11 : #include <netinet/in.h>
12 :
13 : /* Queries advertise EDNS0 with this UDP payload capacity (the DNS
14 : flag day 2020 recommendation: large enough that an A answer never
15 : truncates, small enough to avoid IP fragmentation). */
16 : #define ANSWER_MTU (1232UL)
17 :
18 : /* EDNS0 OPT pseudo-RR appended to each query: root name, TYPE=OPT,
19 : CLASS=payload capacity, TTL=0 (no extended flags), RDLEN=0. */
20 0 : #define OPT_RR_SZ (11UL)
21 : static uchar const opt_rr[ OPT_RR_SZ ] = { 0, 0, 41, (uchar)(ANSWER_MTU>>8), (uchar)(ANSWER_MTU&0xFF), 0, 0, 0, 0, 0, 0 };
22 :
23 : /* Bound receive work per advance so a UDP flood at our ephemeral
24 : port cannot starve the caller's run loop. */
25 0 : #define RECV_BURST_MAX (64UL)
26 :
27 : FD_TL ushort fd_adns_ns_port = 53;
28 :
29 0 : #define NS_MAX (3UL) /* MAXNS */
30 :
31 0 : #define REQ_STATE_IDLE (0)
32 0 : #define REQ_STATE_PENDING (1)
33 0 : #define REQ_STATE_DONE (2)
34 :
35 : struct fd_adns_req {
36 : int state;
37 : uchar query[ FD_DNS_QUERY_MTU ];
38 : int qlen;
39 : int no_edns; /* server answered FORMERR/NOTIMP to EDNS0 */
40 : long deadline_nanos; /* next (re)send */
41 : uint sends_left;
42 : fd_adns_result_t result;
43 : };
44 :
45 : typedef struct fd_adns_req fd_adns_req_t;
46 :
47 : struct fd_adns_private {
48 : int fd;
49 :
50 : uint ns[ NS_MAX ];
51 : ulong ns_cnt;
52 : long retry_nanos;
53 : uint attempts;
54 :
55 : ulong max;
56 : ulong active_cnt; /* PENDING+DONE */
57 : ulong pending_cnt; /* PENDING only */
58 : fd_adns_req_t * reqs;
59 :
60 : ulong magic;
61 : };
62 :
63 : FD_FN_CONST ulong
64 0 : fd_adns_align( void ) {
65 0 : return fd_ulong_max( alignof(fd_adns_t), alignof(fd_adns_req_t) );
66 0 : }
67 :
68 : FD_FN_CONST ulong
69 0 : fd_adns_footprint( ulong max_reqs ) {
70 0 : ulong l = FD_LAYOUT_INIT;
71 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_adns_t), sizeof(fd_adns_t) );
72 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_adns_req_t), max_reqs*sizeof(fd_adns_req_t) );
73 0 : return FD_LAYOUT_FINI( l, fd_adns_align() );
74 0 : }
75 :
76 : void *
77 : fd_adns_new( void * shmem,
78 : ulong max_reqs,
79 0 : int epoll_fd ) {
80 0 : if( FD_UNLIKELY( !shmem ) ) {
81 0 : FD_LOG_WARNING(( "NULL shmem" ));
82 0 : return NULL;
83 0 : }
84 :
85 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_adns_align() ) ) ) {
86 0 : FD_LOG_WARNING(( "unaligned shmem" ));
87 0 : return NULL;
88 0 : }
89 :
90 0 : if( FD_UNLIKELY( !max_reqs ) ) {
91 0 : FD_LOG_WARNING(( "max_reqs must be at least 1" ));
92 0 : return NULL;
93 0 : }
94 :
95 0 : if( FD_UNLIKELY( epoll_fd==-1 ) ) {
96 0 : FD_LOG_WARNING(( "invalid epoll_fd" ));
97 0 : return NULL;
98 0 : }
99 :
100 0 : FD_SCRATCH_ALLOC_INIT( l, shmem );
101 0 : fd_adns_t * adns = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_adns_t), sizeof(fd_adns_t) );
102 0 : adns->reqs = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_adns_req_t), max_reqs*sizeof(fd_adns_req_t) );
103 :
104 0 : adns->max = max_reqs;
105 0 : adns->active_cnt = 0UL;
106 0 : adns->pending_cnt = 0UL;
107 0 : for( ulong i=0UL; i<max_reqs; i++ ) adns->reqs[ i ].state = REQ_STATE_IDLE;
108 :
109 : /* resolv.conf: nameservers plus retry cadence (timeout is the total
110 : budget spread over attempts, like the blocking resolver). */
111 0 : uint ns[ NS_MAX ];
112 0 : uint timeout = 5U;
113 0 : uint attempts = 2U;
114 0 : int ns_cnt = fd_dns_nameservers( ns, NS_MAX, &timeout, &attempts );
115 0 : if( FD_UNLIKELY( ns_cnt<=0 ) ) {
116 0 : FD_LOG_WARNING(( "no usable IPv4 nameservers in /etc/resolv.conf" ));
117 0 : ns_cnt = 0;
118 0 : }
119 0 : adns->ns_cnt = (ulong)ns_cnt;
120 0 : for( ulong i=0UL; i<adns->ns_cnt; i++ ) adns->ns[ i ] = ns[ i ];
121 0 : adns->attempts = fd_uint_max( attempts, 1U );
122 0 : adns->retry_nanos = ((long)timeout*1000L*1000L*1000L)/(long)adns->attempts;
123 :
124 0 : adns->fd = socket( AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0 );
125 0 : if( FD_UNLIKELY( -1==adns->fd ) ) FD_LOG_ERR(( "socket() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
126 0 : struct epoll_event ev = { .events = EPOLLIN, .data.fd = adns->fd };
127 0 : if( FD_UNLIKELY( -1==epoll_ctl( epoll_fd, EPOLL_CTL_ADD, adns->fd, &ev ) ) ) FD_LOG_ERR(( "epoll_ctl(ADD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
128 :
129 0 : FD_COMPILER_MFENCE();
130 0 : FD_VOLATILE( adns->magic ) = FD_ADNS_MAGIC;
131 0 : FD_COMPILER_MFENCE();
132 :
133 0 : return (void *)adns;
134 0 : }
135 :
136 : fd_adns_t *
137 0 : fd_adns_join( void * shadns ) {
138 0 : if( FD_UNLIKELY( !shadns ) ) {
139 0 : FD_LOG_WARNING(( "NULL shadns" ));
140 0 : return NULL;
141 0 : }
142 :
143 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shadns, fd_adns_align() ) ) ) {
144 0 : FD_LOG_WARNING(( "unaligned shadns" ));
145 0 : return NULL;
146 0 : }
147 :
148 0 : fd_adns_t * adns = (fd_adns_t *)shadns;
149 0 : if( FD_UNLIKELY( adns->magic!=FD_ADNS_MAGIC ) ) {
150 0 : FD_LOG_WARNING(( "bad magic" ));
151 0 : return NULL;
152 0 : }
153 :
154 0 : return adns;
155 0 : }
156 :
157 : void *
158 0 : fd_adns_leave( fd_adns_t * adns ) {
159 0 : return (void *)adns;
160 0 : }
161 :
162 : void *
163 0 : fd_adns_delete( void * shadns ) {
164 0 : fd_adns_t * adns = (fd_adns_t *)shadns;
165 0 : if( FD_UNLIKELY( !adns ) ) return NULL;
166 0 : if( FD_UNLIKELY( adns->magic!=FD_ADNS_MAGIC ) ) {
167 0 : FD_LOG_WARNING(( "bad magic" ));
168 0 : return NULL;
169 0 : }
170 :
171 0 : if( FD_UNLIKELY( -1==close( adns->fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
172 :
173 0 : FD_COMPILER_MFENCE();
174 0 : FD_VOLATILE( adns->magic ) = 0UL;
175 0 : FD_COMPILER_MFENCE();
176 :
177 0 : return shadns;
178 0 : }
179 :
180 : int
181 : fd_adns_resolve( fd_adns_t * adns,
182 : char const * name,
183 0 : ulong req_id ) {
184 0 : if( FD_UNLIKELY( adns->active_cnt>=adns->max ) ) return -1;
185 :
186 0 : fd_adns_req_t * req = NULL;
187 0 : for( ulong i=0UL; i<adns->max; i++ ) {
188 0 : if( FD_LIKELY( adns->reqs[ i ].state==REQ_STATE_IDLE ) ) { req = &adns->reqs[ i ]; break; }
189 0 : }
190 0 : FD_TEST( req );
191 :
192 0 : req->result.req_id = req_id;
193 0 : req->result.err = 0;
194 0 : req->result.addr_cnt = 0UL;
195 0 : req->no_edns = 0;
196 :
197 : /* Local names (literals, /etc/hosts) complete without network I/O */
198 0 : int cnt = fd_dns_ip4_local( name, req->result.addrs, FD_ADNS_ADDR_MAX );
199 0 : if( FD_UNLIKELY( cnt ) ) {
200 0 : if( FD_LIKELY( cnt>0 ) ) req->result.addr_cnt = (ulong)cnt;
201 0 : else req->result.err = cnt;
202 0 : req->state = REQ_STATE_DONE;
203 0 : adns->active_cnt++;
204 0 : return 0;
205 0 : }
206 :
207 0 : req->qlen = fd_dns_ip4_query( name, req->query );
208 0 : if( FD_UNLIKELY( req->qlen<0 ) ) {
209 0 : req->result.err = FD_EAI_NONAME;
210 0 : req->state = REQ_STATE_DONE;
211 0 : adns->active_cnt++;
212 0 : return 0;
213 0 : }
214 :
215 : /* Answers are matched by query id; make ids distinct. */
216 0 : for( ulong i=0UL; i<adns->max; i++ ) {
217 0 : if( FD_UNLIKELY( adns->reqs[ i ].state!=REQ_STATE_PENDING ) ) continue;
218 0 : if( FD_UNLIKELY( adns->reqs[ i ].query[ 0 ]==req->query[ 0 ] && adns->reqs[ i ].query[ 1 ]==req->query[ 1 ] ) ) {
219 0 : req->query[ 1 ]++;
220 0 : i = (ulong)-1L; /* restart scan */
221 0 : }
222 0 : }
223 :
224 0 : req->deadline_nanos = 0L; /* due immediately */
225 0 : req->sends_left = adns->attempts;
226 0 : req->state = REQ_STATE_PENDING;
227 0 : adns->active_cnt++;
228 0 : adns->pending_cnt++;
229 0 : return 0;
230 0 : }
231 :
232 : static void
233 : drain_answers( fd_adns_t * adns );
234 :
235 : static void
236 : pending_io( fd_adns_t * adns,
237 0 : long now ) {
238 0 : drain_answers( adns );
239 :
240 0 : if( FD_LIKELY( !adns->pending_cnt ) ) return;
241 :
242 : /* Send due (re)tries to all nameservers in parallel */
243 0 : for( ulong i=0UL; i<adns->max; i++ ) {
244 0 : fd_adns_req_t * req = &adns->reqs[ i ];
245 0 : if( FD_LIKELY( req->state!=REQ_STATE_PENDING ) ) continue;
246 0 : if( FD_LIKELY( req->deadline_nanos>now ) ) continue;
247 :
248 0 : if( FD_UNLIKELY( !req->sends_left || !adns->ns_cnt ) ) {
249 0 : req->result.err = FD_EAI_AGAIN;
250 0 : req->state = REQ_STATE_DONE;
251 0 : adns->pending_cnt--;
252 0 : continue;
253 0 : }
254 :
255 : /* req->query holds the bare question packet (the part answers
256 : echo); the wire packet adds the EDNS0 OPT RR unless the server
257 : rejected it */
258 0 : uchar pkt[ FD_DNS_QUERY_MTU+OPT_RR_SZ ];
259 0 : fd_memcpy( pkt, req->query, (ulong)req->qlen );
260 0 : ulong pkt_sz = (ulong)req->qlen;
261 0 : if( FD_LIKELY( !req->no_edns ) ) {
262 0 : fd_memcpy( pkt+pkt_sz, opt_rr, OPT_RR_SZ );
263 0 : pkt[ 11 ] = 1; /* arcount */
264 0 : pkt_sz += OPT_RR_SZ;
265 0 : }
266 :
267 0 : int sent_any = 0, all_eagain = 1;
268 0 : for( ulong j=0UL; j<adns->ns_cnt; j++ ) {
269 0 : struct sockaddr_in ns_addr = {
270 0 : .sin_family = AF_INET,
271 0 : .sin_port = fd_ushort_bswap( fd_adns_ns_port ),
272 0 : .sin_addr = { .s_addr = adns->ns[ j ] }
273 0 : };
274 0 : long sent;
275 0 : do sent = sendto( adns->fd, pkt, pkt_sz, MSG_NOSIGNAL, fd_type_pun( &ns_addr ), sizeof(ns_addr) );
276 0 : while( FD_UNLIKELY( -1L==sent && errno==EINTR ) ); /* retry before charging the attempt */
277 0 : if( FD_UNLIKELY( -1L==sent ) ) {
278 0 : if( FD_UNLIKELY( errno!=EAGAIN && errno!=ECONNREFUSED && errno!=ENETUNREACH && errno!=EHOSTUNREACH ) )
279 0 : FD_LOG_ERR(( "sendto() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
280 0 : if( FD_LIKELY( errno!=EAGAIN ) ) all_eagain = 0;
281 0 : } else {
282 0 : sent_any = 1;
283 0 : }
284 0 : }
285 : /* Local socket backpressure put nothing on the wire: stay due and
286 : retry next advance without consuming the attempt */
287 0 : if( FD_UNLIKELY( !sent_any && all_eagain && adns->ns_cnt ) ) { req->deadline_nanos = now; continue; }
288 0 : req->sends_left--;
289 0 : req->deadline_nanos = now+adns->retry_nanos;
290 0 : }
291 0 : }
292 :
293 : static void
294 0 : drain_answers( fd_adns_t * adns ) {
295 0 : for( ulong burst=0UL; burst<RECV_BURST_MAX; burst++ ) {
296 0 : uchar answer[ ANSWER_MTU ];
297 0 : struct sockaddr_in sa;
298 0 : struct iovec iov = { .iov_base = answer, .iov_len = sizeof(answer) };
299 0 : struct msghdr mh = {
300 0 : .msg_name = &sa,
301 0 : .msg_namelen = sizeof(sa),
302 0 : .msg_iov = &iov,
303 0 : .msg_iovlen = 1
304 0 : };
305 0 : long rlen = recvmsg( adns->fd, &mh, 0 );
306 0 : if( FD_LIKELY( rlen<0L ) ) {
307 0 : if( FD_LIKELY( errno==EAGAIN || errno==EWOULDBLOCK ) ) break;
308 0 : if( FD_LIKELY( errno==ECONNREFUSED || errno==EINTR ) ) continue;
309 0 : FD_LOG_ERR(( "recvmsg() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
310 0 : }
311 0 : if( FD_UNLIKELY( rlen<12L ) ) continue;
312 :
313 : /* Ignore replies from addresses we didn't send to */
314 0 : ulong j;
315 0 : for( j=0UL; j<adns->ns_cnt; j++ ) {
316 0 : if( FD_LIKELY( sa.sin_family==AF_INET && sa.sin_addr.s_addr==adns->ns[ j ] && sa.sin_port==fd_ushort_bswap( fd_adns_ns_port ) ) ) break;
317 0 : }
318 0 : if( FD_UNLIKELY( j==adns->ns_cnt ) ) continue;
319 :
320 : /* Must be a standard-query response */
321 0 : if( FD_UNLIKELY( (answer[ 2 ]&0x80)!=0x80 || (answer[ 2 ]&0x78)!=0 ) ) continue;
322 :
323 0 : fd_adns_req_t * req = NULL;
324 0 : for( ulong i=0UL; i<adns->max; i++ ) {
325 0 : if( FD_LIKELY( adns->reqs[ i ].state==REQ_STATE_PENDING && adns->reqs[ i ].query[ 0 ]==answer[ 0 ] && adns->reqs[ i ].query[ 1 ]==answer[ 1 ] ) ) {
326 0 : req = &adns->reqs[ i ];
327 0 : break;
328 0 : }
329 0 : }
330 0 : if( FD_UNLIKELY( !req ) ) continue;
331 0 : if( FD_UNLIKELY( rlen<req->qlen ) ) continue;
332 0 : if( FD_UNLIKELY( answer[ 4 ]!=req->query[ 4 ] || answer[ 5 ]!=req->query[ 5 ] ) ) continue;
333 0 : if( FD_UNLIKELY( memcmp( answer+12, req->query+12, (ulong)req->qlen-12UL ) ) ) continue;
334 :
335 : /* FORMERR/NOTIMP from an EDNS0-unaware server: resend plain.
336 : Guarantee a send remains, else attempts:1 expires the request
337 : at the sends_left check instead of retrying. */
338 0 : if( FD_UNLIKELY( ((answer[ 3 ]&15)==1 || (answer[ 3 ]&15)==4) && !req->no_edns ) ) {
339 0 : req->no_edns = 1;
340 0 : req->sends_left = fd_uint_max( req->sends_left, 1U );
341 0 : req->deadline_nanos = 0L; /* due immediately */
342 0 : continue;
343 0 : }
344 :
345 0 : int truncated = !!(answer[ 2 ]&2) | !!(mh.msg_flags&MSG_TRUNC);
346 :
347 0 : int cnt = fd_dns_ip4_answer( answer, (ulong)rlen, req->result.addrs, FD_ADNS_ADDR_MAX );
348 0 : if( FD_UNLIKELY( cnt==FD_EAI_AGAIN && !truncated ) ) continue; /* SERVFAIL: leave pending for retry */
349 : /* Truncated and nothing parseable: with EDNS0 advertising 1232B
350 : this means a pathological answer; the leading records we need
351 : (up to FD_ADNS_ADDR_MAX) essentially always fit, so this is a
352 : terminal failure rather than a TCP fallback. */
353 0 : if( FD_UNLIKELY( cnt<=0 && truncated ) ) cnt = FD_EAI_FAIL;
354 :
355 0 : if( FD_LIKELY( cnt>0 ) ) req->result.addr_cnt = (ulong)cnt;
356 0 : else req->result.err = cnt;
357 0 : req->state = REQ_STATE_DONE;
358 0 : adns->pending_cnt--;
359 0 : }
360 0 : }
361 :
362 : long
363 0 : fd_adns_next_deadline( fd_adns_t const * adns ) {
364 0 : long next = LONG_MAX;
365 0 : if( FD_LIKELY( !adns->pending_cnt ) ) return next;
366 :
367 0 : for( ulong i=0UL; i<adns->max; i++ ) {
368 0 : fd_adns_req_t const * req = &adns->reqs[ i ];
369 0 : if( FD_LIKELY( req->state!=REQ_STATE_PENDING ) ) continue;
370 :
371 0 : next = fd_long_min( next, req->deadline_nanos );
372 0 : }
373 :
374 0 : return next;
375 0 : }
376 :
377 : int
378 : fd_adns_advance( fd_adns_t * adns,
379 : long now,
380 0 : fd_adns_result_t * result ) {
381 0 : pending_io( adns, now );
382 :
383 0 : if( FD_LIKELY( !adns->active_cnt ) ) return 0;
384 :
385 0 : for( ulong i=0UL; i<adns->max; i++ ) {
386 0 : fd_adns_req_t * req = &adns->reqs[ i ];
387 0 : if( FD_LIKELY( req->state!=REQ_STATE_DONE ) ) continue;
388 0 : *result = req->result;
389 0 : req->state = REQ_STATE_IDLE;
390 0 : adns->active_cnt--;
391 0 : return 1;
392 0 : }
393 0 : return 0;
394 0 : }
|