LCOV - code coverage report
Current view: top level - disco/net/xdp - fd_xdp_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 274 779 35.2 %
Date: 2026-11-07 04:26:39 Functions: 17 65 26.2 %

          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 "../fd_net_tile.h"
       6             : #include "../fd_net_tile_private.h"
       7             : 
       8             : #include <errno.h>
       9             : #include <fcntl.h>
      10             : #include <net/if.h>
      11             : #include <netinet/in.h>
      12             : #include <sys/socket.h> /* MSG_DONTWAIT needed before importing the net seccomp filter */
      13             : #include <linux/if_xdp.h>
      14             : #include <sys/epoll.h>
      15             : 
      16             : #include "../../metrics/fd_metrics.h"
      17             : #include "../../netlink/fd_netlink_tile.h" /* neigh4_solicit */
      18             : #include "../../waker/fd_waker.h"
      19             : #include "../../topo/fd_topo.h"
      20             : 
      21             : #include "../../../waltz/ip/fd_fib4.h"
      22             : #include "../../../waltz/ip/fd_iproute.h"
      23             : #include "../../../waltz/neigh/fd_neigh4_map.h"
      24             : #include "../../../waltz/mib/fd_netdev_tbl.h"
      25             : #include "../../../waltz/xdp/fd_xdp_redirect_user.h" /* fd_xsk_activate */
      26             : #include "../../../waltz/xdp/fd_xsk.h"
      27             : #include "../../../util/log/fd_dtrace.h"
      28             : #include "../../../util/net/fd_eth.h"
      29             : #include "../../../util/net/fd_ip4.h"
      30             : #include "../../../util/net/fd_gre.h"
      31             : #include "../../../util/pod/fd_pod_format.h"
      32             : 
      33             : #include <unistd.h>
      34             : #include <linux/if.h> /* struct ifreq */
      35             : #include <sys/ioctl.h>
      36             : #include <linux/if_arp.h>
      37             : #include <linux/rtnetlink.h>
      38             : #include <linux/futex.h>
      39             : 
      40             : #include "generated/fd_xdp_tile_seccomp.h"
      41             : 
      42             : 
      43             : /* FD_XDP_STATS_INTERVAL_NS controls the XDP stats refresh interval.
      44             :    This should be lower than the interval at which the metrics tile
      45             :    collects metrics. */
      46             : 
      47           0 : #define FD_XDP_STATS_INTERVAL_NS (11e6) /* 11ms */
      48             : 
      49             : /* XSK_IDX_{MAIN,LO} are the hardcoded XSK indices in ctx->xsk[ ... ].
      50             :    Only net tile 0 has XSK_IDX_LO, all net tiles have XSK_IDX_MAIN. */
      51             : 
      52          42 : #define XSK_IDX_MAIN 0
      53          36 : #define XSK_IDX_LO   1
      54             : 
      55             : /* XSK 'busy_poll_usecs' value (max amount of time
      56             :    spent spinning in a NAPI poll before returning back to
      57             :    userspace if the processing budget hasn't already ran out).
      58             : 
      59             :    64us chosen based on napibusy configuration tested and
      60             :    shown in https://lwn.net/Articles/997491/ Linux patch
      61             :    cover letter.  Chosen over fullbusy since for Firedancer
      62             :    the values of fullbusy are unnecessarily high and could
      63             :    cause some extra latency to regular non-Firedancer traffic.*/
      64           0 : #define PREFBUSY_TIME_BUDGET_MICROS (64L)
      65             : 
      66             : /* PREFBUSY_RX_BUDGET is the NAPI RX processing budget (max num RX
      67             :    packets that the NIC driver can move from the hw rings into the
      68             :    XSK rings per poll).
      69             : 
      70             :    Default RX budget used by NIC drivers is 64, therefore
      71             :    it is safest to use 64 in prefbusy polling.  Also reduces
      72             :    TX starvation risks as the TX budget set by the NIC driver
      73             :    is also generally 64. */
      74           0 : #define PREFBUSY_RX_BUDGET (64L)
      75             : 
      76             : /* Min time between each prefbusy poll.  Necessary to avoid a no RX
      77             :    scenario livelocking TX with overly frequent sendto calls, given
      78             :    prefbusy polls whenever the RX queue is empty.
      79             : 
      80             :    Value chosen based on experimentation on ixgbe, mlx5 and i40e as well
      81             :    as on varying CPUs and clock speeds.  Too low -> lower max TX
      82             :    throughput when RX is very low.  Too high -> lower max RX and TX throughput. */
      83           0 : #define PREFBUSY_MIN_INTERVAL_NS (5e3) /* 5us */
      84             : 
      85             : /* Max time since last prefbusy poll before a prefbusy poll is
      86             :    forced (has been read that polling can sometimes resolve a stall).
      87             : 
      88             :    Exact value again not particularly important as this is just extra
      89             :    protection against stalls which have not been observed in testing but
      90             :    are still good to protect against since there is no cost to doing so.
      91             : 
      92             :    Value of 150us chosen since it is easily large enough to not interfere
      93             :    with standard prefbusy runtime unless there is a serious problem. */
      94           0 : #define PREFBUSY_STALL_TIMEOUT_NS (150e3) /* 150us */
      95             : 
      96             : /* fd_net_flusher_t controls the pacing of XDP sendto calls for flushing
      97             :    TX batches.  In the 'wakeup' XDP mode, no TX occurs unless the net
      98             :    tile wakes up the kernel periodically using the sendto() syscall.
      99             :    If sendto() is called too frequently, time is wasted on context
     100             :    switches.  If sendto() is called not often enough, packets are
     101             :    delayed or dropped.  sendto() calls make almost no guarantees how
     102             :    much packets are sent out, nor do they indicate when the kernel
     103             :    finishes a wakeup call (asynchronously dispatched).  The net tile
     104             :    thus uses a myriad of flush triggers that were tested for best
     105             :    performance. */
     106             : 
     107             : struct fd_net_flusher {
     108             : 
     109             :   /* Packets that were enqueued after the last sendto() wakeup are
     110             :      considered "pending".  If there are more than pending_wmark packets
     111             :      pending, a wakeup is dispatched.  Thus, this dispatch trigger is
     112             :      proportional to packet rate, but does not trigger if I/O is seldom. */
     113             :   ulong pending_cnt;
     114             :   ulong pending_wmark;
     115             : 
     116             :   /* Sometimes, packets are not flushed out even after a sendto()
     117             :      wakeup.  This can result in the tail of a burst getting delayed or
     118             :      overrun.  If more than tail_flush_backoff ticks pass since the last
     119             :      sendto() wakeup and there are still unacknowledged packets in the
     120             :      TX ring, issues another wakeup. Only used by "softirq" poll mode. */
     121             :   long next_tail_flush_ticks;
     122             :   long tail_flush_backoff;
     123             : 
     124             :   /* When the most recent prefbusy poll was. */
     125             :   long prefbusy_last_poll_ticks;
     126             :   /* Min time between each prefbusy poll. */
     127             :   long prefbusy_min_interval_ticks;
     128             :   /* Max time since last prefbusy poll before a prefbusy poll is
     129             :      forced (has been read that polling can sometimes resolve a stall). */
     130             :   long prefbusy_stall_timeout_ticks;
     131             : };
     132             : 
     133             : typedef struct fd_net_flusher fd_net_flusher_t;
     134             : 
     135             : FD_PROTOTYPES_BEGIN
     136             : 
     137             : /* fd_net_flusher_inc marks a new packet as enqueued. */
     138             : 
     139             : static inline void
     140             : fd_net_flusher_inc( fd_net_flusher_t * flusher,
     141          18 :                     long               now ) {
     142          18 :   flusher->pending_cnt++;
     143          18 :   long next_flush = now + flusher->tail_flush_backoff;
     144          18 :   flusher->next_tail_flush_ticks = fd_long_min( flusher->next_tail_flush_ticks, next_flush );
     145          18 : }
     146             : 
     147             : /* fd_net_flusher_check returns 1 if a sendto() wakeup should be issued
     148             :    immediately.  now is a recent fd_tickcount() value.
     149             :    If tx_ring_empty!=0 then the kernel is caught up with the net tile
     150             :    on the XDP TX ring.  (Otherwise, the kernel is behind the net tile) */
     151             : 
     152             : static inline int
     153             : fd_net_flusher_check( fd_net_flusher_t * flusher,
     154             :                       long               now,
     155          33 :                       int                tx_ring_empty ) {
     156          33 :   int flush_level   = flusher->pending_cnt >= flusher->pending_wmark;
     157          33 :   int flush_timeout = now >= flusher->next_tail_flush_ticks;
     158          33 :   int flush         = flush_level || flush_timeout;
     159          33 :   if( !flush ) return 0;
     160          33 :   if( FD_UNLIKELY( tx_ring_empty ) ) {
     161             :     /* Flush requested but caught up */
     162           3 :     flusher->pending_cnt           = 0UL;
     163           3 :     flusher->next_tail_flush_ticks = LONG_MAX;
     164           3 :     return 0;
     165           3 :   }
     166          30 :   return 1;
     167          33 : }
     168             : 
     169             : /* fd_net_flusher_wakeup signals a sendto() wakeup was done.  now is a
     170             :    recent fd_tickcount() value. */
     171             : 
     172             : static inline void
     173             : fd_net_flusher_wakeup( fd_net_flusher_t * flusher,
     174          30 :                        long               now ) {
     175          30 :   flusher->pending_cnt           = 0UL;
     176          30 :   flusher->next_tail_flush_ticks = now + flusher->tail_flush_backoff;
     177          30 : }
     178             : 
     179             : FD_PROTOTYPES_END
     180             : 
     181             : /* fd_net_free_ring is a FIFO queue that stores pointers to free XDP TX
     182             :    frames. */
     183             : 
     184             : struct fd_net_free_ring {
     185             :   ulong   prod;
     186             :   ulong   cons;
     187             :   ulong   depth;
     188             :   ulong * queue;
     189             : };
     190             : typedef struct fd_net_free_ring fd_net_free_ring_t;
     191             : 
     192             : typedef struct {
     193             :   fd_net_tile_t net;
     194             : 
     195             :   /* An "XSK" is an AF_XDP socket */
     196             :   uint     xsk_cnt;
     197             :   fd_xsk_t xsk[ 2 ];
     198             :   int      prog_link_fds[ 2 ];
     199             :   uint     if_virt;
     200             : 
     201             :   /* UMEM frame region within dcache */
     202             :   void *   umem;    /* Start of UMEM */
     203             :   ulong    umem_sz; /* Size  of UMEM */
     204             : 
     205             :   /* Details pertaining to an inflight send op */
     206             :   struct {
     207             :     uint   xsk_idx;
     208             :     void * frame;
     209             :   } tx_op;
     210             : 
     211             :   /* Round-robin cycle service operations */
     212             :   uint rr_idx;
     213             : 
     214             :   /* Ring tracking free packet buffers */
     215             :   fd_net_free_ring_t free_tx;
     216             : 
     217             :   uchar src_mac_addr[6];
     218             :   uint  default_address;
     219             : 
     220             :   /* XDP stats refresh timer */
     221             :   long xdp_stats_interval_ticks;
     222             :   long next_xdp_stats_refresh;
     223             : 
     224             :   /* TX flush timers */
     225             :   fd_net_flusher_t tx_flusher[2]; /* one per XSK */
     226             : 
     227             :   /* Route and neighbor tables */
     228             :   fd_fib4_t fib_local[1];
     229             :   fd_fib4_t fib_main[1];
     230             :   fd_neigh4_hmap_t  neigh4[1];
     231             : 
     232             :   ulong netlnk_out_idx;
     233             :   uint  solicit_ip;
     234             :   uint  solicit_if_idx;
     235             : 
     236             :   ulong   waker_client_idx;
     237             :   ulong * waker_fseq;
     238             : 
     239             :   /* Netdev table */
     240             :   fd_netdev_tbl_join_t netdev_tbl;    /* local copy in scratch (hot path) */
     241             :   fd_netdev_tbl_join_t netdev_shared; /* shared table in netbase (seqlock protected) */
     242             :   ulong                netdev_seq;    /* netdev_shared seqlock value of the netdev_tbl copy */
     243             : 
     244             :   struct {
     245             :     ulong rx_fill_blocked_cnt;
     246             :     long  rx_busy_cnt;
     247             :     long  rx_idle_cnt;
     248             : 
     249             :     ulong tx_submit_cnt;
     250             :     ulong tx_neigh_fail_cnt;
     251             :     ulong tx_full_fail_cnt;
     252             :     long  tx_busy_cnt;
     253             :     long  tx_idle_cnt;
     254             : 
     255             :     ulong xsk_tx_wakeup_cnt;
     256             :     ulong xsk_rx_wakeup_cnt;
     257             :   } metrics;
     258             : } fd_net_ctx_t;
     259             : 
     260             : fd_fib4_t *
     261             : fd_xdp_tile_fib4_join( fd_fib4_t *            out,
     262             :                        fd_topo_t const *      topo,
     263             :                        fd_topo_tile_t const * tile,
     264           0 :                        int                    main_table ) {
     265           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
     266           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
     267           0 :   (void)FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_net_ctx_t), sizeof(fd_net_ctx_t) );
     268           0 :   (void)FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), tile->xdp.free_ring_depth*sizeof(ulong) );
     269           0 :   (void)FD_SCRATCH_ALLOC_APPEND( l, fd_netdev_tbl_align(), fd_netdev_tbl_footprint( NETDEV_MAX, BOND_MASTER_MAX ) );
     270           0 :   void * local_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_fib4_align(), fd_fib4_footprint( tile->xdp.route_max, tile->xdp.route_peer_max ) );
     271           0 :   void * main_mem  = FD_SCRATCH_ALLOC_APPEND( l, fd_fib4_align(), fd_fib4_footprint( tile->xdp.route_max, tile->xdp.route_peer_max ) );
     272           0 :   return fd_fib4_join( out, main_table ? main_mem : local_mem );
     273           0 : }
     274             : 
     275             : FD_FN_CONST static inline ulong
     276           9 : scratch_align( void ) {
     277           9 :   return 4096UL;
     278           9 : }
     279             : 
     280             : FD_FN_PURE static inline ulong
     281           3 : scratch_footprint( fd_topo_tile_t const * tile ) {
     282           3 :   ulong l = FD_LAYOUT_INIT;
     283           3 :   l = FD_LAYOUT_APPEND( l, alignof(fd_net_ctx_t), sizeof(fd_net_ctx_t)                      );
     284           3 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),        tile->xdp.free_ring_depth * sizeof(ulong) );
     285           3 :   l = FD_LAYOUT_APPEND( l, fd_netdev_tbl_align(), fd_netdev_tbl_footprint( NETDEV_MAX, BOND_MASTER_MAX ) );
     286           9 :   for( ulong i=0UL; i<2UL; i++ ) l = FD_LAYOUT_APPEND( l, fd_fib4_align(), fd_fib4_footprint( tile->xdp.route_max, tile->xdp.route_peer_max ) );
     287           3 :   return FD_LAYOUT_FINI( l, scratch_align() );
     288           3 : }
     289             : 
     290             : static void
     291           6 : metrics_write( fd_net_ctx_t * ctx ) {
     292           6 :   FD_MCNT_SET(   NET, PKT_RX,                ctx->net.metrics.rx_pkt_cnt         );
     293           6 :   FD_MCNT_SET(   NET, PKT_RX_BYTES,          ctx->net.metrics.rx_bytes_total     );
     294           6 :   FD_MCNT_ENUM_COPY( NET, PKT_RX_DROPPED,  ctx->net.metrics.rx_drop_cnt        );
     295           6 :   FD_MCNT_SET(   NET, GRE_PKT_RX,            ctx->net.metrics.rx_gre_cnt         );
     296           6 :   FD_MCNT_SET(   NET, RX_FILL_RING_STALLED,  ctx->metrics.rx_fill_blocked_cnt    );
     297           6 :   FD_MGAUGE_SET( NET, RX_BUFFER_USED, (ulong)fd_long_max( ctx->metrics.rx_busy_cnt, 0L ) );
     298           6 :   FD_MGAUGE_SET( NET, RX_BUFFER_FREE, (ulong)fd_long_max( ctx->metrics.rx_idle_cnt, 0L ) );
     299           6 :   FD_MGAUGE_SET( NET, TX_BUFFER_USED, (ulong)fd_long_max( ctx->metrics.tx_busy_cnt, 0L ) );
     300           6 :   FD_MGAUGE_SET( NET, TX_BUFFER_FREE, (ulong)fd_long_max( ctx->metrics.tx_idle_cnt, 0L ) );
     301             : 
     302           6 :   FD_MCNT_SET( NET, PKT_TX_SUBMITTED,        ctx->metrics.tx_submit_cnt         );
     303           6 :   FD_MCNT_SET( NET, PKT_TX_COMPLETED,        ctx->net.metrics.tx_pkt_cnt        );
     304           6 :   FD_MCNT_SET( NET, PKT_TX_SUBMITTED_BYTES,  ctx->net.metrics.tx_bytes_total    );
     305           6 :   ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_NO_NEIGHBOR_IDX ] = ctx->metrics.tx_neigh_fail_cnt;
     306           6 :   ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_RING_FULL_IDX   ] = ctx->metrics.tx_full_fail_cnt;
     307           6 :   FD_MCNT_ENUM_COPY( NET, PKT_TX_DROPPED,    ctx->net.metrics.tx_drop_cnt       );
     308             : 
     309           6 :   FD_MCNT_SET( NET, XSK_SYSCALL_TX,          ctx->metrics.xsk_tx_wakeup_cnt );
     310           6 :   FD_MCNT_SET( NET, XSK_SYSCALL_RX,          ctx->metrics.xsk_rx_wakeup_cnt );
     311             : 
     312           6 :   FD_MCNT_SET( NET, GRE_PKT_TX_SUBMITTED,    ctx->net.metrics.tx_gre_cnt            );
     313             :   /* fd_fib4_cnt includes the synthetic throw route at index zero. */
     314           6 :   FD_MGAUGE_SET( NET, ROUTE_COUNT_LOCAL, fd_ulong_sat_sub( fd_fib4_cnt( ctx->fib_local ), 1UL ) );
     315           6 :   FD_MGAUGE_SET( NET, ROUTE_COUNT_MAIN,  fd_ulong_sat_sub( fd_fib4_cnt( ctx->fib_main  ), 1UL ) );
     316           6 : }
     317             : 
     318             : struct xdp_statistics_v0 {
     319             :   __u64 rx_dropped; /* Dropped for other reasons */
     320             :   __u64 rx_invalid_descs; /* Dropped due to invalid descriptor */
     321             :   __u64 tx_invalid_descs; /* Dropped due to invalid descriptor */
     322             : };
     323             : 
     324             : struct xdp_statistics_v1 {
     325             :   __u64 rx_dropped; /* Dropped for other reasons */
     326             :   __u64 rx_invalid_descs; /* Dropped due to invalid descriptor */
     327             :   __u64 tx_invalid_descs; /* Dropped due to invalid descriptor */
     328             :   __u64 rx_ring_full; /* Dropped due to rx ring being full */
     329             :   __u64 rx_fill_ring_empty_descs; /* Failed to retrieve item from fill ring */
     330             :   __u64 tx_ring_empty_descs; /* Failed to retrieve item from tx ring */
     331             : };
     332             : 
     333             : static void
     334           0 : poll_xdp_statistics( fd_net_ctx_t * ctx ) {
     335           0 :   struct xdp_statistics_v1 stats = {0};
     336           0 :   ulong xsk_cnt = ctx->xsk_cnt;
     337           0 :   for( ulong j=0UL; j<xsk_cnt; j++ ) {
     338           0 :     struct xdp_statistics_v1 sub_stats = {0};
     339           0 :     uint optlen = (uint)sizeof(struct xdp_statistics_v1);
     340           0 :     if( FD_UNLIKELY( -1==getsockopt( ctx->xsk[ j ].xsk_fd, SOL_XDP, XDP_STATISTICS, &sub_stats, &optlen ) ) )
     341           0 :       FD_LOG_ERR(( "getsockopt(SOL_XDP, XDP_STATISTICS) failed: %s", strerror( errno ) ));
     342           0 :     if( FD_UNLIKELY( optlen!=sizeof(struct xdp_statistics_v0) &&
     343           0 :                      optlen!=sizeof(struct xdp_statistics_v1) ) ) {
     344           0 :       FD_LOG_ERR(( "getsockopt(SOL_XDP, XDP_STATISTICS) returned unexpected size %u", optlen ));
     345           0 :     }
     346           0 :     stats.rx_dropped               += sub_stats.rx_dropped;
     347           0 :     stats.rx_invalid_descs         += sub_stats.rx_invalid_descs;
     348           0 :     stats.tx_invalid_descs         += sub_stats.tx_invalid_descs;
     349           0 :     stats.rx_ring_full             += sub_stats.rx_ring_full;
     350           0 :     stats.rx_fill_ring_empty_descs += sub_stats.rx_fill_ring_empty_descs;
     351           0 :     stats.tx_ring_empty_descs      += sub_stats.tx_ring_empty_descs;
     352           0 :   }
     353             : 
     354           0 :   FD_MCNT_SET( NET, XDP_RX_OTHER_DROPPED,                 stats.rx_dropped               );
     355           0 :   FD_MCNT_SET( NET, XDP_RX_INVALID_DESCRIPTOR_DROPPED,    stats.rx_invalid_descs         );
     356           0 :   FD_MCNT_SET( NET, XDP_TX_INVALID_DESCRIPTOR_DROPPED,    stats.tx_invalid_descs         );
     357           0 :   FD_MCNT_SET( NET, XDP_RX_RING_FULL_DROPPED,             stats.rx_ring_full             );
     358           0 :   FD_MCNT_SET( NET, XDP_RX_FILL_RING_EMPTY,               stats.rx_fill_ring_empty_descs );
     359           0 :   FD_MCNT_SET( NET, XDP_TX_RING_EMPTY,                    stats.tx_ring_empty_descs      );
     360           0 : }
     361             : 
     362             : /* net_is_fatal_xdp_error returns 1 if the given errno returned by an
     363             :    XDP API indicates a non-recoverable error code.  The net tile should
     364             :    crash if it sees such an error so the problem does not go undetected.
     365             :    Otherwise, returns 0. */
     366             : 
     367             : static int
     368           0 : net_is_fatal_xdp_error( int err ) {
     369           0 :   return err==ESOCKTNOSUPPORT || err==EOPNOTSUPP || err==EINVAL ||
     370           0 :          err==EPERM;
     371           0 : }
     372             : 
     373             : /* net_tx_ready returns 1 if we can submit a job to this TX ring, and 0 otherwise.
     374             :    Reasons for block include:
     375             :    - No TX buffer is available (free ring empty)
     376             :    - TX ring is full
     377             : 
     378             :    tx_ring: pointer to the XDP TX ring
     379             :    free_ring: pointer to the free TX ring */
     380             : 
     381             : static int
     382             : net_tx_ready( fd_xdp_ring_t *      tx_ring,
     383          42 :               fd_net_free_ring_t * free_ring ) {
     384          42 :   if( FD_UNLIKELY( free_ring->prod == free_ring->cons ) ) return 0; /* drop - no free buffers */
     385          36 :   if( FD_UNLIKELY( fd_xdp_ring_full( tx_ring ) ) )        return 0; /* drop - tx ring full */
     386          33 :   return 1;
     387          36 : }
     388             : 
     389             : /* net_rx_wakeup triggers xsk_recvmsg to run in the kernel.  Needs to be
     390             :    called periodically in order to receive packets. */
     391             : 
     392             : static void
     393             : net_rx_wakeup( fd_net_ctx_t * ctx,
     394             :                fd_xsk_t *     xsk,
     395           0 :                int *          charge_busy ) {
     396             :   /* write-back local copies to fseqs */
     397           0 :   __atomic_store_n( xsk->ring_rx.cons, xsk->ring_rx.cached_cons, __ATOMIC_RELEASE );
     398           0 :   __atomic_store_n( xsk->ring_fr.prod, xsk->ring_fr.cached_prod, __ATOMIC_RELEASE );
     399           0 :   if( !fd_xsk_rx_need_wakeup( xsk ) ) return;
     400           0 :   *charge_busy = 1;
     401           0 :   struct msghdr _ignored[ 1 ] = { 0 };
     402           0 :   if( FD_UNLIKELY( -1==recvmsg( xsk->xsk_fd, _ignored, MSG_DONTWAIT ) ) ) {
     403           0 :     if( FD_UNLIKELY( net_is_fatal_xdp_error( errno ) ) ) {
     404           0 :       FD_LOG_ERR(( "xsk recvmsg failed xsk_fd=%d (%i-%s)", xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     405           0 :     }
     406           0 :     if( FD_UNLIKELY( errno!=EAGAIN ) ) {
     407           0 :       long ts = fd_log_wallclock();
     408           0 :       if( ts > xsk->log_suppress_until_ns ) {
     409           0 :         FD_LOG_WARNING(( "xsk recvmsg failed xsk_fd=%d (%i-%s)", xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     410           0 :         xsk->log_suppress_until_ns = ts + (long)1e9;
     411           0 :       }
     412           0 :     }
     413           0 :   }
     414           0 :   ctx->metrics.xsk_rx_wakeup_cnt++;
     415           0 : }
     416             : 
     417             : /* net_tx_wakeup triggers xsk_sendmsg to run in the kernel.  Needs to be
     418             :    called periodically in order to transmit packets. Should only be called
     419             :    if there are unconsumed packets in Tx ring. */
     420             : 
     421             : static void
     422             : net_tx_wakeup( fd_net_ctx_t * ctx,
     423             :                fd_xsk_t *     xsk,
     424          30 :                int *          charge_busy ) {
     425             :   /* write-back local copies to fseqs */
     426          30 :   __atomic_store_n( xsk->ring_tx.prod, xsk->ring_tx.cached_prod, __ATOMIC_RELEASE );
     427          30 :   __atomic_store_n( xsk->ring_cr.cons, xsk->ring_cr.cached_cons, __ATOMIC_RELEASE );
     428          30 :   if( !fd_xsk_tx_need_wakeup( xsk ) ) return;
     429           0 :   *charge_busy = 1;
     430           0 :   if( FD_UNLIKELY( -1==sendto( xsk->xsk_fd, NULL, 0, MSG_DONTWAIT, NULL, 0 ) ) ) {
     431           0 :     if( FD_UNLIKELY( net_is_fatal_xdp_error( errno ) ) ) {
     432           0 :       FD_LOG_ERR(( "xsk sendto failed xsk_fd=%d (%i-%s)", xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     433           0 :     }
     434           0 :     if( FD_UNLIKELY( errno!=EAGAIN ) ) {
     435           0 :       long ts = fd_log_wallclock();
     436           0 :       if( ts > xsk->log_suppress_until_ns ) {
     437           0 :         FD_LOG_WARNING(( "xsk sendto failed xsk_fd=%d (%i-%s)", xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     438           0 :         xsk->log_suppress_until_ns = ts + (long)1e9;
     439           0 :       }
     440           0 :     }
     441           0 :   }
     442           0 :   ctx->metrics.xsk_tx_wakeup_cnt++;
     443           0 : }
     444             : 
     445             : /* net_tx_periodic_wakeup does a timer based xsk_sendmsg wakeup. */
     446             : 
     447             : static inline int
     448             : net_tx_periodic_wakeup( fd_net_ctx_t * ctx,
     449             :                         uint           xsk_idx,
     450             :                         long           now,
     451          33 :                         int *          charge_busy ) {
     452          33 :   fd_xdp_ring_t * tx_ring        = &ctx->xsk[ xsk_idx ].ring_tx;
     453          33 :   int             tx_ring_empty  = fd_xdp_ring_empty( tx_ring, FD_XDP_RING_ROLE_PROD );
     454          33 :   if( fd_net_flusher_check( ctx->tx_flusher+xsk_idx, now, tx_ring_empty ) ) {
     455          30 :     net_tx_wakeup( ctx, &ctx->xsk[ xsk_idx ], charge_busy );
     456          30 :     fd_net_flusher_wakeup( ctx->tx_flusher+xsk_idx, now );
     457          30 :   }
     458          33 :   return 0;
     459          33 : }
     460             : 
     461             : static void
     462           0 : during_housekeeping( fd_net_ctx_t * ctx ) {
     463           0 :   long now = fd_tickcount();
     464           0 :   if( FD_UNLIKELY( fd_netdev_tbl_refresh( &ctx->netdev_tbl, &ctx->netdev_shared, &ctx->netdev_seq ) ) ) {
     465           0 :     fd_net_gre_tunnels_refresh( &ctx->net, &ctx->netdev_tbl );
     466           0 :   }
     467             : 
     468           0 :   ctx->metrics.rx_busy_cnt = 0UL;
     469           0 :   ctx->metrics.rx_idle_cnt = 0UL;
     470           0 :   ctx->metrics.tx_idle_cnt = fd_seq_diff( ctx->free_tx.prod, ctx->free_tx.cons );
     471           0 :   ctx->metrics.tx_busy_cnt = (long)ctx->free_tx.depth - ctx->metrics.tx_idle_cnt;
     472           0 :   for( uint j=0U; j<ctx->xsk_cnt; j++ ) {
     473           0 :     fd_xsk_t * xsk = &ctx->xsk[ j ];
     474           0 :     FD_COMPILER_MFENCE();
     475             :     /* Write back local copies to fseqs that we own */
     476           0 :     __atomic_store_n( xsk->ring_fr.prod, xsk->ring_fr.cached_prod, __ATOMIC_RELEASE );
     477           0 :     __atomic_store_n( xsk->ring_rx.cons, xsk->ring_rx.cached_cons, __ATOMIC_RELEASE );
     478           0 :     __atomic_store_n( xsk->ring_tx.prod, xsk->ring_tx.cached_prod, __ATOMIC_RELEASE );
     479           0 :     __atomic_store_n( xsk->ring_cr.cons, xsk->ring_cr.cached_cons, __ATOMIC_RELEASE );
     480             : 
     481             :     /* Refresh kernel-owned seq numbers for accurate stats */
     482           0 :     xsk->ring_fr.cached_cons = __atomic_load_n( xsk->ring_fr.cons, __ATOMIC_ACQUIRE );
     483           0 :     xsk->ring_rx.cached_prod = __atomic_load_n( xsk->ring_rx.prod, __ATOMIC_ACQUIRE );
     484           0 :     xsk->ring_tx.cached_cons = __atomic_load_n( xsk->ring_tx.cons, __ATOMIC_ACQUIRE );
     485           0 :     xsk->ring_cr.cached_prod = __atomic_load_n( xsk->ring_cr.prod, __ATOMIC_ACQUIRE );
     486             : 
     487           0 :     FD_COMPILER_MFENCE();
     488           0 :     ctx->metrics.rx_busy_cnt += (long)(int)( xsk->ring_rx.cached_prod - xsk->ring_rx.cached_cons );
     489           0 :     ctx->metrics.rx_idle_cnt += (long)(int)( xsk->ring_fr.cached_prod - xsk->ring_fr.cached_cons );
     490           0 :   }
     491             : 
     492           0 :   if( now > ctx->next_xdp_stats_refresh ) {
     493           0 :     ctx->next_xdp_stats_refresh = now + ctx->xdp_stats_interval_ticks;
     494           0 :     poll_xdp_statistics( ctx );
     495           0 :   }
     496           0 : }
     497             : 
     498             : 
     499             : /* net_tx_route resolves the src IP address and for non-GRE routes the XSK
     500             :    index and MAC addresses.  Returns 1 on success, 0 on failure.
     501             :    On success, tx_op.xsk_idx and net.tx_route.src_ip are set, and if the dst_ip
     502             :    belongs to a GRE interface, is_gre_inf will set to 1 and net.tx_route.gre_outer_src_ip
     503             :    and net.tx_route.gre_outer_dst_ip will be loaded from the netdev
     504             :    table. is_gre_inf is set to 0 if dst_ip doesn't belong to a GRE interface. */
     505             : 
     506             : static int
     507             : net_tx_route( fd_net_ctx_t * ctx,
     508             :               uint           dst_ip,
     509          45 :               uint *         is_gre_inf ) {
     510          45 :   fd_net_tx_route_t * route = &ctx->net.tx_route;
     511             : 
     512             :   /* Route lookup */
     513             : 
     514          45 :   fd_fib4_hop_t hop[2] = {0};
     515          45 :   hop[0] = fd_fib4_lookup( ctx->fib_local, dst_ip, 0UL );
     516          45 :   hop[1] = fd_fib4_lookup( ctx->fib_main,  dst_ip, 0UL );
     517          45 :   fd_fib4_hop_t const * next_hop = fd_fib4_hop_or( hop+0, hop+1 );
     518             : 
     519          45 :   uint rtype   = next_hop->rtype;
     520          45 :   uint if_idx  = next_hop->if_idx;
     521          45 :   uint ip4_src = next_hop->ip4_src;
     522             : 
     523          45 :   if( FD_UNLIKELY( rtype==FD_FIB4_RTYPE_LOCAL ) ) {
     524           6 :     rtype  = FD_FIB4_RTYPE_UNICAST;
     525           6 :     if_idx = 1;
     526           6 :   }
     527             : 
     528          45 :   if( FD_UNLIKELY( rtype!=FD_FIB4_RTYPE_UNICAST ) ) {
     529           0 :     uint reason = fd_uint_if( rtype==FD_FIB4_RTYPE_THROW,
     530           0 :         FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_NO_ROUTE_IDX,
     531           0 :         FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_UNSUPPORTED_ROUTE_IDX );
     532           0 :     ctx->net.metrics.tx_drop_cnt[ reason ]++;
     533           0 :     return 0;
     534           0 :   }
     535             : 
     536          45 :   fd_netdev_t * netdev = fd_netdev_tbl_query( &ctx->netdev_tbl, if_idx );
     537          45 :   if( !netdev ) {
     538           3 :     ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_INTERFACE_MISSING_IDX ]++;
     539           3 :     return 0;
     540           3 :   }
     541             : 
     542          42 :   ip4_src = fd_uint_if( !!ctx->net.bind_address, ctx->net.bind_address, ip4_src );
     543          42 :   route->src_ip = ip4_src;
     544          42 :   route->mtu    = netdev->mtu;
     545          42 :   ctx->tx_op.xsk_idx = UINT_MAX;
     546             : 
     547          42 :   FD_TEST( is_gre_inf );
     548          42 :   *is_gre_inf = 0;
     549          42 :   if( netdev->dev_type==ARPHRD_LOOPBACK ) {
     550             :     /* Set Ethernet src and dst address to 00:00:00:00:00:00 */
     551           6 :     memset( route->mac_addrs, 0, 12UL );
     552           6 :     ctx->tx_op.xsk_idx = if_idx==ctx->if_virt ? XSK_IDX_MAIN : XSK_IDX_LO;
     553             :     /* Set preferred src address to 127.0.0.1 if no bind address is set */
     554           6 :     if( !route->src_ip ) route->src_ip = FD_IP4_ADDR( 127,0,0,1 );
     555           6 :     return 1;
     556          36 :   } else if( netdev->dev_type==ARPHRD_IPGRE ) {
     557             :     /* skip MAC addrs lookup for GRE inner dst ip */
     558          12 :     if( netdev->gre_src_ip ) route->gre_outer_src_ip = netdev->gre_src_ip;
     559          12 :     route->gre_outer_dst_ip = netdev->gre_dst_ip;
     560          12 :     *is_gre_inf = 1;
     561          12 :     return 1;
     562          12 :   }
     563             : 
     564          24 :   if( FD_UNLIKELY( netdev->dev_type!=ARPHRD_ETHER ) ) {
     565           3 :     ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_UNSUPPORTED_INTERFACE_IDX ]++;
     566           3 :     return 0;
     567           3 :   }
     568             : 
     569          21 :   if( FD_UNLIKELY( if_idx!=ctx->if_virt ) ) {
     570           3 :     ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_UNSUPPORTED_INTERFACE_IDX ]++;
     571           3 :     return 0;
     572           3 :   }
     573          18 :   ctx->tx_op.xsk_idx = XSK_IDX_MAIN;
     574             : 
     575             :   /* Neighbor resolve */
     576          18 :   uint neigh_ip = next_hop->ip4_gw;
     577          18 :   if( !neigh_ip ) neigh_ip = dst_ip;
     578             : 
     579          18 :   fd_neigh4_entry_t neigh[1];
     580          18 :   int neigh_res = fd_neigh4_hmap_query_entry( ctx->neigh4, neigh_ip, neigh );
     581          18 :   if( FD_UNLIKELY( neigh_res!=FD_MAP_SUCCESS ) ) {
     582             :     /* Neighbor not found */
     583           0 :     ctx->solicit_ip     = neigh_ip;
     584           0 :     ctx->solicit_if_idx = if_idx;
     585           0 :     ctx->metrics.tx_neigh_fail_cnt++;
     586           0 :     return 0;
     587           0 :   }
     588          18 :   if( FD_UNLIKELY( neigh->state != FD_NEIGH4_STATE_ACTIVE ) ) {
     589           0 :     ctx->metrics.tx_neigh_fail_cnt++;
     590           0 :     return 0;
     591           0 :   }
     592          18 :   ip4_src = fd_uint_if( !ip4_src, ctx->default_address, ip4_src );
     593          18 :   route->src_ip = ip4_src;
     594          18 :   memcpy( route->mac_addrs+0, neigh->mac_addr, 6 );
     595          18 :   memcpy( route->mac_addrs+6, netdev->mac_addr,  6 );
     596             : 
     597          18 :   return 1;
     598          18 : }
     599             : 
     600             : /* before_frag is called when a new metadata descriptor for a TX job is
     601             :    found.  This callback determines whether this net tile is responsible
     602             :    for the TX job.  If so, it prepares the TX op for the during_frag and
     603             :    after_frag callbacks. */
     604             : 
     605             : static inline int
     606             : before_frag( fd_net_ctx_t * ctx,
     607             :              ulong          in_idx,
     608             :              ulong          seq,
     609          24 :              ulong          sig ) {
     610          24 :   (void)seq;
     611             : 
     612          24 :   if( FD_UNLIKELY( ctx->net.in_kind[ in_idx ]==FD_NET_IN_KIND_IPROUTE ) ) return 0;
     613             : 
     614             :   /* Find interface index of next packet */
     615          24 :   ulong proto = fd_disco_netmux_sig_proto( sig );
     616          24 :   if( FD_UNLIKELY( proto!=DST_PROTO_OUTGOING ) ) return 1;
     617             : 
     618             :   /* Load balance TX */
     619          24 :   ulong tile_cnt   = ctx->net.tile_cnt;
     620          24 :   ulong hash       = fd_disco_netmux_sig_hash( sig );
     621          24 :   ulong target_idx = hash % tile_cnt;
     622          24 :   ulong kind_id    = ctx->net.kind_id;
     623          24 :   uint  dst_ip     = fd_disco_netmux_sig_ip( sig );
     624             : 
     625             :   /* Skip if another net tile is responsible for this packet.
     626             :      Fast path for net tiles other than net_tile 0. */
     627             : 
     628          24 :   if( kind_id!=0UL && kind_id!=target_idx ) return 1; /* ignore */
     629             : 
     630             : 
     631          24 :   fd_net_tx_route_t * route = &ctx->net.tx_route;
     632          24 :   *route = (fd_net_tx_route_t){0};
     633          24 :   uint is_gre_inf = 0;
     634             : 
     635          24 :   if( FD_UNLIKELY( !net_tx_route( ctx, dst_ip, &is_gre_inf ) ) ) {
     636           0 :     return 1; /* metrics incremented by net_tx_route */
     637           0 :   }
     638             : 
     639          24 :   uint xsk_idx     = ctx->tx_op.xsk_idx;
     640             : 
     641          24 :   if( is_gre_inf ) {
     642          12 :     uint inner_src_ip = route->src_ip;
     643          12 :     if( FD_UNLIKELY( !inner_src_ip ) ) {
     644           0 :       ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_GRE_NO_ROUTE_IDX ]++;
     645           0 :       return 1;
     646           0 :     }
     647             :     /* Find the MAC addrs for the eth hdr, and src ip for outer ip4 hdr if not found in netdev tbl */
     648          12 :     route->src_ip = 0;
     649          12 :     is_gre_inf    = 0;
     650             :     /* net_tx_route counts its own drop reason */
     651          12 :     if( FD_UNLIKELY( !net_tx_route( ctx, route->gre_outer_dst_ip, &is_gre_inf ) ) ) return 1;
     652          12 :     if( is_gre_inf ) {
     653             :       /* Only one layer of tunnelling supported */
     654           0 :       ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_GRE_NO_ROUTE_IDX ]++;
     655           0 :       return 1;
     656           0 :     }
     657          12 :     if( !route->gre_outer_src_ip ) {
     658           6 :       route->gre_outer_src_ip = route->src_ip;
     659           6 :     }
     660          12 :     route->use_gre = 1; /* indicate to during_frag to use GRE header */
     661          12 :     route->src_ip  = inner_src_ip;
     662          12 :     xsk_idx = XSK_IDX_MAIN;
     663          12 :   }
     664             : 
     665          24 :   if( xsk_idx==XSK_IDX_LO ) target_idx = 0UL; /* loopback always targets tile 0 */
     666             : 
     667             :   /* Skip if another net tile is responsible for this packet */
     668             : 
     669          24 :   if( kind_id!=target_idx ) return 1; /* ignore */
     670             : 
     671          21 :   if( FD_UNLIKELY( xsk_idx>=ctx->xsk_cnt ) ) {
     672             :     /* Packet does not route to an XDP interface */
     673           3 :     ctx->net.metrics.tx_drop_cnt[ FD_METRICS_ENUM_PKT_TX_DROP_REASON_V_UNSUPPORTED_INTERFACE_IDX ]++;
     674           3 :     return 1;
     675           3 :   }
     676             : 
     677             :   /* Skip if TX is blocked */
     678             : 
     679          18 :   fd_xsk_t *           xsk  = &ctx->xsk[ xsk_idx ];
     680          18 :   fd_net_free_ring_t * free = &ctx->free_tx;
     681          18 :   if( FD_UNLIKELY( !net_tx_ready( &xsk->ring_tx, free ) ) ) {
     682           0 :     ctx->metrics.tx_full_fail_cnt++;
     683           0 :     return 1;
     684           0 :   }
     685             : 
     686             :   /* Allocate buffer for receive */
     687          18 :   ulong                alloc_seq = free->cons;
     688          18 :   void *               frame     = (void *)free->queue[ alloc_seq & (free->depth-1UL) ];
     689          18 :   free->cons = fd_seq_inc( alloc_seq, 1UL );
     690             : 
     691          18 :   ctx->tx_op.frame = frame;
     692             : 
     693          18 :   return 0; /* continue */
     694          18 : }
     695             : 
     696             : /* during_frag is called when before_frag has committed to transmit an
     697             :    outgoing packet. */
     698             : 
     699             : static inline void
     700             : during_frag( fd_net_ctx_t * ctx,
     701             :              ulong          in_idx,
     702             :              ulong          seq FD_PARAM_UNUSED,
     703             :              ulong          sig FD_PARAM_UNUSED,
     704             :              ulong          chunk,
     705             :              ulong          sz,
     706          24 :              ulong          ctl FD_PARAM_UNUSED ) {
     707          24 :   fd_net_in_frag_validate( &ctx->net, in_idx, chunk, sz );
     708             : 
     709          24 :   if( FD_UNLIKELY( fd_net_iproute_msg_stage( &ctx->net, in_idx, chunk, sz ) ) ) return;
     710             : 
     711          24 :   void * frame = ctx->tx_op.frame;
     712          24 :   if( FD_UNLIKELY( (ulong)frame < (ulong)ctx->umem ) )
     713           0 :     FD_LOG_ERR(( "frame %p out of bounds (below %p)", frame, (void *)ctx->umem ));
     714          24 :   ulong umem_off = (ulong)frame - (ulong)ctx->umem;
     715          24 :   if( FD_UNLIKELY( (ulong)umem_off > (ulong)ctx->umem_sz ) )
     716           0 :     FD_LOG_ERR(( "frame %p out of bounds (beyond %p)", frame, (void *)ctx->umem_sz ));
     717             : 
     718             :   /* Speculatively copy frame into buffer from in link */
     719          24 :   fd_net_tx_pkt_cpy( &ctx->net, ctx->tx_op.frame, chunk, sz, in_idx );
     720          24 : }
     721             : 
     722             : /* after_frag is called when the during_frag memcpy was _not_ overrun. */
     723             : 
     724             : static void
     725             : after_frag( fd_net_ctx_t *      ctx,
     726             :             ulong               in_idx,
     727             :             ulong               seq,
     728             :             ulong               sig,
     729             :             ulong               sz,
     730             :             ulong               tsorig,
     731             :             ulong               tspub,
     732          96 :             fd_stem_context_t * stem ) {
     733          96 :   (void)seq; (void)sig; (void)tsorig; (void)tspub; (void)stem;
     734             : 
     735          96 :   if( FD_UNLIKELY( ctx->net.in_kind[ in_idx ]==FD_NET_IN_KIND_IPROUTE ) ) {
     736          72 :     fd_iproute_msg_t const * msg = &ctx->net.iproute_msg;
     737          72 :     if( msg->op==FD_IPROUTE_OP_FLUSH ) {
     738           6 :       fd_fib4_clear( ctx->fib_local );
     739           6 :       fd_fib4_clear( ctx->fib_main );
     740           6 :       return;
     741           6 :     }
     742          66 :     fd_fib4_t * fib;
     743          66 :     if( msg->table_id==RT_TABLE_LOCAL ) fib = ctx->fib_local;
     744          66 :     else if( msg->table_id==RT_TABLE_MAIN ) fib = ctx->fib_main;
     745           0 :     else return;
     746          66 :     if( msg->op==FD_IPROUTE_OP_UPSERT && FD_UNLIKELY( !fd_fib4_insert( fib, msg->dst_addr, msg->prefix, msg->prio, &msg->hop ) ) ) {
     747           6 :       FD_LOG_WARNING(( "route update dropped: route table full (increase [tiles.netlink].max_routes or [tiles.netlink].max_peer_routes)" ));
     748           6 :       if( FD_UNLIKELY( ctx->net.kind_id==0UL ) ) {
     749           3 :         fd_stem_publish( stem, ctx->netlnk_out_idx, FD_NETLINK_ROUTE4_SYNC_SIG, 0UL, 0UL, 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
     750           3 :       }
     751           6 :     }
     752          60 :     else if( msg->op==FD_IPROUTE_OP_DELETE ) fd_fib4_remove( fib, msg->dst_addr, msg->prefix, msg->prio );
     753          66 :     return;
     754          66 :   }
     755             : 
     756             :   /* Current send operation */
     757             : 
     758          24 :   uchar * frame   = ctx->tx_op.frame;
     759          24 :   uint    xsk_idx = ctx->tx_op.xsk_idx;
     760             : 
     761          24 :   if( FD_UNLIKELY( !fd_net_tx_pkt_prep( &ctx->net, frame, &sz, in_idx ) ) ) return;
     762          18 :   if( ctx->net.tx_route.use_gre ) xsk_idx = XSK_IDX_MAIN;
     763             : 
     764             :   /* Submit packet TX job
     765             : 
     766             :      Invariant for ring_tx: prod-cons<length
     767             :      (This invariant breaks if any other packet is sent over this ring
     768             :      between before_frag and this point.) */
     769             : 
     770          18 :   fd_xsk_t      * xsk     = &ctx->xsk[ xsk_idx ];
     771          18 :   fd_xdp_ring_t * tx_ring = &xsk->ring_tx;
     772          18 :   uint            tx_seq  = tx_ring->cached_prod;
     773          18 :   uint            tx_mask = tx_ring->depth - 1U;
     774          18 :   xsk->ring_tx.packet_ring[ tx_seq&tx_mask ] = (struct xdp_desc) {
     775          18 :     .addr    = (ulong)frame - (ulong)ctx->umem,
     776          18 :     .len     = (uint)sz,
     777          18 :     .options = 0
     778          18 :   };
     779             : 
     780             :   /* Frame is now owned by kernel. Clear tx_op. */
     781          18 :   ctx->tx_op.frame = NULL;
     782             : 
     783             :   /* Register newly enqueued packet */
     784          18 :   tx_ring->cached_prod = tx_seq+1U;
     785          18 :   ctx->metrics.tx_submit_cnt++;
     786          18 :   ctx->net.metrics.tx_bytes_total += sz;
     787          18 :   if( ctx->net.tx_route.use_gre ) ctx->net.metrics.tx_gre_cnt++;
     788          18 :   fd_net_flusher_inc( ctx->tx_flusher+xsk_idx, fd_tickcount() );
     789          18 : }
     790             : 
     791             : /* net_comp_event is called when an XDP TX frame is free again. */
     792             : 
     793             : static void
     794             : net_comp_event( fd_net_ctx_t * ctx,
     795             :                 fd_xsk_t *     xsk,
     796           0 :                 uint           comp_seq ) {
     797             : 
     798             :   /* Locate the incoming frame */
     799             : 
     800           0 :   fd_xdp_ring_t * comp_ring  = &xsk->ring_cr;
     801           0 :   uint            comp_mask  = comp_ring->depth - 1U;
     802           0 :   ulong           frame      = FD_VOLATILE_CONST( comp_ring->frame_ring[ comp_seq&comp_mask ] );
     803           0 :   ulong const     frame_mask = FD_NET_MTU - 1UL;
     804           0 :   FD_STATIC_ASSERT( FD_ULONG_IS_POW2( FD_NET_MTU ), "FD_NET_MTU must be a power of two" );
     805           0 :   if( FD_UNLIKELY( frame+FD_NET_MTU > ctx->umem_sz ) ) {
     806           0 :     FD_LOG_ERR(( "Bounds check failed: frame=0x%lx umem_sz=0x%lx",
     807           0 :                  frame, (ulong)ctx->umem_sz ));
     808           0 :   }
     809             : 
     810             :   /* Check if we have space to return the freed frame */
     811             : 
     812           0 :   fd_net_free_ring_t * free      = &ctx->free_tx;
     813           0 :   ulong                free_prod = free->prod;
     814           0 :   ulong                free_mask = free->depth - 1UL;
     815           0 :   ulong                free_cons = free->cons;
     816           0 :   long                 free_cnt = fd_seq_diff( free_prod, free_cons );
     817           0 :   FD_TEST( free_prod >= free_cons );
     818           0 :   if( FD_UNLIKELY( free_cnt>=(long)free->depth ) ) return; /* blocked */
     819             : 
     820           0 :   free->queue[ free_prod&free_mask ] = (ulong)ctx->umem + (frame & (~frame_mask));
     821           0 :   free->prod = fd_seq_inc( free_prod, 1UL );
     822             : 
     823             :   /* Wind up for next iteration */
     824             : 
     825           0 :   comp_ring->cached_cons = comp_seq+1U;
     826           0 :   ctx->net.metrics.tx_pkt_cnt++;
     827           0 : }
     828             : 
     829             : static int
     830           0 : prevent_park( fd_net_ctx_t * ctx ) {
     831           0 :   for( uint i=0U; i<ctx->xsk_cnt; i++ ) {
     832           0 :     fd_xsk_t * xsk = &ctx->xsk[ i ];
     833           0 :     if( FD_UNLIKELY( xsk->prefbusy_poll_enabled ) ) return 1;
     834           0 :     if( FD_UNLIKELY( !fd_xdp_ring_empty( &xsk->ring_rx, FD_XDP_RING_ROLE_CONS ) ) ) return 1;
     835           0 :     if( FD_UNLIKELY( !fd_xdp_ring_empty( &xsk->ring_cr, FD_XDP_RING_ROLE_CONS ) ) ) return 1;
     836           0 :     if( FD_UNLIKELY( ctx->tx_flusher[ i ].pending_cnt ) ) return 1;
     837           0 :     if( FD_UNLIKELY( !fd_xdp_ring_empty( &xsk->ring_tx, FD_XDP_RING_ROLE_PROD ) ) ) return 1; /* the flusher paces the kicks */
     838           0 :   }
     839           0 :   return 0;
     840           0 : }
     841             : 
     842             : /* net_rx_event is called when a new XDP RX frame is available.  Calls
     843             :    fd_net_rx_pkt, then returns the packet back to the kernel via the fill
     844             :    ring.  */
     845             : 
     846             : static void
     847             : net_rx_event( fd_net_ctx_t *      ctx,
     848             :               fd_stem_context_t * stem,
     849             :               fd_xsk_t *          xsk,
     850          36 :               uint                rx_seq ) {
     851             :   /* Locate the incoming frame */
     852             : 
     853          36 :   fd_xdp_ring_t * rx_ring = &xsk->ring_rx;
     854          36 :   uint            rx_mask = rx_ring->depth - 1U;
     855          36 :   struct xdp_desc frame   = FD_VOLATILE_CONST( rx_ring->packet_ring[ rx_seq&rx_mask ] );
     856             : 
     857          36 :   if( FD_UNLIKELY( frame.len>FD_NET_MTU ) )
     858           0 :     FD_LOG_ERR(( "received a UDP packet with a too large payload (%u)", frame.len ));
     859             : 
     860             :   /* Check if we have space in the fill ring to free the frame */
     861             : 
     862          36 :   fd_xdp_ring_t * fill_ring  = &xsk->ring_fr;
     863          36 :   if( FD_UNLIKELY( fd_xdp_ring_full( fill_ring ) ) ) {
     864           0 :     ctx->metrics.rx_fill_blocked_cnt++;
     865           0 :     return; /* blocked */
     866           0 :   }
     867             : 
     868             :   /* Pass it to the receive handler */
     869             : 
     870          36 :   ulong const chunk = ctx->net.pkt_buf_chunk0 + (frame.addr >> FD_CHUNK_LG_SZ);
     871          36 :   ulong const tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
     872          36 :   ulong       freed_chunk;
     873          36 :   fd_net_rx_pkt( &ctx->net, stem, chunk, frame.addr & (FD_CHUNK_SZ-1UL), frame.len, tspub, &freed_chunk, NULL );
     874             : 
     875          36 :   FD_COMPILER_MFENCE();
     876          36 :   rx_ring->cached_cons = rx_seq+1U;
     877             : 
     878             :   /* Every RX operation returns one frame to the FILL ring.  If the
     879             :      packet was forwarded to a downstream ring, the newly shadowed frame
     880             :      is returned.  Otherwise, the frame just received is returned. */
     881             : 
     882          36 :   FD_STATIC_ASSERT( FD_ULONG_IS_POW2( FD_NET_MTU ), "FD_NET_MTU must be a power of two" );
     883             :   /* Packets can start partway into a UMEM frame, so align down to
     884             :      the frame start. */
     885          36 :   ulong const frame_sz_chunks = FD_NET_MTU>>FD_CHUNK_LG_SZ;
     886          36 :   freed_chunk = ctx->net.pkt_buf_chunk0 + fd_ulong_align_dn( freed_chunk-ctx->net.pkt_buf_chunk0, frame_sz_chunks );
     887          36 :   if( FD_UNLIKELY( freed_chunk<ctx->net.pkt_buf_chunk0 || freed_chunk>ctx->net.pkt_buf_wmark ) ) {
     888           0 :     FD_LOG_CRIT(( "mcache corruption detected: buffer chunk %lu out of bounds [%lu,%lu]",
     889           0 :                   freed_chunk, ctx->net.pkt_buf_chunk0, ctx->net.pkt_buf_wmark ));
     890           0 :   }
     891          36 :   ulong const freed_off = (freed_chunk-ctx->net.pkt_buf_chunk0)<<FD_CHUNK_LG_SZ;
     892             : 
     893          36 :   uint fill_prod = fill_ring->cached_prod;
     894          36 :   uint fill_mask = (fill_ring->depth)-1U;
     895          36 :   fill_ring->frame_ring[ fill_prod&fill_mask ] = freed_off;
     896          36 :   fill_ring->cached_prod = fill_prod+1U;
     897          36 : }
     898             : 
     899             : static void
     900             : before_credit_softirq( fd_net_ctx_t *      ctx,
     901             :                        fd_stem_context_t * stem,
     902             :                        int *               charge_busy,
     903             :                        uint                rr_idx,
     904          33 :                        fd_xsk_t *          rr_xsk ) {
     905             : 
     906          33 :   net_tx_periodic_wakeup( ctx, rr_idx, fd_tickcount(), charge_busy );
     907             : 
     908             :   /* Fire RX event if we have RX desc avail */
     909          33 :   if( !fd_xdp_ring_empty( &rr_xsk->ring_rx, FD_XDP_RING_ROLE_CONS ) ) {
     910          33 :     *charge_busy = 1;
     911          33 :     net_rx_event( ctx, stem, rr_xsk, rr_xsk->ring_rx.cached_cons );
     912          33 :   } else {
     913           0 :     net_rx_wakeup( ctx, rr_xsk, charge_busy );
     914             : 
     915             :     /* Iterate onto the next NAPI queue. */
     916           0 :     ctx->rr_idx++;
     917           0 :     ctx->rr_idx = fd_uint_if( ctx->rr_idx>=ctx->xsk_cnt, 0, ctx->rr_idx );
     918           0 :   }
     919          33 : }
     920             : 
     921             : static int
     922             : net_prefbusy_poll_ready( fd_xsk_t *         rr_xsk,
     923             :                          fd_net_flusher_t * flusher,
     924           0 :                          long               now ) {
     925             : 
     926           0 :   if( FD_LIKELY( now < ( flusher->prefbusy_last_poll_ticks + flusher->prefbusy_min_interval_ticks ) ) ) return 0;
     927           0 :   if( FD_UNLIKELY( now > ( flusher->prefbusy_last_poll_ticks + flusher->prefbusy_stall_timeout_ticks ) ) ) return 1;
     928             : 
     929           0 :   int rx_empty = fd_xdp_ring_empty( &rr_xsk->ring_rx, FD_XDP_RING_ROLE_CONS );
     930             : 
     931           0 :   return rx_empty;
     932           0 : }
     933             : 
     934             : static void
     935             : net_prefbusy_poll_flush( fd_net_flusher_t * flusher,
     936           0 :                          long               now ) {
     937           0 :   flusher->prefbusy_last_poll_ticks = now;
     938           0 : }
     939             : 
     940             : static void
     941             : before_credit_prefbusy( fd_net_ctx_t *      ctx,
     942             :                         fd_stem_context_t * stem,
     943             :                         int *               charge_busy,
     944             :                         uint                rr_idx,
     945           0 :                         fd_xsk_t *          rr_xsk ) {
     946             : 
     947           0 :   fd_net_flusher_t * flusher = ctx->tx_flusher+rr_idx;
     948           0 :   if( FD_UNLIKELY( net_prefbusy_poll_ready( rr_xsk, flusher, fd_tickcount() ) ) ) {
     949             :     /* NAPI needs to be polled to process new TX from
     950             :        Firedancer's net tile and process new RX from the NIC. */
     951             : 
     952             :     /* write-back local copies to fseqs */
     953           0 :     __atomic_store_n( rr_xsk->ring_tx.prod, rr_xsk->ring_tx.cached_prod, __ATOMIC_RELEASE );
     954           0 :     __atomic_store_n( rr_xsk->ring_cr.cons, rr_xsk->ring_cr.cached_cons, __ATOMIC_RELEASE );
     955           0 :     __atomic_store_n( rr_xsk->ring_rx.cons, rr_xsk->ring_rx.cached_cons, __ATOMIC_RELEASE );
     956           0 :     __atomic_store_n( rr_xsk->ring_fr.prod, rr_xsk->ring_fr.cached_prod, __ATOMIC_RELEASE );
     957             : 
     958           0 :     if( FD_UNLIKELY( -1==sendto( rr_xsk->xsk_fd, NULL, 0, MSG_DONTWAIT, NULL, 0 ) ) ) {
     959           0 :       if( FD_UNLIKELY( net_is_fatal_xdp_error( errno ) ) ) {
     960           0 :         FD_LOG_ERR(( "xsk sendto failed xsk_fd=%d (%i-%s)", rr_xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     961           0 :       }
     962           0 :       if( FD_UNLIKELY( errno!=EAGAIN ) ) {
     963           0 :         long ts = fd_log_wallclock();
     964           0 :         if( ts > rr_xsk->log_suppress_until_ns ) {
     965           0 :           FD_LOG_WARNING(( "xsk sendto failed xsk_fd=%d (%i-%s)", rr_xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     966           0 :           rr_xsk->log_suppress_until_ns = ts + (long)1e9;
     967           0 :         }
     968           0 :       }
     969           0 :     }
     970             :     /* Since xsk sendmsg in prefbusy mode drives both rx and tx, both are incremented */
     971           0 :     ctx->metrics.xsk_tx_wakeup_cnt++;
     972           0 :     ctx->metrics.xsk_rx_wakeup_cnt++;
     973             : 
     974           0 :     net_prefbusy_poll_flush( flusher, fd_tickcount() );
     975           0 :   }
     976             : 
     977             :   /* Process new RX from xsk ring if there is any. */
     978           0 :   if( !fd_xdp_ring_empty( &rr_xsk->ring_rx, FD_XDP_RING_ROLE_CONS ) ) {
     979           0 :     *charge_busy = 1;
     980           0 :     net_rx_event( ctx, stem, rr_xsk, rr_xsk->ring_rx.cached_cons );
     981           0 :   }
     982             :   /* Iterate onto the next NAPI queue. */
     983           0 :   ctx->rr_idx++;
     984           0 :   ctx->rr_idx = fd_uint_if( ctx->rr_idx>=ctx->xsk_cnt, 0, ctx->rr_idx );
     985           0 : }
     986             : 
     987             : /* before_credit is called every loop iteration. */
     988             : 
     989             : static void
     990             : before_credit( fd_net_ctx_t *      ctx,
     991             :                fd_stem_context_t * stem,
     992          33 :                int *               charge_busy ) {
     993          33 :   if( FD_UNLIKELY( ctx->solicit_ip ) ) {
     994           0 :     fd_stem_publish( stem, ctx->netlnk_out_idx, fd_netlink_neigh4_solicit_sig( ctx->solicit_ip, ctx->solicit_if_idx ), 0UL, 0UL, 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
     995           0 :     ctx->solicit_ip = 0U;
     996           0 :   }
     997             : 
     998             :   /* A previous send attempt was overrun.  A corrupt copy of the packet was
     999             :      placed into an XDP frame, but the frame was not yet submitted to the
    1000             :      TX ring.  Return the tx buffer to the free list. */
    1001             : 
    1002          33 :   if( ctx->tx_op.frame ) {
    1003           0 :     *charge_busy = 1;
    1004           0 :     fd_net_free_ring_t * free      = &ctx->free_tx;
    1005           0 :     ulong                alloc_seq = free->prod;
    1006           0 :     free->queue[ alloc_seq & (free->depth-1UL) ] = (ulong)ctx->tx_op.frame;
    1007           0 :     free->prod = fd_seq_inc( alloc_seq, 1UL );
    1008           0 :     ctx->tx_op.frame = NULL;
    1009           0 :   }
    1010             : 
    1011             :   /* Check if new packets are available or if TX frames are free again
    1012             :      (Round-robin through sockets) */
    1013             : 
    1014          33 :   uint       rr_idx = ctx->rr_idx;
    1015          33 :   fd_xsk_t * rr_xsk = &ctx->xsk[ rr_idx ];
    1016             : 
    1017          33 :   if( FD_LIKELY( !rr_xsk->prefbusy_poll_enabled ) ) {
    1018             :     /* Default poll mode which relies on irqs and wakeups */
    1019          33 :     before_credit_softirq( ctx, stem, charge_busy, rr_idx, rr_xsk );
    1020          33 :   } else {
    1021           0 :     before_credit_prefbusy( ctx, stem, charge_busy, rr_idx, rr_xsk );
    1022           0 :   }
    1023             : 
    1024             :   /* Fire comp event if we have comp desc avail */
    1025          33 :   if( !fd_xdp_ring_empty( &rr_xsk->ring_cr, FD_XDP_RING_ROLE_CONS ) ) {
    1026           0 :     *charge_busy = 1;
    1027           0 :     net_comp_event( ctx, rr_xsk, rr_xsk->ring_cr.cached_cons );
    1028           0 :   }
    1029             : 
    1030          33 :   if( FD_UNLIKELY( ctx->waker_fseq && fd_fseq_query( ctx->waker_fseq )==1UL ) ) {
    1031           0 :     for( uint i=0U; i<ctx->xsk_cnt; i++ ) {
    1032           0 :       if( FD_UNLIKELY( !fd_xdp_ring_empty( &ctx->xsk[ i ].ring_rx, FD_XDP_RING_ROLE_CONS ) ) ) return;
    1033           0 :       if( FD_UNLIKELY( !fd_xdp_ring_empty( &ctx->xsk[ i ].ring_cr, FD_XDP_RING_ROLE_CONS ) ) ) return;
    1034           0 :     }
    1035           0 :     fd_fseq_update( ctx->waker_fseq, 0UL );
    1036           0 :     fd_waker_client_rearm( ctx->waker_client_idx );
    1037           0 :   }
    1038          33 : }
    1039             : 
    1040             : /* net_xsk_bootstrap assigns UMEM frames to the FILL ring. */
    1041             : 
    1042             : static ulong
    1043             : net_xsk_bootstrap( fd_net_ctx_t * ctx,
    1044             :                    uint           xsk_idx,
    1045           0 :                    ulong          frame_off ) {
    1046           0 :   fd_xsk_t * xsk = &ctx->xsk[ xsk_idx ];
    1047             : 
    1048           0 :   ulong const frame_sz  = FD_NET_MTU;
    1049           0 :   ulong const fr_depth  = ctx->xsk[ xsk_idx ].ring_fr.depth/2UL;
    1050             : 
    1051           0 :   fd_xdp_ring_t * fill      = &xsk->ring_fr;
    1052           0 :   uint            fill_prod = fill->cached_prod;
    1053           0 :   for( ulong j=0UL; j<fr_depth; j++ ) {
    1054           0 :     fill->frame_ring[ j ] = frame_off;
    1055           0 :     frame_off += frame_sz;
    1056           0 :   }
    1057           0 :   fill->cached_prod = fill_prod + (uint)fr_depth;
    1058           0 :   __atomic_store_n( fill->prod, fill->cached_prod, __ATOMIC_RELEASE );
    1059             : 
    1060           0 :   return frame_off;
    1061           0 : }
    1062             : 
    1063             : /* FIXME source MAC address from netlnk tile instead */
    1064             : 
    1065             : static void
    1066             : interface_addrs( const char * interface,
    1067             :                  uchar *      mac,
    1068           0 :                  uint *       ip4_addr ) {
    1069           0 :   int fd = socket( AF_INET, SOCK_DGRAM, 0 );
    1070           0 :   struct ifreq ifr;
    1071           0 :   ifr.ifr_addr.sa_family = AF_INET;
    1072             : 
    1073           0 :   strncpy( ifr.ifr_name, interface, IFNAMSIZ );
    1074           0 :   if( FD_UNLIKELY( ioctl( fd, SIOCGIFHWADDR, &ifr ) ) )
    1075           0 :     FD_LOG_ERR(( "could not get MAC address of interface `%s`: (%i-%s)", interface, errno, fd_io_strerror( errno ) ));
    1076           0 :   fd_memcpy( mac, ifr.ifr_hwaddr.sa_data, 6 );
    1077             : 
    1078           0 :   if( FD_UNLIKELY( ioctl( fd, SIOCGIFADDR, &ifr ) ) )
    1079           0 :     FD_LOG_ERR(( "could not get IP address of interface `%s`: (%i-%s)", interface, errno, fd_io_strerror( errno ) ));
    1080           0 :   *ip4_addr = ((struct sockaddr_in *)fd_type_pun( &ifr.ifr_addr ))->sin_addr.s_addr;
    1081             : 
    1082           0 :   if( FD_UNLIKELY( close(fd) ) )
    1083           0 :     FD_LOG_ERR(( "could not close socket (%i-%s)", errno, fd_io_strerror( errno ) ));
    1084           0 : }
    1085             : 
    1086             : /* privileged_init does the following initialization steps:
    1087             : 
    1088             :    - Create an AF_XDP socket
    1089             :    - Map XDP metadata rings
    1090             :    - Register UMEM data region with socket
    1091             :    - Insert AF_XDP socket into xsk_map
    1092             : 
    1093             :    Net tile 0 repeats these steps for the loopback device, unless the
    1094             :    main interface is already loopback.  XDP programs are installed before
    1095             :    tiles start.
    1096             : 
    1097             :    Kernel object references:
    1098             : 
    1099             :      BPF_LINK file descriptor
    1100             :       |
    1101             :       +-> XDP program installation on NIC
    1102             :       |    |
    1103             :       |    +-> XDP program <-- BPF_PROG file descriptor (prog_fd)
    1104             :       |
    1105             :       +-> XSKMAP object <-- BPF_MAP file descriptor (xsk_map) */
    1106             : 
    1107             : FD_FN_UNUSED static void
    1108             : privileged_init( fd_topo_t const *      topo,
    1109           0 :                  fd_topo_tile_t const * tile ) {
    1110           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1111             : 
    1112           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1113           0 :   fd_net_ctx_t * ctx     = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_net_ctx_t), sizeof(fd_net_ctx_t) );
    1114           0 :   ulong *        free_tx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), tile->xdp.free_ring_depth * sizeof(ulong) );;
    1115             : 
    1116           0 :   fd_memset( ctx, 0, sizeof(fd_net_ctx_t) );
    1117             : 
    1118           0 :   interface_addrs( tile->xdp.if_virt, ctx->src_mac_addr, &ctx->default_address );
    1119           0 :   ctx->if_virt = if_nametoindex( tile->xdp.if_virt ); FD_TEST( ctx->if_virt );
    1120             : 
    1121             :   /* Load up dcache containing UMEM */
    1122             : 
    1123           0 :   void * const dcache_mem          = fd_topo_obj_laddr( topo, tile->net.umem_dcache_obj_id );
    1124           0 :   void * const umem                = fd_dcache_join( dcache_mem );
    1125           0 :   ulong  const umem_dcache_data_sz = fd_dcache_data_sz( umem );
    1126           0 :   ulong  const umem_frame_sz       = 2048UL;
    1127           0 :   ulong  const umem_sz             = fd_ulong_align_dn( umem_dcache_data_sz, umem_frame_sz );
    1128             : 
    1129             :   /* Derive chunk bounds */
    1130             : 
    1131           0 :   void * const umem_base   = fd_wksp_containing( dcache_mem );
    1132           0 :   ulong  const umem_chunk0 = ( (ulong)umem - (ulong)umem_base )>>FD_CHUNK_LG_SZ;
    1133           0 :   ulong  const umem_wmark  = umem_chunk0 + ( ( umem_sz-umem_frame_sz )>>FD_CHUNK_LG_SZ );
    1134             : 
    1135           0 :   if( FD_UNLIKELY( umem_chunk0>UINT_MAX || umem_wmark>UINT_MAX || umem_chunk0>umem_wmark ) ) {
    1136           0 :     FD_LOG_ERR(( "Calculated invalid UMEM bounds [%lu,%lu]", umem_chunk0, umem_wmark ));
    1137           0 :   }
    1138             : 
    1139           0 :   if( FD_UNLIKELY( !umem_base ) ) FD_LOG_ERR(( "UMEM dcache is not in a workspace" ));
    1140             : 
    1141           0 :   ctx->umem                 = umem;
    1142           0 :   ctx->umem_sz              = umem_sz;
    1143           0 :   ctx->net.pkt_buf_wksp_base = umem_base;
    1144           0 :   ctx->net.pkt_buf_chunk0    = umem_chunk0;
    1145           0 :   ctx->net.pkt_buf_wmark     = umem_wmark;
    1146             : 
    1147           0 :   ctx->free_tx.queue = free_tx;
    1148           0 :   ctx->free_tx.depth = tile->xdp.free_ring_depth;
    1149             : 
    1150             :   /* Firedancer requires the ring be a power of two. */
    1151           0 :   FD_TEST( fd_ulong_is_pow2( tile->xdp.free_ring_depth ) );
    1152             : 
    1153             :   /* Create and install XSKs */
    1154             : 
    1155           0 :   uint if_phys_if_idx = if_nametoindex( tile->xdp.if_phys );
    1156           0 :   if( FD_UNLIKELY( !if_phys_if_idx ) ) FD_LOG_ERR(( "if_nametoindex(%s) failed", tile->xdp.if_phys ));
    1157             : 
    1158           0 :   fd_xsk_params_t params0 = {
    1159           0 :     .if_idx      = if_phys_if_idx,
    1160           0 :     .if_queue_id = tile->xdp.if_queue,
    1161             : 
    1162             :     /* Some kernels produce EOPNOTSUP errors on sendto calls when
    1163             :        starting up without either XDP_ZEROCOPY or XDP_COPY
    1164             :        (e.g. 5.14.0-503.23.1.el9_5 with i40e) */
    1165           0 :     .bind_flags  = tile->xdp.zero_copy ? XDP_ZEROCOPY : XDP_COPY,
    1166             : 
    1167           0 :     .prefbusy_time_budget_micros = PREFBUSY_TIME_BUDGET_MICROS,
    1168             : 
    1169           0 :     .prefbusy_rx_budget = PREFBUSY_RX_BUDGET,
    1170             : 
    1171           0 :     .fr_depth  = tile->xdp.xdp_rx_queue_size*2,
    1172           0 :     .rx_depth  = tile->xdp.xdp_rx_queue_size,
    1173           0 :     .cr_depth  = tile->xdp.xdp_tx_queue_size,
    1174           0 :     .tx_depth  = tile->xdp.xdp_tx_queue_size,
    1175             : 
    1176           0 :     .umem_addr = umem,
    1177           0 :     .frame_sz  = umem_frame_sz,
    1178           0 :     .umem_sz   = umem_sz,
    1179           0 :   };
    1180             : 
    1181           0 :   fd_cstr_ncpy( params0.poll_mode, tile->xdp.poll_mode, sizeof(params0.poll_mode) );
    1182             : 
    1183             :   /* Re-derive XDP file descriptors */
    1184             : 
    1185           0 :   fd_xdp_fds_t xdp_fds[ FD_TOPO_XDP_FDS_MAX ];
    1186           0 :   uint         xdp_fds_cnt = FD_TOPO_XDP_FDS_MAX;
    1187           0 :   fd_topo_install_xdp( topo, xdp_fds, &xdp_fds_cnt, 0U, /* dry_run */ 1 );
    1188             : 
    1189           0 :   int xsk_map_fd = -1;
    1190           0 :   for( uint i=0U; i<xdp_fds_cnt; i++ ) {
    1191           0 :     if( xdp_fds[ i ].if_idx==if_phys_if_idx ) {
    1192           0 :       xsk_map_fd              = xdp_fds[ i ].xsk_map_fd;
    1193           0 :       ctx->prog_link_fds[ 0 ] = xdp_fds[ i ].prog_link_fd;
    1194           0 :       xdp_fds[ i ].prog_link_fd = -1; /* mark as used */
    1195           0 :       break;
    1196           0 :     }
    1197           0 :   }
    1198           0 :   FD_TEST( xsk_map_fd>=0 );
    1199             : 
    1200             :   /* Init XSK */
    1201           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 ));
    1202           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 ));
    1203           0 :   ctx->xsk_cnt = 1;
    1204             : 
    1205             :   /* Networking tile at index 0 also binds to loopback (only queue 0 available on lo) */
    1206             : 
    1207           0 :   if( FD_UNLIKELY( strcmp( tile->xdp.if_virt, "lo" ) && !tile->kind_id ) ) {
    1208           0 :     ctx->xsk_cnt = 2;
    1209             : 
    1210           0 :     uint lo_idx = if_nametoindex( "lo" );
    1211           0 :     if( FD_UNLIKELY( !lo_idx ) ) FD_LOG_ERR(( "if_nametoindex(lo) failed" ));
    1212             : 
    1213           0 :     int lo_xsk_map_fd = -1;
    1214           0 :     for( uint i=0U; i<xdp_fds_cnt; i++ ) {
    1215           0 :       if( xdp_fds[ i ].if_idx==lo_idx ) {
    1216           0 :         lo_xsk_map_fd           = xdp_fds[ i ].xsk_map_fd;
    1217           0 :         ctx->prog_link_fds[ 1 ] = xdp_fds[ i ].prog_link_fd;
    1218           0 :         xdp_fds[ i ].prog_link_fd = -1; /* mark as used */
    1219           0 :         break;
    1220           0 :       }
    1221           0 :     }
    1222           0 :     FD_TEST( lo_xsk_map_fd>=0 );
    1223             : 
    1224             :     /* init xsk 1 */
    1225           0 :     fd_xsk_params_t params1 = params0;
    1226           0 :     params1.if_idx      = lo_idx; /* probably always 1 */
    1227           0 :     params1.if_queue_id = 0;
    1228           0 :     params1.bind_flags  = 0;
    1229           0 :     if( FD_UNLIKELY( !fd_xsk_init( &ctx->xsk[ 1 ], &params1 ) ) )          FD_LOG_ERR(( "failed to bind lo_xsk" ));
    1230           0 :     if( FD_UNLIKELY( !fd_xsk_activate( &ctx->xsk[ 1 ], lo_xsk_map_fd ) ) ) FD_LOG_ERR(( "failed to activate lo_xsk" ));
    1231           0 :   }
    1232             : 
    1233             :   /* Close unused XDP fds */
    1234             : 
    1235           0 :   if( FD_UNLIKELY( fd_sandbox_gettid()==fd_sandbox_getpid() ) ) {
    1236             :     /* Kind of gross.. in single threaded mode we don't want to close the xsk_map_fd
    1237             :        since it's shared with other net tiles.  Just check for that by seeing if we
    1238             :        are the only thread in the process. */
    1239           0 :     for( uint i=0U; i<xdp_fds_cnt; i++ ) {
    1240           0 :       if( -1==close( xdp_fds[ i ].xsk_map_fd ) ) {
    1241           0 :         FD_LOG_ERR(( "close(%d) failed (%d-%s)", xsk_map_fd, errno, fd_io_strerror( errno ) ));
    1242           0 :       }
    1243           0 :       if( xdp_fds[ i ].prog_link_fd>0 &&
    1244           0 :           -1==close( xdp_fds[ i ].prog_link_fd ) ) {
    1245           0 :         FD_LOG_ERR(( "close(%d) failed (%d-%s)", xsk_map_fd, errno, fd_io_strerror( errno ) ));
    1246           0 :       }
    1247           0 :     }
    1248           0 :   }
    1249             : 
    1250           0 :   ctx->waker_client_idx = tile->waker_client_idx;
    1251           0 :   if( FD_UNLIKELY( topo->sleep_obj_id!=ULONG_MAX ) ) {
    1252           0 :     FD_TEST( ctx->waker_client_idx!=ULONG_MAX );
    1253           0 :     for( uint i=0U; i<ctx->xsk_cnt; i++ ) {
    1254           0 :       struct epoll_event ev = { .events = EPOLLIN, .data.fd = ctx->xsk[ i ].xsk_fd };
    1255           0 :       if( FD_UNLIKELY( -1==epoll_ctl( FD_WAKER_INNER_FD( ctx->waker_client_idx ), EPOLL_CTL_ADD, ctx->xsk[ i ].xsk_fd, &ev ) ) )
    1256           0 :         FD_LOG_ERR(( "epoll_ctl(ADD,xsk_fd) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1257           0 :     }
    1258           0 :   }
    1259             : 
    1260           0 :   double tick_per_ns = fd_tempo_tick_per_ns( NULL );
    1261           0 :   ctx->xdp_stats_interval_ticks = (long)( FD_XDP_STATS_INTERVAL_NS * tick_per_ns );
    1262             : 
    1263           0 :   ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
    1264           0 :   if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
    1265           0 :     FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
    1266           0 : }
    1267             : 
    1268             : static void
    1269             : init_device_table( fd_net_ctx_t * ctx,
    1270             :                    void *         netdev_tbl_shm,
    1271           3 :                    void *         netdev_tbl_local ) {
    1272           3 :   FD_TEST( fd_netdev_tbl_join( &ctx->netdev_shared, netdev_tbl_shm ) );
    1273           3 :   FD_TEST( fd_netdev_tbl_new( netdev_tbl_local, NETDEV_MAX, BOND_MASTER_MAX ) );
    1274           3 :   FD_TEST( fd_netdev_tbl_join( &ctx->netdev_tbl, netdev_tbl_local ) );
    1275           3 :   ctx->netdev_seq = fd_netdev_tbl_copy( &ctx->netdev_tbl, &ctx->netdev_shared );
    1276           3 :   fd_net_gre_tunnels_refresh( &ctx->net, &ctx->netdev_tbl );
    1277           3 : }
    1278             : 
    1279             : FD_FN_UNUSED static void
    1280             : unprivileged_init( fd_topo_t const *      topo,
    1281           0 :                    fd_topo_tile_t const * tile ) {
    1282           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1283             : 
    1284           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1285           0 :   fd_net_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_net_ctx_t), sizeof(fd_net_ctx_t) );
    1286           0 :   FD_TEST( ctx->xsk_cnt!=0 );
    1287           0 :   FD_TEST( ctx->free_tx.queue!=NULL );
    1288           0 :   (void)FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), tile->xdp.free_ring_depth * sizeof(ulong) );
    1289           0 :   void * netdev_tbl_local = FD_SCRATCH_ALLOC_APPEND( l, fd_netdev_tbl_align(), fd_netdev_tbl_footprint( NETDEV_MAX, BOND_MASTER_MAX ) );
    1290           0 :   void * fib_local_mem         = FD_SCRATCH_ALLOC_APPEND( l, fd_fib4_align(), fd_fib4_footprint( tile->xdp.route_max, tile->xdp.route_peer_max ) );
    1291           0 :   void * fib_main_mem          = FD_SCRATCH_ALLOC_APPEND( l, fd_fib4_align(), fd_fib4_footprint( tile->xdp.route_max, tile->xdp.route_peer_max ) );
    1292           0 :   FD_TEST( fd_fib4_join( ctx->fib_local,         fd_fib4_new( fib_local_mem,         tile->xdp.route_max, tile->xdp.route_peer_max, tile->xdp.route_peer_seed ) ) );
    1293           0 :   FD_TEST( fd_fib4_join( ctx->fib_main,          fd_fib4_new( fib_main_mem,          tile->xdp.route_max, tile->xdp.route_peer_max, tile->xdp.route_peer_seed ) ) );
    1294             : 
    1295           0 :   ctx->net.kind_id  = tile->kind_id;
    1296           0 :   ctx->net.tile_cnt = fd_topo_tile_name_cnt( topo, tile->name );
    1297             : 
    1298           0 :   ctx->net.bind_address = tile->net.bind_address;
    1299             : 
    1300             :   /* Put a bound on chunks we read from the input, to make sure they
    1301             :      are within in the data region of the workspace. */
    1302             : 
    1303           0 :   if( FD_UNLIKELY( !tile->in_cnt ) ) FD_LOG_ERR(( "net tile in link cnt is zero" ));
    1304           0 :   if( FD_UNLIKELY( tile->in_cnt>FD_NET_IN_MAX ) ) FD_LOG_ERR(( "net tile in link cnt %lu exceeds FD_NET_IN_MAX %lu", tile->in_cnt, FD_NET_IN_MAX ));
    1305           0 :   FD_TEST( tile->in_cnt<=32 );
    1306           0 :   for( ulong i=0UL; i<tile->in_cnt; i++ ) {
    1307           0 :     fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
    1308           0 :     if( !strcmp( link->name, "iproute_out" ) ) ctx->net.in_kind[i] = FD_NET_IN_KIND_IPROUTE;
    1309           0 :     else {
    1310           0 :       ctx->net.in_kind[i] = FD_NET_IN_KIND_TX;
    1311           0 :       if( FD_UNLIKELY( link->mtu!=FD_NET_MTU ) ) FD_LOG_ERR(( "net tile in link %s does not have a normal MTU", link->name ));
    1312           0 :     }
    1313             : 
    1314           0 :     ctx->net.in_dcache_ctx[ i ].wksp_base = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
    1315           0 :     ctx->net.in_dcache_ctx[ i ].chunk0    = fd_dcache_compact_chunk0( ctx->net.in_dcache_ctx[ i ].wksp_base, link->dcache );
    1316           0 :     ctx->net.in_dcache_ctx[ i ].wmark     = fd_dcache_compact_wmark( ctx->net.in_dcache_ctx[ i ].wksp_base, link->dcache, link->mtu );
    1317           0 :   }
    1318             : 
    1319           0 :   fd_net_rx_dst_ports_init( &ctx->net, topo, tile );
    1320             : 
    1321           0 :   ctx->netlnk_out_idx = fd_topo_find_tile_out_link( topo, tile, "net_netlnk", tile->kind_id );
    1322           0 :   if( FD_UNLIKELY( ctx->netlnk_out_idx==ULONG_MAX ) ) FD_LOG_ERR(( "netlink request link not found" ));
    1323           0 :   ctx->solicit_ip = 0U;
    1324             : 
    1325           0 :   ctx->waker_fseq = NULL;
    1326           0 :   if( FD_UNLIKELY( tile->is_waker_client ) ) {
    1327           0 :     ctx->waker_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, tile->waker_fseq_obj_id ) );
    1328           0 :     FD_TEST( ctx->waker_fseq );
    1329           0 :   }
    1330             : 
    1331           0 :   for( uint j=0U; j<2U; j++ ) {
    1332           0 :     ctx->tx_flusher[ j ].pending_wmark         = (ulong)( (double)tile->xdp.xdp_tx_queue_size * 0.7 );
    1333           0 :     ctx->tx_flusher[ j ].tail_flush_backoff    = (long)( (double)tile->xdp.tx_flush_timeout_ns * fd_tempo_tick_per_ns( NULL ) );
    1334           0 :     ctx->tx_flusher[ j ].next_tail_flush_ticks = LONG_MAX;
    1335             : 
    1336           0 :     ctx->tx_flusher[ j ].prefbusy_last_poll_ticks     = 0L;
    1337           0 :     ctx->tx_flusher[ j ].prefbusy_min_interval_ticks  = (long)( PREFBUSY_MIN_INTERVAL_NS * fd_tempo_tick_per_ns( NULL ) );
    1338           0 :     ctx->tx_flusher[ j ].prefbusy_stall_timeout_ticks = (long)( PREFBUSY_STALL_TIMEOUT_NS * fd_tempo_tick_per_ns( NULL ) );
    1339           0 :   }
    1340             : 
    1341           0 :   ulong neigh4_obj_id = tile->xdp.neigh4_obj_id;
    1342           0 :   ulong ele_max   = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.ele_max",   neigh4_obj_id );
    1343           0 :   ulong probe_max = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.probe_max", neigh4_obj_id );
    1344           0 :   ulong seed      = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.seed",      neigh4_obj_id );
    1345           0 :   if( FD_UNLIKELY( (ele_max==ULONG_MAX) | (probe_max==ULONG_MAX) | (seed==ULONG_MAX) ) )
    1346           0 :     FD_LOG_ERR(( "neigh4 hmap properties not set" ));
    1347           0 :   if( FD_UNLIKELY( !fd_neigh4_hmap_join(
    1348           0 :       ctx->neigh4,
    1349           0 :       fd_topo_obj_laddr( topo, neigh4_obj_id ),
    1350           0 :       ele_max,
    1351           0 :       probe_max,
    1352           0 :       seed ) ) ) {
    1353           0 :     FD_LOG_ERR(( "fd_neigh4_hmap_join failed" ));
    1354           0 :   }
    1355             : 
    1356           0 :   init_device_table( ctx, fd_topo_obj_laddr( topo, tile->xdp.netdev_tbl_obj_id ), netdev_tbl_local );
    1357             : 
    1358             :   /* Initialize TX free ring */
    1359             : 
    1360           0 :   ulong const frame_sz  = 2048UL;
    1361           0 :   ulong       frame_off = 0UL;
    1362           0 :   ulong const tx_depth  = ctx->free_tx.depth;
    1363           0 :   for( ulong j=0; j<tx_depth; j++ ) {
    1364           0 :     ctx->free_tx.queue[ j ] = (ulong)ctx->umem + frame_off;
    1365           0 :     frame_off += frame_sz;
    1366           0 :   }
    1367           0 :   ctx->free_tx.prod = tx_depth;
    1368             : 
    1369             :   /* Initialize RX mcache chunks */
    1370             : 
    1371           0 :   for( ulong i=0UL; i<(tile->out_cnt); i++ ) {
    1372           0 :     fd_topo_link_t const * out_link = &topo->links[ tile->out_link_id[ i  ] ];
    1373           0 :     fd_frag_meta_t * mcache = out_link->mcache;
    1374           0 :     for( ulong j=0UL; j<fd_mcache_depth( mcache ); j++ ) {
    1375           0 :       mcache[ j ].chunk = (uint)( ctx->net.pkt_buf_chunk0 + (frame_off>>FD_CHUNK_LG_SZ) );
    1376           0 :       frame_off += frame_sz;
    1377           0 :     }
    1378           0 :   }
    1379             : 
    1380             :   /* Initialize FILL ring */
    1381             : 
    1382           0 :   int _charge_busy = 0;
    1383           0 :   for( uint j=0U; j<ctx->xsk_cnt; j++ ) {
    1384           0 :     frame_off = net_xsk_bootstrap( ctx, j, frame_off );
    1385           0 :     net_rx_wakeup( ctx, &ctx->xsk[ j ], &_charge_busy );
    1386           0 :     net_tx_wakeup( ctx, &ctx->xsk[ j ], &_charge_busy );
    1387           0 :   }
    1388             : 
    1389           0 :   if( FD_UNLIKELY( frame_off > ctx->umem_sz ) ) {
    1390           0 :     FD_LOG_ERR(( "UMEM is too small" ));
    1391           0 :   }
    1392           0 : }
    1393             : 
    1394             : FD_FN_UNUSED static ulong
    1395             : populate_allowed_seccomp( fd_topo_t const *      topo,
    1396             :                           fd_topo_tile_t const * tile,
    1397             :                           ulong                  out_cnt,
    1398           0 :                           struct sock_filter *   out ) {
    1399           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1400           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1401           0 :   fd_net_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_net_ctx_t ), sizeof( fd_net_ctx_t ) );
    1402             : 
    1403             :   /* A bit of a hack, if there is no loopback XSK for this tile, we still need to pass
    1404             :      two "allow" FD arguments to the net policy, so we just make them both the same. */
    1405           0 :   int allow_fd2 = ctx->xsk_cnt>1UL ? ctx->xsk[ 1 ].xsk_fd : ctx->xsk[ 0 ].xsk_fd;
    1406           0 :   FD_TEST( ctx->xsk[ 0 ].xsk_fd >= 0 && allow_fd2 >= 0 );
    1407             : 
    1408             :   /* not a waker client in performance mode: no fd matches, epoll_ctl denied */
    1409           0 :   uint epoll_inner_fd = tile->is_waker_client ? (uint)FD_WAKER_INNER_FD( tile->waker_client_idx ) : (uint)-1;
    1410           0 :   uint epoll_outer_fd = tile->is_waker_client ? (uint)FD_WAKER_OUTER_FD : (uint)-1;
    1411           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, epoll_inner_fd, epoll_outer_fd );
    1412           0 :   return sock_filter_policy_fd_xdp_tile_instr_cnt;
    1413           0 : }
    1414             : 
    1415             : FD_FN_UNUSED static ulong
    1416             : populate_allowed_fds( fd_topo_t const *      topo,
    1417             :                       fd_topo_tile_t const * tile,
    1418             :                       ulong                  out_fds_cnt,
    1419           0 :                       int *                  out_fds ) {
    1420           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1421           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1422           0 :   fd_net_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_net_ctx_t ), sizeof( fd_net_ctx_t ) );
    1423             : 
    1424           0 :   if( FD_UNLIKELY( out_fds_cnt<8UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
    1425             : 
    1426           0 :   ulong out_cnt = 0UL;
    1427             : 
    1428           0 :   out_fds[ out_cnt++ ] = 2; /* stderr */
    1429           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
    1430           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
    1431             : 
    1432           0 :                                       out_fds[ out_cnt++ ] = ctx->xsk[ 0 ].xsk_fd;
    1433           0 :                                       out_fds[ out_cnt++ ] = ctx->prog_link_fds[ 0 ];
    1434           0 :   if( FD_LIKELY( ctx->xsk_cnt>1UL ) ) out_fds[ out_cnt++ ] = ctx->xsk[ 1 ].xsk_fd;
    1435           0 :   if( FD_LIKELY( ctx->xsk_cnt>1UL ) ) out_fds[ out_cnt++ ] = ctx->prog_link_fds[ 1 ];
    1436           0 :   if( FD_UNLIKELY( tile->is_waker_client ) ) {
    1437           0 :     out_fds[ out_cnt++ ] = FD_WAKER_OUTER_FD;                           /* waker outer epoll fd (rearm) */
    1438           0 :     out_fds[ out_cnt++ ] = FD_WAKER_INNER_FD( tile->waker_client_idx ); /* waker inner epoll fd */
    1439           0 :   }
    1440           0 :   return out_cnt;
    1441           0 : }
    1442             : 
    1443           0 : #define STEM_BURST (1UL)
    1444           0 : #define STEM_LAZY ((ulong)30e3) /* 30 us */
    1445             : 
    1446           0 : #define STEM_CALLBACK_CONTEXT_TYPE  fd_net_ctx_t
    1447           0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_net_ctx_t)
    1448             : 
    1449           0 : #define STEM_CALLBACK_METRICS_WRITE       metrics_write
    1450           0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
    1451             : #define STEM_CALLBACK_PREVENT_PARK        prevent_park
    1452           0 : #define STEM_CALLBACK_BEFORE_CREDIT       before_credit
    1453           0 : #define STEM_CALLBACK_BEFORE_FRAG         before_frag
    1454           0 : #define STEM_CALLBACK_DURING_FRAG         during_frag
    1455           0 : #define STEM_CALLBACK_AFTER_FRAG          after_frag
    1456             : 
    1457             : #include "../../stem/fd_stem.c"
    1458             : 
    1459             : #ifndef FD_TILE_TEST
    1460             : fd_topo_run_tile_t fd_tile_net = {
    1461             :   .name                     = "net",
    1462             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    1463             :   .populate_allowed_fds     = populate_allowed_fds,
    1464             :   .scratch_align            = scratch_align,
    1465             :   .scratch_footprint        = scratch_footprint,
    1466             :   .privileged_init          = privileged_init,
    1467             :   .unprivileged_init        = unprivileged_init,
    1468             :   .run                      = stem_run,
    1469             : };
    1470             : #endif

Generated by: LCOV version 1.14