LCOV - code coverage report
Current view: top level - disco/net/xdp - fd_xdp_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 486 861 56.4 %
Date: 2025-09-18 04:41:32 Functions: 41 93 44.1 %

          Line data    Source code
       1             : /* The xdp tile translates between AF_XDP and fd_tango
       2             :    traffic.  It is responsible for setting up the XDP and
       3             :    XSK socket configuration. */
       4             : 
       5             : #include <errno.h>
       6             : #include <fcntl.h>
       7             : #include <net/if.h>
       8             : #include <netinet/in.h>
       9             : #include <sys/socket.h> /* MSG_DONTWAIT needed before importing the net seccomp filter */
      10             : #include <linux/if_xdp.h>
      11             : 
      12             : #include "../fd_net_common.h"
      13             : #include "../../metrics/fd_metrics.h"
      14             : #include "../../netlink/fd_netlink_tile.h" /* neigh4_solicit */
      15             : #include "../../topo/fd_topo.h"
      16             : 
      17             : #include "../../../waltz/ip/fd_fib4.h"
      18             : #include "../../../waltz/neigh/fd_neigh4_map.h"
      19             : #include "../../../waltz/mib/fd_netdev_tbl.h"
      20             : #include "../../../waltz/mib/fd_dbl_buf.h"
      21             : #include "../../../waltz/xdp/fd_xdp_redirect_user.h" /* fd_xsk_activate */
      22             : #include "../../../waltz/xdp/fd_xsk.h"
      23             : #include "../../../util/log/fd_dtrace.h"
      24             : #include "../../../util/net/fd_eth.h"
      25             : #include "../../../util/net/fd_ip4.h"
      26             : #include "../../../util/net/fd_gre.h"
      27             : 
      28             : #include <unistd.h>
      29             : #include <linux/if.h> /* struct ifreq */
      30             : #include <sys/ioctl.h>
      31             : #include <linux/unistd.h>
      32             : #include <linux/if_arp.h>
      33             : 
      34             : #include "generated/fd_xdp_tile_seccomp.h"
      35             : 
      36             : /* MAX_NET_INS controls the max number of TX links that a net tile can
      37             :    serve. */
      38             : 
      39             : #define MAX_NET_INS (32UL)
      40             : 
      41             : /* FD_XDP_STATS_INTERVAL_NS controls the XDP stats refresh interval.
      42             :    This should be lower than the interval at which the metrics tile
      43             :    collects metrics. */
      44             : 
      45           0 : #define FD_XDP_STATS_INTERVAL_NS (11e6) /* 11ms */
      46             : 
      47             : /* XSK_IDX_{MAIN,LO} are the hardcoded XSK indices in ctx->xsk[ ... ].
      48             :    Only net tile 0 has XSK_IDX_LO, all net tiles have XSK_IDX_MAIN. */
      49             : 
      50         420 : #define XSK_IDX_MAIN 0
      51         276 : #define XSK_IDX_LO   1
      52             : 
      53             : /* fd_net_in_ctx_t contains consumer information for an incoming tango
      54             :    link.  It is used as part of the TX path. */
      55             : 
      56             : typedef struct {
      57             :   fd_wksp_t * mem;
      58             :   ulong       chunk0;
      59             :   ulong       wmark;
      60             : } fd_net_in_ctx_t;
      61             : 
      62             : /* fd_net_out_ctx_t contains publisher information for a link to a
      63             :    downstream app tile.  It is used as part of the RX path. */
      64             : 
      65             : typedef struct {
      66             :   fd_frag_meta_t * mcache;
      67             :   ulong *          sync;
      68             :   ulong            depth;
      69             :   ulong            seq;
      70             : } fd_net_out_ctx_t;
      71             : 
      72             : /* fd_net_flusher_t controls the pacing of XDP sendto calls for flushing
      73             :    TX batches.  In the 'wakeup' XDP mode, no TX occurs unless the net
      74             :    tile wakes up the kernel periodically using the sendto() syscall.
      75             :    If sendto() is called too frequently, time is wasted on context
      76             :    switches.  If sendto() is called not often enough, packets are
      77             :    delayed or dropped.  sendto() calls make almost no guarantees how
      78             :    much packets are sent out, nor do they indicate when the kernel
      79             :    finishes a wakeup call (asynchronously dispatched).  The net tile
      80             :    thus uses a myraid of flush triggers that were tested for best
      81             :    performance. */
      82             : 
      83             : struct fd_net_flusher {
      84             : 
      85             :   /* Packets that were enqueued after the last sendto() wakeup are
      86             :      considered "pending".  If there are more than pending_wmark packets
      87             :      pending, a wakeup is dispatched.  Thus, this dispatch trigger is
      88             :      proportional to packet rate, but does not trigger if I/O is seldom. */
      89             :   ulong pending_cnt;
      90             :   ulong pending_wmark;
      91             : 
      92             :   /* Sometimes, packets are not flushed out even after a sendto()
      93             :      wakeup.  This can result in the tail of a burst getting delayed or
      94             :      overrun.  If more than tail_flush_backoff ticks pass since the last
      95             :      sendto() wakeup and there are still unacknowledged packets in the
      96             :      TX ring, issues another wakeup. */
      97             :   long next_tail_flush_ticks;
      98             :   long tail_flush_backoff;
      99             : 
     100             : };
     101             : 
     102             : typedef struct fd_net_flusher fd_net_flusher_t;
     103             : 
     104             : FD_PROTOTYPES_BEGIN
     105             : 
     106             : /* fd_net_flusher_inc marks a new packet as enqueued. */
     107             : 
     108             : static inline void
     109             : fd_net_flusher_inc( fd_net_flusher_t * flusher,
     110         276 :                     long               now ) {
     111         276 :   flusher->pending_cnt++;
     112         276 :   long next_flush = now + flusher->tail_flush_backoff;
     113         276 :   flusher->next_tail_flush_ticks = fd_long_min( flusher->next_tail_flush_ticks, next_flush );
     114         276 : }
     115             : 
     116             : /* fd_net_flusher_check returns 1 if a sendto() wakeup should be issued
     117             :    immediately.  now is a recent fd_tickcount() value.
     118             :    If tx_ring_empty==0 then the kernel is caught up with the net tile
     119             :    on the XDP TX ring.  (Otherwise, the kernel is behind the net tile) */
     120             : 
     121             : static inline int
     122             : fd_net_flusher_check( fd_net_flusher_t * flusher,
     123             :                       long               now,
     124         402 :                       int                tx_ring_empty ) {
     125         402 :   int flush_level   = flusher->pending_cnt >= flusher->pending_wmark;
     126         402 :   int flush_timeout = now >= flusher->next_tail_flush_ticks;
     127         402 :   int flush         = flush_level || flush_timeout;
     128         402 :   if( !flush ) return 0;
     129          26 :   if( FD_UNLIKELY( tx_ring_empty ) ) {
     130             :     /* Flush requested but caught up */
     131           6 :     flusher->pending_cnt           = 0UL;
     132           6 :     flusher->next_tail_flush_ticks = LONG_MAX;
     133           6 :     return 0;
     134           6 :   }
     135          20 :   return 1;
     136          26 : }
     137             : 
     138             : /* fd_net_flusher_wakeup signals a sendto() wakeup was done.  now is a
     139             :    recent fd_tickcount() value. */
     140             : 
     141             : static inline void
     142             : fd_net_flusher_wakeup( fd_net_flusher_t * flusher,
     143          20 :                        long               now ) {
     144          20 :   flusher->pending_cnt           = 0UL;
     145          20 :   flusher->next_tail_flush_ticks = now + flusher->tail_flush_backoff;
     146          20 : }
     147             : 
     148             : FD_PROTOTYPES_END
     149             : 
     150             : /* fd_net_free_ring is a FIFO queue that stores pointers to free XDP TX
     151             :    frames. */
     152             : 
     153             : struct fd_net_free_ring {
     154             :   ulong   prod;
     155             :   ulong   cons;
     156             :   ulong   depth;
     157             :   ulong * queue;
     158             : };
     159             : typedef struct fd_net_free_ring fd_net_free_ring_t;
     160             : 
     161             : typedef struct {
     162             :   /* An "XSK" is an AF_XDP socket */
     163             :   uint     xsk_cnt;
     164             :   fd_xsk_t xsk[ 2 ];
     165             :   int      prog_link_fds[ 2 ];
     166             : 
     167             :   /* UMEM frame region within dcache */
     168             :   void *   umem_frame0; /* First UMEM frame */
     169             :   ulong    umem_sz;     /* Usable UMEM size starting at frame0 */
     170             : 
     171             :   /* UMEM chunk region within workspace */
     172             :   uint     umem_chunk0; /* Lowest allowed chunk number */
     173             :   uint     umem_wmark;  /* Highest allowed chunk number */
     174             : 
     175             :   /* All net tiles are subscribed to the same TX links.  (These are
     176             :      incoming links from app tiles asking the net tile to send out packets)
     177             :      The net tiles "take turns" doing TX jobs based on the L3+L4 dst hash.
     178             :      net_tile_id is the index of the current interface, net_tile_cnt is the
     179             :      total amount of interfaces. */
     180             :   uint net_tile_id;
     181             :   uint net_tile_cnt;
     182             : 
     183             :   /* Details pertaining to an inflight send op */
     184             :   struct {
     185             :     uint   xsk_idx;
     186             :     void * frame;
     187             :     uchar  mac_addrs[12];     /* First 12 bytes of Ethernet header */
     188             :     uint   src_ip;            /* src_ip in net order */
     189             : 
     190             :     uint   use_gre;           /* The tx packet will be GRE-encapsulated */
     191             :     uint   gre_outer_src_ip;  /* For GRE: Outer iphdr's src_ip in net order */
     192             :     uint   gre_outer_dst_ip;  /* For GRE: Outer iphdr's dst_ip in net order */
     193             :   } tx_op;
     194             : 
     195             :   /* Round-robin cycle serivce operations */
     196             :   uint rr_idx;
     197             : 
     198             :   /* Ring tracking free packet buffers */
     199             :   fd_net_free_ring_t free_tx;
     200             : 
     201             :   uchar  src_mac_addr[6];
     202             : 
     203             :   uint   default_address;
     204             :   uint   bind_address;
     205             :   ushort shred_listen_port;
     206             :   ushort quic_transaction_listen_port;
     207             :   ushort legacy_transaction_listen_port;
     208             :   ushort gossip_listen_port;
     209             :   ushort repair_intake_listen_port;
     210             :   ushort repair_serve_listen_port;
     211             :   ushort send_src_port;
     212             : 
     213             :   ulong in_cnt;
     214             :   fd_net_in_ctx_t in[ MAX_NET_INS ];
     215             : 
     216             :   fd_net_out_ctx_t quic_out[1];
     217             :   fd_net_out_ctx_t shred_out[1];
     218             :   fd_net_out_ctx_t gossvf_out[1];
     219             :   fd_net_out_ctx_t repair_out[1];
     220             :   fd_net_out_ctx_t send_out[1];
     221             : 
     222             :   /* XDP stats refresh timer */
     223             :   long xdp_stats_interval_ticks;
     224             :   long next_xdp_stats_refresh;
     225             : 
     226             :   /* TX flush timers */
     227             :   fd_net_flusher_t tx_flusher[2]; /* one per XSK */
     228             : 
     229             :   /* Route and neighbor tables */
     230             :   fd_fib4_t const * fib_local;
     231             :   fd_fib4_t const * fib_main;
     232             :   fd_neigh4_hmap_t  neigh4[1];
     233             :   fd_netlink_neigh4_solicit_link_t neigh4_solicit[1];
     234             : 
     235             :   /* Netdev table */
     236             :   fd_dbl_buf_t *       netdev_dbl_buf;    /* remote copy of device table */
     237             :   uchar *              netdev_buf;        /* local copy of device table */
     238             :   ulong                netdev_buf_sz;
     239             :   fd_netdev_tbl_join_t netdev_tbl;        /* join to local copy of device table */
     240             :   int                  has_gre_interface; /* enable GRE support? */
     241             : 
     242             :   struct {
     243             :     ulong rx_pkt_cnt;
     244             :     ulong rx_bytes_total;
     245             :     ulong rx_undersz_cnt;
     246             :     ulong rx_fill_blocked_cnt;
     247             :     ulong rx_backp_cnt;
     248             :     long  rx_busy_cnt;
     249             :     long  rx_idle_cnt;
     250             : 
     251             :     ulong tx_submit_cnt;
     252             :     ulong tx_complete_cnt;
     253             :     ulong tx_bytes_total;
     254             :     ulong tx_route_fail_cnt;
     255             :     ulong tx_no_xdp_cnt;
     256             :     ulong tx_neigh_fail_cnt;
     257             :     ulong tx_full_fail_cnt;
     258             :     long  tx_busy_cnt;
     259             :     long  tx_idle_cnt;
     260             : 
     261             :     ulong xsk_tx_wakeup_cnt;
     262             :     ulong xsk_rx_wakeup_cnt;
     263             : 
     264             :     ulong rx_gre_cnt;
     265             :     ulong rx_gre_ignored_cnt;
     266             :     ulong rx_gre_inv_pkt_cnt;
     267             :     ulong tx_gre_cnt;
     268             :     ulong tx_gre_route_fail_cnt;
     269             :   } metrics;
     270             : } fd_net_ctx_t;
     271             : 
     272             : FD_FN_CONST static inline ulong
     273          18 : scratch_align( void ) {
     274          18 :   return 4096UL;
     275          18 : }
     276             : 
     277             : FD_FN_PURE static inline ulong
     278           6 : scratch_footprint( fd_topo_tile_t const * tile ) {
     279           6 :   ulong l = FD_LAYOUT_INIT;
     280           6 :   l = FD_LAYOUT_APPEND( l, alignof(fd_net_ctx_t), sizeof(fd_net_ctx_t)                      );
     281           6 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),        tile->xdp.free_ring_depth * sizeof(ulong) );
     282           6 :   l = FD_LAYOUT_APPEND( l, fd_netdev_tbl_align(), fd_netdev_tbl_footprint( NETDEV_MAX, BOND_MASTER_MAX ) );
     283           6 :   return FD_LAYOUT_FINI( l, scratch_align() );
     284           6 : }
     285             : 
     286             : static void
     287           0 : metrics_write( fd_net_ctx_t * ctx ) {
     288           0 :   FD_MCNT_SET(   NET, RX_PKT_CNT,          ctx->metrics.rx_pkt_cnt          );
     289           0 :   FD_MCNT_SET(   NET, RX_BYTES_TOTAL,      ctx->metrics.rx_bytes_total      );
     290           0 :   FD_MCNT_SET(   NET, RX_UNDERSZ_CNT,      ctx->metrics.rx_undersz_cnt      );
     291           0 :   FD_MCNT_SET(   NET, RX_FILL_BLOCKED_CNT, ctx->metrics.rx_fill_blocked_cnt );
     292           0 :   FD_MCNT_SET(   NET, RX_BACKPRESSURE_CNT, ctx->metrics.rx_backp_cnt        );
     293           0 :   FD_MGAUGE_SET( NET, RX_BUSY_CNT, (ulong)fd_long_max( ctx->metrics.rx_busy_cnt, 0L ) );
     294           0 :   FD_MGAUGE_SET( NET, RX_IDLE_CNT, (ulong)fd_long_max( ctx->metrics.rx_idle_cnt, 0L ) );
     295           0 :   FD_MGAUGE_SET( NET, TX_BUSY_CNT, (ulong)fd_long_max( ctx->metrics.tx_busy_cnt, 0L ) );
     296           0 :   FD_MGAUGE_SET( NET, TX_IDLE_CNT, (ulong)fd_long_max( ctx->metrics.tx_idle_cnt, 0L ) );
     297             : 
     298           0 :   FD_MCNT_SET( NET, TX_SUBMIT_CNT,        ctx->metrics.tx_submit_cnt     );
     299           0 :   FD_MCNT_SET( NET, TX_COMPLETE_CNT,      ctx->metrics.tx_complete_cnt   );
     300           0 :   FD_MCNT_SET( NET, TX_BYTES_TOTAL,       ctx->metrics.tx_bytes_total    );
     301           0 :   FD_MCNT_SET( NET, TX_ROUTE_FAIL_CNT,    ctx->metrics.tx_route_fail_cnt );
     302           0 :   FD_MCNT_SET( NET, TX_NEIGHBOR_FAIL_CNT, ctx->metrics.tx_neigh_fail_cnt );
     303           0 :   FD_MCNT_SET( NET, TX_FULL_FAIL_CNT,     ctx->metrics.tx_full_fail_cnt  );
     304             : 
     305           0 :   FD_MCNT_SET( NET, XSK_TX_WAKEUP_CNT,    ctx->metrics.xsk_tx_wakeup_cnt    );
     306           0 :   FD_MCNT_SET( NET, XSK_RX_WAKEUP_CNT,    ctx->metrics.xsk_rx_wakeup_cnt    );
     307             : 
     308           0 :   FD_MCNT_SET( NET, RX_GRE_CNT,            ctx->metrics.rx_gre_cnt            );
     309           0 :   FD_MCNT_SET( NET, RX_GRE_INVALID_CNT,    ctx->metrics.rx_gre_inv_pkt_cnt    );
     310           0 :   FD_MCNT_SET( NET, RX_GRE_IGNORED_CNT,    ctx->metrics.rx_gre_ignored_cnt    );
     311           0 :   FD_MCNT_SET( NET, TX_GRE_CNT,            ctx->metrics.tx_gre_cnt            );
     312           0 :   FD_MCNT_SET( NET, TX_GRE_ROUTE_FAIL_CNT, ctx->metrics.tx_gre_route_fail_cnt );
     313           0 : }
     314             : 
     315             : struct xdp_statistics_v0 {
     316             :   __u64 rx_dropped; /* Dropped for other reasons */
     317             :   __u64 rx_invalid_descs; /* Dropped due to invalid descriptor */
     318             :   __u64 tx_invalid_descs; /* Dropped due to invalid descriptor */
     319             : };
     320             : 
     321             : struct xdp_statistics_v1 {
     322             :   __u64 rx_dropped; /* Dropped for other reasons */
     323             :   __u64 rx_invalid_descs; /* Dropped due to invalid descriptor */
     324             :   __u64 tx_invalid_descs; /* Dropped due to invalid descriptor */
     325             :   __u64 rx_ring_full; /* Dropped due to rx ring being full */
     326             :   __u64 rx_fill_ring_empty_descs; /* Failed to retrieve item from fill ring */
     327             :   __u64 tx_ring_empty_descs; /* Failed to retrieve item from tx ring */
     328             : };
     329             : 
     330             : static void
     331           0 : poll_xdp_statistics( fd_net_ctx_t * ctx ) {
     332           0 :   struct xdp_statistics_v1 stats = {0};
     333           0 :   ulong xsk_cnt = ctx->xsk_cnt;
     334           0 :   for( ulong j=0UL; j<xsk_cnt; j++ ) {
     335           0 :     struct xdp_statistics_v1 sub_stats;
     336           0 :     uint optlen = (uint)sizeof(struct xdp_statistics_v1);
     337           0 :     if( FD_UNLIKELY( -1==getsockopt( ctx->xsk[ j ].xsk_fd, SOL_XDP, XDP_STATISTICS, &sub_stats, &optlen ) ) )
     338           0 :       FD_LOG_ERR(( "getsockopt(SOL_XDP, XDP_STATISTICS) failed: %s", strerror( errno ) ));
     339           0 :     if( FD_UNLIKELY( optlen!=sizeof(struct xdp_statistics_v0) &&
     340           0 :                      optlen!=sizeof(struct xdp_statistics_v1) ) ) {
     341           0 :       FD_LOG_ERR(( "getsockopt(SOL_XDP, XDP_STATISTICS) returned unexpected size %u", optlen ));
     342           0 :     }
     343           0 :     stats.rx_dropped               += sub_stats.rx_dropped;
     344           0 :     stats.rx_invalid_descs         += sub_stats.rx_invalid_descs;
     345           0 :     stats.tx_invalid_descs         += sub_stats.tx_invalid_descs;
     346           0 :     stats.rx_ring_full             += sub_stats.rx_ring_full;
     347           0 :     stats.rx_fill_ring_empty_descs += sub_stats.rx_fill_ring_empty_descs;
     348           0 :     stats.tx_ring_empty_descs      += sub_stats.tx_ring_empty_descs;
     349           0 :   }
     350             : 
     351           0 :   FD_MCNT_SET( NET, XDP_RX_DROPPED_OTHER,         stats.rx_dropped               );
     352           0 :   FD_MCNT_SET( NET, XDP_RX_INVALID_DESCS,         stats.rx_invalid_descs         );
     353           0 :   FD_MCNT_SET( NET, XDP_TX_INVALID_DESCS,         stats.tx_invalid_descs         );
     354           0 :   FD_MCNT_SET( NET, XDP_RX_RING_FULL,             stats.rx_ring_full             );
     355           0 :   FD_MCNT_SET( NET, XDP_RX_FILL_RING_EMPTY_DESCS, stats.rx_fill_ring_empty_descs );
     356           0 :   FD_MCNT_SET( NET, XDP_TX_RING_EMPTY_DESCS,      stats.tx_ring_empty_descs      );
     357           0 : }
     358             : 
     359             : /* net_is_fatal_xdp_error returns 1 if the given errno returned by an
     360             :    XDP API indicates a non-recoverable error code.  The net tile should
     361             :    crash if it sees such an error so the problem does not go undetected.
     362             :    Otherwise, returns 0. */
     363             : 
     364             : static int
     365           0 : net_is_fatal_xdp_error( int err ) {
     366           0 :   return err==ESOCKTNOSUPPORT || err==EOPNOTSUPP || err==EINVAL ||
     367           0 :          err==EPERM;
     368           0 : }
     369             : 
     370             : /* Load the netdev table to ctx->netdev_buf. Create a join in ctx->netdev_tbl_handle  */
     371             : 
     372             : static void
     373         372 : net_load_netdev_tbl( fd_net_ctx_t * ctx ) {
     374             :   /* Copy netdev table from netlink tile.  This could fail briefly
     375             :      during startup if the netlink tile is late to start up. */
     376         372 :   if( FD_UNLIKELY( !fd_dbl_buf_read( ctx->netdev_dbl_buf, ctx->netdev_buf_sz, ctx->netdev_buf, NULL ) ) ) return;
     377             : 
     378             :   /* Join local copy */
     379           0 :   if( FD_UNLIKELY( !fd_netdev_tbl_join( &ctx->netdev_dbl_buf, ctx->netdev_buf ) ) ) FD_LOG_ERR(("netdev table join failed"));
     380           0 : }
     381             : 
     382             : /* Query the netdev table. Return a fd_netdev_t pointer to the net device of the
     383             : interface specified by if_idx. Null if the if_idx is invalid */
     384             : 
     385             : static fd_netdev_t *
     386             : net_query_netdev_tbl( fd_net_ctx_t * ctx,
     387         423 :                       uint           if_idx ) {
     388             :   /* dev_tbl is one-indexed */
     389         423 :   if( if_idx>ctx->netdev_tbl.hdr->dev_cnt ) return NULL;
     390         420 :   return &ctx->netdev_tbl.dev_tbl[ if_idx ];
     391         423 : }
     392             : 
     393             : /* Iterates the netdev table and returns 1 if a GRE interface exists, 0 otherwise.
     394             :    Only called in privileged_init and during_housekeeping */
     395             : 
     396             : static int
     397         384 : net_check_gre_interface_exists( fd_net_ctx_t * ctx ) {
     398         384 :   fd_netdev_t * dev_tbl = ctx->netdev_tbl.dev_tbl;
     399         384 :   ushort        dev_cnt = ctx->netdev_tbl.hdr->dev_cnt;
     400             : 
     401       12858 :   for( ushort if_idx = 0; if_idx<dev_cnt; if_idx++ ) {
     402       12852 :     if( dev_tbl[if_idx].dev_type==ARPHRD_IPGRE ) return 1;
     403       12852 :   }
     404           6 :   return 0;
     405         384 : }
     406             : 
     407             : 
     408             : /* net_tx_ready returns 1 if the current XSK is ready to submit a TX send
     409             :    job.  If the XSK is blocked for sends, returns 0.  Reasons for block
     410             :    include:
     411             :    - No XSK TX buffer is available
     412             :    - XSK TX ring is full */
     413             : 
     414             : static int
     415             : net_tx_ready( fd_net_ctx_t * ctx,
     416         276 :               uint           xsk_idx ) {
     417         276 :   fd_xsk_t *           xsk     = &ctx->xsk[ xsk_idx ];
     418         276 :   fd_xdp_ring_t *      tx_ring = &xsk->ring_tx;
     419         276 :   fd_net_free_ring_t * free    = &ctx->free_tx;
     420         276 :   if( free->prod == free->cons ) return 0; /* drop */
     421         276 :   if( tx_ring->prod - tx_ring->cons >= tx_ring->depth ) return 0; /* drop */
     422         276 :   return 1;
     423         276 : }
     424             : 
     425             : /* net_rx_wakeup triggers xsk_recvmsg to run in the kernel.  Needs to be
     426             :    called periodically in order to receive packets. */
     427             : 
     428             : static void
     429             : net_rx_wakeup( fd_net_ctx_t * ctx,
     430             :                fd_xsk_t *     xsk,
     431           3 :                int *          charge_busy ) {
     432           3 :   if( !fd_xsk_rx_need_wakeup( xsk ) ) return;
     433           0 :   *charge_busy = 1;
     434           0 :   struct msghdr _ignored[ 1 ] = { 0 };
     435           0 :   if( FD_UNLIKELY( -1==recvmsg( xsk->xsk_fd, _ignored, MSG_DONTWAIT ) ) ) {
     436           0 :     if( FD_UNLIKELY( net_is_fatal_xdp_error( errno ) ) ) {
     437           0 :       FD_LOG_ERR(( "xsk recvmsg failed xsk_fd=%d (%i-%s)", xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     438           0 :     }
     439           0 :     if( FD_UNLIKELY( errno!=EAGAIN ) ) {
     440           0 :       long ts = fd_log_wallclock();
     441           0 :       if( ts > xsk->log_suppress_until_ns ) {
     442           0 :         FD_LOG_WARNING(( "xsk recvmsg failed xsk_fd=%d (%i-%s)", xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     443           0 :         xsk->log_suppress_until_ns = ts + (long)1e9;
     444           0 :       }
     445           0 :     }
     446           0 :   }
     447           0 :   ctx->metrics.xsk_rx_wakeup_cnt++;
     448           0 : }
     449             : 
     450             : /* net_tx_wakeup triggers xsk_sendmsg to run in the kernel.  Needs to be
     451             :    called periodically in order to transmit packets. */
     452             : 
     453             : static void
     454             : net_tx_wakeup( fd_net_ctx_t * ctx,
     455             :                fd_xsk_t *     xsk,
     456          23 :                int *          charge_busy ) {
     457          23 :   if( !fd_xsk_tx_need_wakeup( xsk ) ) return;
     458           0 :   if( FD_VOLATILE_CONST( *xsk->ring_tx.prod )==FD_VOLATILE_CONST( *xsk->ring_tx.cons ) ) return;
     459           0 :   *charge_busy = 1;
     460           0 :   if( FD_UNLIKELY( -1==sendto( xsk->xsk_fd, NULL, 0, MSG_DONTWAIT, NULL, 0 ) ) ) {
     461           0 :     if( FD_UNLIKELY( net_is_fatal_xdp_error( errno ) ) ) {
     462           0 :       FD_LOG_ERR(( "xsk sendto failed xsk_fd=%d (%i-%s)", xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     463           0 :     }
     464           0 :     if( FD_UNLIKELY( errno!=EAGAIN ) ) {
     465           0 :       long ts = fd_log_wallclock();
     466           0 :       if( ts > xsk->log_suppress_until_ns ) {
     467           0 :         FD_LOG_WARNING(( "xsk sendto failed xsk_fd=%d (%i-%s)", xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     468           0 :         xsk->log_suppress_until_ns = ts + (long)1e9;
     469           0 :       }
     470           0 :     }
     471           0 :   }
     472           0 :   ctx->metrics.xsk_tx_wakeup_cnt++;
     473           0 : }
     474             : 
     475             : /* net_tx_periodic_wakeup does a timer based xsk_sendmsg wakeup. */
     476             : 
     477             : static inline int
     478             : net_tx_periodic_wakeup( fd_net_ctx_t * ctx,
     479             :                         uint           xsk_idx,
     480             :                         long           now,
     481         402 :                         int *          charge_busy ) {
     482         402 :   uint tx_prod = FD_VOLATILE_CONST( *ctx->xsk[ xsk_idx ].ring_tx.prod );
     483         402 :   uint tx_cons = FD_VOLATILE_CONST( *ctx->xsk[ xsk_idx ].ring_tx.cons );
     484         402 :   int tx_ring_empty = tx_prod==tx_cons;
     485         402 :   if( fd_net_flusher_check( ctx->tx_flusher+xsk_idx, now, tx_ring_empty ) ) {
     486          20 :     net_tx_wakeup( ctx, &ctx->xsk[ xsk_idx ], charge_busy );
     487          20 :     fd_net_flusher_wakeup( ctx->tx_flusher+xsk_idx, now );
     488          20 :   }
     489         402 :   return 0;
     490         402 : }
     491             : 
     492             : static void
     493         372 : during_housekeeping( fd_net_ctx_t * ctx ) {
     494         372 :   long now = fd_tickcount();
     495         372 :   net_load_netdev_tbl( ctx );
     496         372 :   ctx->has_gre_interface = net_check_gre_interface_exists( ctx );
     497             : 
     498         372 :   ctx->metrics.rx_busy_cnt = 0UL;
     499         372 :   ctx->metrics.rx_idle_cnt = 0UL;
     500         372 :   ctx->metrics.tx_busy_cnt = 0UL;
     501         372 :   ctx->metrics.tx_idle_cnt = fd_seq_diff( ctx->free_tx.prod, ctx->free_tx.cons );
     502         744 :   for( uint j=0U; j<ctx->xsk_cnt; j++ ) {
     503         372 :     fd_xsk_t * xsk = &ctx->xsk[ j ];
     504             :     /* Refresh all sequence numbers (consumer first, then producer) */
     505         372 :     FD_COMPILER_MFENCE();
     506         372 :     xsk->ring_fr.cached_cons = FD_VOLATILE_CONST( *xsk->ring_fr.cons );
     507         372 :     xsk->ring_fr.cached_prod = FD_VOLATILE_CONST( *xsk->ring_fr.prod );
     508         372 :     xsk->ring_rx.cached_cons = FD_VOLATILE_CONST( *xsk->ring_rx.cons );
     509         372 :     xsk->ring_rx.cached_prod = FD_VOLATILE_CONST( *xsk->ring_rx.prod );
     510         372 :     xsk->ring_tx.cached_cons = FD_VOLATILE_CONST( *xsk->ring_tx.cons );
     511         372 :     xsk->ring_tx.cached_prod = FD_VOLATILE_CONST( *xsk->ring_tx.prod );
     512         372 :     xsk->ring_cr.cached_cons = FD_VOLATILE_CONST( *xsk->ring_cr.cons );
     513         372 :     xsk->ring_cr.cached_prod = FD_VOLATILE_CONST( *xsk->ring_cr.prod );
     514         372 :     FD_COMPILER_MFENCE();
     515         372 :     ctx->metrics.rx_busy_cnt += (long)(int)( xsk->ring_rx.cached_prod - xsk->ring_rx.cached_cons );
     516         372 :     ctx->metrics.rx_idle_cnt += (long)(int)( xsk->ring_fr.cached_prod - xsk->ring_fr.cached_cons );
     517         372 :     ctx->metrics.tx_busy_cnt += (long)(int)( xsk->ring_tx.cached_prod - xsk->ring_tx.cached_cons );
     518         372 :     ctx->metrics.tx_busy_cnt += (long)(int)( xsk->ring_cr.cached_prod - xsk->ring_cr.cached_cons );
     519         372 :   }
     520             : 
     521         372 :   if( now > ctx->next_xdp_stats_refresh ) {
     522           0 :     ctx->next_xdp_stats_refresh = now + ctx->xdp_stats_interval_ticks;
     523           0 :     poll_xdp_statistics( ctx );
     524           0 :   }
     525         372 : }
     526             : 
     527             : 
     528             : /* net_tx_route resolves the xsk index, src ip address, src MAC address, and
     529             :    dst MAC address.  Returns 1 on success, 0 on failure.
     530             :    On success, tx_op->{xsk_idx,src_ip,mac_addrs} is set, and if the dst_ip
     531             :    belongs to a GRE interface, is_gre_inf will set to 1 and
     532             :    tx_op->{gre_outer_src_ip, gre_outer_dst_ip} will be loaded from the netdev
     533             :    table. is_gre_inf is set to 0 if dst_ip doesn't belong to a GRE interface. */
     534             : 
     535             : static int
     536             : net_tx_route( fd_net_ctx_t * ctx,
     537             :               uint           dst_ip,
     538         552 :               uint *         is_gre_inf ) {
     539             : 
     540             :   /* Route lookup */
     541             : 
     542         552 :   fd_fib4_hop_t hop[2] = {0};
     543         552 :   fd_fib4_lookup( ctx->fib_local, hop+0, dst_ip, 0UL );
     544         552 :   fd_fib4_lookup( ctx->fib_main,  hop+1, dst_ip, 0UL );
     545         552 :   fd_fib4_hop_t const * next_hop = fd_fib4_hop_or( hop+0, hop+1 );
     546             : 
     547         552 :   uint rtype   = next_hop->rtype;
     548         552 :   uint if_idx  = next_hop->if_idx;
     549         552 :   uint ip4_src = next_hop->ip4_src;
     550             : 
     551         552 :   if( FD_UNLIKELY( rtype==FD_FIB4_RTYPE_LOCAL ) ) {
     552           0 :     rtype  = FD_FIB4_RTYPE_UNICAST;
     553           0 :     if_idx = 1;
     554           0 :   }
     555             : 
     556         552 :   if( FD_UNLIKELY( rtype!=FD_FIB4_RTYPE_UNICAST ) ) {
     557         129 :     ctx->metrics.tx_route_fail_cnt++;
     558         129 :     return 0;
     559         129 :   }
     560             : 
     561         423 :   fd_netdev_t * netdev = net_query_netdev_tbl( ctx, if_idx );
     562         423 :   if( !netdev ) {
     563           3 :     ctx->metrics.tx_route_fail_cnt++;
     564           3 :     return 0;
     565           3 :   }
     566             : 
     567         420 :   ip4_src = fd_uint_if( !!ctx->bind_address, ctx->bind_address, ip4_src );
     568         420 :   ctx->tx_op.src_ip  = ip4_src;
     569         420 :   ctx->tx_op.xsk_idx = UINT_MAX;
     570             : 
     571         420 :   FD_TEST( is_gre_inf );
     572         420 :   *is_gre_inf = 0;
     573         420 :   if( netdev->dev_type==ARPHRD_LOOPBACK ) {
     574             :     /* Set Ethernet src and dst address to 00:00:00:00:00:00 */
     575           0 :     memset( ctx->tx_op.mac_addrs, 0, 12UL );
     576           0 :     ctx->tx_op.xsk_idx = XSK_IDX_LO;
     577             :     /* Set preferred src address to 127.0.0.1 if no bind address is set */
     578           0 :     if( !ctx->tx_op.src_ip ) ctx->tx_op.src_ip = FD_IP4_ADDR( 127,0,0,1 );
     579           0 :     return 1;
     580         420 :   } else if( netdev->dev_type==ARPHRD_IPGRE ) {
     581             :     /* skip MAC addrs lookup for GRE inner dst ip */
     582         144 :     if( netdev->gre_src_ip ) ctx->tx_op.gre_outer_src_ip = netdev->gre_src_ip;
     583         144 :     ctx->tx_op.gre_outer_dst_ip = netdev->gre_dst_ip;
     584         144 :     *is_gre_inf = 1;
     585         144 :     return 1;
     586         144 :   }
     587             : 
     588         276 :   if( FD_UNLIKELY( netdev->dev_type!=ARPHRD_ETHER ) ) return 0; // drop
     589             : 
     590         276 :   if( FD_UNLIKELY( if_idx!=ctx->xsk[ XSK_IDX_MAIN ].if_idx ) ) {
     591           0 :     ctx->metrics.tx_no_xdp_cnt++;
     592           0 :     return 0;
     593           0 :   }
     594         276 :   ctx->tx_op.xsk_idx = XSK_IDX_MAIN;
     595             : 
     596             :   /* Neighbor resolve */
     597         276 :   uint neigh_ip = next_hop->ip4_gw;
     598         276 :   if( !neigh_ip ) neigh_ip = dst_ip;
     599             : 
     600         276 :   fd_neigh4_hmap_query_t neigh_query[1];
     601         276 :   int neigh_res = fd_neigh4_hmap_query_try( ctx->neigh4, &neigh_ip, NULL, neigh_query, 0 );
     602         276 :   if( FD_UNLIKELY( neigh_res!=FD_MAP_SUCCESS ) ) {
     603             :     /* Neighbor not found */
     604           0 :     fd_netlink_neigh4_solicit( ctx->neigh4_solicit, neigh_ip, if_idx, fd_frag_meta_ts_comp( fd_tickcount() ) );
     605           0 :     ctx->metrics.tx_neigh_fail_cnt++;
     606           0 :     return 0;
     607           0 :   }
     608         276 :   fd_neigh4_entry_t const * neigh = fd_neigh4_hmap_query_ele_const( neigh_query );
     609         276 :   if( FD_UNLIKELY( neigh->state != FD_NEIGH4_STATE_ACTIVE ) ) {
     610           0 :     ctx->metrics.tx_neigh_fail_cnt++;
     611           0 :     return 0;
     612           0 :   }
     613         276 :   ip4_src = fd_uint_if( !ip4_src, ctx->default_address, ip4_src );
     614         276 :   ctx->tx_op.src_ip = ip4_src;
     615         276 :   memcpy( ctx->tx_op.mac_addrs+0, neigh->mac_addr,  6 );
     616         276 :   memcpy( ctx->tx_op.mac_addrs+6, netdev->mac_addr, 6 );
     617             : 
     618         276 :   if( FD_UNLIKELY( fd_neigh4_hmap_query_test( neigh_query ) ) ) {
     619           0 :     ctx->metrics.tx_neigh_fail_cnt++;
     620           0 :     return 0;
     621           0 :   }
     622             : 
     623         276 :   return 1;
     624         276 : }
     625             : 
     626             : /* before_frag is called when a new metadata descriptor for a TX job is
     627             :    found.  This callback determines whether this net tile is responsible
     628             :    for the TX job.  If so, it prepares the TX op for the during_frag and
     629             :    after_frag callbacks. */
     630             : 
     631             : static inline int
     632             : before_frag( fd_net_ctx_t * ctx,
     633             :              ulong          in_idx,
     634             :              ulong          seq,
     635         402 :              ulong          sig ) {
     636         402 :   (void)in_idx; (void)seq;
     637             : 
     638             :   /* Find interface index of next packet */
     639         402 :   ulong proto = fd_disco_netmux_sig_proto( sig );
     640         402 :   if( FD_UNLIKELY( proto!=DST_PROTO_OUTGOING ) ) return 1;
     641             : 
     642             :   /* Load balance TX */
     643         402 :   uint net_tile_cnt = ctx->net_tile_cnt;
     644         402 :   uint hash         = (uint)fd_disco_netmux_sig_hash( sig );
     645         402 :   uint target_idx   = hash % net_tile_cnt;
     646         402 :   uint net_tile_id  = ctx->net_tile_id;
     647         402 :   uint dst_ip       = fd_disco_netmux_sig_ip( sig );
     648             : 
     649         402 :   ctx->tx_op.use_gre          = 0;
     650         402 :   ctx->tx_op.gre_outer_dst_ip = 0;
     651         402 :   ctx->tx_op.gre_outer_src_ip = 0;
     652         402 :   uint is_gre_inf             = 0;
     653             : 
     654         402 :   if( FD_UNLIKELY( !net_tx_route( ctx, dst_ip, &is_gre_inf ) ) ) {
     655         126 :     return 1; /* metrics incremented by net_tx_route */
     656         126 :   }
     657             : 
     658         276 :   uint xsk_idx     = ctx->tx_op.xsk_idx;
     659             : 
     660         276 :   if( is_gre_inf ) {
     661         144 :     uint inner_src_ip = ctx->tx_op.src_ip;
     662         144 :     if( FD_UNLIKELY( !inner_src_ip ) ) {
     663           0 :       ctx->metrics.tx_gre_route_fail_cnt++;
     664           0 :       return 1;
     665           0 :     }
     666             :     /* Find the MAC addrs for the eth hdr, and src ip for outer ip4 hdr if not found in netdev tbl */
     667         144 :     ctx->tx_op.src_ip  = 0;
     668         144 :     is_gre_inf         = 0;
     669         144 :     if( FD_UNLIKELY( !net_tx_route( ctx, ctx->tx_op.gre_outer_dst_ip, &is_gre_inf ) ) ) {
     670           0 :       ctx->metrics.tx_gre_route_fail_cnt++;
     671           0 :       return 1;
     672           0 :     }
     673         144 :     if( is_gre_inf ) {
     674             :       /* Only one layer of tunnelling supported */
     675           0 :       ctx->metrics.tx_gre_route_fail_cnt++;
     676           0 :       return 1;
     677           0 :     }
     678         144 :     if( !ctx->tx_op.gre_outer_src_ip ) {
     679          72 :       ctx->tx_op.gre_outer_src_ip = ctx->tx_op.src_ip;
     680          72 :     }
     681         144 :     ctx->tx_op.use_gre = 1; /* indicate to during_frag to use GRE header */
     682         144 :     ctx->tx_op.src_ip  = inner_src_ip;
     683         144 :     xsk_idx = XSK_IDX_MAIN;
     684         144 :   }
     685             : 
     686         276 :   if( FD_UNLIKELY( xsk_idx>=ctx->xsk_cnt ) ) {
     687             :     /* Packet does not route to an XDP interface */
     688           0 :     ctx->metrics.tx_no_xdp_cnt++;
     689           0 :     return 1;
     690           0 :   }
     691             : 
     692         276 :   if( xsk_idx==XSK_IDX_LO ) target_idx = 0; /* loopback always targets tile 0 */
     693             : 
     694             :   /* Skip if another net tile is responsible for this packet */
     695             : 
     696         276 :   if( net_tile_id!=target_idx ) return 1; /* ignore */
     697             : 
     698             :   /* Skip if TX is blocked */
     699             : 
     700         276 :   if( FD_UNLIKELY( !net_tx_ready( ctx, xsk_idx ) ) ) {
     701           0 :     ctx->metrics.tx_full_fail_cnt++;
     702           0 :     return 1;
     703           0 :   }
     704             : 
     705             :   /* Allocate buffer for receive */
     706             : 
     707         276 :   fd_net_free_ring_t * free      = &ctx->free_tx;
     708         276 :   ulong                alloc_seq = free->cons;
     709         276 :   void *               frame     = (void *)free->queue[ alloc_seq % free->depth ];
     710         276 :   free->cons = fd_seq_inc( alloc_seq, 1UL );
     711             : 
     712         276 :   ctx->tx_op.frame = frame;
     713             : 
     714         276 :   return 0; /* continue */
     715         276 : }
     716             : 
     717             : /* during_frag is called when before_frag has committed to transmit an
     718             :    outgoing packet. */
     719             : 
     720             : static inline void
     721             : during_frag( fd_net_ctx_t * ctx,
     722             :              ulong          in_idx,
     723             :              ulong          seq FD_PARAM_UNUSED,
     724             :              ulong          sig FD_PARAM_UNUSED,
     725             :              ulong          chunk,
     726             :              ulong          sz,
     727         276 :              ulong          ctl FD_PARAM_UNUSED ) {
     728         276 :   if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>FD_NET_MTU ) )
     729           0 :     FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
     730             : 
     731         276 :   if( FD_UNLIKELY( sz<( sizeof(fd_eth_hdr_t)+sizeof(fd_ip4_hdr_t) ) ) )
     732           0 :     FD_LOG_ERR(( "packet too small %lu (in_idx=%lu)", sz, in_idx ));
     733             : 
     734         276 :   if( FD_UNLIKELY( sz>FD_ETH_PAYLOAD_MAX ) )
     735           0 :     FD_LOG_ERR(( "packet too big %lu (in_idx=%lu)", sz, in_idx ));
     736             : 
     737         276 :   void * frame = ctx->tx_op.frame;
     738         276 :   if( FD_UNLIKELY( (ulong)frame < (ulong)ctx->umem_frame0 ) )
     739           0 :     FD_LOG_ERR(( "frame %p out of bounds (below %p)", frame, (void *)ctx->umem_frame0 ));
     740         276 :   ulong umem_off = (ulong)frame - (ulong)ctx->umem_frame0;
     741         276 :   if( FD_UNLIKELY( (ulong)umem_off > (ulong)ctx->umem_sz ) )
     742           0 :     FD_LOG_ERR(( "frame %p out of bounds (beyond %p)", frame, (void *)ctx->umem_sz ));
     743             : 
     744             :   /* Speculatively copy frame into XDP buffer */
     745         276 :   uchar const * src = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     746             : 
     747         276 :   if( ctx->tx_op.use_gre ) {
     748             :     /* Discard the ethernet hdr from src. Copy the rest to where the inner ip4_hdr is.
     749             :        Safe from overflow: FD_ETH_PAYLOAD_MAX + header overhead < frame size (2048UL) */
     750         144 :     ulong overhead = sizeof(fd_eth_hdr_t) + sizeof(fd_ip4_hdr_t) + sizeof(fd_gre_hdr_t);
     751         144 :     fd_memcpy( (void *)( (ulong)ctx->tx_op.frame + overhead ), src + sizeof(fd_eth_hdr_t), sz - sizeof(fd_eth_hdr_t) );
     752         144 :   } else {
     753         132 :     fd_memcpy( ctx->tx_op.frame, src, sz );
     754         132 :   }
     755         276 : }
     756             : 
     757             : /* after_frag is called when the during_frag memcpy was _not_ overrun. */
     758             : 
     759             : static void
     760             : after_frag( fd_net_ctx_t *      ctx,
     761             :             ulong               in_idx,
     762             :             ulong               seq,
     763             :             ulong               sig,
     764             :             ulong               sz,
     765             :             ulong               tsorig,
     766             :             ulong               tspub,
     767         276 :             fd_stem_context_t * stem ) {
     768         276 :   (void)in_idx; (void)seq; (void)sig; (void)tsorig; (void)tspub; (void)stem;
     769             : 
     770             :   /* Current send operation */
     771             : 
     772         276 :   uchar *    frame   = ctx->tx_op.frame;
     773         276 :   uint       xsk_idx = ctx->tx_op.xsk_idx;
     774             : 
     775             :   /* Select Ethernet addresses */
     776         276 :   memcpy( frame, ctx->tx_op.mac_addrs, 12 );
     777             : 
     778         276 :   uchar * iphdr = frame + sizeof(fd_eth_hdr_t);
     779             : 
     780         276 :   if( ctx->tx_op.use_gre ) {
     781             : 
     782             :     /* For GRE packets, the ethertype will always be FD_ETH_HDR_TYPE_IP. outer source ip can't be 0 */
     783         144 :     if( FD_UNLIKELY( ctx->tx_op.gre_outer_src_ip==0 ) ) {
     784           0 :       ctx->metrics.tx_gre_route_fail_cnt++;
     785           0 :       return;
     786           0 :     }
     787             : 
     788             :     /* Write the last two bytes for eth_hdr */
     789         144 :     FD_STORE( ushort, frame+12, fd_ushort_bswap( FD_ETH_HDR_TYPE_IP ) );
     790             : 
     791         144 :     uchar * outer_iphdr       = frame + sizeof(fd_eth_hdr_t);
     792         144 :     uchar * gre_hdr           = outer_iphdr + sizeof(fd_ip4_hdr_t);
     793         144 :     uchar * inner_iphdr       = gre_hdr + sizeof(fd_gre_hdr_t);
     794             : 
     795             :     /* outer hdr + gre hdr + inner net_tot_len */
     796         144 :     ushort  outer_net_tot_len = (ushort)( sizeof(fd_ip4_hdr_t) + sizeof(fd_gre_hdr_t) + fd_ushort_bswap( ( (fd_ip4_hdr_t *)inner_iphdr )->net_tot_len ) );
     797             : 
     798             :     /* Construct outer ip header */
     799         144 :     fd_ip4_hdr_t ip4_outer = (fd_ip4_hdr_t) {
     800         144 :       .verihl       = FD_IP4_VERIHL( 4,5 ),
     801         144 :       .tos          = 0,
     802         144 :       .net_tot_len  = fd_ushort_bswap( outer_net_tot_len ),
     803         144 :       .net_id       = 0,
     804         144 :       .net_frag_off = fd_ushort_bswap( FD_IP4_HDR_FRAG_OFF_DF ),
     805         144 :       .ttl          = 64,
     806         144 :       .protocol     = FD_IP4_HDR_PROTOCOL_GRE,
     807         144 :       .check        = 0,
     808         144 :       .saddr        = ctx->tx_op.gre_outer_src_ip,
     809         144 :       .daddr        = ctx->tx_op.gre_outer_dst_ip,
     810         144 :     };
     811         144 :     ip4_outer.check = fd_ip4_hdr_check_fast( &ip4_outer );
     812         144 :     FD_STORE( fd_ip4_hdr_t, outer_iphdr, ip4_outer );
     813             : 
     814             :     /* Construct gre header */
     815         144 :     fd_gre_hdr_t gre_hdr_ = {
     816         144 :       .flags_version = FD_GRE_HDR_FLG_VER_BASIC,
     817         144 :       .protocol      = fd_ushort_bswap( FD_ETH_HDR_TYPE_IP )
     818         144 :     };
     819         144 :     FD_STORE( fd_gre_hdr_t, gre_hdr, gre_hdr_ );
     820             : 
     821         144 :     iphdr   = inner_iphdr;
     822         144 :     sz      = sizeof(fd_eth_hdr_t) + outer_net_tot_len;
     823         144 :     xsk_idx = 0;
     824         144 :   }
     825             : 
     826             :   /* Construct (inner) ip header */
     827         276 :   uint   ihl         = FD_IP4_GET_LEN( *(fd_ip4_hdr_t *)iphdr );
     828         276 :   uint   ver         = FD_IP4_GET_VERSION( *(fd_ip4_hdr_t *)iphdr );
     829         276 :   uint   ip4_saddr   = FD_LOAD( uint, iphdr+12 );
     830         276 :   ushort ethertype   = FD_LOAD( ushort, frame+12 );
     831         276 :   if( ethertype==fd_ushort_bswap( FD_ETH_HDR_TYPE_IP ) && ver!=0x4 ) {
     832           0 :     ctx->metrics.tx_route_fail_cnt++; // Not an IPv4 packet. drop
     833           0 :     return;
     834           0 :   }
     835             : 
     836         276 :   if( ethertype==fd_ushort_bswap( FD_ETH_HDR_TYPE_IP ) && ip4_saddr==0 ) {
     837         276 :     if( FD_UNLIKELY( ctx->tx_op.src_ip==0 ||
     838         276 :                      ihl<sizeof(fd_ip4_hdr_t) ||
     839         276 :                      (sizeof(fd_eth_hdr_t)+ihl)>sz ) ) {
     840             :       /* Outgoing IPv4 packet with unknown src IP or invalid IHL */
     841             :       /* FIXME should select first IPv4 address of device table here */
     842           0 :       ctx->metrics.tx_route_fail_cnt++;
     843           0 :       return;
     844           0 :     }
     845             :     /* Recompute checksum after changing header */
     846         276 :     FD_STORE( uint,   iphdr+12, ctx->tx_op.src_ip );
     847         276 :     FD_STORE( ushort, iphdr+10, 0 );
     848         276 :     FD_STORE( ushort, iphdr+10, fd_ip4_hdr_check( iphdr ) );
     849         276 :   }
     850             : 
     851             :   /* Submit packet TX job
     852             : 
     853             :      Invariant for ring_tx: prod-cons<length
     854             :      (This invariant breaks if any other packet is sent over this ring
     855             :      between before_frag and this point, e.g. send_arp_probe.) */
     856             : 
     857             : 
     858         276 :   fd_xsk_t      * xsk     = &ctx->xsk[ xsk_idx ];
     859         276 :   fd_xdp_ring_t * tx_ring = &xsk->ring_tx;
     860         276 :   uint            tx_seq  = FD_VOLATILE_CONST( *tx_ring->prod );
     861         276 :   uint            tx_mask = tx_ring->depth - 1U;
     862         276 :   xsk->ring_tx.packet_ring[ tx_seq&tx_mask ] = (struct xdp_desc) {
     863         276 :     .addr    = (ulong)frame - (ulong)ctx->umem_frame0,
     864         276 :     .len     = (uint)sz,
     865         276 :     .options = 0
     866         276 :   };
     867             : 
     868             :   /* Frame is now owned by kernel. Clear tx_op. */
     869         276 :   ctx->tx_op.frame = NULL;
     870             : 
     871             :   /* Register newly enqueued packet */
     872         276 :   FD_VOLATILE( *xsk->ring_tx.prod ) = tx_ring->cached_prod = tx_seq+1U;
     873         276 :   ctx->metrics.tx_submit_cnt++;
     874         276 :   ctx->metrics.tx_bytes_total += sz;
     875         276 :   if( ctx->tx_op.use_gre ) ctx->metrics.tx_gre_cnt++;
     876         276 :   fd_net_flusher_inc( ctx->tx_flusher+xsk_idx, fd_tickcount() );
     877             : 
     878         276 : }
     879             : 
     880             : /* net_rx_packet is called when a new Ethernet frame is available.
     881             :    Attempts to copy out the frame to a downstream tile. */
     882             : 
     883             : static void
     884             : net_rx_packet( fd_net_ctx_t * ctx,
     885             :                ulong          umem_off,
     886             :                ulong          sz,
     887         402 :                uint *         freed_chunk ) {
     888             : 
     889         402 :   if( FD_UNLIKELY( sz<sizeof(fd_eth_hdr_t)+sizeof(fd_ip4_hdr_t)+sizeof(fd_udp_hdr_t) ) ) {
     890           0 :     FD_DTRACE_PROBE( net_tile_err_rx_undersz );
     891           0 :     ctx->metrics.rx_undersz_cnt++;
     892           0 :     return;
     893           0 :   }
     894             : 
     895         402 :   uchar        * packet     = (uchar *)ctx->umem_frame0 + umem_off;
     896         402 :   uchar const  * packet_end = packet + sz;
     897         402 :   fd_ip4_hdr_t * iphdr      = (fd_ip4_hdr_t *)(packet + sizeof(fd_eth_hdr_t));
     898             : 
     899         402 :   if( FD_UNLIKELY( ((fd_eth_hdr_t *)packet)->net_type!=fd_ushort_bswap( FD_ETH_HDR_TYPE_IP ) ) ) return;
     900             : 
     901         402 :   int is_packet_gre = 0;
     902             :   /* Discard the GRE overhead (outer iphdr and gre hdr) */
     903         402 :   if( iphdr->protocol == FD_IP4_HDR_PROTOCOL_GRE ) {
     904         204 :     if( FD_UNLIKELY( ctx->has_gre_interface==0 ) ) {
     905           0 :       ctx->metrics.rx_gre_ignored_cnt++; // drop. No gre interface in netdev table
     906           0 :       return;
     907           0 :     }
     908         204 :     if( FD_UNLIKELY( FD_IP4_GET_VERSION( *iphdr )!=0x4 ) ) {
     909           0 :       ctx->metrics.rx_gre_inv_pkt_cnt++; // drop. IP version!=IPv4
     910           0 :       return;
     911           0 :     }
     912             : 
     913         204 :     ulong overhead = FD_IP4_GET_LEN( *iphdr ) + sizeof(fd_gre_hdr_t);
     914             : 
     915         204 :     if( FD_UNLIKELY( (uchar *)iphdr+overhead+sizeof(fd_ip4_hdr_t)>packet_end ) ) {
     916           0 :       FD_DTRACE_PROBE( net_tile_err_rx_undersz );
     917           0 :       ctx->metrics.rx_undersz_cnt++;  // inner ip4 header invalid
     918           0 :       return;
     919           0 :     }
     920             : 
     921             :     /* The new iphdr is where the inner iphdr was. Copy over the eth_hdr */
     922         204 :     iphdr              = (fd_ip4_hdr_t *)((uchar *)iphdr + overhead);
     923         204 :     uchar * new_packet = (uchar *)iphdr - sizeof(fd_eth_hdr_t);
     924         204 :     fd_memcpy( new_packet, packet, sizeof(fd_eth_hdr_t) );
     925         204 :     sz                 -= overhead;
     926         204 :     packet             = new_packet;
     927         204 :     umem_off           = (ulong)( packet - (uchar *)ctx->umem_frame0 );
     928         204 :     is_packet_gre      = 1;
     929         204 :   }
     930             : 
     931             :   /* Translate packet to UMEM frame index */
     932         402 :   ulong chunk       = ctx->umem_chunk0 + (umem_off>>FD_CHUNK_LG_SZ);
     933         402 :   ulong ctl         = umem_off & 0x3fUL;
     934             : 
     935             :   /* Filter for UDP/IPv4 packets. */
     936         402 :   if( FD_UNLIKELY( ( FD_IP4_GET_VERSION( *iphdr )!=0x4 ) ||
     937         402 :                    ( iphdr->protocol!=FD_IP4_HDR_PROTOCOL_UDP ) ) ) return;
     938             : 
     939             :   /* IPv4 is variable-length, so lookup IHL to find start of UDP */
     940         402 :   uint iplen        = FD_IP4_GET_LEN( *iphdr );
     941         402 :   uchar const * udp = (uchar *)iphdr + iplen;
     942             : 
     943         402 :   if( FD_UNLIKELY( udp+sizeof(fd_udp_hdr_t) > packet_end ) ) {
     944           0 :     FD_DTRACE_PROBE( net_tile_err_rx_undersz );
     945           0 :     ctx->metrics.rx_undersz_cnt++;
     946           0 :     return;
     947           0 :   }
     948             : 
     949             :   /* Extract IP dest addr and UDP src/dest port */
     950         402 :   fd_udp_hdr_t * udp_hdr = (fd_udp_hdr_t *)udp;
     951         402 :   uint ip_srcaddr        = iphdr->saddr;
     952         402 :   ushort udp_srcport     = fd_ushort_bswap( udp_hdr->net_sport );
     953         402 :   ushort udp_dstport     = fd_ushort_bswap( udp_hdr->net_dport );
     954             : 
     955         402 :   FD_DTRACE_PROBE_4( net_tile_pkt_rx, ip_srcaddr, udp_srcport, udp_dstport, sz );
     956             : 
     957             :   /* Route packet to downstream tile */
     958         402 :   ushort proto;
     959         402 :   fd_net_out_ctx_t * out;
     960         402 :   if(      FD_UNLIKELY( udp_dstport==ctx->shred_listen_port ) ) {
     961          18 :     proto = DST_PROTO_SHRED;
     962          18 :     out = ctx->shred_out;
     963         384 :   } else if( FD_UNLIKELY( udp_dstport==ctx->quic_transaction_listen_port ) ) {
     964         384 :     proto = DST_PROTO_TPU_QUIC;
     965         384 :     out = ctx->quic_out;
     966         384 :   } else if( FD_UNLIKELY( udp_dstport==ctx->legacy_transaction_listen_port ) ) {
     967           0 :     proto = DST_PROTO_TPU_UDP;
     968           0 :     out = ctx->quic_out;
     969           0 :   } else if( FD_UNLIKELY( udp_dstport==ctx->gossip_listen_port ) ) {
     970           0 :     proto = DST_PROTO_GOSSIP;
     971           0 :     out = ctx->gossvf_out;
     972           0 :   } else if( FD_UNLIKELY( udp_dstport==ctx->repair_intake_listen_port ) ) {
     973           0 :     proto = DST_PROTO_REPAIR;
     974           0 :     if( FD_UNLIKELY( sz == REPAIR_PING_SZ ) ) out = ctx->repair_out; /* ping-pong */
     975           0 :     else                                      out = ctx->shred_out;
     976           0 :   } else if( FD_UNLIKELY( udp_dstport==ctx->repair_serve_listen_port ) ) {
     977           0 :     proto = DST_PROTO_REPAIR;
     978           0 :     out = ctx->repair_out;
     979           0 :   } else if( FD_UNLIKELY( udp_dstport==ctx->send_src_port ) ) {
     980           0 :     proto = DST_PROTO_SEND;
     981           0 :     out = ctx->send_out;
     982           0 :   } else {
     983             : 
     984           0 :     FD_LOG_ERR(( "Firedancer received a UDP packet on port %hu which was not expected. "
     985           0 :                   "Only the following ports should be configured to forward packets: "
     986           0 :                   "%hu, %hu, %hu, %hu, %hu, %hu (excluding any 0 ports, which can be ignored)."
     987           0 :                   "Please report this error to Firedancer maintainers.",
     988           0 :                   udp_dstport,
     989           0 :                   ctx->shred_listen_port,
     990           0 :                   ctx->quic_transaction_listen_port,
     991           0 :                   ctx->legacy_transaction_listen_port,
     992           0 :                   ctx->gossip_listen_port,
     993           0 :                   ctx->repair_intake_listen_port,
     994           0 :                   ctx->repair_serve_listen_port ));
     995           0 :   }
     996             : 
     997             :   /* tile can decide how to partition based on src ip addr and src port */
     998         402 :   ulong sig              = fd_disco_netmux_sig( ip_srcaddr, udp_srcport, ip_srcaddr, proto, 14UL+8UL+iplen );
     999             : 
    1000             :   /* Peek the mline for an old frame */
    1001         402 :   fd_frag_meta_t * mline = out->mcache + fd_mcache_line_idx( out->seq, out->depth );
    1002         402 :   *freed_chunk           = mline->chunk;
    1003             : 
    1004             :   /* Overwrite the mline with the new frame */
    1005         402 :   ulong tspub            = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
    1006         402 :   fd_mcache_publish( out->mcache, out->depth, out->seq, sig, chunk, sz, ctl, 0, tspub );
    1007             : 
    1008             :   /* Wind up for the next iteration */
    1009         402 :   out->seq               = fd_seq_inc( out->seq, 1UL );
    1010             : 
    1011         402 :   if( is_packet_gre ) ctx->metrics.rx_gre_cnt++;
    1012         402 :   ctx->metrics.rx_pkt_cnt++;
    1013         402 :   ctx->metrics.rx_bytes_total += sz;
    1014         402 : }
    1015             : 
    1016             : /* net_comp_event is called when an XDP TX frame is free again. */
    1017             : 
    1018             : static void
    1019             : net_comp_event( fd_net_ctx_t * ctx,
    1020             :                 fd_xsk_t *     xsk,
    1021           0 :                 uint           comp_seq ) {
    1022             : 
    1023             :   /* Locate the incoming frame */
    1024             : 
    1025           0 :   fd_xdp_ring_t * comp_ring  = &xsk->ring_cr;
    1026           0 :   uint            comp_mask  = comp_ring->depth - 1U;
    1027           0 :   ulong           frame      = FD_VOLATILE_CONST( comp_ring->frame_ring[ comp_seq&comp_mask ] );
    1028           0 :   ulong const     frame_mask = FD_NET_MTU - 1UL;
    1029           0 :   if( FD_UNLIKELY( frame+FD_NET_MTU > ctx->umem_sz ) ) {
    1030           0 :     FD_LOG_ERR(( "Bounds check failed: frame=0x%lx umem_sz=0x%lx",
    1031           0 :                  frame, (ulong)ctx->umem_sz ));
    1032           0 :   }
    1033             : 
    1034             :   /* Check if we have space to return the freed frame */
    1035             : 
    1036           0 :   fd_net_free_ring_t * free      = &ctx->free_tx;
    1037           0 :   ulong                free_prod = free->prod;
    1038           0 :   ulong                free_mask = free->depth - 1UL;
    1039           0 :   long free_cnt = fd_seq_diff( free_prod, free->cons );
    1040           0 :   if( FD_UNLIKELY( free_cnt>=(long)free->depth ) ) return; /* blocked */
    1041             : 
    1042           0 :   free->queue[ free_prod&free_mask ] = (ulong)ctx->umem_frame0 + (frame & (~frame_mask));
    1043           0 :   free->prod = fd_seq_inc( free_prod, 1UL );
    1044             : 
    1045             :   /* Wind up for next iteration */
    1046             : 
    1047           0 :   FD_VOLATILE( *comp_ring->cons ) = comp_ring->cached_cons = comp_seq+1U;
    1048             : 
    1049           0 :   ctx->metrics.tx_complete_cnt++;
    1050             : 
    1051           0 : }
    1052             : 
    1053             : /* net_rx_event is called when a new XDP RX frame is available.  Calls
    1054             :    net_rx_packet, then returns the packet back to the kernel via the fill
    1055             :    ring.  */
    1056             : 
    1057             : static void
    1058             : net_rx_event( fd_net_ctx_t * ctx,
    1059             :               fd_xsk_t *     xsk,
    1060         402 :               uint           rx_seq ) {
    1061             :   /* Locate the incoming frame */
    1062             : 
    1063         402 :   fd_xdp_ring_t * rx_ring = &xsk->ring_rx;
    1064         402 :   uint            rx_mask = rx_ring->depth - 1U;
    1065         402 :   struct xdp_desc frame   = FD_VOLATILE_CONST( rx_ring->packet_ring[ rx_seq&rx_mask ] );
    1066             : 
    1067         402 :   if( FD_UNLIKELY( frame.len>FD_NET_MTU ) )
    1068           0 :     FD_LOG_ERR(( "received a UDP packet with a too large payload (%u)", frame.len ));
    1069             : 
    1070             :   /* Check if we have space in the fill ring to free the frame */
    1071             : 
    1072         402 :   fd_xdp_ring_t * fill_ring  = &xsk->ring_fr;
    1073         402 :   uint            fill_depth = fill_ring->depth;
    1074         402 :   uint            fill_mask  = fill_depth-1U;
    1075         402 :   ulong           frame_mask = FD_NET_MTU - 1UL;
    1076         402 :   uint            fill_prod  = FD_VOLATILE_CONST( *fill_ring->prod );
    1077         402 :   uint            fill_cons  = FD_VOLATILE_CONST( *fill_ring->cons );
    1078             : 
    1079         402 :   if( FD_UNLIKELY( (int)(fill_prod-fill_cons) >= (int)fill_depth ) ) {
    1080           0 :     ctx->metrics.rx_fill_blocked_cnt++;
    1081           0 :     return; /* blocked */
    1082           0 :   }
    1083             : 
    1084             :   /* Pass it to the receive handler */
    1085             : 
    1086         402 :   uint freed_chunk = (uint)( ctx->umem_chunk0 + (frame.addr>>FD_CHUNK_LG_SZ) );
    1087         402 :   net_rx_packet( ctx, frame.addr, frame.len, &freed_chunk );
    1088             : 
    1089         402 :   FD_COMPILER_MFENCE();
    1090         402 :   FD_VOLATILE( *rx_ring->cons ) = rx_ring->cached_cons = rx_seq+1U;
    1091             : 
    1092             :   /* Every RX operation returns one frame to the FILL ring.  If the
    1093             :      packet was forwarded to a downstream ring, the newly shadowed frame
    1094             :      is returned.  Otherwise, the frame just received is returned. */
    1095             : 
    1096         402 :   if( FD_UNLIKELY( ( freed_chunk < ctx->umem_chunk0 ) |
    1097         402 :                     ( freed_chunk > ctx->umem_wmark ) ) ) {
    1098           0 :     FD_LOG_CRIT(( "mcache corruption detected: chunk=%u chunk0=%u wmark=%u",
    1099           0 :                   freed_chunk, ctx->umem_chunk0, ctx->umem_wmark ));
    1100           0 :   }
    1101         402 :   ulong freed_off = (freed_chunk - ctx->umem_chunk0)<<FD_CHUNK_LG_SZ;
    1102         402 :   fill_ring->frame_ring[ fill_prod&fill_mask ] = freed_off & (~frame_mask);
    1103         402 :   FD_VOLATILE( *fill_ring->prod ) = fill_ring->cached_prod = fill_prod+1U;
    1104             : 
    1105         402 : }
    1106             : 
    1107             : /* before_credit is called every loop iteration. */
    1108             : 
    1109             : static void
    1110             : before_credit( fd_net_ctx_t *      ctx,
    1111             :                fd_stem_context_t * stem,
    1112         402 :                int *               charge_busy ) {
    1113         402 :   (void)stem;
    1114             :   /* A previous send attempt was overrun.  A corrupt copy of the packet was
    1115             :      placed into an XDP frame, but the frame was not yet submitted to the
    1116             :      TX ring.  Return the tx buffer to the free list. */
    1117             : 
    1118         402 :   if( ctx->tx_op.frame ) {
    1119           0 :     *charge_busy = 1;
    1120           0 :     fd_net_free_ring_t * free      = &ctx->free_tx;
    1121           0 :     ulong                alloc_seq = free->prod;
    1122           0 :     free->queue[ alloc_seq % free->depth ] = (ulong)ctx->tx_op.frame;
    1123           0 :     free->prod = fd_seq_inc( alloc_seq, 1UL );
    1124           0 :     ctx->tx_op.frame = NULL;
    1125           0 :   }
    1126             : 
    1127             :   /* Check if new packets are available or if TX frames are free again
    1128             :      (Round-robin through sockets) */
    1129             : 
    1130         402 :   uint       rr_idx = ctx->rr_idx;
    1131         402 :   fd_xsk_t * rr_xsk = &ctx->xsk[ rr_idx ];
    1132             : 
    1133         402 :   net_tx_periodic_wakeup( ctx, rr_idx, fd_tickcount(), charge_busy );
    1134             : 
    1135         402 :   uint rx_cons = rr_xsk->ring_rx.cached_cons;
    1136         402 :   uint rx_prod = FD_VOLATILE_CONST( *rr_xsk->ring_rx.prod );
    1137         402 :   if( rx_cons!=rx_prod ) {
    1138         402 :     *charge_busy = 1;
    1139         402 :     rr_xsk->ring_rx.cached_prod = rx_prod;
    1140         402 :     net_rx_event( ctx, rr_xsk, rx_cons );
    1141         402 :   } else {
    1142           0 :     net_rx_wakeup( ctx, rr_xsk, charge_busy );
    1143           0 :     ctx->rr_idx++;
    1144           0 :     ctx->rr_idx = fd_uint_if( ctx->rr_idx>=ctx->xsk_cnt, 0, ctx->rr_idx );
    1145           0 :   }
    1146             : 
    1147         402 :   uint comp_cons = FD_VOLATILE_CONST( *rr_xsk->ring_cr.cons );
    1148         402 :   uint comp_prod = FD_VOLATILE_CONST( *rr_xsk->ring_cr.prod );
    1149         402 :   if( comp_cons!=comp_prod ) {
    1150           0 :     *charge_busy = 1;
    1151           0 :     rr_xsk->ring_cr.cached_prod = comp_prod;
    1152           0 :     net_comp_event( ctx, rr_xsk, comp_cons );
    1153           0 :   }
    1154             : 
    1155         402 : }
    1156             : 
    1157             : /* net_xsk_bootstrap assigns UMEM frames to the FILL ring. */
    1158             : 
    1159             : static ulong
    1160             : net_xsk_bootstrap( fd_net_ctx_t * ctx,
    1161             :                    uint           xsk_idx,
    1162           3 :                    ulong          frame_off ) {
    1163           3 :   fd_xsk_t * xsk = &ctx->xsk[ xsk_idx ];
    1164             : 
    1165           3 :   ulong const frame_sz  = FD_NET_MTU;
    1166           3 :   ulong const fr_depth  = ctx->xsk[ xsk_idx ].ring_fr.depth/2UL;
    1167             : 
    1168           3 :   fd_xdp_ring_t * fill      = &xsk->ring_fr;
    1169           3 :   uint            fill_prod = fill->cached_prod;
    1170       98307 :   for( ulong j=0UL; j<fr_depth; j++ ) {
    1171       98304 :     fill->frame_ring[ j ] = frame_off;
    1172       98304 :     frame_off += frame_sz;
    1173       98304 :   }
    1174           3 :   FD_VOLATILE( *fill->prod ) = fill->cached_prod = fill_prod + (uint)fr_depth;
    1175             : 
    1176           3 :   return frame_off;
    1177           3 : }
    1178             : 
    1179             : /* FIXME source MAC address from netlnk tile instead */
    1180             : 
    1181             : static void
    1182             : interface_addrs( const char * interface,
    1183             :                  uchar *      mac,
    1184           0 :                  uint *       ip4_addr ) {
    1185           0 :   int fd = socket( AF_INET, SOCK_DGRAM, 0 );
    1186           0 :   struct ifreq ifr;
    1187           0 :   ifr.ifr_addr.sa_family = AF_INET;
    1188             : 
    1189           0 :   strncpy( ifr.ifr_name, interface, IFNAMSIZ );
    1190           0 :   if( FD_UNLIKELY( ioctl( fd, SIOCGIFHWADDR, &ifr ) ) )
    1191           0 :     FD_LOG_ERR(( "could not get MAC address of interface `%s`: (%i-%s)", interface, errno, fd_io_strerror( errno ) ));
    1192           0 :   fd_memcpy( mac, ifr.ifr_hwaddr.sa_data, 6 );
    1193             : 
    1194           0 :   if( FD_UNLIKELY( ioctl( fd, SIOCGIFADDR, &ifr ) ) )
    1195           0 :     FD_LOG_ERR(( "could not get IP address of interface `%s`: (%i-%s)", interface, errno, fd_io_strerror( errno ) ));
    1196           0 :   *ip4_addr = ((struct sockaddr_in *)fd_type_pun( &ifr.ifr_addr ))->sin_addr.s_addr;
    1197             : 
    1198           0 :   if( FD_UNLIKELY( close(fd) ) )
    1199           0 :     FD_LOG_ERR(( "could not close socket (%i-%s)", errno, fd_io_strerror( errno ) ));
    1200           0 : }
    1201             : 
    1202             : /* privileged_init does the following initialization steps:
    1203             : 
    1204             :    - Create an AF_XDP socket
    1205             :    - Map XDP metadata rings
    1206             :    - Register UMEM data region with socket
    1207             :    - Insert AF_XDP socket into xsk_map
    1208             : 
    1209             :    Net tile 0 also runs fd_xdp_install and repeats the above step for
    1210             :    the loopback device.  (Unless the main interface is already loopback)
    1211             : 
    1212             :    Kernel object references:
    1213             : 
    1214             :      BPF_LINK file descriptor
    1215             :       |
    1216             :       +-> XDP program installation on NIC
    1217             :       |    |
    1218             :       |    +-> XDP program <-- BPF_PROG file descriptor (prog_fd)
    1219             :       |
    1220             :       +-> XSKMAP object <-- BPF_MAP file descriptor (xsk_map) */
    1221             : 
    1222             : FD_FN_UNUSED static void
    1223             : privileged_init( fd_topo_t *      topo,
    1224           0 :                  fd_topo_tile_t * tile ) {
    1225           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1226             : 
    1227           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1228           0 :   fd_net_ctx_t * ctx     = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_net_ctx_t), sizeof(fd_net_ctx_t) );
    1229           0 :   ulong *        free_tx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), tile->xdp.free_ring_depth * sizeof(ulong) );;
    1230             : 
    1231           0 :   fd_memset( ctx, 0, sizeof(fd_net_ctx_t) );
    1232             : 
    1233           0 :   uint if_idx = if_nametoindex( tile->xdp.interface );
    1234           0 :   if( FD_UNLIKELY( !if_idx ) ) FD_LOG_ERR(( "if_nametoindex(%s) failed", tile->xdp.interface ));
    1235             : 
    1236           0 :   interface_addrs( tile->xdp.interface, ctx->src_mac_addr, &ctx->default_address );
    1237             : 
    1238             :   /* Load up dcache containing UMEM */
    1239             : 
    1240           0 :   void * const dcache_mem          = fd_topo_obj_laddr( topo, tile->net.umem_dcache_obj_id );
    1241           0 :   void * const umem_dcache         = fd_dcache_join( dcache_mem );
    1242           0 :   ulong  const umem_dcache_data_sz = fd_dcache_data_sz( umem_dcache );
    1243           0 :   ulong  const umem_frame_sz       = 2048UL;
    1244             : 
    1245             :   /* Left shrink UMEM region to be 4096 byte aligned */
    1246             : 
    1247           0 :   void * const umem_frame0 = (void *)fd_ulong_align_up( (ulong)umem_dcache, 4096UL );
    1248           0 :   ulong        umem_sz     = umem_dcache_data_sz - ((ulong)umem_frame0 - (ulong)umem_dcache);
    1249           0 :   umem_sz = fd_ulong_align_dn( umem_sz, umem_frame_sz );
    1250             : 
    1251             :   /* Derive chunk bounds */
    1252             : 
    1253           0 :   void * const umem_base   = fd_wksp_containing( dcache_mem );
    1254           0 :   ulong  const umem_chunk0 = ( (ulong)umem_frame0 - (ulong)umem_base )>>FD_CHUNK_LG_SZ;
    1255           0 :   ulong  const umem_wmark  = umem_chunk0 + ( ( umem_sz-umem_frame_sz )>>FD_CHUNK_LG_SZ );
    1256             : 
    1257           0 :   if( FD_UNLIKELY( umem_chunk0>UINT_MAX || umem_wmark>UINT_MAX || umem_chunk0>umem_wmark ) ) {
    1258           0 :     FD_LOG_ERR(( "Calculated invalid UMEM bounds [%lu,%lu]", umem_chunk0, umem_wmark ));
    1259           0 :   }
    1260             : 
    1261           0 :   if( FD_UNLIKELY( !umem_base   ) ) FD_LOG_ERR(( "UMEM dcache is not in a workspace" ));
    1262           0 :   if( FD_UNLIKELY( !umem_dcache ) ) FD_LOG_ERR(( "Failed to join UMEM dcache" ));
    1263             : 
    1264           0 :   ctx->umem_frame0 = umem_frame0;
    1265           0 :   ctx->umem_sz     = umem_sz;
    1266           0 :   ctx->umem_chunk0 = (uint)umem_chunk0;
    1267           0 :   ctx->umem_wmark  = (uint)umem_wmark;
    1268             : 
    1269           0 :   ctx->free_tx.queue = free_tx;
    1270           0 :   ctx->free_tx.depth = tile->xdp.xdp_tx_queue_size;
    1271             : 
    1272             :   /* Create and install XSKs */
    1273             : 
    1274           0 :   fd_xsk_params_t params0 = {
    1275           0 :     .if_idx      = if_idx,
    1276           0 :     .if_queue_id = (uint)tile->kind_id,
    1277             : 
    1278             :     /* Some kernels produce EOPNOTSUP errors on sendto calls when
    1279             :        starting up without either XDP_ZEROCOPY or XDP_COPY
    1280             :        (e.g. 5.14.0-503.23.1.el9_5 with i40e) */
    1281           0 :     .bind_flags  = tile->xdp.zero_copy ? XDP_ZEROCOPY : XDP_COPY,
    1282             : 
    1283           0 :     .fr_depth  = tile->xdp.xdp_rx_queue_size*2,
    1284           0 :     .rx_depth  = tile->xdp.xdp_rx_queue_size,
    1285           0 :     .cr_depth  = tile->xdp.xdp_tx_queue_size,
    1286           0 :     .tx_depth  = tile->xdp.xdp_tx_queue_size,
    1287             : 
    1288           0 :     .umem_addr = umem_frame0,
    1289           0 :     .frame_sz  = umem_frame_sz,
    1290           0 :     .umem_sz   = umem_sz
    1291           0 :   };
    1292             : 
    1293           0 :   int xsk_map_fd = 123462;
    1294           0 :   ctx->prog_link_fds[ 0 ] = 123463;
    1295             :   /* Init XSK */
    1296           0 :   if( FD_UNLIKELY( !fd_xsk_init( &ctx->xsk[ 0 ], &params0 ) ) )       FD_LOG_ERR(( "failed to bind xsk for net tile %lu", tile->kind_id ));
    1297           0 :   if( FD_UNLIKELY( !fd_xsk_activate( &ctx->xsk[ 0 ], xsk_map_fd ) ) ) FD_LOG_ERR(( "failed to activate xsk for net tile %lu", tile->kind_id ));
    1298           0 :   ctx->xsk_cnt = 1;
    1299             : 
    1300           0 :   if( FD_UNLIKELY( fd_sandbox_gettid()==fd_sandbox_getpid() ) ) {
    1301             :     /* Kind of gross.. in single threaded mode we don't want to close the xsk_map_fd
    1302             :        since it's shared with other net tiles.  Just check for that by seeing if we
    1303             :        are the only thread in the process. */
    1304           0 :     if( FD_UNLIKELY( -1==close( xsk_map_fd ) ) )                     FD_LOG_ERR(( "close(%d) failed (%d-%s)", xsk_map_fd, errno, fd_io_strerror( errno ) ));
    1305           0 :   }
    1306             : 
    1307             :   /* Networking tile at index 0 also binds to loopback (only queue 0 available on lo) */
    1308             : 
    1309           0 :   if( FD_UNLIKELY( strcmp( tile->xdp.interface, "lo" ) && !tile->kind_id ) ) {
    1310           0 :     ctx->xsk_cnt = 2;
    1311             : 
    1312           0 :     ushort udp_port_candidates[] = {
    1313           0 :       (ushort)tile->xdp.net.legacy_transaction_listen_port,
    1314           0 :       (ushort)tile->xdp.net.quic_transaction_listen_port,
    1315           0 :       (ushort)tile->xdp.net.shred_listen_port,
    1316           0 :       (ushort)tile->xdp.net.gossip_listen_port,
    1317           0 :       (ushort)tile->xdp.net.repair_intake_listen_port,
    1318           0 :       (ushort)tile->xdp.net.repair_serve_listen_port,
    1319           0 :       (ushort)tile->xdp.net.send_src_port
    1320           0 :     };
    1321             : 
    1322           0 :     uint lo_idx = if_nametoindex( "lo" );
    1323           0 :     if( FD_UNLIKELY( !lo_idx ) ) FD_LOG_ERR(( "if_nametoindex(lo) failed" ));
    1324             : 
    1325             :     /* FIXME move this to fd_topo_run */
    1326           0 :     fd_xdp_fds_t lo_fds = fd_xdp_install( lo_idx,
    1327           0 :                                           tile->net.bind_address,
    1328           0 :                                           sizeof(udp_port_candidates)/sizeof(udp_port_candidates[0]),
    1329           0 :                                           udp_port_candidates,
    1330           0 :                                           "skb" );
    1331             : 
    1332           0 :     ctx->prog_link_fds[ 1 ] = lo_fds.prog_link_fd;
    1333             :     /* init xsk 1 */
    1334           0 :     fd_xsk_params_t params1 = params0;
    1335           0 :     params1.if_idx      = lo_idx; /* probably always 1 */
    1336           0 :     params1.if_queue_id = 0;
    1337           0 :     params1.bind_flags  = 0;
    1338           0 :     if( FD_UNLIKELY( !fd_xsk_init( &ctx->xsk[ 1 ], &params1 ) ) )              FD_LOG_ERR(( "failed to bind lo_xsk" ));
    1339           0 :     if( FD_UNLIKELY( !fd_xsk_activate( &ctx->xsk[ 1 ], lo_fds.xsk_map_fd ) ) ) FD_LOG_ERR(( "failed to activate lo_xsk" ));
    1340           0 :     if( FD_UNLIKELY( -1==close( lo_fds.xsk_map_fd ) ) )                        FD_LOG_ERR(( "close(%d) failed (%d-%s)", xsk_map_fd, errno, fd_io_strerror( errno ) ));
    1341           0 :   }
    1342             : 
    1343           0 :   double tick_per_ns = fd_tempo_tick_per_ns( NULL );
    1344           0 :   ctx->xdp_stats_interval_ticks = (long)( FD_XDP_STATS_INTERVAL_NS * tick_per_ns );
    1345             : 
    1346           0 :   ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, 1UL );
    1347           0 :   if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
    1348           0 :     FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
    1349           0 : }
    1350             : 
    1351             : /* init_device_table joins the net tile to the netlink tile's device
    1352             :    table.  The device table is very frequently read, and rarely updated.
    1353             :    Therefore, the net tile keeps a local copy of the device table in
    1354             :    scratch memory.  This table is periodically copied over from the
    1355             :    netlink tile via a double buffer (netdev_dbl_buf).
    1356             : 
    1357             :    On startup, the netlink tile might not have produced its initial
    1358             :    device table.  Therefore, initialize the local copy to an empty
    1359             :    table. */
    1360             : 
    1361             : static void
    1362             : init_device_table( fd_net_ctx_t * ctx,
    1363           6 :                    void *         netdev_dbl_buf ) {
    1364             : 
    1365             :   /* Join remote double buffer containing device table updates */
    1366           6 :   ctx->netdev_dbl_buf = fd_dbl_buf_join( netdev_dbl_buf );
    1367           6 :   if( FD_UNLIKELY( !ctx->netdev_dbl_buf ) ) FD_LOG_ERR(( "fd_dbl_buf_join failed" ));
    1368           6 :   ctx->netdev_buf_sz  = fd_netdev_tbl_footprint( NETDEV_MAX, BOND_MASTER_MAX );
    1369             : 
    1370             :   /* Create temporary empty device table during startup */
    1371           6 :   FD_TEST( fd_netdev_tbl_join( &ctx->netdev_tbl, fd_netdev_tbl_new( ctx->netdev_buf, 1, 1 ) ) );
    1372             : 
    1373           6 : }
    1374             : 
    1375             : FD_FN_UNUSED static void
    1376             : unprivileged_init( fd_topo_t *      topo,
    1377           3 :                    fd_topo_tile_t * tile ) {
    1378           3 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1379             : 
    1380           3 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1381           3 :   fd_net_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_net_ctx_t), sizeof(fd_net_ctx_t) );
    1382           3 :   FD_TEST( ctx->xsk_cnt!=0 );
    1383           3 :   FD_TEST( ctx->free_tx.queue!=NULL );
    1384           3 :   (void)FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), tile->xdp.free_ring_depth * sizeof(ulong) );
    1385           3 :   ctx->netdev_buf              = FD_SCRATCH_ALLOC_APPEND( l, fd_netdev_tbl_align(), ctx->netdev_buf_sz );
    1386             : 
    1387           0 :   ctx->net_tile_id  = (uint)tile->kind_id;
    1388           3 :   ctx->net_tile_cnt = (uint)fd_topo_tile_name_cnt( topo, tile->name );
    1389             : 
    1390           3 :   ctx->bind_address                   = tile->net.bind_address;
    1391           3 :   ctx->shred_listen_port              = tile->net.shred_listen_port;
    1392           3 :   ctx->quic_transaction_listen_port   = tile->net.quic_transaction_listen_port;
    1393           3 :   ctx->legacy_transaction_listen_port = tile->net.legacy_transaction_listen_port;
    1394           3 :   ctx->gossip_listen_port             = tile->net.gossip_listen_port;
    1395           3 :   ctx->repair_intake_listen_port      = tile->net.repair_intake_listen_port;
    1396           3 :   ctx->repair_serve_listen_port       = tile->net.repair_serve_listen_port;
    1397           3 :   ctx->send_src_port                  = tile->net.send_src_port;
    1398             : 
    1399             :   /* Put a bound on chunks we read from the input, to make sure they
    1400             :      are within in the data region of the workspace. */
    1401             : 
    1402           3 :   if( FD_UNLIKELY( !tile->in_cnt ) ) FD_LOG_ERR(( "net tile in link cnt is zero" ));
    1403           3 :   if( FD_UNLIKELY( tile->in_cnt>MAX_NET_INS ) ) FD_LOG_ERR(( "net tile in link cnt %lu exceeds MAX_NET_INS %lu", tile->in_cnt, MAX_NET_INS ));
    1404           3 :   FD_TEST( tile->in_cnt<=32 );
    1405          18 :   for( ulong i=0UL; i<tile->in_cnt; i++ ) {
    1406          15 :     fd_topo_link_t * link = &topo->links[ tile->in_link_id[ i ] ];
    1407          15 :     if( FD_UNLIKELY( link->mtu!=FD_NET_MTU ) ) FD_LOG_ERR(( "net tile in link %s does not have a normal MTU", link->name ));
    1408             : 
    1409          15 :     ctx->in[ i ].mem    = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
    1410          15 :     ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
    1411          15 :     ctx->in[ i ].wmark  = fd_dcache_compact_wmark( ctx->in[ i ].mem, link->dcache, link->mtu );
    1412          15 :   }
    1413             : 
    1414          21 :   for( ulong i = 0; i < tile->out_cnt; i++ ) {
    1415          18 :     fd_topo_link_t * out_link = &topo->links[ tile->out_link_id[ i  ] ];
    1416          18 :     if( strcmp( out_link->name, "net_quic" ) == 0 ) {
    1417           3 :       fd_topo_link_t * quic_out = out_link;
    1418           3 :       ctx->quic_out->mcache = quic_out->mcache;
    1419           3 :       ctx->quic_out->sync   = fd_mcache_seq_laddr( ctx->quic_out->mcache );
    1420           3 :       ctx->quic_out->depth  = fd_mcache_depth( ctx->quic_out->mcache );
    1421           3 :       ctx->quic_out->seq    = fd_mcache_seq_query( ctx->quic_out->sync );
    1422          15 :     } else if( strcmp( out_link->name, "net_shred" ) == 0 ) {
    1423           3 :       fd_topo_link_t * shred_out = out_link;
    1424           3 :       ctx->shred_out->mcache = shred_out->mcache;
    1425           3 :       ctx->shred_out->sync   = fd_mcache_seq_laddr( ctx->shred_out->mcache );
    1426           3 :       ctx->shred_out->depth  = fd_mcache_depth( ctx->shred_out->mcache );
    1427           3 :       ctx->shred_out->seq    = fd_mcache_seq_query( ctx->shred_out->sync );
    1428          12 :     } else if( strcmp( out_link->name, "net_gossvf" ) == 0 ) {
    1429           3 :       fd_topo_link_t * gossip_out = out_link;
    1430           3 :       ctx->gossvf_out->mcache = gossip_out->mcache;
    1431           3 :       ctx->gossvf_out->sync   = fd_mcache_seq_laddr( ctx->gossvf_out->mcache );
    1432           3 :       ctx->gossvf_out->depth  = fd_mcache_depth( ctx->gossvf_out->mcache );
    1433           3 :       ctx->gossvf_out->seq    = fd_mcache_seq_query( ctx->gossvf_out->sync );
    1434           9 :     } else if( strcmp( out_link->name, "net_repair" ) == 0 ) {
    1435           3 :       fd_topo_link_t * repair_out = out_link;
    1436           3 :       ctx->repair_out->mcache = repair_out->mcache;
    1437           3 :       ctx->repair_out->sync   = fd_mcache_seq_laddr( ctx->repair_out->mcache );
    1438           3 :       ctx->repair_out->depth  = fd_mcache_depth( ctx->repair_out->mcache );
    1439           3 :       ctx->repair_out->seq    = fd_mcache_seq_query( ctx->repair_out->sync );
    1440           6 :     } else if( strcmp( out_link->name, "net_netlnk" ) == 0 ) {
    1441           3 :       fd_topo_link_t * netlink_out = out_link;
    1442           3 :       ctx->neigh4_solicit->mcache = netlink_out->mcache;
    1443           3 :       ctx->neigh4_solicit->depth  = fd_mcache_depth( ctx->neigh4_solicit->mcache );
    1444           3 :       ctx->neigh4_solicit->seq    = fd_mcache_seq_query( fd_mcache_seq_laddr( ctx->neigh4_solicit->mcache ) );
    1445           3 :     } else if( strcmp( out_link->name, "net_send" ) == 0 ) {
    1446           3 :       fd_topo_link_t * send_out = out_link;
    1447           3 :       ctx->send_out->mcache = send_out->mcache;
    1448           3 :       ctx->send_out->sync   = fd_mcache_seq_laddr( ctx->send_out->mcache );
    1449           3 :       ctx->send_out->depth  = fd_mcache_depth( ctx->send_out->mcache );
    1450           3 :       ctx->send_out->seq    = fd_mcache_seq_query( ctx->send_out->sync );
    1451           3 :     } else {
    1452           0 :       FD_LOG_ERR(( "unrecognized out link `%s`", out_link->name ));
    1453           0 :     }
    1454          18 :   }
    1455             : 
    1456             :   /* Check if any of the tiles we set a listen port for do not have an outlink. */
    1457           3 :   if( FD_UNLIKELY( ctx->shred_listen_port!=0 && ctx->shred_out->mcache==NULL ) ) {
    1458           0 :     FD_LOG_ERR(( "shred listen port set but no out link was found" ));
    1459           3 :   } else if( FD_UNLIKELY( ctx->quic_transaction_listen_port!=0 && ctx->quic_out->mcache==NULL ) ) {
    1460           0 :     FD_LOG_ERR(( "quic transaction listen port set but no out link was found" ));
    1461           3 :   } else if( FD_UNLIKELY( ctx->legacy_transaction_listen_port!=0 && ctx->quic_out->mcache==NULL ) ) {
    1462           0 :     FD_LOG_ERR(( "legacy transaction listen port set but no out link was found" ));
    1463           3 :   } else if( FD_UNLIKELY( ctx->gossip_listen_port!=0 && ctx->gossvf_out->mcache==NULL ) ) {
    1464           0 :     FD_LOG_ERR(( "gossip listen port set but no out link was found" ));
    1465           3 :   } else if( FD_UNLIKELY( ctx->repair_intake_listen_port!=0 && ctx->repair_out->mcache==NULL ) ) {
    1466           0 :     FD_LOG_ERR(( "repair intake port set but no out link was found" ));
    1467           3 :   } else if( FD_UNLIKELY( ctx->repair_serve_listen_port!=0 && ctx->repair_out->mcache==NULL ) ) {
    1468           0 :     FD_LOG_ERR(( "repair serve listen port set but no out link was found" ));
    1469           3 :   } else if( FD_UNLIKELY( ctx->neigh4_solicit->mcache==NULL ) ) {
    1470           0 :     FD_LOG_ERR(( "netlink request link not found" ));
    1471           3 :   } else if( FD_UNLIKELY( ctx->send_src_port!=0 && ctx->send_out->mcache==NULL ) ) {
    1472           0 :     FD_LOG_ERR(( "send listen port set but no out link was found" ));
    1473           0 :   }
    1474             : 
    1475           9 :   for( uint j=0U; j<2U; j++ ) {
    1476           6 :     ctx->tx_flusher[ j ].pending_wmark         = (ulong)( (double)tile->xdp.xdp_tx_queue_size * 0.7 );
    1477           6 :     ctx->tx_flusher[ j ].tail_flush_backoff    = (long)( (double)tile->xdp.tx_flush_timeout_ns * fd_tempo_tick_per_ns( NULL ) );
    1478           6 :     ctx->tx_flusher[ j ].next_tail_flush_ticks = LONG_MAX;
    1479           6 :   }
    1480             : 
    1481             :   /* Join netbase objects */
    1482           3 :   ctx->fib_local = fd_fib4_join( fd_topo_obj_laddr( topo, tile->xdp.fib4_local_obj_id ) );
    1483           3 :   ctx->fib_main  = fd_fib4_join( fd_topo_obj_laddr( topo, tile->xdp.fib4_main_obj_id  ) );
    1484           3 :   if( FD_UNLIKELY( !ctx->fib_local || !ctx->fib_main ) ) FD_LOG_ERR(( "fd_fib4_join failed" ));
    1485           3 :   if( FD_UNLIKELY( !fd_neigh4_hmap_join(
    1486           3 :       ctx->neigh4,
    1487           3 :       fd_topo_obj_laddr( topo, tile->xdp.neigh4_obj_id ),
    1488           3 :       fd_topo_obj_laddr( topo, tile->xdp.neigh4_ele_obj_id ) ) ) ) {
    1489           0 :     FD_LOG_ERR(( "fd_neigh4_hmap_join failed" ));
    1490           0 :   }
    1491             : 
    1492           3 :   init_device_table( ctx, fd_topo_obj_laddr( topo, tile->xdp.netdev_dbl_buf_obj_id ) );
    1493             : 
    1494             :   /* Initialize TX free ring */
    1495             : 
    1496           3 :   ulong const frame_sz  = 2048UL;
    1497           3 :   ulong       frame_off = 0UL;
    1498           3 :   ulong const tx_depth  = ctx->free_tx.depth;
    1499       98307 :   for( ulong j=0; j<tx_depth; j++ ) {
    1500       98304 :     ctx->free_tx.queue[ j ] = (ulong)ctx->umem_frame0 + frame_off;
    1501       98304 :     frame_off += frame_sz;
    1502       98304 :   }
    1503           3 :   ctx->free_tx.prod = tx_depth;
    1504             : 
    1505             :   /* Initialize RX mcache chunks */
    1506             : 
    1507          21 :   for( ulong i=0UL; i<(tile->out_cnt); i++ ) {
    1508          18 :     fd_topo_link_t * out_link = &topo->links[ tile->out_link_id[ i  ] ];
    1509          18 :     fd_frag_meta_t * mcache   = out_link->mcache;
    1510      246162 :     for( ulong j=0UL; j<fd_mcache_depth( mcache ); j++ ) {
    1511      246144 :       mcache[ j ].chunk = (uint)( ctx->umem_chunk0 + (frame_off>>FD_CHUNK_LG_SZ) );
    1512      246144 :       frame_off += frame_sz;
    1513      246144 :     }
    1514          18 :   }
    1515             : 
    1516             :   /* Initialize FILL ring */
    1517             : 
    1518           3 :   int _charge_busy = 0;
    1519           6 :   for( uint j=0U; j<ctx->xsk_cnt; j++ ) {
    1520           3 :     frame_off = net_xsk_bootstrap( ctx, j, frame_off );
    1521           3 :     net_rx_wakeup( ctx, &ctx->xsk[ j ], &_charge_busy );
    1522           3 :     net_tx_wakeup( ctx, &ctx->xsk[ j ], &_charge_busy );
    1523           3 :   }
    1524             : 
    1525           3 :   if( FD_UNLIKELY( frame_off > ctx->umem_sz ) ) {
    1526           0 :     FD_LOG_ERR(( "UMEM is too small" ));
    1527           0 :   }
    1528           3 : }
    1529             : 
    1530             : FD_FN_UNUSED static ulong
    1531             : populate_allowed_seccomp( fd_topo_t const *      topo,
    1532             :                           fd_topo_tile_t const * tile,
    1533             :                           ulong                  out_cnt,
    1534           0 :                           struct sock_filter *   out ) {
    1535           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1536           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1537           0 :   fd_net_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_net_ctx_t ), sizeof( fd_net_ctx_t ) );
    1538             : 
    1539             :   /* A bit of a hack, if there is no loopback XSK for this tile, we still need to pass
    1540             :      two "allow" FD arguments to the net policy, so we just make them both the same. */
    1541           0 :   int allow_fd2 = ctx->xsk_cnt>1UL ? ctx->xsk[ 1 ].xsk_fd : ctx->xsk[ 0 ].xsk_fd;
    1542           0 :   FD_TEST( ctx->xsk[ 0 ].xsk_fd >= 0 && allow_fd2 >= 0 );
    1543             : 
    1544           0 :   populate_sock_filter_policy_fd_xdp_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), (uint)ctx->xsk[ 0 ].xsk_fd, (uint)allow_fd2 );
    1545           0 :   return sock_filter_policy_fd_xdp_tile_instr_cnt;
    1546           0 : }
    1547             : 
    1548             : FD_FN_UNUSED static ulong
    1549             : populate_allowed_fds( fd_topo_t const *      topo,
    1550             :                       fd_topo_tile_t const * tile,
    1551             :                       ulong                  out_fds_cnt,
    1552           0 :                       int *                  out_fds ) {
    1553           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1554           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1555           0 :   fd_net_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_net_ctx_t ), sizeof( fd_net_ctx_t ) );
    1556             : 
    1557           0 :   if( FD_UNLIKELY( out_fds_cnt<6UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
    1558             : 
    1559           0 :   ulong out_cnt = 0UL;
    1560             : 
    1561           0 :   out_fds[ out_cnt++ ] = 2; /* stderr */
    1562           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
    1563           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
    1564             : 
    1565           0 :                                       out_fds[ out_cnt++ ] = ctx->xsk[ 0 ].xsk_fd;
    1566           0 :                                       out_fds[ out_cnt++ ] = ctx->prog_link_fds[ 0 ];
    1567           0 :   if( FD_LIKELY( ctx->xsk_cnt>1UL ) ) out_fds[ out_cnt++ ] = ctx->xsk[ 1 ].xsk_fd;
    1568           0 :   if( FD_LIKELY( ctx->xsk_cnt>1UL ) ) out_fds[ out_cnt++ ] = ctx->prog_link_fds[ 1 ];
    1569           0 :   return out_cnt;
    1570           0 : }
    1571             : 
    1572           0 : #define STEM_BURST (1UL)
    1573           0 : #define STEM_LAZY ((ulong)30e3) /* 30 us */
    1574             : 
    1575           0 : #define STEM_CALLBACK_CONTEXT_TYPE  fd_net_ctx_t
    1576           0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_net_ctx_t)
    1577             : 
    1578           0 : #define STEM_CALLBACK_METRICS_WRITE       metrics_write
    1579           0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
    1580           0 : #define STEM_CALLBACK_BEFORE_CREDIT       before_credit
    1581           0 : #define STEM_CALLBACK_BEFORE_FRAG         before_frag
    1582           0 : #define STEM_CALLBACK_DURING_FRAG         during_frag
    1583           0 : #define STEM_CALLBACK_AFTER_FRAG          after_frag
    1584             : 
    1585             : #include "../../stem/fd_stem.c"
    1586             : 
    1587             : #ifndef FD_TILE_TEST
    1588             : fd_topo_run_tile_t fd_tile_net = {
    1589             :   .name                     = "net",
    1590             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    1591             :   .populate_allowed_fds     = populate_allowed_fds,
    1592             :   .scratch_align            = scratch_align,
    1593             :   .scratch_footprint        = scratch_footprint,
    1594             :   .privileged_init          = privileged_init,
    1595             :   .unprivileged_init        = unprivileged_init,
    1596             :   .run                      = stem_run,
    1597             : };
    1598             : #endif

Generated by: LCOV version 1.14