LCOV - code coverage report
Current view: top level - waltz/ip - fd_fib4_netlink.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 119 200 59.5 %
Date: 2025-07-15 04:56:17 Functions: 5 6 83.3 %

          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          33 :                  ulong           rta_sz ) {
      40          33 :   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          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          33 :                      ulong           rta_sz ) {
      52          33 :   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          33 :   hop->ip4_src = FD_LOAD( uint, rta ); /* big endian */
      58          33 : }
      59             : 
      60             : static int
      61             : fd_fib4_netlink_translate( fd_fib4_t *             fib,
      62             :                            struct nlmsghdr const * msg_hdr,
      63          66 :                            uint                    table_id ) {
      64          66 :   uint ip4_dst = 0U;
      65          66 :   int  prefix  = -1; /* -1 indicates unset ip4_dst / prefix */
      66          66 :   uint prio    = 0U; /* default metric */
      67             : 
      68          66 :   fd_fib4_hop_t hop[1] = {0};
      69             : 
      70          66 :   struct rtmsg *  msg    = NLMSG_DATA( msg_hdr );
      71          66 :   struct rtattr * rat    = RTM_RTA( msg );
      72          66 :   long            rat_sz = (long)(int)RTM_PAYLOAD( msg_hdr );
      73             : 
      74          66 :   if( FD_UNLIKELY(msg->rtm_flags & RTM_F_CLONED) ) {
      75           0 :     return 0;
      76           0 :   }
      77             : 
      78          66 :   if( FD_UNLIKELY(msg->rtm_table != RT_TABLE_UNSPEC &&
      79          66 :        msg->rtm_table != table_id ) ) {
      80          33 :     return 0;
      81          33 :   }
      82             : 
      83          33 :   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          12 :   case RTN_BROADCAST:
      91          12 :     hop->rtype = FD_FIB4_RTYPE_BROADCAST;
      92          12 :     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          33 :   }
     105             : 
     106         138 :   for( ; RTA_OK( rat, rat_sz ); rat=RTA_NEXT( rat, rat_sz ) ) {
     107         138 :     void * rta    = RTA_DATA( rat );
     108         138 :     ulong  rta_sz = RTA_PAYLOAD( rat );
     109             : 
     110         138 :     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          30 :     case RTA_DST:
     117          30 :       if( FD_UNLIKELY( rta_sz!=4UL ) ) {
     118           0 :         hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
     119           0 :         continue;
     120           0 :       }
     121          30 :       ip4_dst = FD_LOAD( uint, rta ); /* big endian */
     122          30 :       prefix  = msg->rtm_dst_len;
     123          30 :       break;
     124             : 
     125          33 :     case RTA_OIF:
     126          33 :       fd_fib4_rta_oif( hop, rta, rta_sz );
     127          33 :       break;
     128             : 
     129          33 :     case RTA_PREFSRC:
     130          33 :       fd_fib4_rta_prefsrc( hop, rta, rta_sz );
     131          33 :       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          33 :     case RTA_TABLE:
     142             :       /* Skip routes that aren't in the requested table */
     143          33 :       if( FD_UNLIKELY( rta_sz!=4UL ) ) {
     144           0 :         hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
     145           0 :         continue;
     146           0 :       }
     147          33 :       if( FD_LOAD( uint, rta )!=table_id ) return 0;
     148          33 :       break;
     149             : 
     150          33 :     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         138 :     }
     155         138 :   }
     156             : 
     157          33 :   if( fd_fib4_free_cnt( fib )==0UL ) return ENOSPC;
     158          33 :   *fd_fib4_append( fib, ip4_dst, prefix, prio ) = *hop;
     159             : 
     160          33 :   return 0;
     161          33 : }
     162             : 
     163             : int
     164             : fd_fib4_netlink_load_table( fd_fib4_t *    fib,
     165             :                             fd_netlink_t * netlink,
     166           6 :                             uint           table_id ) {
     167             : 
     168           6 :   uint seq = netlink->seq++;
     169             : 
     170           6 :   struct {
     171           6 :     struct nlmsghdr nlh;  /* Netlink header */
     172           6 :     struct rtmsg    rtm;  /* Payload - route message */
     173           6 :     struct rtattr   rta;
     174           6 :     uint            table_id;
     175           6 :   } request;
     176           6 :   request.nlh = (struct nlmsghdr) {
     177           6 :     .nlmsg_type  = RTM_GETROUTE,
     178           6 :     .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
     179           6 :     .nlmsg_len   = sizeof(request),
     180           6 :     .nlmsg_seq   = seq
     181           6 :   };
     182           6 :   request.rtm = (struct rtmsg) {
     183           6 :     .rtm_family = AF_INET, /* IPv4 */
     184           6 :   };
     185           6 :   request.rta = (struct rtattr) {
     186           6 :     .rta_type = RTA_TABLE,
     187           6 :     .rta_len  = RTA_LENGTH( sizeof(uint) )
     188           6 :   };
     189           6 :   request.table_id = table_id;
     190             : 
     191           6 :   long send_res = sendto( netlink->fd, &request, sizeof(request), 0, NULL, 0 );
     192           6 :   if( FD_UNLIKELY( send_res<0 ) ) {
     193           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 ) ));
     194           0 :     return errno;
     195           0 :   }
     196           6 :   if( FD_UNLIKELY( send_res!=sizeof(request) ) ) {
     197           0 :     FD_LOG_WARNING(( "netlink send(%d,RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP) failed (short write)", netlink->fd ));
     198           0 :     return EPIPE;
     199           0 :   }
     200             : 
     201           6 :   fd_fib4_clear( fib );
     202             : 
     203           6 :   int   dump_intr = 0;
     204           6 :   int   no_space  = 0;
     205           6 :   ulong route_cnt = 0UL;
     206             : 
     207           6 :   uchar buf[ 4096 ];
     208           6 :   fd_netlink_iter_t iter[1];
     209           6 :   for( fd_netlink_iter_init( iter, netlink, buf, sizeof(buf) );
     210          72 :        !fd_netlink_iter_done( iter );
     211          66 :        fd_netlink_iter_next( iter, netlink ) ) {
     212          66 :     struct nlmsghdr const * nlh = fd_netlink_iter_msg( iter );
     213          66 :     if( FD_UNLIKELY( nlh->nlmsg_flags & NLM_F_DUMP_INTR ) ) dump_intr = 1;
     214          66 :     if( FD_UNLIKELY( nlh->nlmsg_type==NLMSG_ERROR ) ) {
     215           0 :       struct nlmsgerr * err = NLMSG_DATA( nlh );
     216           0 :       int nl_err = -err->error;
     217           0 :       FD_LOG_WARNING(( "netlink RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP failed (%d-%s)", nl_err, fd_io_strerror( nl_err ) ));
     218           0 :       return nl_err;
     219           0 :     }
     220          66 :     if( FD_UNLIKELY( nlh->nlmsg_type!=RTM_NEWROUTE ) ) {
     221           0 :       FD_LOG_DEBUG(( "unexpected nlmsg_type %u", nlh->nlmsg_type ));
     222           0 :       continue;
     223           0 :     }
     224          66 :     route_cnt++;
     225             : 
     226          66 :     int translate_err = fd_fib4_netlink_translate( fib, nlh, table_id );
     227          66 :     if( FD_UNLIKELY( translate_err==ENOSPC ) ) {
     228           0 :       no_space = 1;
     229           0 :       break;
     230           0 :     }
     231          66 :   }
     232           6 :   if( FD_UNLIKELY( iter->err > 0 ) ) return FD_FIB_NETLINK_ERR_IO;
     233           6 :   ulong drain_cnt = fd_netlink_iter_drain( iter, netlink );
     234             : 
     235           6 :   if( no_space ) {
     236           0 :     FD_LOG_WARNING(( "Routing table is too small! `ip route show table %u` returned %lu entries, which exceeds the configured maximum of %lu",
     237           0 :                      table_id, route_cnt+drain_cnt, fd_fib4_max( fib ) ));
     238           0 :     fd_fib4_clear( fib );
     239           0 :     return FD_FIB_NETLINK_ERR_SPACE;
     240           0 :   }
     241             : 
     242           6 :   if( dump_intr ) {
     243           0 :     FD_LOG_DEBUG(( "received NLM_F_DUMP_INTR (our read of the routing table was overrun by a concurrent write)" ));
     244           0 :     return FD_FIB_NETLINK_ERR_INTR;
     245           0 :   }
     246             : 
     247           6 :   if( FD_UNLIKELY( drain_cnt ) ) {
     248           0 :     FD_LOG_WARNING(( "Unexpectedly skipped %lu routes. This is a bug!", drain_cnt ));
     249           0 :     return FD_FIB_NETLINK_ERR_OOPS;
     250           0 :   }
     251             : 
     252           6 :   return 0;
     253           6 : }
     254             : 
     255             : FD_FN_CONST char const *
     256           0 : fd_fib4_netlink_strerror( int err ) {
     257           0 :   switch( err ) {
     258           0 :   case FD_FIB_NETLINK_SUCCESS:
     259           0 :     return "success";
     260           0 :   case FD_FIB_NETLINK_ERR_OOPS:
     261           0 :     return "oops";
     262           0 :   case FD_FIB_NETLINK_ERR_IO:
     263           0 :     return "io";
     264           0 :   case FD_FIB_NETLINK_ERR_INTR:
     265           0 :     return "interrupt";
     266           0 :   case FD_FIB_NETLINK_ERR_SPACE:
     267           0 :     return "out of space";
     268           0 :   default:
     269           0 :     return "unknown";
     270           0 :   }
     271           0 : }

Generated by: LCOV version 1.14