LCOV - code coverage report
Current view: top level - discof/ipecho - fd_ipecho_server_port_check.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 176 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 15 0.0 %

          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 : }

Generated by: LCOV version 1.14