Line data Source code
1 : #include "fd_dns_resolve.h"
2 : #include "../../waltz/resolv/fd_adns.h"
3 : #include "../../waltz/resolv/fd_netdb.h"
4 : #include "../../util/fd_util.h"
5 :
6 : #include <errno.h>
7 : #include <sys/epoll.h>
8 : #include <sys/socket.h>
9 : #include <netinet/in.h>
10 : #include <string.h>
11 : #include <stdlib.h>
12 : #include <unistd.h>
13 :
14 0 : #define FD_DNS_RESOLVE_TIMEOUT_NANOS (90L*1000L*1000L*1000L)
15 :
16 : int
17 : fd_dns_resolve_address( char const * address,
18 0 : uint * ip_addr ) {
19 0 : if( FD_LIKELY( fd_cstr_to_ip4_addr( address, ip_addr ) ) ) return 1;
20 :
21 0 : struct addrinfo hints = { .ai_family = AF_INET };
22 0 : struct addrinfo * res;
23 0 : int err = getaddrinfo( address, NULL, &hints, &res );
24 0 : if( FD_UNLIKELY( err ) ) {
25 0 : FD_LOG_WARNING(( "cannot resolve address \"%s\": %i-%s", address, err, gai_strerror( err ) ));
26 0 : return 0;
27 0 : }
28 :
29 0 : int resolved = 0;
30 0 : for( struct addrinfo * cur=res; cur; cur=cur->ai_next ) {
31 0 : if( FD_UNLIKELY( cur->ai_addr->sa_family!=AF_INET ) ) continue;
32 0 : struct sockaddr_in const * addr = (struct sockaddr_in const *)cur->ai_addr;
33 0 : *ip_addr = addr->sin_addr.s_addr;
34 0 : resolved = 1;
35 0 : break;
36 0 : }
37 :
38 0 : freeaddrinfo( res );
39 0 : return resolved;
40 0 : }
41 :
42 : int
43 : fd_dns_resolve_addresses( char const * address,
44 : struct addrinfo const * hints,
45 : fd_ip4_port_t * ip_addrs,
46 0 : ulong ip_addr_cnt ) {
47 0 : struct addrinfo default_hints = { .ai_family = AF_INET };
48 0 : if( FD_UNLIKELY( !hints ) ) {
49 0 : hints = &default_hints;
50 0 : }
51 :
52 0 : struct addrinfo * res;
53 0 : int err = getaddrinfo( address, NULL, hints, &res );
54 0 : if( FD_UNLIKELY( err ) ) {
55 0 : FD_LOG_WARNING(( "cannot resolve address \"%s\": %i-%s", address, err, gai_strerror( err ) ));
56 0 : return 0;
57 0 : }
58 :
59 0 : int resolved = 0;
60 0 : for( struct addrinfo * cur=res; cur; cur=cur->ai_next ) {
61 0 : if( FD_UNLIKELY( (ulong)resolved>=ip_addr_cnt ) ) break;
62 0 : if( FD_UNLIKELY( cur->ai_addr->sa_family!=AF_INET ) ) continue;
63 0 : struct sockaddr_in const * addr = (struct sockaddr_in const *)cur->ai_addr;
64 0 : ip_addrs[ resolved ].addr = addr->sin_addr.s_addr;
65 0 : resolved++;
66 0 : }
67 :
68 0 : freeaddrinfo( res );
69 0 : return resolved;
70 0 : }
71 :
72 : void
73 : fd_dns_peer_parse( char const * peer,
74 : char const * config_str,
75 : char hostname[ static FD_FQDN_BUF_MAX ],
76 : ushort * port,
77 0 : int * is_https ) {
78 :
79 : /* Split host:port */
80 0 : int https = 0;
81 0 : char const * host_port = peer;
82 0 : if( FD_LIKELY( strncmp( peer, "http://", 7UL )==0 ) ) {
83 0 : host_port += 7UL;
84 0 : } else if( FD_LIKELY( strncmp( peer, "https://", 8UL )==0 ) ) {
85 0 : host_port += 8UL;
86 0 : https = 1;
87 0 : }
88 0 : if( FD_LIKELY( is_https ) ) *is_https = https;
89 :
90 0 : char const * colon = strrchr( host_port, ':' );
91 0 : char const * host_end = colon;
92 0 : if( FD_UNLIKELY( !colon && !https ) ) {
93 0 : FD_LOG_ERR(( "invalid [%s] entry \"%s\": no port number", config_str, host_port ));
94 0 : } else if( FD_LIKELY( !colon && https ) ) {
95 0 : host_end = host_port + strlen( host_port );
96 0 : }
97 :
98 0 : ulong fqdn_len = (ulong)( host_end-host_port );
99 0 : if( FD_UNLIKELY( !fqdn_len ) ) {
100 0 : FD_LOG_ERR(( "invalid [%s] entry \"%s\": no hostname", config_str, host_port ));
101 0 : }
102 0 : if( FD_UNLIKELY( fqdn_len>=FD_FQDN_BUF_MAX ) ) {
103 0 : FD_LOG_ERR(( "invalid [%s] entry \"%s\": hostname too long", config_str, host_port ));
104 0 : }
105 0 : fd_memcpy( hostname, host_port, fqdn_len );
106 0 : hostname[ fqdn_len ] = '\0';
107 :
108 0 : if( FD_LIKELY( colon ) ) {
109 0 : char const * port_str = host_end+1;
110 0 : char const * endptr = NULL;
111 0 : ulong _port = strtoul( port_str, (char **)&endptr, 10 );
112 0 : if( FD_UNLIKELY( endptr==port_str || !_port || _port>USHORT_MAX || *endptr!='\0' ) ) {
113 0 : FD_LOG_ERR(( "invalid [%s] entry \"%s\": invalid port number", config_str, host_port ));
114 0 : }
115 0 : *port = fd_ushort_bswap( (ushort)_port );
116 0 : } else {
117 : /* use default https port */
118 0 : *port = fd_ushort_bswap( 443U );
119 0 : }
120 0 : }
121 :
122 : void
123 : fd_dns_resolve_peers( char const * peers,
124 : ulong peer_stride,
125 : ulong peer_cnt,
126 : char const * config_str,
127 0 : fd_ip4_port_t * out ) {
128 0 : if( FD_UNLIKELY( peer_cnt>FD_DNS_RESOLVE_PEERS_MAX ) ) FD_LOG_ERR(( "too many [%s] entries (%lu)", config_str, peer_cnt ));
129 0 : if( FD_UNLIKELY( !peer_cnt ) ) return;
130 :
131 0 : char hostname[ FD_DNS_RESOLVE_PEERS_MAX ][ FD_FQDN_BUF_MAX ];
132 0 : for( ulong i=0UL; i<peer_cnt; i++ ) {
133 0 : fd_dns_peer_parse( peers+i*peer_stride, config_str, hostname[ i ], &out[ i ].port, NULL );
134 0 : }
135 :
136 : /* All queries in flight concurrently on one socket, so the wall
137 : clock cost is that of the slowest single lookup. The fds are
138 : opened and closed within this call: callers are about to enter a
139 : sandbox that requires no stray fds. */
140 0 : fd_netdb_fds_t netdb_fds[1];
141 0 : int netdb_opened = !!fd_netdb_open_fds( netdb_fds ); /* NULL if already open (owned by caller) */
142 :
143 : /* Thread local: tiles resolve concurrently under
144 : fd_topo_run_single_process (and netdb fds are FD_TL already) */
145 0 : static FD_TL uchar shadns[ 16384UL ] __attribute__((aligned(32UL)));
146 0 : FD_TEST( fd_adns_footprint( FD_DNS_RESOLVE_PEERS_MAX )<=sizeof(shadns) );
147 0 : int epoll_fd = epoll_create1( 0 );
148 0 : if( FD_UNLIKELY( -1==epoll_fd ) ) FD_LOG_ERR(( "epoll_create1 failed (%i-%s)", errno, fd_io_strerror( errno ) ));
149 0 : fd_adns_t * adns = fd_adns_join( fd_adns_new( shadns, peer_cnt, epoll_fd ) );
150 0 : FD_TEST( adns );
151 :
152 0 : for( ulong i=0UL; i<peer_cnt; i++ ) FD_TEST( !fd_adns_resolve( adns, hostname[ i ], i ) );
153 :
154 0 : ulong resolved_cnt = 0UL;
155 0 : long deadline = fd_log_wallclock()+FD_DNS_RESOLVE_TIMEOUT_NANOS;
156 0 : for(;;) {
157 0 : long now = fd_log_wallclock();
158 0 : fd_adns_result_t res[ 1 ];
159 0 : while( fd_adns_advance( adns, now, res ) ) {
160 0 : if( FD_UNLIKELY( res->err ) )
161 0 : FD_LOG_ERR(( "failed to resolve [%s] entry \"%s\" (%s)", config_str, peers+res->req_id*peer_stride, fd_gai_strerror( res->err ) ));
162 0 : out[ res->req_id ].addr = res->addrs[ 0 ];
163 0 : resolved_cnt++;
164 0 : }
165 0 : if( FD_LIKELY( resolved_cnt==peer_cnt ) ) break;
166 0 : if( FD_UNLIKELY( now>deadline ) ) FD_LOG_ERR(( "timed out resolving [%s]", config_str ));
167 : /* Wait for an answer, or the next (re)send */
168 0 : long wait_nanos = fd_long_max( 0L, fd_long_min( fd_adns_next_deadline( adns ), deadline )-now );
169 0 : struct epoll_event ev;
170 0 : if( FD_UNLIKELY( -1==epoll_wait( epoll_fd, &ev, 1, (int)(wait_nanos/1000000L) ) && errno!=EINTR ) ) FD_LOG_ERR(( "epoll_wait failed (%i-%s)", errno, fd_io_strerror( errno ) ));
171 0 : }
172 :
173 0 : fd_adns_delete( fd_adns_leave( adns ) );
174 0 : if( FD_UNLIKELY( -1==close( epoll_fd ) ) ) FD_LOG_ERR(( "close(epoll_fd) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
175 0 : if( FD_LIKELY( netdb_opened ) ) fd_netdb_close_fds();
176 0 : }
|