LCOV - code coverage report
Current view: top level - waltz/ip - fd_fib4_netlink.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 134 214 62.6 %
Date: 2026-07-20 05:32:00 Functions: 7 8 87.5 %

          Line data    Source code
       1             : #include "fd_fib4_netlink.h"
       2             : #include "fd_fib4.h"
       3             : #include "fd_netlink1.h"
       4             : 
       5             : #if !defined(__linux__)
       6             : #error "fd_fib4_netlink.c requires a Linux system with kernel headers"
       7             : #endif
       8             : 
       9             : #include <errno.h>
      10             : #include <linux/netlink.h>
      11             : #include <linux/rtnetlink.h>
      12             : #include <arpa/inet.h>
      13             : #include "../../util/fd_util.h"
      14             : 
      15             : FD_STATIC_ASSERT( FD_FIB4_RTYPE_UNSPEC   ==RTN_UNSPEC,    linux );
      16             : FD_STATIC_ASSERT( FD_FIB4_RTYPE_UNICAST  ==RTN_UNICAST,   linux );
      17             : FD_STATIC_ASSERT( FD_FIB4_RTYPE_LOCAL    ==RTN_LOCAL,     linux );
      18             : FD_STATIC_ASSERT( FD_FIB4_RTYPE_BROADCAST==RTN_BROADCAST, linux );
      19             : FD_STATIC_ASSERT( FD_FIB4_RTYPE_MULTICAST==RTN_MULTICAST, linux );
      20             : FD_STATIC_ASSERT( FD_FIB4_RTYPE_BLACKHOLE==RTN_BLACKHOLE, linux );
      21             : FD_STATIC_ASSERT( FD_FIB4_RTYPE_THROW    ==RTN_THROW,     linux );
      22             : 
      23             : static void
      24             : fd_fib4_rta_gateway( fd_fib4_hop_t * hop,
      25             :                      void const *    rta,
      26           3 :                      ulong           rta_sz ) {
      27           3 :   if( FD_UNLIKELY( rta_sz!=4UL ) ) {
      28           0 :     FD_LOG_HEXDUMP_DEBUG(( "Failed to parse RTA_GATEWAY", rta, rta_sz ));
      29           0 :     hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
      30           0 :     return;
      31           0 :   }
      32           3 :   uint ip_addr = FD_LOAD( uint, rta ); /* big endian */
      33           3 :   hop->ip4_gw = ip_addr;
      34           3 : }
      35             : 
      36             : static void
      37             : fd_fib4_rta_oif( fd_fib4_hop_t * hop,
      38             :                  void const *    rta,
      39          27 :                  ulong           rta_sz ) {
      40          27 :   if( FD_UNLIKELY( rta_sz!=4UL ) ) {
      41           0 :     FD_LOG_HEXDUMP_DEBUG(( "Failed to parse RTA_OIF", rta, rta_sz ));
      42           0 :     hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
      43           0 :     return;
      44           0 :   }
      45          27 :   hop->if_idx = FD_LOAD( uint, rta ); /* host byte order */
      46          27 : }
      47             : 
      48             : static void
      49             : fd_fib4_rta_prefsrc( fd_fib4_hop_t * hop,
      50             :                      void const *    rta,
      51          27 :                      ulong           rta_sz ) {
      52          27 :   if( FD_UNLIKELY( rta_sz!=4UL ) ) {
      53           0 :     FD_LOG_HEXDUMP_DEBUG(( "Failed to parse RTA_PREFSRC", rta, rta_sz ));
      54           0 :     hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
      55           0 :     return;
      56           0 :   }
      57          27 :   hop->ip4_src = FD_LOAD( uint, rta ); /* big endian */
      58          27 : }
      59             : 
      60             : int
      61             : fd_fib4_netlink_translate( struct nlmsghdr const * msg_hdr,
      62             :                            uint                    table_id,
      63          54 :                            fd_iproute_msg_t *      route ) {
      64          54 :   uint ip4_dst = 0U;
      65          54 :   int  prefix  = -1; /* -1 indicates unset ip4_dst / prefix */
      66          54 :   uint prio    = 0U; /* default metric */
      67             : 
      68          54 :   fd_fib4_hop_t hop[1] = {0};
      69             : 
      70          54 :   struct rtmsg *  msg    = NLMSG_DATA( msg_hdr );
      71          54 :   struct rtattr * rat    = RTM_RTA( msg );
      72          54 :   long            rat_sz = (long)(int)RTM_PAYLOAD( msg_hdr );
      73             : 
      74          54 :   if( FD_UNLIKELY(msg->rtm_flags & RTM_F_CLONED) ) {
      75           0 :     return 0;
      76           0 :   }
      77             : 
      78          54 :   if( FD_UNLIKELY(msg->rtm_table != RT_TABLE_UNSPEC &&
      79          54 :        msg->rtm_table != table_id ) ) {
      80          27 :     return 0;
      81          27 :   }
      82             : 
      83          27 :   switch( msg->rtm_type ) {
      84           9 :   case RTN_UNICAST:
      85           9 :     hop->rtype = FD_FIB4_RTYPE_UNICAST;
      86           9 :     break;
      87          12 :   case RTN_LOCAL:
      88          12 :     hop->rtype = FD_FIB4_RTYPE_LOCAL;
      89          12 :     break;
      90           6 :   case RTN_BROADCAST:
      91           6 :     hop->rtype = FD_FIB4_RTYPE_BROADCAST;
      92           6 :     break;
      93           0 :   case RTN_MULTICAST:
      94           0 :     hop->rtype = FD_FIB4_RTYPE_MULTICAST;
      95           0 :     break;
      96           0 :   case RTN_BLACKHOLE:
      97           0 :     hop->rtype = FD_FIB4_RTYPE_BLACKHOLE;
      98           0 :     break;
      99           0 :   default:
     100           0 :     FD_LOG_DEBUG(( "Unsupported route type (%u-%s)", msg->rtm_type, fd_netlink_rtm_type_str( msg->rtm_type ) ));
     101           0 :     hop->rtype = FD_FIB4_RTYPE_BLACKHOLE;
     102           0 :     hop->flags |= FD_FIB4_FLAG_RTYPE_UNSUPPORTED;
     103           0 :     break;
     104          27 :   }
     105             : 
     106         114 :   for( ; RTA_OK( rat, rat_sz ); rat=RTA_NEXT( rat, rat_sz ) ) {
     107         114 :     void * rta    = RTA_DATA( rat );
     108         114 :     ulong  rta_sz = RTA_PAYLOAD( rat );
     109             : 
     110         114 :     switch( rat->rta_type ) {
     111             : 
     112           3 :     case RTA_GATEWAY:
     113           3 :       fd_fib4_rta_gateway( hop, rta, rta_sz );
     114           3 :       break;
     115             : 
     116          24 :     case RTA_DST:
     117          24 :       if( FD_UNLIKELY( rta_sz!=4UL ) ) {
     118           0 :         hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
     119           0 :         continue;
     120           0 :       }
     121          24 :       ip4_dst = FD_LOAD( uint, rta ); /* big endian */
     122          24 :       prefix  = msg->rtm_dst_len;
     123          24 :       break;
     124             : 
     125          27 :     case RTA_OIF:
     126          27 :       fd_fib4_rta_oif( hop, rta, rta_sz );
     127          27 :       break;
     128             : 
     129          27 :     case RTA_PREFSRC:
     130          27 :       fd_fib4_rta_prefsrc( hop, rta, rta_sz );
     131          27 :       break;
     132             : 
     133           6 :     case RTA_PRIORITY:
     134           6 :       if( FD_UNLIKELY( rta_sz!=4UL ) ) {
     135           0 :         hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
     136           0 :         continue;
     137           0 :       }
     138           6 :       prio = FD_LOAD( uint, rta ); /* host byte order */
     139           6 :       break;
     140             : 
     141          27 :     case RTA_TABLE:
     142             :       /* Skip routes that aren't in the requested table */
     143          27 :       if( FD_UNLIKELY( rta_sz!=4UL ) ) {
     144           0 :         hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
     145           0 :         continue;
     146           0 :       }
     147          27 :       if( FD_LOAD( uint, rta )!=table_id ) return 0;
     148          27 :       break;
     149             : 
     150          27 :     default:
     151           0 :       FD_LOG_DEBUG(( "Unsupported route table attribute (%u-%s)", rat->rta_type, fd_netlink_rtattr_str( rat->rta_type ) ));
     152           0 :       hop->flags |= FD_FIB4_FLAG_RTA_UNSUPPORTED;
     153           0 :       break;
     154         114 :     }
     155         114 :   }
     156             : 
     157          27 :   if( FD_UNLIKELY( prefix<0 ) ) prefix = 0; /* default route omits RTA_DST */
     158          27 :   *route = (fd_iproute_msg_t) {
     159          27 :     .hop       = *hop,
     160          27 :     .dst_addr  = ip4_dst,
     161          27 :     .prio      = prio,
     162          27 :     .table_id  = table_id,
     163          27 :     .op        = msg_hdr->nlmsg_type==RTM_DELROUTE ? FD_IPROUTE_OP_DELETE : FD_IPROUTE_OP_UPSERT,
     164          27 :     .prefix    = (uchar)prefix,
     165          27 :   };
     166          27 :   return 1;
     167          27 : }
     168             : 
     169             : int
     170             : fd_fib4_netlink_dump_table( fd_netlink_t *                 netlink,
     171             :                             uint                           table_id,
     172             :                             fd_fib4_netlink_route_fn_t      fn,
     173           6 :                             void *                         arg ) {
     174             : 
     175           6 :   uint seq = netlink->seq++;
     176             : 
     177           6 :   struct {
     178           6 :     struct nlmsghdr nlh;  /* Netlink header */
     179           6 :     struct rtmsg    rtm;  /* Payload - route message */
     180           6 :     struct rtattr   rta;
     181           6 :     uint            table_id;
     182           6 :   } request;
     183           6 :   request.nlh = (struct nlmsghdr) {
     184           6 :     .nlmsg_type  = RTM_GETROUTE,
     185           6 :     .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
     186           6 :     .nlmsg_len   = sizeof(request),
     187           6 :     .nlmsg_seq   = seq
     188           6 :   };
     189           6 :   request.rtm = (struct rtmsg) {
     190           6 :     .rtm_family = AF_INET, /* IPv4 */
     191           6 :   };
     192           6 :   request.rta = (struct rtattr) {
     193           6 :     .rta_type = RTA_TABLE,
     194           6 :     .rta_len  = RTA_LENGTH( sizeof(uint) )
     195           6 :   };
     196           6 :   request.table_id = table_id;
     197             : 
     198           6 :   long send_res = sendto( netlink->fd, &request, sizeof(request), 0, NULL, 0 );
     199           6 :   if( FD_UNLIKELY( send_res<0 ) ) {
     200           0 :     FD_LOG_WARNING(( "netlink send(%d,RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP) failed (%d-%s)", netlink->fd, errno, fd_io_strerror( errno ) ));
     201           0 :     return errno;
     202           0 :   }
     203           6 :   if( FD_UNLIKELY( send_res!=sizeof(request) ) ) {
     204           0 :     FD_LOG_WARNING(( "netlink send(%d,RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP) failed (short write)", netlink->fd ));
     205           0 :     return EPIPE;
     206           0 :   }
     207             : 
     208           6 :   int   dump_intr = 0;
     209             : 
     210           6 :   uchar buf[ 4096 ];
     211           6 :   fd_netlink_iter_t iter[1];
     212           6 :   for( fd_netlink_iter_init( iter, netlink, buf, sizeof(buf) );
     213          60 :        !fd_netlink_iter_done( iter );
     214          54 :        fd_netlink_iter_next( iter, netlink ) ) {
     215          54 :     struct nlmsghdr const * nlh = fd_netlink_iter_msg( iter );
     216          54 :     if( FD_UNLIKELY( nlh->nlmsg_flags & NLM_F_DUMP_INTR ) ) dump_intr = 1;
     217          54 :     if( FD_UNLIKELY( nlh->nlmsg_type==NLMSG_ERROR ) ) {
     218           0 :       struct nlmsgerr * err = NLMSG_DATA( nlh );
     219           0 :       int nl_err = -err->error;
     220           0 :       FD_LOG_WARNING(( "netlink RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP failed (%d-%s)", nl_err, fd_io_strerror( nl_err ) ));
     221           0 :       return nl_err;
     222           0 :     }
     223          54 :     if( FD_UNLIKELY( nlh->nlmsg_type!=RTM_NEWROUTE ) ) {
     224           0 :       FD_LOG_DEBUG(( "unexpected nlmsg_type %u", nlh->nlmsg_type ));
     225           0 :       continue;
     226           0 :     }
     227          54 :     fd_iproute_msg_t route;
     228          54 :     if( fd_fib4_netlink_translate( nlh, table_id, &route ) && FD_UNLIKELY( fn( arg, &route ) ) ) break;
     229          54 :   }
     230           6 :   if( FD_UNLIKELY( iter->err > 0 ) ) return FD_FIB_NETLINK_ERR_IO;
     231           6 :   ulong drain_cnt = fd_netlink_iter_drain( iter, netlink );
     232             : 
     233           6 :   if( dump_intr ) {
     234           0 :     FD_LOG_DEBUG(( "received NLM_F_DUMP_INTR (our read of the routing table was overrun by a concurrent write)" ));
     235           0 :     return FD_FIB_NETLINK_ERR_INTR;
     236           0 :   }
     237             : 
     238           6 :   if( FD_UNLIKELY( drain_cnt ) ) {
     239           0 :     FD_LOG_WARNING(( "Unexpectedly skipped %lu routes. This is a bug!", drain_cnt ));
     240           0 :     return FD_FIB_NETLINK_ERR_OOPS;
     241           0 :   }
     242             : 
     243           6 :   return 0;
     244           6 : }
     245             : 
     246             : typedef struct {
     247             :   fd_fib4_t * fib;
     248             :   int         no_space;
     249             : } fd_fib4_load_ctx_t;
     250             : 
     251             : static int
     252          27 : fd_fib4_load_route( void * arg, fd_iproute_msg_t const * route ) {
     253          27 :   fd_fib4_load_ctx_t * ctx = arg;
     254          27 :   if( FD_UNLIKELY( !fd_fib4_insert( ctx->fib, route->dst_addr, route->prefix, route->prio, &route->hop ) ) ) {
     255           0 :     ctx->no_space = 1;
     256           0 :     return 1;
     257           0 :   }
     258          27 :   return 0;
     259          27 : }
     260             : 
     261             : int
     262             : fd_fib4_netlink_load_table( fd_fib4_t *    fib,
     263             :                             fd_netlink_t * netlink,
     264           6 :                             uint           table_id ) {
     265           6 :   fd_fib4_clear( fib );
     266           6 :   fd_fib4_load_ctx_t ctx = { .fib = fib };
     267           6 :   int err = fd_fib4_netlink_dump_table( netlink, table_id, fd_fib4_load_route, &ctx );
     268           6 :   if( FD_UNLIKELY( ctx.no_space ) ) {
     269           0 :     FD_LOG_WARNING(( "Routing table is too small for table %u (max %lu)", table_id, fd_fib4_max( fib ) ));
     270           0 :     fd_fib4_clear( fib );
     271           0 :     return FD_FIB_NETLINK_ERR_SPACE;
     272           0 :   }
     273           6 :   if( FD_UNLIKELY( err ) ) fd_fib4_clear( fib );
     274           6 :   return err;
     275           6 : }
     276             : 
     277             : FD_FN_CONST char const *
     278           0 : fd_fib4_netlink_strerror( int err ) {
     279           0 :   switch( err ) {
     280           0 :   case FD_FIB_NETLINK_SUCCESS:
     281           0 :     return "success";
     282           0 :   case FD_FIB_NETLINK_ERR_OOPS:
     283           0 :     return "oops";
     284           0 :   case FD_FIB_NETLINK_ERR_IO:
     285           0 :     return "io";
     286           0 :   case FD_FIB_NETLINK_ERR_INTR:
     287           0 :     return "interrupt";
     288           0 :   case FD_FIB_NETLINK_ERR_SPACE:
     289           0 :     return "out of space";
     290           0 :   default:
     291           0 :     return "unknown";
     292           0 :   }
     293           0 : }

Generated by: LCOV version 1.14