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

Generated by: LCOV version 1.14