Line data Source code
1 : #define _GNU_SOURCE
2 : #include "fd_ipecho_server_port_check.h"
3 :
4 : #include "../../util/fd_util.h"
5 : #include "../../util/net/fd_ip4.h"
6 : #include "../../tango/tempo/fd_tempo.h"
7 :
8 : #include <errno.h>
9 : #include <unistd.h>
10 : #include <sys/socket.h>
11 : #include <netinet/in.h>
12 :
13 : /* The maximum number of outgoing TCP connections the port check makes
14 : for each joiner IP address. We use 4 because that is the maximum
15 : number of TCP ports that can be listed in an IpEchoServerMessage.
16 : This enables us to create all connections for a joiner node at once,
17 : whilst protecting us from malicious joiner nodes flooding us with
18 : IpEchoServerMessages. */
19 :
20 : #define FD_IPECHO_SERVER_PORT_CHECK_MAX_PER_IP (4UL)
21 :
22 : /* How long we wait for the TCP handshake to be completed before
23 : closing the connection. We use 4s because Linux re-transmits
24 : unanswered SYN packets after 1s and 3s, so we want to give the
25 : kernel time to re-transmit unanswered SYNs. The Agave joining node
26 : gives up after 5s, so there is no point waiting close to 5s.
27 : Therefore, we wait 4s. */
28 :
29 0 : #define FD_IPECHO_SERVER_PORT_CHECK_TIMEOUT_NS ((long)4e9)
30 :
31 : struct conn {
32 : int fd;
33 : long deadline_ticks;
34 : uint ipv4;
35 : ushort gen;
36 : };
37 :
38 : typedef struct conn conn_t;
39 :
40 : /* A connection's ID is a combination of the index into the connections
41 : array and the "generation" - every time we re-use a connection
42 : index we increment the generation. This is to distinguish events
43 : relating to connections that do not exist anymore. */
44 :
45 0 : #define CONN_ID( gen, idx ) ( ((ulong)(gen)<<16) | (ulong)(idx) )
46 0 : #define CONN_ID_IDX( t ) ( (t) & 0xFFFFUL )
47 : #define CONN_ID_GEN( t ) ( ((t)>>16) & 0xFFFFUL )
48 :
49 : struct fd_ipecho_server_port_check {
50 : int udp_sockfd;
51 : long timeout_ticks;
52 :
53 : ulong conn_cnt;
54 : conn_t conns[ FD_IPECHO_SERVER_PORT_CHECK_CONN_MAX ];
55 :
56 : fd_ipecho_server_port_check_metrics_t metrics[ 1 ];
57 :
58 : ulong magic;
59 : };
60 :
61 : FD_FN_CONST ulong
62 0 : fd_ipecho_server_port_check_align( void ) {
63 0 : return alignof(fd_ipecho_server_port_check_t);
64 0 : }
65 :
66 : FD_FN_CONST ulong
67 0 : fd_ipecho_server_port_check_footprint( void ) {
68 0 : ulong l = FD_LAYOUT_INIT;
69 0 : l = FD_LAYOUT_APPEND( l, fd_ipecho_server_port_check_align(), sizeof(fd_ipecho_server_port_check_t) );
70 0 : return FD_LAYOUT_FINI( l, fd_ipecho_server_port_check_align() );
71 0 : }
72 :
73 : void *
74 0 : fd_ipecho_server_port_check_new( void * shmem ) {
75 0 : if( FD_UNLIKELY( !shmem ) ) {
76 0 : FD_LOG_WARNING(( "NULL shmem" ));
77 0 : return NULL;
78 0 : }
79 :
80 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_ipecho_server_port_check_align() ) ) ) {
81 0 : FD_LOG_WARNING(( "misaligned shmem" ));
82 0 : return NULL;
83 0 : }
84 :
85 0 : fd_ipecho_server_port_check_t * pc = shmem;
86 0 : for( ulong i=0UL; i<FD_IPECHO_SERVER_PORT_CHECK_CONN_MAX; i++ ) pc->conns[ i ] = (conn_t){ .fd = -1 };
87 0 : pc->conn_cnt = 0UL;
88 :
89 0 : pc->udp_sockfd = -1;
90 :
91 0 : memset( pc->metrics, 0, sizeof(pc->metrics) );
92 :
93 0 : FD_COMPILER_MFENCE();
94 0 : FD_VOLATILE( pc->magic ) = FD_IPECHO_SERVER_PORT_CHECK_MAGIC;
95 0 : FD_COMPILER_MFENCE();
96 :
97 0 : return pc;
98 0 : }
99 :
100 : fd_ipecho_server_port_check_t *
101 0 : fd_ipecho_server_port_check_join( void * shpc ) {
102 0 : if( FD_UNLIKELY( !shpc ) ) {
103 0 : FD_LOG_WARNING(( "NULL shpc" ));
104 0 : return NULL;
105 0 : }
106 :
107 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shpc, fd_ipecho_server_port_check_align() ) ) ) {
108 0 : FD_LOG_WARNING(( "misaligned shpc" ));
109 0 : return NULL;
110 0 : }
111 :
112 0 : fd_ipecho_server_port_check_t * pc = (fd_ipecho_server_port_check_t *)shpc;
113 :
114 0 : if( FD_UNLIKELY( pc->magic!=FD_IPECHO_SERVER_PORT_CHECK_MAGIC ) ) {
115 0 : FD_LOG_WARNING(( "bad magic" ));
116 0 : return NULL;
117 0 : }
118 :
119 0 : return pc;
120 0 : }
121 :
122 : void
123 : fd_ipecho_server_port_check_init( fd_ipecho_server_port_check_t * pc,
124 0 : uint address ) {
125 0 : pc->timeout_ticks = (long)( (double)FD_IPECHO_SERVER_PORT_CHECK_TIMEOUT_NS*fd_tempo_tick_per_ns( NULL ) );
126 0 : pc->udp_sockfd = socket( AF_INET, SOCK_DGRAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0 );
127 0 : if( FD_UNLIKELY( -1==pc->udp_sockfd ) ) {
128 0 : FD_LOG_ERR(( "socket() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
129 0 : }
130 :
131 0 : struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = 0, .sin_addr.s_addr = address };
132 0 : if( FD_UNLIKELY( -1==bind( pc->udp_sockfd, fd_type_pun( &addr ), sizeof(addr) ) ) ) {
133 0 : FD_LOG_ERR(( "bind(" FD_IP4_ADDR_FMT ":0) failed (%i-%s)",
134 0 : FD_IP4_ADDR_FMT_ARGS( address ), errno, fd_io_strerror( errno ) ));
135 0 : }
136 :
137 : /* We don't need to ever read from this socket, so set the receive
138 : buffer to the minimum so that the kernel will drop any inbound
139 : packets. */
140 0 : int rcvbuf = 0;
141 0 : if( FD_UNLIKELY( -1==setsockopt( pc->udp_sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf) ) ) ) {
142 0 : FD_LOG_ERR(( "setsockopt(SO_RCVBUF) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
143 0 : }
144 0 : }
145 :
146 : void
147 0 : fd_ipecho_server_port_check_fini( fd_ipecho_server_port_check_t * pc ) {
148 0 : fd_ipecho_server_port_check_close_all( pc );
149 0 : if( FD_LIKELY( -1!=pc->udp_sockfd ) ) {
150 0 : FD_TEST( -1!=close( pc->udp_sockfd ) );
151 0 : pc->udp_sockfd = -1;
152 0 : }
153 0 : }
154 :
155 : void
156 0 : fd_ipecho_server_port_check_close_all( fd_ipecho_server_port_check_t * pc ) {
157 0 : for( ulong i=0UL; i<FD_IPECHO_SERVER_PORT_CHECK_CONN_MAX; i++ ) {
158 0 : if( FD_LIKELY( -1==pc->conns[ i ].fd ) ) continue;
159 0 : FD_TEST( -1!=close( pc->conns[ i ].fd ) );
160 0 : pc->conns[ i ].fd = -1;
161 0 : }
162 0 : pc->conn_cnt = 0UL;
163 0 : pc->metrics->active = 0UL;
164 0 : }
165 :
166 : static void
167 : conn_finish( fd_ipecho_server_port_check_t * pc,
168 : conn_t * conn,
169 0 : int outcome ) {
170 0 : if( FD_UNLIKELY( -1==close( conn->fd ) ) ) FD_LOG_ERR(( "close failed (%i-%s)", errno, fd_io_strerror( errno ) ));
171 0 : conn->fd = -1;
172 :
173 0 : pc->metrics->tcp[ outcome ]++;
174 0 : pc->conn_cnt--;
175 0 : pc->metrics->active--;
176 0 : }
177 :
178 : void
179 : fd_ipecho_server_port_check_udp( fd_ipecho_server_port_check_t * pc,
180 : uint ipv4,
181 0 : ushort port ) {
182 0 : struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = fd_ushort_bswap( port ), .sin_addr.s_addr = ipv4 };
183 0 : uchar const zero = 0;
184 0 : if( FD_UNLIKELY( -1L==sendto(
185 0 : pc->udp_sockfd, &zero, 1UL, MSG_NOSIGNAL, fd_type_pun_const( &addr ), sizeof(addr) ) ) ) {
186 0 : if( FD_UNLIKELY( errno==EBADF || errno==ENOTSOCK || errno==EFAULT ) ) {
187 0 : FD_LOG_ERR(( "sendto failed (%i-%s)", errno, fd_io_strerror( errno ) ));
188 0 : }
189 0 : return;
190 0 : }
191 0 : pc->metrics->udp_sent++;
192 0 : }
193 :
194 : ulong
195 : fd_ipecho_server_port_check_tcp( fd_ipecho_server_port_check_t * pc,
196 : uint ipv4,
197 : ushort port,
198 0 : long now ) {
199 : /* Check that we haven't exceeded the maximum amount of active
200 : connections to this IP. */
201 0 : ulong in_flight_cnt = 0UL;
202 0 : for( ulong i=0UL; i<FD_IPECHO_SERVER_PORT_CHECK_CONN_MAX; i++ ) {
203 0 : in_flight_cnt += (ulong)( pc->conns[ i ].fd!=-1 && pc->conns[ i ].ipv4==ipv4 );
204 0 : }
205 0 : if( FD_UNLIKELY( in_flight_cnt>=FD_IPECHO_SERVER_PORT_CHECK_MAX_PER_IP ) ) {
206 0 : pc->metrics->tcp[ FD_IPECHO_SERVER_PORT_CHECK_TCP_REJECTED_PER_IP ]++;
207 0 : return ULONG_MAX;
208 0 : }
209 :
210 : /* Check that we haven't exceeded the maximum amount of total active
211 : connections. If we have, evict the oldest one. */
212 0 : if( FD_UNLIKELY( pc->conn_cnt>=FD_IPECHO_SERVER_PORT_CHECK_CONN_MAX ) ) {
213 0 : ulong oldest = 0UL;
214 0 : long oldest_deadline_ticks = LONG_MAX;
215 0 : for( ulong i=0UL; i<FD_IPECHO_SERVER_PORT_CHECK_CONN_MAX; i++ ) {
216 0 : long deadline_ticks = pc->conns[ i ].deadline_ticks;
217 0 : if( FD_UNLIKELY( deadline_ticks<oldest_deadline_ticks ) ) {
218 0 : oldest = i;
219 0 : oldest_deadline_ticks = deadline_ticks;
220 0 : }
221 0 : }
222 0 : conn_finish( pc, &pc->conns[ oldest ], FD_IPECHO_SERVER_PORT_CHECK_TCP_EVICTED );
223 0 : }
224 :
225 : /* Open a TCP connection to the given ip and port. */
226 0 : int fd = socket( AF_INET, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0 );
227 0 : if( FD_UNLIKELY( -1==fd ) ) {
228 0 : if( FD_UNLIKELY( errno==EBADF || errno==ENOTSOCK || errno==EFAULT ) ) {
229 0 : FD_LOG_ERR(( "socket() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
230 0 : }
231 0 : pc->metrics->tcp[ FD_IPECHO_SERVER_PORT_CHECK_TCP_DROPPED ]++;
232 0 : return ULONG_MAX;
233 0 : }
234 :
235 0 : struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = fd_ushort_bswap( port ), .sin_addr.s_addr = ipv4 };
236 0 : if( FD_UNLIKELY( -1==connect( fd, fd_type_pun_const( &addr ), sizeof(addr) ) && errno!=EINPROGRESS ) ) {
237 0 : if( FD_UNLIKELY( errno==EBADF || errno==ENOTSOCK || errno==EFAULT ) ) {
238 0 : FD_LOG_ERR(( "connect() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
239 0 : }
240 0 : if( FD_UNLIKELY( -1==close( fd ) ) ) {
241 0 : FD_LOG_ERR(( "close failed (%i-%s)", errno, fd_io_strerror( errno ) ));
242 0 : }
243 0 : pc->metrics->tcp[ FD_IPECHO_SERVER_PORT_CHECK_TCP_FAILED ]++;
244 0 : return ULONG_MAX;
245 0 : }
246 :
247 : /* Return the index of the connection for the caller to pass in
248 : to fd_ipecho_server_port_check_handle_event */
249 0 : ulong idx = 0UL;
250 0 : while( pc->conns[ idx ].fd!=-1 ) idx++;
251 0 : ushort gen = (ushort)( pc->conns[ idx ].gen+1 );
252 0 : pc->conns[ idx ] = (conn_t){ .fd = fd, .deadline_ticks = now+pc->timeout_ticks, .ipv4 = ipv4, .gen = gen };
253 0 : pc->conn_cnt++;
254 0 : pc->metrics->active++;
255 0 : return CONN_ID( gen, idx );
256 0 : }
257 :
258 : void
259 : fd_ipecho_server_port_check_handle_event( fd_ipecho_server_port_check_t * pc,
260 0 : ulong id ) {
261 0 : ulong idx = CONN_ID_IDX( id );
262 0 : FD_TEST( idx<FD_IPECHO_SERVER_PORT_CHECK_CONN_MAX );
263 0 : conn_t * conn = &pc->conns[ idx ];
264 0 : if( FD_UNLIKELY( conn->fd==-1 || conn->gen!=CONN_ID_GEN( id ) ) ) return; /* stale event */
265 :
266 0 : int err = 0;
267 0 : socklen_t err_len = sizeof(err);
268 0 : if( FD_UNLIKELY( -1==getsockopt( conn->fd, SOL_SOCKET, SO_ERROR, &err, &err_len ) ) ) err = errno;
269 :
270 0 : conn_finish( pc, conn, err ? FD_IPECHO_SERVER_PORT_CHECK_TCP_FAILED : FD_IPECHO_SERVER_PORT_CHECK_TCP_CONNECTED );
271 0 : }
272 :
273 : void
274 : fd_ipecho_server_port_check_prune( fd_ipecho_server_port_check_t * pc,
275 0 : long now ) {
276 0 : if( FD_LIKELY( !pc->conn_cnt ) ) return;
277 0 : for( ulong i=0UL; i<FD_IPECHO_SERVER_PORT_CHECK_CONN_MAX; i++ ) {
278 0 : conn_t * conn = &pc->conns[ i ];
279 0 : if( FD_UNLIKELY( conn->fd!=-1 && now>=conn->deadline_ticks ) ) {
280 0 : conn_finish( pc, conn, FD_IPECHO_SERVER_PORT_CHECK_TCP_TIMEOUT );
281 0 : }
282 0 : }
283 0 : }
284 :
285 : fd_ipecho_server_port_check_metrics_t *
286 0 : fd_ipecho_server_port_check_metrics( fd_ipecho_server_port_check_t * pc ) {
287 0 : return pc->metrics;
288 0 : }
289 :
290 : int
291 : fd_ipecho_server_port_check_conn_fd( fd_ipecho_server_port_check_t * pc,
292 0 : ulong id ) {
293 0 : ulong idx = CONN_ID_IDX( id );
294 0 : FD_TEST( idx<FD_IPECHO_SERVER_PORT_CHECK_CONN_MAX );
295 0 : FD_TEST( pc->conns[ idx ].fd!=-1 && pc->conns[ idx ].gen==CONN_ID_GEN( id ) );
296 0 : return pc->conns[ idx ].fd;
297 0 : }
298 :
299 : int
300 0 : fd_ipecho_server_port_check_udp_sockfd( fd_ipecho_server_port_check_t * pc ) {
301 0 : return pc->udp_sockfd;
302 0 : }
|