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