LCOV - code coverage report
Current view: top level - waltz/ip - fd_fib4_netlink.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 114 193 59.1 %
Date: 2025-03-20 12:08:36 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          39 :                  ulong           rta_sz ) {
      40          39 :   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          39 :   hop->if_idx = FD_LOAD( uint, rta ); /* host byte order */
      46          39 : }
      47             : 
      48             : static void
      49             : fd_fib4_rta_prefsrc( fd_fib4_hop_t * hop,
      50             :                      void const *    rta,
      51          36 :                      ulong           rta_sz ) {
      52          36 :   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          36 :   hop->ip4_src = FD_LOAD( uint, rta ); /* big endian */
      58          36 : }
      59             : 
      60             : static int
      61             : fd_fib4_netlink_translate( fd_fib4_t *             fib,
      62             :                            struct nlmsghdr const * msg_hdr,
      63          78 :                            uint                    table_id ) {
      64          78 :   uint ip4_dst = 0U;
      65          78 :   int  prefix  = -1; /* -1 indicates unset ip4_dst / prefix */
      66          78 :   uint prio    = 0U; /* default metric */
      67             : 
      68          78 :   fd_fib4_hop_t hop[1] = {0};
      69             : 
      70          78 :   struct rtmsg *  msg    = NLMSG_DATA( msg_hdr );
      71          78 :   struct rtattr * rat    = RTM_RTA( msg );
      72          78 :   long            rat_sz = (long)(int)RTM_PAYLOAD( msg_hdr );
      73             : 
      74          78 :   switch( msg->rtm_type ) {
      75          18 :   case RTN_UNICAST:
      76          18 :     hop->rtype = FD_FIB4_RTYPE_UNICAST;
      77          18 :     break;
      78          24 :   case RTN_LOCAL:
      79          24 :     hop->rtype = FD_FIB4_RTYPE_LOCAL;
      80          24 :     break;
      81          36 :   case RTN_BROADCAST:
      82          36 :     hop->rtype = FD_FIB4_RTYPE_BROADCAST;
      83          36 :     break;
      84           0 :   case RTN_MULTICAST:
      85           0 :     hop->rtype = FD_FIB4_RTYPE_MULTICAST;
      86           0 :     break;
      87           0 :   case RTN_BLACKHOLE:
      88           0 :     hop->rtype = FD_FIB4_RTYPE_BLACKHOLE;
      89           0 :     break;
      90           0 :   default:
      91           0 :     FD_LOG_DEBUG(( "Unsupported route type (%u-%s)", msg->rtm_type, fd_netlink_rtm_type_str( msg->rtm_type ) ));
      92           0 :     hop->rtype = FD_FIB4_RTYPE_BLACKHOLE;
      93           0 :     hop->flags |= FD_FIB4_FLAG_RTYPE_UNSUPPORTED;
      94           0 :     break;
      95          78 :   }
      96             : 
      97         198 :   for( ; RTA_OK( rat, rat_sz ); rat=RTA_NEXT( rat, rat_sz ) ) {
      98         198 :     void * rta    = RTA_DATA( rat );
      99         198 :     ulong  rta_sz = RTA_PAYLOAD( rat );
     100             : 
     101         198 :     switch( rat->rta_type ) {
     102             : 
     103           3 :     case RTA_GATEWAY:
     104           3 :       fd_fib4_rta_gateway( hop, rta, rta_sz );
     105           3 :       break;
     106             : 
     107          36 :     case RTA_DST:
     108          36 :       if( FD_UNLIKELY( rta_sz!=4UL ) ) {
     109           0 :         hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
     110           0 :         continue;
     111           0 :       }
     112          36 :       ip4_dst = FD_LOAD( uint, rta ); /* big endian */
     113          36 :       prefix  = msg->rtm_dst_len;
     114          36 :       break;
     115             : 
     116          39 :     case RTA_OIF:
     117          39 :       fd_fib4_rta_oif( hop, rta, rta_sz );
     118          39 :       break;
     119             : 
     120          36 :     case RTA_PREFSRC:
     121          36 :       fd_fib4_rta_prefsrc( hop, rta, rta_sz );
     122          36 :       break;
     123             : 
     124           6 :     case RTA_PRIORITY:
     125           6 :       if( FD_UNLIKELY( rta_sz!=4UL ) ) {
     126           0 :         hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
     127           0 :         continue;
     128           0 :       }
     129           6 :       prio = FD_LOAD( uint, rta ); /* host byte order */
     130           6 :       break;
     131             : 
     132          78 :     case RTA_TABLE:
     133             :       /* Skip routes that aren't in the requested table */
     134          78 :       if( FD_UNLIKELY( rta_sz!=4UL ) ) {
     135           0 :         hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
     136           0 :         continue;
     137           0 :       }
     138          78 :       if( FD_LOAD( uint, rta )!=table_id ) return 0;
     139          39 :       break;
     140             : 
     141          39 :     default:
     142           0 :       FD_LOG_DEBUG(( "Unsupported route table attribute (%u-%s)", rat->rta_type, fd_netlink_rtattr_str( rat->rta_type ) ));
     143           0 :       hop->flags |= FD_FIB4_FLAG_RTA_UNSUPPORTED;
     144           0 :       break;
     145         198 :     }
     146         198 :   }
     147             : 
     148          39 :   if( fd_fib4_free_cnt( fib )==0UL ) return ENOSPC;
     149          39 :   *fd_fib4_append( fib, ip4_dst, prefix, prio ) = *hop;
     150             : 
     151          39 :   return 0;
     152          39 : }
     153             : 
     154             : int
     155             : fd_fib4_netlink_load_table( fd_fib4_t *    fib,
     156             :                             fd_netlink_t * netlink,
     157           6 :                             uint           table_id ) {
     158             : 
     159           6 :   uint seq = netlink->seq++;
     160             : 
     161           6 :   struct {
     162           6 :     struct nlmsghdr nlh;  /* Netlink header */
     163           6 :     struct rtmsg    rtm;  /* Payload - route message */
     164           6 :     struct rtattr   rta;
     165           6 :     uint            table_id;
     166           6 :   } request;
     167           6 :   request.nlh = (struct nlmsghdr) {
     168           6 :     .nlmsg_type  = RTM_GETROUTE,
     169           6 :     .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
     170           6 :     .nlmsg_len   = sizeof(request),
     171           6 :     .nlmsg_seq   = seq
     172           6 :   };
     173           6 :   request.rtm = (struct rtmsg) {
     174           6 :     .rtm_family = AF_INET, /* IPv4 */
     175           6 :   };
     176           6 :   request.rta = (struct rtattr) {
     177           6 :     .rta_type = RTA_TABLE,
     178           6 :     .rta_len  = RTA_LENGTH( sizeof(uint) )
     179           6 :   };
     180           6 :   request.table_id = table_id;
     181             : 
     182           6 :   long send_res = sendto( netlink->fd, &request, sizeof(request), 0, NULL, 0 );
     183           6 :   if( FD_UNLIKELY( send_res<0 ) ) {
     184           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 ) ));
     185           0 :     return errno;
     186           0 :   }
     187           6 :   if( FD_UNLIKELY( send_res!=sizeof(request) ) ) {
     188           0 :     FD_LOG_WARNING(( "netlink send(%d,RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP) failed (short write)", netlink->fd ));
     189           0 :     return EPIPE;
     190           0 :   }
     191             : 
     192           6 :   fd_fib4_clear( fib );
     193             : 
     194           6 :   int   dump_intr = 0;
     195           6 :   int   no_space  = 0;
     196           6 :   ulong route_cnt = 0UL;
     197             : 
     198           6 :   uchar buf[ 4096 ];
     199           6 :   fd_netlink_iter_t iter[1];
     200           6 :   for( fd_netlink_iter_init( iter, netlink, buf, sizeof(buf) );
     201          84 :        !fd_netlink_iter_done( iter );
     202          78 :        fd_netlink_iter_next( iter, netlink ) ) {
     203          78 :     struct nlmsghdr const * nlh = fd_netlink_iter_msg( iter );
     204          78 :     if( FD_UNLIKELY( nlh->nlmsg_flags & NLM_F_DUMP_INTR ) ) dump_intr = 1;
     205          78 :     if( FD_UNLIKELY( nlh->nlmsg_type==NLMSG_ERROR ) ) {
     206           0 :       struct nlmsgerr * err = NLMSG_DATA( nlh );
     207           0 :       int nl_err = -err->error;
     208           0 :       FD_LOG_WARNING(( "netlink RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP failed (%d-%s)", nl_err, fd_io_strerror( nl_err ) ));
     209           0 :       return nl_err;
     210           0 :     }
     211          78 :     if( FD_UNLIKELY( nlh->nlmsg_type!=RTM_NEWROUTE ) ) {
     212           0 :       FD_LOG_DEBUG(( "unexpected nlmsg_type %u", nlh->nlmsg_type ));
     213           0 :       continue;
     214           0 :     }
     215          78 :     route_cnt++;
     216             : 
     217          78 :     int translate_err = fd_fib4_netlink_translate( fib, nlh, table_id );
     218          78 :     if( FD_UNLIKELY( translate_err==ENOSPC ) ) {
     219           0 :       no_space = 1;
     220           0 :       break;
     221           0 :     }
     222          78 :   }
     223           6 :   if( FD_UNLIKELY( iter->err > 0 ) ) return FD_FIB_NETLINK_ERR_IO;
     224           6 :   ulong drain_cnt = fd_netlink_iter_drain( iter, netlink );
     225             : 
     226           6 :   if( no_space ) {
     227           0 :     FD_LOG_WARNING(( "Routing table is too small! `ip route show table %u` returned %lu entries, which exceeds the configured maximum of %lu",
     228           0 :                      table_id, route_cnt+drain_cnt, fd_fib4_max( fib ) ));
     229           0 :     fd_fib4_clear( fib );
     230           0 :     return FD_FIB_NETLINK_ERR_SPACE;
     231           0 :   }
     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             : FD_FN_CONST char const *
     247           0 : fd_fib4_netlink_strerror( int err ) {
     248           0 :   switch( err ) {
     249           0 :   case FD_FIB_NETLINK_SUCCESS:
     250           0 :     return "success";
     251           0 :   case FD_FIB_NETLINK_ERR_OOPS:
     252           0 :     return "oops";
     253           0 :   case FD_FIB_NETLINK_ERR_IO:
     254           0 :     return "io";
     255           0 :   case FD_FIB_NETLINK_ERR_INTR:
     256           0 :     return "interrupt";
     257           0 :   case FD_FIB_NETLINK_ERR_SPACE:
     258           0 :     return "out of space";
     259           0 :   default:
     260           0 :     return "unknown";
     261           0 :   }
     262           0 : }

Generated by: LCOV version 1.14