LCOV - code coverage report
Current view: top level - disco/net/xdp - fd_xdp_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 362 1044 34.7 %
Date: 2026-09-08 04:28:46 Functions: 18 67 26.9 %

          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             : 
       7             : #include <errno.h>
       8             : #include <fcntl.h>
       9             : #include <net/if.h>
      10             : #include <netinet/in.h>
      11             : #include <sys/socket.h> /* MSG_DONTWAIT needed before importing the net seccomp filter */
      12             : #include <linux/if_xdp.h>
      13             : 
      14             : #include "../fd_net_common.h"
      15             : #include "../../../discof/repair/fd_repair.h"
      16             : #include "../../metrics/fd_metrics.h"
      17             : #include "../../netlink/fd_netlink_tile.h" /* neigh4_solicit */
      18             : #include "../../topo/fd_topo.h"
      19             : 
      20             : #include "../../../waltz/ip/fd_fib4.h"
      21             : #include "../../../waltz/ip/fd_iproute.h"
      22             : #include "../../../waltz/neigh/fd_neigh4_map.h"
      23             : #include "../../../waltz/mib/fd_netdev_tbl.h"
      24             : #include "../../../waltz/xdp/fd_xdp_redirect_user.h" /* fd_xsk_activate */
      25             : #include "../../../waltz/xdp/fd_xsk.h"
      26             : #include "../../../util/log/fd_dtrace.h"
      27             : #include "../../../util/net/fd_eth.h"
      28             : #include "../../../util/net/fd_ip4.h"
      29             : #include "../../../util/net/fd_gre.h"
      30             : #include "../../../util/pod/fd_pod_format.h"
      31             : 
      32             : #include <unistd.h>
      33             : #include <linux/if.h> /* struct ifreq */
      34             : #include <sys/ioctl.h>
      35             : #include <linux/if_arp.h>
      36             : #include <linux/rtnetlink.h>
      37             : 
      38             : #include "generated/fd_xdp_tile_seccomp.h"
      39             : 
      40             : /* MAX_NET_INS controls the max number of TX links that a net tile can
      41             :    serve. */
      42             : 
      43             : #define MAX_NET_INS (32UL)
      44             : 
      45           0 : #define IN_KIND_NET     (0U)
      46           3 : #define IN_KIND_IPROUTE (1U)
      47             : 
      48             : /* FD_XDP_STATS_INTERVAL_NS controls the XDP stats refresh interval.
      49             :    This should be lower than the interval at which the metrics tile
      50             :    collects metrics. */
      51             : 
      52           0 : #define FD_XDP_STATS_INTERVAL_NS (11e6) /* 11ms */
      53             : 
      54             : /* XSK_IDX_{MAIN,LO} are the hardcoded XSK indices in ctx->xsk[ ... ].
      55             :    Only net tile 0 has XSK_IDX_LO, all net tiles have XSK_IDX_MAIN. */
      56             : 
      57          30 : #define XSK_IDX_MAIN 0
      58          18 : #define XSK_IDX_LO   1
      59             : 
      60             : /* XSK 'busy_poll_usecs' value (max amount of time
      61             :    spent spinning in a NAPI poll before returning back to
      62             :    userspace if the processing budget hasn't already ran out).
      63             : 
      64             :    64us chosen based on napibusy configuration tested and
      65             :    shown in https://lwn.net/Articles/997491/ Linux patch
      66             :    cover letter.  Chosen over fullbusy since for Firedancer
      67             :    the values of fullbusy are unnecessarily high and could
      68             :    cause some extra latency to regular non-Firedancer traffic.*/
      69           0 : #define PREFBUSY_TIME_BUDGET_MICROS (64L)
      70             : 
      71             : /* PREFBUSY_RX_BUDGET is the NAPI RX processing budget (max num RX
      72             :    packets that the NIC driver can move from the hw rings into the
      73             :    XSK rings per poll).
      74             : 
      75             :    Default RX budget used by NIC drivers is 64, therefore
      76             :    it is safest to use 64 in prefbusy polling.  Also reduces
      77             :    TX starvation risks as the TX budget set by the NIC driver
      78             :    is also generally 64. */
      79           0 : #define PREFBUSY_RX_BUDGET (64L)
      80             : 
      81             : /* Min time between each prefbusy poll.  Necessary to avoid a no RX
      82             :    scenario livelocking TX with overly frequent sendto calls, given
      83             :    prefbusy polls whenever the RX queue is empty.
      84             : 
      85             :    Value chosen based on experimentation on ixgbe, mlx5 and i40e as well
      86             :    as on varying CPUs and clock speeds.  Too low -> lower max TX
      87             :    throughput when RX is very low.  Too high -> lower max RX and TX throughput. */
      88           0 : #define PREFBUSY_MIN_INTERVAL_NS (5e3) /* 5us */
      89             : 
      90             : /* Max time since last prefbusy poll before a prefbusy poll is
      91             :    forced (has been read that polling can sometimes resolve a stall).
      92             : 
      93             :    Exact value again not particularly important as this is just extra
      94             :    protection against stalls which have not been observed in testing but
      95             :    are still good to protect against since there is no cost to doing so.
      96             : 
      97             :    Value of 150us chosen since it is easily large enough to not interfere
      98             :    with standard prefbusy runtime unless there is a serious problem. */
      99           0 : #define PREFBUSY_STALL_TIMEOUT_NS (150e3) /* 150us */
     100             : 
     101             : /* MAX_GRE_CNT is the maximum number of GRE tunnels the XDP tile will
     102             :    monitor.  If a packet comes in with a source IP that doesn't match
     103             :    the endpoint of one of the first MAX_GRE_CNT tunnels (in the order
     104             :    the OS enumerates them), it will be dropped.  This is limited for
     105             :    performance reasons. */
     106         111 : #define MAX_GRE_CNT 4UL
     107             : 
     108             : /* fd_net_in_ctx_t contains consumer information for an incoming tango
     109             :    link.  It is used as part of the TX path. */
     110             : 
     111             : typedef struct {
     112             :   fd_wksp_t * mem;
     113             :   ulong       chunk0;
     114             :   ulong       wmark;
     115             : } fd_net_in_ctx_t;
     116             : 
     117             : /* fd_net_out_ctx_t contains publisher information for a link to a
     118             :    downstream app tile.  It is used as part of the RX path. */
     119             : 
     120             : typedef struct {
     121             :   fd_frag_meta_t * mcache;
     122             :   ulong *          sync;
     123             :   ulong            depth;
     124             :   ulong            seq;
     125             : } fd_net_out_ctx_t;
     126             : 
     127             : /* fd_net_flusher_t controls the pacing of XDP sendto calls for flushing
     128             :    TX batches.  In the 'wakeup' XDP mode, no TX occurs unless the net
     129             :    tile wakes up the kernel periodically using the sendto() syscall.
     130             :    If sendto() is called too frequently, time is wasted on context
     131             :    switches.  If sendto() is called not often enough, packets are
     132             :    delayed or dropped.  sendto() calls make almost no guarantees how
     133             :    much packets are sent out, nor do they indicate when the kernel
     134             :    finishes a wakeup call (asynchronously dispatched).  The net tile
     135             :    thus uses a myriad of flush triggers that were tested for best
     136             :    performance. */
     137             : 
     138             : struct fd_net_flusher {
     139             : 
     140             :   /* Packets that were enqueued after the last sendto() wakeup are
     141             :      considered "pending".  If there are more than pending_wmark packets
     142             :      pending, a wakeup is dispatched.  Thus, this dispatch trigger is
     143             :      proportional to packet rate, but does not trigger if I/O is seldom. */
     144             :   ulong pending_cnt;
     145             :   ulong pending_wmark;
     146             : 
     147             :   /* Sometimes, packets are not flushed out even after a sendto()
     148             :      wakeup.  This can result in the tail of a burst getting delayed or
     149             :      overrun.  If more than tail_flush_backoff ticks pass since the last
     150             :      sendto() wakeup and there are still unacknowledged packets in the
     151             :      TX ring, issues another wakeup. Only used by "softirq" poll mode. */
     152             :   long next_tail_flush_ticks;
     153             :   long tail_flush_backoff;
     154             : 
     155             :   /* When the most recent prefbusy poll was. */
     156             :   long prefbusy_last_poll_ticks;
     157             :   /* Min time between each prefbusy poll. */
     158             :   long prefbusy_min_interval_ticks;
     159             :   /* Max time since last prefbusy poll before a prefbusy poll is
     160             :      forced (has been read that polling can sometimes resolve a stall). */
     161             :   long prefbusy_stall_timeout_ticks;
     162             : };
     163             : 
     164             : typedef struct fd_net_flusher fd_net_flusher_t;
     165             : 
     166             : FD_PROTOTYPES_BEGIN
     167             : 
     168             : /* fd_net_flusher_inc marks a new packet as enqueued. */
     169             : 
     170             : static inline void
     171             : fd_net_flusher_inc( fd_net_flusher_t * flusher,
     172          18 :                     long               now ) {
     173          18 :   flusher->pending_cnt++;
     174          18 :   long next_flush = now + flusher->tail_flush_backoff;
     175          18 :   flusher->next_tail_flush_ticks = fd_long_min( flusher->next_tail_flush_ticks, next_flush );
     176          18 : }
     177             : 
     178             : /* fd_net_flusher_check returns 1 if a sendto() wakeup should be issued
     179             :    immediately.  now is a recent fd_tickcount() value.
     180             :    If tx_ring_empty==0 then the kernel is caught up with the net tile
     181             :    on the XDP TX ring.  (Otherwise, the kernel is behind the net tile) */
     182             : 
     183             : static inline int
     184             : fd_net_flusher_check( fd_net_flusher_t * flusher,
     185             :                       long               now,
     186          27 :                       int                tx_ring_empty ) {
     187          27 :   int flush_level   = flusher->pending_cnt >= flusher->pending_wmark;
     188          27 :   int flush_timeout = now >= flusher->next_tail_flush_ticks;
     189          27 :   int flush         = flush_level || flush_timeout;
     190          27 :   if( !flush ) return 0;
     191          27 :   if( FD_UNLIKELY( tx_ring_empty ) ) {
     192             :     /* Flush requested but caught up */
     193           3 :     flusher->pending_cnt           = 0UL;
     194           3 :     flusher->next_tail_flush_ticks = LONG_MAX;
     195           3 :     return 0;
     196           3 :   }
     197          24 :   return 1;
     198          27 : }
     199             : 
     200             : /* fd_net_flusher_wakeup signals a sendto() wakeup was done.  now is a
     201             :    recent fd_tickcount() value. */
     202             : 
     203             : static inline void
     204             : fd_net_flusher_wakeup( fd_net_flusher_t * flusher,
     205          24 :                        long               now ) {
     206          24 :   flusher->pending_cnt           = 0UL;
     207          24 :   flusher->next_tail_flush_ticks = now + flusher->tail_flush_backoff;
     208          24 : }
     209             : 
     210             : FD_PROTOTYPES_END
     211             : 
     212             : /* fd_net_free_ring is a FIFO queue that stores pointers to free XDP TX
     213             :    frames. */
     214             : 
     215             : struct fd_net_free_ring {
     216             :   ulong   prod;
     217             :   ulong   cons;
     218             :   ulong   depth;
     219             :   ulong * queue;
     220             : };
     221             : typedef struct fd_net_free_ring fd_net_free_ring_t;
     222             : 
     223             : typedef struct {
     224             :   /* An "XSK" is an AF_XDP socket */
     225             :   uint     xsk_cnt;
     226             :   fd_xsk_t xsk[ 2 ];
     227             :   int      prog_link_fds[ 2 ];
     228             :   uint     if_virt;
     229             : 
     230             :   /* UMEM frame region within dcache */
     231             :   void *   umem;    /* Start of UMEM */
     232             :   ulong    umem_sz; /* Size  of UMEM */
     233             : 
     234             :   /* UMEM chunk region within workspace */
     235             :   uint     umem_chunk0; /* Chunk number of the first byte of UMEM */
     236             : 
     237             :   /* All net tiles are subscribed to the same TX links.  (These are
     238             :      incoming links from app tiles asking the net tile to send out packets)
     239             :      The net tiles "take turns" doing TX jobs based on the L3+L4 dst hash.
     240             :      net_tile_id is the index of the current interface, net_tile_cnt is the
     241             :      total amount of interfaces. */
     242             :   uint net_tile_id;
     243             :   uint net_tile_cnt;
     244             : 
     245             :   /* Details pertaining to an inflight send op */
     246             :   struct {
     247             :     uint   xsk_idx;
     248             :     void * frame;
     249             :     uchar  mac_addrs[12];     /* First 12 bytes of Ethernet header */
     250             :     uint   src_ip;            /* src_ip in net order */
     251             : 
     252             :     uint   use_gre;           /* The tx packet will be GRE-encapsulated */
     253             :     uint   gre_outer_src_ip;  /* For GRE: Outer iphdr's src_ip in net order */
     254             :     uint   gre_outer_dst_ip;  /* For GRE: Outer iphdr's dst_ip in net order */
     255             :   } tx_op;
     256             : 
     257             :   /* Round-robin cycle service operations */
     258             :   uint rr_idx;
     259             : 
     260             :   /* Ring tracking free packet buffers */
     261             :   fd_net_free_ring_t free_tx;
     262             : 
     263             :   uchar  src_mac_addr[6];
     264             :   uint   default_address;
     265             : 
     266             :   uint   bind_address;
     267             :   ushort shred_listen_port;
     268             :   ushort quic_transaction_listen_port;
     269             :   ushort legacy_transaction_listen_port;
     270             :   ushort gossip_listen_port;
     271             :   ushort repair_client_listen_port;
     272             :   ushort repair_serve_listen_port;
     273             :   ushort txsend_src_port;
     274             :   ushort votor_quic_client_listen_port;
     275             :   ushort votor_quic_server_listen_port;
     276             : 
     277             :   ulong in_cnt;
     278             :   fd_net_in_ctx_t in[ MAX_NET_INS ];
     279             :   uchar in_kind[ MAX_NET_INS ];
     280             :   fd_iproute_msg_t iproute_msg;
     281             : 
     282             :   fd_net_out_ctx_t quic_out[1];
     283             :   fd_net_out_ctx_t shred_out[1];
     284             :   fd_net_out_ctx_t gossvf_out[1];
     285             :   fd_net_out_ctx_t repair_out[1];
     286             :   fd_net_out_ctx_t txsend_out[1];
     287             : 
     288             :   fd_net_out_ctx_t rserve_out[1];
     289             :   int rserve_enabled;
     290             : 
     291             :   fd_net_out_ctx_t votor_out[1];
     292             :   int votor_enabled;
     293             : 
     294             :   /* XDP stats refresh timer */
     295             :   long xdp_stats_interval_ticks;
     296             :   long next_xdp_stats_refresh;
     297             : 
     298             :   /* TX flush timers */
     299             :   fd_net_flusher_t tx_flusher[2]; /* one per XSK */
     300             : 
     301             :   /* Route and neighbor tables */
     302             :   fd_fib4_t fib_local[1];
     303             :   fd_fib4_t fib_main[1];
     304             :   fd_neigh4_hmap_t  neigh4[1];
     305             :   fd_netlink_neigh4_solicit_link_t neigh4_solicit[1];
     306             : 
     307             :   /* Netdev table */
     308             :   fd_netdev_tbl_join_t netdev_tbl;                 /* local copy in scratch (hot path) */
     309             :   fd_netdev_tbl_join_t netdev_shared;              /* shared table in netbase (seqlock protected) */
     310             :   uint                 gre_tunnel_ip[MAX_GRE_CNT]; /* 0 means unused */
     311             : 
     312             :   struct {
     313             :     ulong rx_pkt_cnt;
     314             :     ulong rx_bytes_total;
     315             :     ulong rx_src_addr_invalid_cnt;
     316             :     ulong rx_undersz_cnt;
     317             :     ulong rx_fill_blocked_cnt;
     318             :     ulong rx_backp_cnt;
     319             :     long  rx_busy_cnt;
     320             :     long  rx_idle_cnt;
     321             : 
     322             :     ulong tx_submit_cnt;
     323             :     ulong tx_complete_cnt;
     324             :     ulong tx_bytes_total;
     325             :     ulong tx_route_fail_cnt[ FD_METRICS_COUNTER_NET_PKT_TX_ROUTE_FAIL_CNT ];
     326             :     ulong tx_invalid_cnt;
     327             :     ulong tx_no_xdp_cnt;
     328             :     ulong tx_neigh_fail_cnt;
     329             :     ulong tx_full_fail_cnt;
     330             :     long  tx_busy_cnt;
     331             :     long  tx_idle_cnt;
     332             : 
     333             :     ulong xsk_tx_wakeup_cnt;
     334             :     ulong xsk_rx_wakeup_cnt;
     335             : 
     336             :     ulong rx_gre_cnt;
     337             :     ulong rx_gre_ignored_cnt;
     338             :     ulong rx_gre_inv_pkt_cnt;
     339             :     ulong tx_gre_cnt;
     340             :     ulong tx_gre_route_fail_cnt;
     341             :   } metrics;
     342             : } fd_net_ctx_t;
     343             : 
     344             : fd_fib4_t *
     345             : fd_net_tile_fib4_join( fd_fib4_t *              out,
     346             :                        fd_topo_t const *         topo,
     347             :                        fd_topo_tile_t const *    tile,
     348           0 :                        int                      main_table ) {
     349           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
     350           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
     351           0 :   (void)FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_net_ctx_t), sizeof(fd_net_ctx_t) );
     352           0 :   (void)FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), tile->xdp.free_ring_depth*sizeof(ulong) );
     353           0 :   (void)FD_SCRATCH_ALLOC_APPEND( l, fd_netdev_tbl_align(), fd_netdev_tbl_footprint( NETDEV_MAX, BOND_MASTER_MAX ) );
     354           0 :   void * local_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_fib4_align(), fd_fib4_footprint( tile->xdp.route_max, tile->xdp.route_peer_max ) );
     355           0 :   void * main_mem  = FD_SCRATCH_ALLOC_APPEND( l, fd_fib4_align(), fd_fib4_footprint( tile->xdp.route_max, tile->xdp.route_peer_max ) );
     356           0 :   return fd_fib4_join( out, main_table ? main_mem : local_mem );
     357           0 : }
     358             : 
     359             : FD_FN_CONST static inline ulong
     360           9 : scratch_align( void ) {
     361           9 :   return 4096UL;
     362           9 : }
     363             : 
     364             : FD_FN_PURE static inline ulong
     365           3 : scratch_footprint( fd_topo_tile_t const * tile ) {
     366           3 :   ulong l = FD_LAYOUT_INIT;
     367           3 :   l = FD_LAYOUT_APPEND( l, alignof(fd_net_ctx_t), sizeof(fd_net_ctx_t)                      );
     368           3 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),        tile->xdp.free_ring_depth * sizeof(ulong) );
     369           3 :   l = FD_LAYOUT_APPEND( l, fd_netdev_tbl_align(), fd_netdev_tbl_footprint( NETDEV_MAX, BOND_MASTER_MAX ) );
     370           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 ) );
     371           3 :   return FD_LAYOUT_FINI( l, scratch_align() );
     372           3 : }
     373             : 
     374             : static void
     375           0 : metrics_write( fd_net_ctx_t * ctx ) {
     376           0 :   FD_MCNT_SET(   NET, PKT_RX,              ctx->metrics.rx_pkt_cnt          );
     377           0 :   FD_MCNT_SET(   NET, PKT_RX_BYTES,        ctx->metrics.rx_bytes_total      );
     378           0 :   FD_MCNT_SET(   NET, PKT_RX_UNDERSIZE,    ctx->metrics.rx_undersz_cnt      );
     379           0 :   FD_MCNT_SET(   NET, PKT_RX_FILL_RING_FULL, ctx->metrics.rx_fill_blocked_cnt );
     380           0 :   FD_MCNT_SET(   NET, PKT_RX_BACKPRESSURE, ctx->metrics.rx_backp_cnt        );
     381           0 :   FD_MGAUGE_SET( NET, RX_BUFFER_BUSY, (ulong)fd_long_max( ctx->metrics.rx_busy_cnt, 0L ) );
     382           0 :   FD_MGAUGE_SET( NET, RX_BUFFER_IDLE, (ulong)fd_long_max( ctx->metrics.rx_idle_cnt, 0L ) );
     383           0 :   FD_MGAUGE_SET( NET, TX_BUFFER_BUSY, (ulong)fd_long_max( ctx->metrics.tx_busy_cnt, 0L ) );
     384           0 :   FD_MGAUGE_SET( NET, TX_BUFFER_IDLE, (ulong)fd_long_max( ctx->metrics.tx_idle_cnt, 0L ) );
     385             : 
     386           0 :   FD_MCNT_SET( NET, PKT_TX_SUBMITTED,     ctx->metrics.tx_submit_cnt     );
     387           0 :   FD_MCNT_SET( NET, PKT_TX_COMPLETED,     ctx->metrics.tx_complete_cnt   );
     388           0 :   FD_MCNT_SET( NET, PKT_TX_BYTES,         ctx->metrics.tx_bytes_total    );
     389           0 :   FD_MCNT_ENUM_COPY( NET, PKT_TX_ROUTE_FAIL, ctx->metrics.tx_route_fail_cnt );
     390           0 :   FD_MCNT_SET( NET, PKT_TX_INVALID,     ctx->metrics.tx_invalid_cnt    );
     391           0 :   FD_MCNT_SET( NET, PKT_TX_NO_NEIGHBOR, ctx->metrics.tx_neigh_fail_cnt );
     392           0 :   FD_MCNT_SET( NET, PKT_TX_RING_FULL,     ctx->metrics.tx_full_fail_cnt  );
     393             : 
     394           0 :   FD_MCNT_SET( NET, XSK_SYSCALL_TX,    ctx->metrics.xsk_tx_wakeup_cnt    );
     395           0 :   FD_MCNT_SET( NET, XSK_SYSCALL_RX,    ctx->metrics.xsk_rx_wakeup_cnt    );
     396             : 
     397           0 :   FD_MCNT_SET( NET, GRE_PKT_RX,            ctx->metrics.rx_gre_cnt            );
     398           0 :   FD_MCNT_SET( NET, GRE_PKT_RX_INVALID,    ctx->metrics.rx_gre_inv_pkt_cnt    );
     399           0 :   FD_MCNT_SET( NET, GRE_PKT_RX_IGNORED,    ctx->metrics.rx_gre_ignored_cnt    );
     400           0 :   FD_MCNT_SET( NET, GRE_PKT_TX_SUBMITTED,            ctx->metrics.tx_gre_cnt            );
     401           0 :   FD_MCNT_SET( NET, GRE_PKT_TX_NO_ROUTE, ctx->metrics.tx_gre_route_fail_cnt );
     402           0 :   FD_MCNT_SET( NET, PKT_RX_SRC_INVALID, ctx->metrics.rx_src_addr_invalid_cnt );
     403             :   /* fd_fib4_cnt includes the synthetic throw route at index zero. */
     404           0 :   FD_MGAUGE_SET( NET, ROUTE_COUNT_LOCAL, fd_ulong_sat_sub( fd_fib4_cnt( ctx->fib_local ), 1UL ) );
     405           0 :   FD_MGAUGE_SET( NET, ROUTE_COUNT_MAIN,  fd_ulong_sat_sub( fd_fib4_cnt( ctx->fib_main  ), 1UL ) );
     406           0 : }
     407             : 
     408             : struct xdp_statistics_v0 {
     409             :   __u64 rx_dropped; /* Dropped for other reasons */
     410             :   __u64 rx_invalid_descs; /* Dropped due to invalid descriptor */
     411             :   __u64 tx_invalid_descs; /* Dropped due to invalid descriptor */
     412             : };
     413             : 
     414             : struct xdp_statistics_v1 {
     415             :   __u64 rx_dropped; /* Dropped for other reasons */
     416             :   __u64 rx_invalid_descs; /* Dropped due to invalid descriptor */
     417             :   __u64 tx_invalid_descs; /* Dropped due to invalid descriptor */
     418             :   __u64 rx_ring_full; /* Dropped due to rx ring being full */
     419             :   __u64 rx_fill_ring_empty_descs; /* Failed to retrieve item from fill ring */
     420             :   __u64 tx_ring_empty_descs; /* Failed to retrieve item from tx ring */
     421             : };
     422             : 
     423             : static void
     424           0 : poll_xdp_statistics( fd_net_ctx_t * ctx ) {
     425           0 :   struct xdp_statistics_v1 stats = {0};
     426           0 :   ulong xsk_cnt = ctx->xsk_cnt;
     427           0 :   for( ulong j=0UL; j<xsk_cnt; j++ ) {
     428           0 :     struct xdp_statistics_v1 sub_stats = {0};
     429           0 :     uint optlen = (uint)sizeof(struct xdp_statistics_v1);
     430           0 :     if( FD_UNLIKELY( -1==getsockopt( ctx->xsk[ j ].xsk_fd, SOL_XDP, XDP_STATISTICS, &sub_stats, &optlen ) ) )
     431           0 :       FD_LOG_ERR(( "getsockopt(SOL_XDP, XDP_STATISTICS) failed: %s", strerror( errno ) ));
     432           0 :     if( FD_UNLIKELY( optlen!=sizeof(struct xdp_statistics_v0) &&
     433           0 :                      optlen!=sizeof(struct xdp_statistics_v1) ) ) {
     434           0 :       FD_LOG_ERR(( "getsockopt(SOL_XDP, XDP_STATISTICS) returned unexpected size %u", optlen ));
     435           0 :     }
     436           0 :     stats.rx_dropped               += sub_stats.rx_dropped;
     437           0 :     stats.rx_invalid_descs         += sub_stats.rx_invalid_descs;
     438           0 :     stats.tx_invalid_descs         += sub_stats.tx_invalid_descs;
     439           0 :     stats.rx_ring_full             += sub_stats.rx_ring_full;
     440           0 :     stats.rx_fill_ring_empty_descs += sub_stats.rx_fill_ring_empty_descs;
     441           0 :     stats.tx_ring_empty_descs      += sub_stats.tx_ring_empty_descs;
     442           0 :   }
     443             : 
     444           0 :   FD_MCNT_SET( NET, XDP_RX_OTHER_DROPPED,         stats.rx_dropped               );
     445           0 :   FD_MCNT_SET( NET, XDP_RX_INVALID_DESCRIPTOR,    stats.rx_invalid_descs         );
     446           0 :   FD_MCNT_SET( NET, XDP_TX_INVALID_DESCRIPTOR,    stats.tx_invalid_descs         );
     447           0 :   FD_MCNT_SET( NET, XDP_RX_RING_FULL,             stats.rx_ring_full             );
     448           0 :   FD_MCNT_SET( NET, XDP_RX_FILL_RING_EMPTY,       stats.rx_fill_ring_empty_descs );
     449           0 :   FD_MCNT_SET( NET, XDP_TX_RING_EMPTY,            stats.tx_ring_empty_descs      );
     450           0 : }
     451             : 
     452             : /* net_is_fatal_xdp_error returns 1 if the given errno returned by an
     453             :    XDP API indicates a non-recoverable error code.  The net tile should
     454             :    crash if it sees such an error so the problem does not go undetected.
     455             :    Otherwise, returns 0. */
     456             : 
     457             : static int
     458           0 : net_is_fatal_xdp_error( int err ) {
     459           0 :   return err==ESOCKTNOSUPPORT || err==EOPNOTSUPP || err==EINVAL ||
     460           0 :          err==EPERM;
     461           0 : }
     462             : 
     463             : /* net_gre_tunnel_ip fills ctx->gre_tunnel_ip.  The first gre_tunnel_cnt
     464             :    entries will be populated with the IP address of the GRE tunnel peer
     465             :    for the first gre_tunnel_cnt untagged GRE tunnels, and the rest of
     466             :    the entries will be set to 0, where gre_tunnel_cnt = min(MAX_GRE_CNT,
     467             :    the number of untagged GRE tunnels).  Returns gre_tunnel_cnt. */
     468             : 
     469             : static ulong
     470           6 : net_gre_tunnel_ip( fd_net_ctx_t * ctx ) {
     471           6 :   fd_netdev_t * dev_tbl = ctx->netdev_tbl.dev_tbl;
     472           6 :   ushort        dev_cnt = ctx->netdev_tbl.hdr->dev_cnt;
     473             : 
     474           6 :   ulong gre_tunnel_cnt = 0UL;
     475           6 :   memset( ctx->gre_tunnel_ip, '\0', MAX_GRE_CNT*sizeof(uint) );
     476          21 :   for( ushort if_idx = 0; (if_idx<dev_cnt) & (gre_tunnel_cnt<MAX_GRE_CNT); if_idx++ ) {
     477          15 :     fd_netdev_t const * dev = dev_tbl+if_idx;
     478          15 :     if( dev->dev_type==ARPHRD_IPGRE && dev->gre_dst_ip ) ctx->gre_tunnel_ip[ gre_tunnel_cnt++ ] = dev->gre_dst_ip;
     479          15 :   }
     480           6 :   return gre_tunnel_cnt;
     481           6 : }
     482             : 
     483             : 
     484             : /* net_tx_ready returns 1 if we can submit a job to this TX ring, and 0 otherwise.
     485             :    Reasons for block include:
     486             :    - No TX buffer is available (free ring empty)
     487             :    - TX ring is full
     488             : 
     489             :    tx_ring: pointer to the XDP TX ring
     490             :    free_ring: pointer to the free TX ring */
     491             : 
     492             : static int
     493             : net_tx_ready( fd_xdp_ring_t *      tx_ring,
     494          42 :               fd_net_free_ring_t * free_ring ) {
     495          42 :   if( FD_UNLIKELY( free_ring->prod == free_ring->cons ) ) return 0; /* drop - no free buffers */
     496          36 :   if( FD_UNLIKELY( fd_xdp_ring_full( tx_ring ) ) )        return 0; /* drop - tx ring full */
     497          33 :   return 1;
     498          36 : }
     499             : 
     500             : /* net_rx_wakeup triggers xsk_recvmsg to run in the kernel.  Needs to be
     501             :    called periodically in order to receive packets. */
     502             : 
     503             : static void
     504             : net_rx_wakeup( fd_net_ctx_t * ctx,
     505             :                fd_xsk_t *     xsk,
     506           0 :                int *          charge_busy ) {
     507             :   /* write-back local copies to fseqs */
     508           0 :   __atomic_store_n( xsk->ring_rx.cons, xsk->ring_rx.cached_cons, __ATOMIC_RELEASE );
     509           0 :   __atomic_store_n( xsk->ring_fr.prod, xsk->ring_fr.cached_prod, __ATOMIC_RELEASE );
     510           0 :   if( !fd_xsk_rx_need_wakeup( xsk ) ) return;
     511           0 :   *charge_busy = 1;
     512           0 :   struct msghdr _ignored[ 1 ] = { 0 };
     513           0 :   if( FD_UNLIKELY( -1==recvmsg( xsk->xsk_fd, _ignored, MSG_DONTWAIT ) ) ) {
     514           0 :     if( FD_UNLIKELY( net_is_fatal_xdp_error( errno ) ) ) {
     515           0 :       FD_LOG_ERR(( "xsk recvmsg failed xsk_fd=%d (%i-%s)", xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     516           0 :     }
     517           0 :     if( FD_UNLIKELY( errno!=EAGAIN ) ) {
     518           0 :       long ts = fd_log_wallclock();
     519           0 :       if( ts > xsk->log_suppress_until_ns ) {
     520           0 :         FD_LOG_WARNING(( "xsk recvmsg failed xsk_fd=%d (%i-%s)", xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     521           0 :         xsk->log_suppress_until_ns = ts + (long)1e9;
     522           0 :       }
     523           0 :     }
     524           0 :   }
     525           0 :   ctx->metrics.xsk_rx_wakeup_cnt++;
     526           0 : }
     527             : 
     528             : /* net_tx_wakeup triggers xsk_sendmsg to run in the kernel.  Needs to be
     529             :    called periodically in order to transmit packets. Should only be called
     530             :    if there are unconsumed packets in Tx ring. */
     531             : 
     532             : static void
     533             : net_tx_wakeup( fd_net_ctx_t * ctx,
     534             :                fd_xsk_t *     xsk,
     535          24 :                int *          charge_busy ) {
     536             :   /* write-back local copies to fseqs */
     537          24 :   __atomic_store_n( xsk->ring_tx.prod, xsk->ring_tx.cached_prod, __ATOMIC_RELEASE );
     538          24 :   __atomic_store_n( xsk->ring_cr.cons, xsk->ring_cr.cached_cons, __ATOMIC_RELEASE );
     539          24 :   if( !fd_xsk_tx_need_wakeup( xsk ) ) return;
     540           0 :   *charge_busy = 1;
     541           0 :   if( FD_UNLIKELY( -1==sendto( xsk->xsk_fd, NULL, 0, MSG_DONTWAIT, NULL, 0 ) ) ) {
     542           0 :     if( FD_UNLIKELY( net_is_fatal_xdp_error( errno ) ) ) {
     543           0 :       FD_LOG_ERR(( "xsk sendto failed xsk_fd=%d (%i-%s)", xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     544           0 :     }
     545           0 :     if( FD_UNLIKELY( errno!=EAGAIN ) ) {
     546           0 :       long ts = fd_log_wallclock();
     547           0 :       if( ts > xsk->log_suppress_until_ns ) {
     548           0 :         FD_LOG_WARNING(( "xsk sendto failed xsk_fd=%d (%i-%s)", xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
     549           0 :         xsk->log_suppress_until_ns = ts + (long)1e9;
     550           0 :       }
     551           0 :     }
     552           0 :   }
     553           0 :   ctx->metrics.xsk_tx_wakeup_cnt++;
     554           0 : }
     555             : 
     556             : /* net_tx_periodic_wakeup does a timer based xsk_sendmsg wakeup. */
     557             : 
     558             : static inline int
     559             : net_tx_periodic_wakeup( fd_net_ctx_t * ctx,
     560             :                         uint           xsk_idx,
     561             :                         long           now,
     562          27 :                         int *          charge_busy ) {
     563          27 :   fd_xdp_ring_t * tx_ring        = &ctx->xsk[ xsk_idx ].ring_tx;
     564          27 :   int             tx_ring_empty  = fd_xdp_ring_empty( tx_ring, FD_XDP_RING_ROLE_PROD );
     565          27 :   if( fd_net_flusher_check( ctx->tx_flusher+xsk_idx, now, tx_ring_empty ) ) {
     566          24 :     net_tx_wakeup( ctx, &ctx->xsk[ xsk_idx ], charge_busy );
     567          24 :     fd_net_flusher_wakeup( ctx->tx_flusher+xsk_idx, now );
     568          24 :   }
     569          27 :   return 0;
     570          27 : }
     571             : 
     572             : static void
     573           0 : during_housekeeping( fd_net_ctx_t * ctx ) {
     574           0 :   long now = fd_tickcount();
     575           0 :   if( FD_LIKELY( !fd_seqlock_locked_hint( &ctx->netdev_shared.hdr->seqlock ) ) ) {
     576           0 :     fd_netdev_tbl_copy( &ctx->netdev_tbl, &ctx->netdev_shared );
     577           0 :   }
     578           0 :   net_gre_tunnel_ip( ctx );
     579             : 
     580           0 :   ctx->metrics.rx_busy_cnt = 0UL;
     581           0 :   ctx->metrics.rx_idle_cnt = 0UL;
     582           0 :   ctx->metrics.tx_busy_cnt = 0UL;
     583           0 :   ctx->metrics.tx_idle_cnt = fd_seq_diff( ctx->free_tx.prod, ctx->free_tx.cons );
     584           0 :   for( uint j=0U; j<ctx->xsk_cnt; j++ ) {
     585           0 :     fd_xsk_t * xsk = &ctx->xsk[ j ];
     586           0 :     FD_COMPILER_MFENCE();
     587             :     /* Write back local copies to fseqs that we own */
     588           0 :     __atomic_store_n( xsk->ring_fr.prod, xsk->ring_fr.cached_prod, __ATOMIC_RELEASE );
     589           0 :     __atomic_store_n( xsk->ring_rx.cons, xsk->ring_rx.cached_cons, __ATOMIC_RELEASE );
     590           0 :     __atomic_store_n( xsk->ring_tx.prod, xsk->ring_tx.cached_prod, __ATOMIC_RELEASE );
     591           0 :     __atomic_store_n( xsk->ring_cr.cons, xsk->ring_cr.cached_cons, __ATOMIC_RELEASE );
     592             : 
     593             :     /* Refresh kernel-owned seq numbers for accurate stats */
     594           0 :     xsk->ring_fr.cached_cons = __atomic_load_n( xsk->ring_fr.cons, __ATOMIC_ACQUIRE );
     595           0 :     xsk->ring_rx.cached_prod = __atomic_load_n( xsk->ring_rx.prod, __ATOMIC_ACQUIRE );
     596           0 :     xsk->ring_tx.cached_cons = __atomic_load_n( xsk->ring_tx.cons, __ATOMIC_ACQUIRE );
     597           0 :     xsk->ring_cr.cached_prod = __atomic_load_n( xsk->ring_cr.prod, __ATOMIC_ACQUIRE );
     598             : 
     599           0 :     FD_COMPILER_MFENCE();
     600           0 :     ctx->metrics.rx_busy_cnt += (long)(int)( xsk->ring_rx.cached_prod - xsk->ring_rx.cached_cons );
     601           0 :     ctx->metrics.rx_idle_cnt += (long)(int)( xsk->ring_fr.cached_prod - xsk->ring_fr.cached_cons );
     602           0 :     ctx->metrics.tx_busy_cnt += (long)(int)( xsk->ring_tx.cached_prod - xsk->ring_tx.cached_cons );
     603           0 :     ctx->metrics.tx_busy_cnt += (long)(int)( xsk->ring_cr.cached_prod - xsk->ring_cr.cached_cons );
     604           0 :   }
     605             : 
     606           0 :   if( now > ctx->next_xdp_stats_refresh ) {
     607           0 :     ctx->next_xdp_stats_refresh = now + ctx->xdp_stats_interval_ticks;
     608           0 :     poll_xdp_statistics( ctx );
     609           0 :   }
     610           0 : }
     611             : 
     612             : 
     613             : /* net_tx_route resolves the xsk index, src ip address, src MAC address, and
     614             :    dst MAC address.  Returns 1 on success, 0 on failure.
     615             :    On success, tx_op->{xsk_idx,src_ip,mac_addrs} is set, and if the dst_ip
     616             :    belongs to a GRE interface, is_gre_inf will set to 1 and
     617             :    tx_op->{gre_outer_src_ip, gre_outer_dst_ip} will be loaded from the netdev
     618             :    table. is_gre_inf is set to 0 if dst_ip doesn't belong to a GRE interface. */
     619             : 
     620             : static int
     621             : net_tx_route( fd_net_ctx_t * ctx,
     622             :               uint           dst_ip,
     623          33 :               uint *         is_gre_inf ) {
     624             : 
     625             :   /* Route lookup */
     626             : 
     627          33 :   fd_fib4_hop_t hop[2] = {0};
     628          33 :   hop[0] = fd_fib4_lookup( ctx->fib_local, dst_ip, 0UL );
     629          33 :   hop[1] = fd_fib4_lookup( ctx->fib_main,  dst_ip, 0UL );
     630          33 :   fd_fib4_hop_t const * next_hop = fd_fib4_hop_or( hop+0, hop+1 );
     631             : 
     632          33 :   uint rtype   = next_hop->rtype;
     633          33 :   uint if_idx  = next_hop->if_idx;
     634          33 :   uint ip4_src = next_hop->ip4_src;
     635             : 
     636          33 :   if( FD_UNLIKELY( rtype==FD_FIB4_RTYPE_LOCAL ) ) {
     637           0 :     rtype  = FD_FIB4_RTYPE_UNICAST;
     638           0 :     if_idx = 1;
     639           0 :   }
     640             : 
     641          33 :   if( FD_UNLIKELY( rtype!=FD_FIB4_RTYPE_UNICAST ) ) {
     642           0 :     uint reason = fd_uint_if( rtype==FD_FIB4_RTYPE_THROW,
     643           0 :         FD_METRICS_ENUM_ROUTE_FAIL_V_NO_ROUTE_IDX,
     644           0 :         FD_METRICS_ENUM_ROUTE_FAIL_V_ROUTE_TYPE_IDX );
     645           0 :     ctx->metrics.tx_route_fail_cnt[ reason ]++;
     646           0 :     return 0;
     647           0 :   }
     648             : 
     649          33 :   fd_netdev_t * netdev = fd_netdev_tbl_query( &ctx->netdev_tbl, if_idx );
     650          33 :   if( !netdev ) {
     651           3 :     ctx->metrics.tx_route_fail_cnt[ FD_METRICS_ENUM_ROUTE_FAIL_V_INTERFACE_IDX ]++;
     652           3 :     return 0;
     653           3 :   }
     654             : 
     655          30 :   ip4_src = fd_uint_if( !!ctx->bind_address, ctx->bind_address, ip4_src );
     656          30 :   ctx->tx_op.src_ip  = ip4_src;
     657          30 :   ctx->tx_op.xsk_idx = UINT_MAX;
     658             : 
     659          30 :   FD_TEST( is_gre_inf );
     660          30 :   *is_gre_inf = 0;
     661          30 :   if( netdev->dev_type==ARPHRD_LOOPBACK ) {
     662             :     /* Set Ethernet src and dst address to 00:00:00:00:00:00 */
     663           0 :     memset( ctx->tx_op.mac_addrs, 0, 12UL );
     664           0 :     ctx->tx_op.xsk_idx = XSK_IDX_LO;
     665             :     /* Set preferred src address to 127.0.0.1 if no bind address is set */
     666           0 :     if( !ctx->tx_op.src_ip ) ctx->tx_op.src_ip = FD_IP4_ADDR( 127,0,0,1 );
     667           0 :     return 1;
     668          30 :   } else if( netdev->dev_type==ARPHRD_IPGRE ) {
     669             :     /* skip MAC addrs lookup for GRE inner dst ip */
     670          12 :     if( netdev->gre_src_ip ) ctx->tx_op.gre_outer_src_ip = netdev->gre_src_ip;
     671          12 :     ctx->tx_op.gre_outer_dst_ip = netdev->gre_dst_ip;
     672          12 :     *is_gre_inf = 1;
     673          12 :     return 1;
     674          12 :   }
     675             : 
     676          18 :   if( FD_UNLIKELY( netdev->dev_type!=ARPHRD_ETHER ) ) return 0; // drop
     677             : 
     678          18 :   if( FD_UNLIKELY( if_idx!=ctx->if_virt ) ) {
     679           0 :     ctx->metrics.tx_no_xdp_cnt++;
     680           0 :     return 0;
     681           0 :   }
     682          18 :   ctx->tx_op.xsk_idx = XSK_IDX_MAIN;
     683             : 
     684             :   /* Neighbor resolve */
     685          18 :   uint neigh_ip = next_hop->ip4_gw;
     686          18 :   if( !neigh_ip ) neigh_ip = dst_ip;
     687             : 
     688          18 :   fd_neigh4_entry_t neigh[1];
     689          18 :   int neigh_res = fd_neigh4_hmap_query_entry( ctx->neigh4, neigh_ip, neigh );
     690          18 :   if( FD_UNLIKELY( neigh_res!=FD_MAP_SUCCESS ) ) {
     691             :     /* Neighbor not found */
     692           0 :     fd_netlink_neigh4_solicit( ctx->neigh4_solicit, neigh_ip, if_idx, fd_frag_meta_ts_comp( fd_tickcount() ) );
     693           0 :     ctx->metrics.tx_neigh_fail_cnt++;
     694           0 :     return 0;
     695           0 :   }
     696          18 :   if( FD_UNLIKELY( neigh->state != FD_NEIGH4_STATE_ACTIVE ) ) {
     697           0 :     ctx->metrics.tx_neigh_fail_cnt++;
     698           0 :     return 0;
     699           0 :   }
     700          18 :   ip4_src = fd_uint_if( !ip4_src, ctx->default_address, ip4_src );
     701          18 :   ctx->tx_op.src_ip = ip4_src;
     702          18 :   memcpy( ctx->tx_op.mac_addrs+0, neigh->mac_addr, 6 );
     703          18 :   memcpy( ctx->tx_op.mac_addrs+6, netdev->mac_addr,  6 );
     704             : 
     705          18 :   return 1;
     706          18 : }
     707             : 
     708             : /* before_frag is called when a new metadata descriptor for a TX job is
     709             :    found.  This callback determines whether this net tile is responsible
     710             :    for the TX job.  If so, it prepares the TX op for the during_frag and
     711             :    after_frag callbacks. */
     712             : 
     713             : static inline int
     714             : before_frag( fd_net_ctx_t * ctx,
     715             :              ulong          in_idx,
     716             :              ulong          seq,
     717          18 :              ulong          sig ) {
     718          18 :   (void)seq;
     719             : 
     720          18 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_IPROUTE ) ) return 0;
     721             : 
     722             :   /* Find interface index of next packet */
     723          18 :   ulong proto = fd_disco_netmux_sig_proto( sig );
     724          18 :   if( FD_UNLIKELY( proto!=DST_PROTO_OUTGOING ) ) return 1;
     725             : 
     726             :   /* Load balance TX */
     727          18 :   uint net_tile_cnt = ctx->net_tile_cnt;
     728          18 :   uint hash         = (uint)fd_disco_netmux_sig_hash( sig );
     729          18 :   uint target_idx   = hash % net_tile_cnt;
     730          18 :   uint net_tile_id  = ctx->net_tile_id;
     731          18 :   uint dst_ip       = fd_disco_netmux_sig_ip( sig );
     732             : 
     733             :   /* Skip if another net tile is responsible for this packet.
     734             :      Fast path for net tiles other than net_tile 0. */
     735             : 
     736          18 :   if( net_tile_id!=0 && net_tile_id!=target_idx ) return 1; /* ignore */
     737             : 
     738             : 
     739          18 :   ctx->tx_op.use_gre          = 0;
     740          18 :   ctx->tx_op.gre_outer_dst_ip = 0;
     741          18 :   ctx->tx_op.gre_outer_src_ip = 0;
     742          18 :   uint is_gre_inf             = 0;
     743             : 
     744          18 :   if( FD_UNLIKELY( !net_tx_route( ctx, dst_ip, &is_gre_inf ) ) ) {
     745           0 :     return 1; /* metrics incremented by net_tx_route */
     746           0 :   }
     747             : 
     748          18 :   uint xsk_idx     = ctx->tx_op.xsk_idx;
     749             : 
     750          18 :   if( is_gre_inf ) {
     751          12 :     uint inner_src_ip = ctx->tx_op.src_ip;
     752          12 :     if( FD_UNLIKELY( !inner_src_ip ) ) {
     753           0 :       ctx->metrics.tx_gre_route_fail_cnt++;
     754           0 :       return 1;
     755           0 :     }
     756             :     /* Find the MAC addrs for the eth hdr, and src ip for outer ip4 hdr if not found in netdev tbl */
     757          12 :     ctx->tx_op.src_ip  = 0;
     758          12 :     is_gre_inf         = 0;
     759          12 :     if( FD_UNLIKELY( !net_tx_route( ctx, ctx->tx_op.gre_outer_dst_ip, &is_gre_inf ) ) ) {
     760           0 :       ctx->metrics.tx_gre_route_fail_cnt++;
     761           0 :       return 1;
     762           0 :     }
     763          12 :     if( is_gre_inf ) {
     764             :       /* Only one layer of tunnelling supported */
     765           0 :       ctx->metrics.tx_gre_route_fail_cnt++;
     766           0 :       return 1;
     767           0 :     }
     768          12 :     if( !ctx->tx_op.gre_outer_src_ip ) {
     769           6 :       ctx->tx_op.gre_outer_src_ip = ctx->tx_op.src_ip;
     770           6 :     }
     771          12 :     ctx->tx_op.use_gre = 1; /* indicate to during_frag to use GRE header */
     772          12 :     ctx->tx_op.src_ip  = inner_src_ip;
     773          12 :     xsk_idx = XSK_IDX_MAIN;
     774          12 :   }
     775             : 
     776          18 :   if( FD_UNLIKELY( xsk_idx>=ctx->xsk_cnt ) ) {
     777             :     /* Packet does not route to an XDP interface */
     778           0 :     ctx->metrics.tx_no_xdp_cnt++;
     779           0 :     return 1;
     780           0 :   }
     781             : 
     782          18 :   if( xsk_idx==XSK_IDX_LO ) target_idx = 0; /* loopback always targets tile 0 */
     783             : 
     784             :   /* Skip if another net tile is responsible for this packet */
     785             : 
     786          18 :   if( net_tile_id!=target_idx ) return 1; /* ignore */
     787             : 
     788             :   /* Skip if TX is blocked */
     789             : 
     790          18 :   fd_xsk_t *           xsk  = &ctx->xsk[ xsk_idx ];
     791          18 :   fd_net_free_ring_t * free = &ctx->free_tx;
     792          18 :   if( FD_UNLIKELY( !net_tx_ready( &xsk->ring_tx, free ) ) ) {
     793           0 :     ctx->metrics.tx_full_fail_cnt++;
     794           0 :     return 1;
     795           0 :   }
     796             : 
     797             :   /* Allocate buffer for receive */
     798          18 :   ulong                alloc_seq = free->cons;
     799          18 :   void *               frame     = (void *)free->queue[ alloc_seq % free->depth ];
     800          18 :   free->cons = fd_seq_inc( alloc_seq, 1UL );
     801             : 
     802          18 :   ctx->tx_op.frame = frame;
     803             : 
     804          18 :   return 0; /* continue */
     805          18 : }
     806             : 
     807             : /* during_frag is called when before_frag has committed to transmit an
     808             :    outgoing packet. */
     809             : 
     810             : static inline void
     811             : during_frag( fd_net_ctx_t * ctx,
     812             :              ulong          in_idx,
     813             :              ulong          seq FD_PARAM_UNUSED,
     814             :              ulong          sig FD_PARAM_UNUSED,
     815             :              ulong          chunk,
     816             :              ulong          sz,
     817          18 :              ulong          ctl FD_PARAM_UNUSED ) {
     818          18 :   if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>FD_NET_MTU ) )
     819           0 :     FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
     820             : 
     821          18 :   if( FD_UNLIKELY( ctx->in_kind[in_idx]==IN_KIND_IPROUTE ) ) {
     822           0 :     if( FD_UNLIKELY( sz!=sizeof(fd_iproute_msg_t) ) ) FD_LOG_ERR(( "invalid iproute message size %lu", sz ));
     823           0 :     fd_memcpy( &ctx->iproute_msg, fd_chunk_to_laddr_const( ctx->in[in_idx].mem, chunk ), sizeof(fd_iproute_msg_t) );
     824           0 :     return;
     825           0 :   }
     826             : 
     827          18 :   if( FD_UNLIKELY( sz<( sizeof(fd_eth_hdr_t)+sizeof(fd_ip4_hdr_t) ) ) )
     828           0 :     FD_LOG_ERR(( "packet too small %lu (in_idx=%lu)", sz, in_idx ));
     829             : 
     830          18 :   if( FD_UNLIKELY( sz>FD_ETH_PAYLOAD_MAX ) )
     831           0 :     FD_LOG_ERR(( "packet too big %lu (in_idx=%lu)", sz, in_idx ));
     832             : 
     833          18 :   void * frame = ctx->tx_op.frame;
     834          18 :   if( FD_UNLIKELY( (ulong)frame < (ulong)ctx->umem ) )
     835           0 :     FD_LOG_ERR(( "frame %p out of bounds (below %p)", frame, (void *)ctx->umem ));
     836          18 :   ulong umem_off = (ulong)frame - (ulong)ctx->umem;
     837          18 :   if( FD_UNLIKELY( (ulong)umem_off > (ulong)ctx->umem_sz ) )
     838           0 :     FD_LOG_ERR(( "frame %p out of bounds (beyond %p)", frame, (void *)ctx->umem_sz ));
     839             : 
     840             :   /* Speculatively copy frame into XDP buffer */
     841          18 :   uchar const * src = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     842             : 
     843          18 :   if( ctx->tx_op.use_gre ) {
     844             :     /* Discard the ethernet hdr from src. Copy the rest to where the inner ip4_hdr is.
     845             :        Safe from overflow: FD_ETH_PAYLOAD_MAX + header overhead < frame size (2048UL) */
     846          12 :     ulong overhead = sizeof(fd_eth_hdr_t) + sizeof(fd_ip4_hdr_t) + sizeof(fd_gre_hdr_t);
     847          12 :     fd_memcpy( (void *)( (ulong)ctx->tx_op.frame + overhead ), src + sizeof(fd_eth_hdr_t), sz - sizeof(fd_eth_hdr_t) );
     848          12 :   } else {
     849           6 :     fd_memcpy( ctx->tx_op.frame, src, sz );
     850           6 :   }
     851          18 : }
     852             : 
     853             : /* after_frag is called when the during_frag memcpy was _not_ overrun. */
     854             : 
     855             : static void
     856             : after_frag( fd_net_ctx_t *      ctx,
     857             :             ulong               in_idx,
     858             :             ulong               seq,
     859             :             ulong               sig,
     860             :             ulong               sz,
     861             :             ulong               tsorig,
     862             :             ulong               tspub,
     863          90 :             fd_stem_context_t * stem ) {
     864          90 :   (void)seq; (void)sig; (void)tsorig; (void)tspub; (void)stem;
     865             : 
     866          90 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_IPROUTE ) ) {
     867          72 :     fd_iproute_msg_t const * msg = &ctx->iproute_msg;
     868          72 :     if( msg->op==FD_IPROUTE_OP_FLUSH ) {
     869           6 :       fd_fib4_clear( ctx->fib_local );
     870           6 :       fd_fib4_clear( ctx->fib_main );
     871           6 :       return;
     872           6 :     }
     873          66 :     fd_fib4_t * fib;
     874          66 :     if( msg->table_id==RT_TABLE_LOCAL ) fib = ctx->fib_local;
     875          66 :     else if( msg->table_id==RT_TABLE_MAIN ) fib = ctx->fib_main;
     876           0 :     else return;
     877          66 :     if( msg->op==FD_IPROUTE_OP_UPSERT && FD_UNLIKELY( !fd_fib4_insert( fib, msg->dst_addr, msg->prefix, msg->prio, &msg->hop ) ) ) {
     878           6 :       FD_LOG_WARNING(( "route update dropped: route table full (increase [net.max_routes] or [net.max_peer_routes])" ));
     879           6 :       if( FD_UNLIKELY( ctx->net_tile_id==0U ) ) {
     880           3 :         fd_netlink_route4_sync( ctx->neigh4_solicit, fd_frag_meta_ts_comp( fd_tickcount() ) );
     881           3 :       }
     882           6 :     }
     883          60 :     else if( msg->op==FD_IPROUTE_OP_DELETE ) fd_fib4_remove( fib, msg->dst_addr, msg->prefix, msg->prio );
     884          66 :     return;
     885          66 :   }
     886             : 
     887             :   /* Current send operation */
     888             : 
     889          18 :   uchar *    frame   = ctx->tx_op.frame;
     890          18 :   uint       xsk_idx = ctx->tx_op.xsk_idx;
     891             : 
     892             :   /* Select Ethernet addresses */
     893          18 :   memcpy( frame, ctx->tx_op.mac_addrs, 12 );
     894             : 
     895          18 :   uchar * iphdr = frame + sizeof(fd_eth_hdr_t);
     896             : 
     897          18 :   if( ctx->tx_op.use_gre ) {
     898             : 
     899             :     /* For GRE packets, the ethertype will always be FD_ETH_HDR_TYPE_IP. outer source ip can't be 0 */
     900          12 :     if( FD_UNLIKELY( ctx->tx_op.gre_outer_src_ip==0 ) ) {
     901           0 :       ctx->metrics.tx_gre_route_fail_cnt++;
     902           0 :       return;
     903           0 :     }
     904             : 
     905             :     /* Write the last two bytes for eth_hdr */
     906          12 :     FD_STORE( ushort, frame+12, fd_ushort_bswap( FD_ETH_HDR_TYPE_IP ) );
     907             : 
     908          12 :     uchar * outer_iphdr       = frame + sizeof(fd_eth_hdr_t);
     909          12 :     uchar * gre_hdr           = outer_iphdr + sizeof(fd_ip4_hdr_t);
     910          12 :     uchar * inner_iphdr       = gre_hdr + sizeof(fd_gre_hdr_t);
     911             : 
     912          12 :     ulong inner_net_tot_len = fd_ushort_bswap( FD_LOAD( ushort, inner_iphdr+offsetof(fd_ip4_hdr_t, net_tot_len) ) );
     913          12 :     ulong outer_net_tot_len = sizeof(fd_ip4_hdr_t) + sizeof(fd_gre_hdr_t) + inner_net_tot_len;
     914          12 :     ulong tx_sz = sizeof(fd_eth_hdr_t) + outer_net_tot_len;
     915          12 :     if( FD_UNLIKELY( inner_net_tot_len!=sz-sizeof(fd_eth_hdr_t) || tx_sz>FD_NET_MTU ) ) {
     916           0 :       ctx->metrics.tx_invalid_cnt++;
     917           0 :       return;
     918           0 :     }
     919             : 
     920             :     /* Construct outer ip header */
     921          12 :     fd_ip4_hdr_t ip4_outer = (fd_ip4_hdr_t) {
     922          12 :       .verihl       = FD_IP4_VERIHL( 4,5 ),
     923          12 :       .tos          = 0,
     924          12 :       .net_tot_len  = fd_ushort_bswap( (ushort)outer_net_tot_len ),
     925          12 :       .net_id       = 0,
     926          12 :       .net_frag_off = fd_ushort_bswap( FD_IP4_HDR_FRAG_OFF_DF ),
     927          12 :       .ttl          = 64,
     928          12 :       .protocol     = FD_IP4_HDR_PROTOCOL_GRE,
     929          12 :       .check        = 0,
     930          12 :       .saddr        = ctx->tx_op.gre_outer_src_ip,
     931          12 :       .daddr        = ctx->tx_op.gre_outer_dst_ip,
     932          12 :     };
     933          12 :     ip4_outer.check = fd_ip4_hdr_check_fast( &ip4_outer );
     934          12 :     FD_STORE( fd_ip4_hdr_t, outer_iphdr, ip4_outer );
     935             : 
     936             :     /* Construct gre header */
     937          12 :     fd_gre_hdr_t gre_hdr_ = {
     938          12 :       .flags_version = FD_GRE_HDR_FLG_VER_BASIC,
     939          12 :       .protocol      = fd_ushort_bswap( FD_ETH_HDR_TYPE_IP )
     940          12 :     };
     941          12 :     FD_STORE( fd_gre_hdr_t, gre_hdr, gre_hdr_ );
     942             : 
     943          12 :     iphdr   = inner_iphdr;
     944          12 :     sz      = tx_sz;
     945          12 :     xsk_idx = 0;
     946          12 :   }
     947             : 
     948             :   /* Construct (inner) ip header */
     949          18 :   uint   ihl       = FD_IP4_GET_LEN( *(fd_ip4_hdr_t *)iphdr );
     950          18 :   uint   ver       = FD_IP4_GET_VERSION( *(fd_ip4_hdr_t *)iphdr );
     951          18 :   uint   ip4_saddr = FD_LOAD( uint, iphdr+12 );
     952          18 :   ushort ethertype = FD_LOAD( ushort, frame+12 );
     953             : 
     954          18 :   if( FD_UNLIKELY( ethertype!=fd_ushort_bswap( FD_ETH_HDR_TYPE_IP ) ) ) {
     955           0 :     FD_LOG_CRIT(( "in link %lu attempted to send packet with invalid ethertype %04x",
     956           0 :                   in_idx, fd_ushort_bswap( ethertype ) ));
     957           0 :   }
     958             : 
     959          18 :   if( FD_UNLIKELY( ver!=0x4 ) ) {
     960           0 :     FD_LOG_WARNING(( "in_idx %lu: invalid IP version (%u)", in_idx, ver ));
     961           0 :     ctx->metrics.tx_invalid_cnt++;
     962           0 :     return;
     963           0 :   }
     964             : 
     965          18 :   if( FD_UNLIKELY( ihl<sizeof(fd_ip4_hdr_t) ||
     966          18 :                    (sizeof(fd_eth_hdr_t)+ihl)>sz ) ) {
     967           0 :     FD_LOG_WARNING(( "in_idx %lu: invalid IHL (%u)", in_idx, ihl ));
     968           0 :     ctx->metrics.tx_invalid_cnt++;
     969           0 :     return;
     970           0 :   }
     971             : 
     972          18 :   if( ip4_saddr==0 ) {
     973          18 :     if( FD_UNLIKELY( ctx->tx_op.src_ip==0 ) ) {
     974             :       /* Outgoing IPv4 packet with unknown src IP or invalid IHL */
     975           0 :       ctx->metrics.tx_route_fail_cnt[ FD_METRICS_ENUM_ROUTE_FAIL_V_SOURCE_IP_IDX ]++;
     976           0 :       return;
     977           0 :     }
     978             :     /* Recompute checksum after changing header */
     979          18 :     FD_STORE( uint,   iphdr+12, ctx->tx_op.src_ip );
     980          18 :     FD_STORE( ushort, iphdr+10, 0 );
     981          18 :     FD_STORE( ushort, iphdr+10, fd_ip4_hdr_check( iphdr ) );
     982          18 :   }
     983             : 
     984             :   /* Submit packet TX job
     985             : 
     986             :      Invariant for ring_tx: prod-cons<length
     987             :      (This invariant breaks if any other packet is sent over this ring
     988             :      between before_frag and this point, e.g. send_arp_probe.) */
     989             : 
     990          18 :   fd_xsk_t      * xsk     = &ctx->xsk[ xsk_idx ];
     991          18 :   fd_xdp_ring_t * tx_ring = &xsk->ring_tx;
     992          18 :   uint            tx_seq  = tx_ring->cached_prod;
     993          18 :   uint            tx_mask = tx_ring->depth - 1U;
     994          18 :   xsk->ring_tx.packet_ring[ tx_seq&tx_mask ] = (struct xdp_desc) {
     995          18 :     .addr    = (ulong)frame - (ulong)ctx->umem,
     996          18 :     .len     = (uint)sz,
     997          18 :     .options = 0
     998          18 :   };
     999             : 
    1000             :   /* Frame is now owned by kernel. Clear tx_op. */
    1001          18 :   ctx->tx_op.frame = NULL;
    1002             : 
    1003             :   /* Register newly enqueued packet */
    1004          18 :   tx_ring->cached_prod = tx_seq+1U;
    1005          18 :   ctx->metrics.tx_submit_cnt++;
    1006          18 :   ctx->metrics.tx_bytes_total += sz;
    1007          18 :   if( ctx->tx_op.use_gre ) ctx->metrics.tx_gre_cnt++;
    1008          18 :   fd_net_flusher_inc( ctx->tx_flusher+xsk_idx, fd_tickcount() );
    1009          18 : }
    1010             : 
    1011             : /* net_rx_packet is called when a new Ethernet frame is available.
    1012             :    Attempts to copy out the frame to a downstream tile. */
    1013             : 
    1014             : static void
    1015             : net_rx_packet( fd_net_ctx_t * ctx,
    1016             :                ulong          umem_off,
    1017             :                ulong          sz,
    1018          27 :                uint *         freed_chunk ) {
    1019             : 
    1020          27 :   if( FD_UNLIKELY( sz<sizeof(fd_eth_hdr_t)+sizeof(fd_ip4_hdr_t)+sizeof(fd_udp_hdr_t) ) ) {
    1021           0 :     FD_DTRACE_PROBE( net_tile_err_rx_undersz );
    1022           0 :     ctx->metrics.rx_undersz_cnt++;
    1023           0 :     return;
    1024           0 :   }
    1025             : 
    1026          27 :   uchar        * packet     = (uchar *)ctx->umem + umem_off;
    1027          27 :   uchar const  * packet_end = packet + sz;
    1028          27 :   fd_ip4_hdr_t * iphdr      = (fd_ip4_hdr_t *)(packet + sizeof(fd_eth_hdr_t));
    1029             : 
    1030          27 :   if( FD_UNLIKELY( ((fd_eth_hdr_t *)packet)->net_type!=fd_ushort_bswap( FD_ETH_HDR_TYPE_IP ) ) ) return;
    1031             : 
    1032          27 :   int is_packet_gre = 0;
    1033             :   /* Discard the GRE overhead (outer iphdr and gre hdr) */
    1034          27 :   if( iphdr->protocol == FD_IP4_HDR_PROTOCOL_GRE ) {
    1035          15 :     if( FD_UNLIKELY( !ctx->gre_tunnel_ip[0] ) ) { /* if the first entry is 0, they all are */
    1036           0 :       ctx->metrics.rx_gre_ignored_cnt++;
    1037           0 :       return;
    1038           0 :     }
    1039          15 :     ulong gre_ipver = FD_IP4_GET_VERSION( *iphdr );
    1040          15 :     ulong gre_iplen = FD_IP4_GET_LEN( *iphdr );
    1041          15 :     if( FD_UNLIKELY( gre_ipver!=0x4 || gre_iplen<20 ) ) {
    1042           0 :       FD_DTRACE_PROBE( net_tile_err_rx_noip );
    1043           0 :       ctx->metrics.rx_gre_inv_pkt_cnt++; /* drop IPv6 packets */
    1044           0 :       return;
    1045           0 :     }
    1046             : 
    1047          15 :     int found = 0;
    1048          75 :     for( ulong i=0UL; i<MAX_GRE_CNT; i++ ) found |= (iphdr->saddr==ctx->gre_tunnel_ip[i]);
    1049          15 :     if( FD_UNLIKELY( (!found) | (iphdr->saddr==0U) ) ) {
    1050           3 :       ctx->metrics.rx_src_addr_invalid_cnt++;
    1051           3 :       return;
    1052           3 :     }
    1053             : 
    1054          12 :     ulong overhead = gre_iplen + sizeof(fd_gre_hdr_t);
    1055          12 :     if( FD_UNLIKELY( (uchar *)iphdr+overhead+sizeof(fd_ip4_hdr_t)>packet_end ) ) {
    1056           0 :       FD_DTRACE_PROBE( net_tile_err_rx_undersz );
    1057           0 :       ctx->metrics.rx_undersz_cnt++;  /* inner ip4 header invalid */
    1058           0 :       return;
    1059           0 :     }
    1060             : 
    1061             :     /* The new iphdr is where the inner iphdr was. Copy over the eth_hdr */
    1062          12 :     iphdr              = (fd_ip4_hdr_t *)((uchar *)iphdr + overhead);
    1063          12 :     uchar * new_packet = (uchar *)iphdr - sizeof(fd_eth_hdr_t);
    1064          12 :     fd_memcpy( new_packet, packet, sizeof(fd_eth_hdr_t) );
    1065          12 :     sz                 -= overhead;
    1066          12 :     packet             = new_packet;
    1067          12 :     umem_off           = (ulong)( packet - (uchar *)ctx->umem );
    1068          12 :     is_packet_gre      = 1;
    1069          12 :   }
    1070             : 
    1071             :   /* Translate packet to UMEM frame index */
    1072          24 :   ulong chunk       = ctx->umem_chunk0 + (umem_off>>FD_CHUNK_LG_SZ);
    1073          24 :   ulong ctl         = umem_off & 0x3fUL;
    1074             : 
    1075             :   /* Filter for UDP/IPv4 packets. */
    1076          24 :   ulong ipver = FD_IP4_GET_VERSION( *iphdr );
    1077          24 :   ulong iplen = FD_IP4_GET_LEN    ( *iphdr );
    1078          24 :   if( FD_UNLIKELY( ipver!=0x4 || iplen<20 ||
    1079          24 :                    iphdr->protocol!=FD_IP4_HDR_PROTOCOL_UDP ) ) {
    1080           0 :     FD_DTRACE_PROBE( net_tile_err_rx_noip );
    1081           0 :     ctx->metrics.rx_undersz_cnt++; /* drop IPv6 packets */
    1082           0 :     return;
    1083           0 :   }
    1084             : 
    1085          24 :   uchar const * udp = (uchar *)iphdr + iplen;
    1086          24 :   if( FD_UNLIKELY( udp+sizeof(fd_udp_hdr_t) > packet_end ) ) {
    1087           0 :     FD_DTRACE_PROBE( net_tile_err_rx_undersz );
    1088           0 :     ctx->metrics.rx_undersz_cnt++;
    1089           0 :     return;
    1090           0 :   }
    1091             : 
    1092          24 :   fd_udp_hdr_t const * udp_hdr = (fd_udp_hdr_t const *)udp;
    1093          24 :   ulong        const   udp_sz  = fd_ushort_bswap( udp_hdr->net_len );
    1094          24 :   if( FD_UNLIKELY( (udp_sz<sizeof(fd_udp_hdr_t)) | (udp+udp_sz>packet_end) ) ) {
    1095           6 :     FD_DTRACE_PROBE( net_tile_err_rx_undersz );
    1096           6 :     ctx->metrics.rx_undersz_cnt++;
    1097           6 :     return;
    1098           6 :   }
    1099             : 
    1100             :   /* Extract IP dest addr and UDP src/dest port */
    1101          18 :   uint   ip_srcaddr   =  iphdr->saddr;
    1102          18 :   ushort udp_srcport  =  fd_ushort_bswap( udp_hdr->net_sport );
    1103          18 :   ushort udp_dstport  =  fd_ushort_bswap( udp_hdr->net_dport );
    1104             : 
    1105          18 :   if( FD_UNLIKELY( fd_ip4_addr_is_mcast( ip_srcaddr ) ) ) {
    1106           0 :     ctx->metrics.rx_src_addr_invalid_cnt++;
    1107           0 :     return;
    1108           0 :   }
    1109             : 
    1110          18 :   FD_DTRACE_PROBE_4( net_tile_pkt_rx, ip_srcaddr, udp_srcport, udp_dstport, sz );
    1111             : 
    1112             :   /* Route packet to downstream tile */
    1113          18 :   ushort proto;
    1114          18 :   fd_net_out_ctx_t * out;
    1115          18 :   if(      FD_UNLIKELY( udp_dstport==ctx->shred_listen_port ) ) {
    1116          18 :     proto = DST_PROTO_SHRED;
    1117          18 :     out = ctx->shred_out;
    1118          18 :   } else if( FD_UNLIKELY( udp_dstport==ctx->quic_transaction_listen_port ) ) {
    1119           0 :     proto = DST_PROTO_TPU_QUIC;
    1120           0 :     out = ctx->quic_out;
    1121           0 :   } else if( FD_UNLIKELY( udp_dstport==ctx->legacy_transaction_listen_port ) ) {
    1122           0 :     proto = DST_PROTO_TPU_UDP;
    1123           0 :     out = ctx->quic_out;
    1124           0 :   } else if( FD_UNLIKELY( udp_dstport==ctx->gossip_listen_port ) ) {
    1125           0 :     proto = DST_PROTO_GOSSIP;
    1126           0 :     out = ctx->gossvf_out;
    1127           0 :   } else if( FD_UNLIKELY( udp_dstport==ctx->repair_client_listen_port ) ) {
    1128           0 :     proto = DST_PROTO_REPAIR;
    1129           0 :     if( FD_UNLIKELY( udp_sz-sizeof(fd_udp_hdr_t) <= AG_REPAIR_RESPONSE_MAX_SZ ) ) out = ctx->repair_out; /* ping-pongs, blockid repair responses */
    1130           0 :     else                                                                          out = ctx->shred_out;
    1131           0 :   } else if( FD_UNLIKELY( udp_dstport==ctx->repair_serve_listen_port ) ) {
    1132           0 :     if( FD_UNLIKELY( !ctx->rserve_enabled ) ) return;
    1133           0 :     proto = DST_PROTO_RSERVE;
    1134           0 :     out = ctx->rserve_out;
    1135           0 :   } else if( FD_UNLIKELY( udp_dstport==ctx->txsend_src_port ) ) {
    1136           0 :     proto = DST_PROTO_SEND;
    1137           0 :     out = ctx->txsend_out;
    1138           0 :   } else if( FD_UNLIKELY( udp_dstport==ctx->votor_quic_client_listen_port || udp_dstport==ctx->votor_quic_server_listen_port ) ) {
    1139             :     /* the client src port carries the replies to the votor tile's own outbound QUIC connections */
    1140           0 :     if( FD_UNLIKELY( !ctx->votor_enabled ) ) return;
    1141           0 :     proto = DST_PROTO_VOTOR;
    1142           0 :     out = ctx->votor_out;
    1143           0 :   } else {
    1144           0 :     FD_LOG_ERR(( "Firedancer received a UDP packet on port %hu which was not expected. "
    1145           0 :                   "Only the following ports should be configured to forward packets: "
    1146           0 :                   "%hu, %hu, %hu, %hu, %hu, %hu, %hu, %hu (excluding any 0 ports, which can be ignored)."
    1147           0 :                   "Please report this error to Firedancer maintainers.",
    1148           0 :                   udp_dstport,
    1149           0 :                   ctx->shred_listen_port,
    1150           0 :                   ctx->quic_transaction_listen_port,
    1151           0 :                   ctx->legacy_transaction_listen_port,
    1152           0 :                   ctx->gossip_listen_port,
    1153           0 :                   ctx->repair_client_listen_port,
    1154           0 :                   ctx->repair_serve_listen_port,
    1155           0 :                   ctx->votor_quic_client_listen_port,
    1156           0 :                   ctx->votor_quic_server_listen_port ));
    1157           0 :   }
    1158             : 
    1159             :   /* tile can decide how to partition based on src ip addr and src port */
    1160          18 :   ulong sig              = fd_disco_netmux_sig( ip_srcaddr, udp_srcport, ip_srcaddr, proto, 14UL+8UL+iplen );
    1161             : 
    1162             :   /* Peek the mline for an old frame */
    1163          18 :   fd_frag_meta_t * mline = out->mcache + fd_mcache_line_idx( out->seq, out->depth );
    1164          18 :   *freed_chunk           = mline->chunk;
    1165             : 
    1166             :   /* Overwrite the mline with the new frame */
    1167          18 :   ulong tspub            = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
    1168          18 : # if FD_HAS_AVX
    1169          18 :   fd_mcache_publish_avx( out->mcache, out->depth, out->seq, sig, chunk, sz, ctl, 0, tspub );
    1170             : # elif FD_HAS_ARM
    1171             :   fd_mcache_publish_arm( out->mcache, out->depth, out->seq, sig, chunk, sz, ctl, 0, tspub );
    1172             : # else
    1173             :   fd_mcache_publish( out->mcache, out->depth, out->seq, sig, chunk, sz, ctl, 0, tspub );
    1174             : # endif
    1175             : 
    1176             :   /* Wind up for the next iteration */
    1177          18 :   out->seq               = fd_seq_inc( out->seq, 1UL );
    1178             : 
    1179          18 :   if( is_packet_gre ) ctx->metrics.rx_gre_cnt++;
    1180          18 :   ctx->metrics.rx_pkt_cnt++;
    1181          18 :   ctx->metrics.rx_bytes_total += sz;
    1182          18 : }
    1183             : 
    1184             : /* net_comp_event is called when an XDP TX frame is free again. */
    1185             : 
    1186             : static void
    1187             : net_comp_event( fd_net_ctx_t * ctx,
    1188             :                 fd_xsk_t *     xsk,
    1189           0 :                 uint           comp_seq ) {
    1190             : 
    1191             :   /* Locate the incoming frame */
    1192             : 
    1193           0 :   fd_xdp_ring_t * comp_ring  = &xsk->ring_cr;
    1194           0 :   uint            comp_mask  = comp_ring->depth - 1U;
    1195           0 :   ulong           frame      = FD_VOLATILE_CONST( comp_ring->frame_ring[ comp_seq&comp_mask ] );
    1196           0 :   ulong const     frame_mask = FD_NET_MTU - 1UL;
    1197           0 :   FD_STATIC_ASSERT( FD_ULONG_IS_POW2( FD_NET_MTU ), "FD_NET_MTU must be a power of two" );
    1198           0 :   if( FD_UNLIKELY( frame+FD_NET_MTU > ctx->umem_sz ) ) {
    1199           0 :     FD_LOG_ERR(( "Bounds check failed: frame=0x%lx umem_sz=0x%lx",
    1200           0 :                  frame, (ulong)ctx->umem_sz ));
    1201           0 :   }
    1202             : 
    1203             :   /* Check if we have space to return the freed frame */
    1204             : 
    1205           0 :   fd_net_free_ring_t * free      = &ctx->free_tx;
    1206           0 :   ulong                free_prod = free->prod;
    1207           0 :   ulong                free_mask = free->depth - 1UL;
    1208           0 :   ulong                free_cons = free->cons;
    1209           0 :   long                 free_cnt = fd_seq_diff( free_prod, free_cons );
    1210           0 :   FD_TEST( free_prod >= free_cons );
    1211           0 :   if( FD_UNLIKELY( free_cnt>=(long)free->depth ) ) return; /* blocked */
    1212             : 
    1213           0 :   free->queue[ free_prod&free_mask ] = (ulong)ctx->umem + (frame & (~frame_mask));
    1214           0 :   free->prod = fd_seq_inc( free_prod, 1UL );
    1215             : 
    1216             :   /* Wind up for next iteration */
    1217             : 
    1218           0 :   comp_ring->cached_cons = comp_seq+1U;
    1219           0 :   ctx->metrics.tx_complete_cnt++;
    1220           0 : }
    1221             : 
    1222             : /* net_rx_event is called when a new XDP RX frame is available.  Calls
    1223             :    net_rx_packet, then returns the packet back to the kernel via the fill
    1224             :    ring.  */
    1225             : 
    1226             : static void
    1227             : net_rx_event( fd_net_ctx_t * ctx,
    1228             :               fd_xsk_t *     xsk,
    1229          27 :               uint           rx_seq ) {
    1230             :   /* Locate the incoming frame */
    1231             : 
    1232          27 :   fd_xdp_ring_t * rx_ring = &xsk->ring_rx;
    1233          27 :   uint            rx_mask = rx_ring->depth - 1U;
    1234          27 :   struct xdp_desc frame   = FD_VOLATILE_CONST( rx_ring->packet_ring[ rx_seq&rx_mask ] );
    1235             : 
    1236          27 :   if( FD_UNLIKELY( frame.len>FD_NET_MTU ) )
    1237           0 :     FD_LOG_ERR(( "received a UDP packet with a too large payload (%u)", frame.len ));
    1238             : 
    1239             :   /* Check if we have space in the fill ring to free the frame */
    1240             : 
    1241          27 :   fd_xdp_ring_t * fill_ring  = &xsk->ring_fr;
    1242          27 :   if( FD_UNLIKELY( fd_xdp_ring_full( fill_ring ) ) ) {
    1243           0 :     ctx->metrics.rx_fill_blocked_cnt++;
    1244           0 :     return; /* blocked */
    1245           0 :   }
    1246             : 
    1247             :   /* Pass it to the receive handler */
    1248             : 
    1249          27 :   uint freed_chunk = (uint)( ctx->umem_chunk0 + (frame.addr>>FD_CHUNK_LG_SZ) );
    1250          27 :   net_rx_packet( ctx, frame.addr, frame.len, &freed_chunk );
    1251          27 :   FD_COMPILER_MFENCE();
    1252          27 :   rx_ring->cached_cons = rx_seq+1U;
    1253             : 
    1254             :   /* Every RX operation returns one frame to the FILL ring.  If the
    1255             :      packet was forwarded to a downstream ring, the newly shadowed frame
    1256             :      is returned.  Otherwise, the frame just received is returned. */
    1257             : 
    1258          27 :   FD_STATIC_ASSERT( FD_ULONG_IS_POW2( FD_NET_MTU ), "FD_NET_MTU must be a power of two" );
    1259          27 :   ulong frame_mask = FD_NET_MTU - 1UL;
    1260          27 :   ulong freed_off  = ( (ulong)( freed_chunk - ctx->umem_chunk0 )<<FD_CHUNK_LG_SZ ) & (~frame_mask);
    1261          27 :   if( FD_UNLIKELY( freed_off+FD_NET_MTU > ctx->umem_sz ) ) {
    1262           0 :     FD_LOG_CRIT(( "mcache corruption detected: chunk=%u chunk0=%u frame=0x%lx umem_sz=0x%lx",
    1263           0 :                   freed_chunk, ctx->umem_chunk0, freed_off, ctx->umem_sz ));
    1264           0 :   }
    1265             : 
    1266          27 :   uint fill_prod = fill_ring->cached_prod;
    1267          27 :   uint fill_mask = (fill_ring->depth)-1U;
    1268          27 :   fill_ring->frame_ring[ fill_prod&fill_mask ] = freed_off;
    1269          27 :   fill_ring->cached_prod = fill_prod+1U;
    1270          27 : }
    1271             : 
    1272             : static void
    1273             : before_credit_softirq( fd_net_ctx_t *      ctx,
    1274             :                        int *               charge_busy,
    1275             :                        uint                rr_idx,
    1276          27 :                        fd_xsk_t *          rr_xsk ) {
    1277             : 
    1278          27 :   net_tx_periodic_wakeup( ctx, rr_idx, fd_tickcount(), charge_busy );
    1279             : 
    1280             :   /* Fire RX event if we have RX desc avail */
    1281          27 :   if( !fd_xdp_ring_empty( &rr_xsk->ring_rx, FD_XDP_RING_ROLE_CONS ) ) {
    1282          27 :     *charge_busy = 1;
    1283          27 :     net_rx_event( ctx, rr_xsk, rr_xsk->ring_rx.cached_cons );
    1284          27 :   } else {
    1285           0 :     net_rx_wakeup( ctx, rr_xsk, charge_busy );
    1286             : 
    1287             :     /* Iterate onto the next NAPI queue. */
    1288           0 :     ctx->rr_idx++;
    1289           0 :     ctx->rr_idx = fd_uint_if( ctx->rr_idx>=ctx->xsk_cnt, 0, ctx->rr_idx );
    1290           0 :   }
    1291          27 : }
    1292             : 
    1293             : static int
    1294             : net_prefbusy_poll_ready( fd_xsk_t *         rr_xsk,
    1295             :                          fd_net_flusher_t * flusher,
    1296           0 :                          long               now ) {
    1297             : 
    1298           0 :   if( FD_LIKELY( now < ( flusher->prefbusy_last_poll_ticks + flusher->prefbusy_min_interval_ticks ) ) ) return 0;
    1299           0 :   if( FD_UNLIKELY( now > ( flusher->prefbusy_last_poll_ticks + flusher->prefbusy_stall_timeout_ticks ) ) ) return 1;
    1300             : 
    1301           0 :   int rx_empty = fd_xdp_ring_empty( &rr_xsk->ring_rx, FD_XDP_RING_ROLE_CONS );
    1302             : 
    1303           0 :   return rx_empty;
    1304           0 : }
    1305             : 
    1306             : static void
    1307             : net_prefbusy_poll_flush( fd_net_flusher_t * flusher,
    1308           0 :                          long               now ) {
    1309           0 :   flusher->prefbusy_last_poll_ticks = now;
    1310           0 : }
    1311             : 
    1312             : static void
    1313             : before_credit_prefbusy( fd_net_ctx_t *      ctx,
    1314             :                         int *               charge_busy,
    1315             :                         uint                rr_idx,
    1316           0 :                         fd_xsk_t *          rr_xsk ) {
    1317             : 
    1318           0 :   fd_net_flusher_t * flusher = ctx->tx_flusher+rr_idx;
    1319           0 :   if( FD_UNLIKELY( net_prefbusy_poll_ready( rr_xsk, flusher, fd_tickcount() ) ) ) {
    1320             :     /* NAPI needs to be polled to process new TX from
    1321             :        Firedancer's net tile and process new RX from the NIC. */
    1322             : 
    1323             :     /* write-back local copies to fseqs */
    1324           0 :     __atomic_store_n( rr_xsk->ring_tx.prod, rr_xsk->ring_tx.cached_prod, __ATOMIC_RELEASE );
    1325           0 :     __atomic_store_n( rr_xsk->ring_cr.cons, rr_xsk->ring_cr.cached_cons, __ATOMIC_RELEASE );
    1326           0 :     __atomic_store_n( rr_xsk->ring_rx.cons, rr_xsk->ring_rx.cached_cons, __ATOMIC_RELEASE );
    1327           0 :     __atomic_store_n( rr_xsk->ring_fr.prod, rr_xsk->ring_fr.cached_prod, __ATOMIC_RELEASE );
    1328             : 
    1329           0 :     if( FD_UNLIKELY( -1==sendto( rr_xsk->xsk_fd, NULL, 0, MSG_DONTWAIT, NULL, 0 ) ) ) {
    1330           0 :       if( FD_UNLIKELY( net_is_fatal_xdp_error( errno ) ) ) {
    1331           0 :         FD_LOG_ERR(( "xsk sendto failed xsk_fd=%d (%i-%s)", rr_xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
    1332           0 :       }
    1333           0 :       if( FD_UNLIKELY( errno!=EAGAIN ) ) {
    1334           0 :         long ts = fd_log_wallclock();
    1335           0 :         if( ts > rr_xsk->log_suppress_until_ns ) {
    1336           0 :           FD_LOG_WARNING(( "xsk sendto failed xsk_fd=%d (%i-%s)", rr_xsk->xsk_fd, errno, fd_io_strerror( errno ) ));
    1337           0 :           rr_xsk->log_suppress_until_ns = ts + (long)1e9;
    1338           0 :         }
    1339           0 :       }
    1340           0 :     }
    1341             :     /* Since xsk sendmsg in prefbusy mode drives both rx and tx, both are incremented */
    1342           0 :     ctx->metrics.xsk_tx_wakeup_cnt++;
    1343           0 :     ctx->metrics.xsk_rx_wakeup_cnt++;
    1344             : 
    1345           0 :     net_prefbusy_poll_flush( flusher, fd_tickcount() );
    1346           0 :   }
    1347             : 
    1348             :   /* Process new RX from xsk ring if there is any. */
    1349           0 :   if( !fd_xdp_ring_empty( &rr_xsk->ring_rx, FD_XDP_RING_ROLE_CONS ) ) {
    1350           0 :     *charge_busy = 1;
    1351           0 :     net_rx_event( ctx, rr_xsk, rr_xsk->ring_rx.cached_cons );
    1352           0 :   }
    1353             :   /* Iterate onto the next NAPI queue. */
    1354           0 :   ctx->rr_idx++;
    1355           0 :   ctx->rr_idx = fd_uint_if( ctx->rr_idx>=ctx->xsk_cnt, 0, ctx->rr_idx );
    1356           0 : }
    1357             : 
    1358             : /* before_credit is called every loop iteration. */
    1359             : 
    1360             : static void
    1361             : before_credit( fd_net_ctx_t *      ctx,
    1362             :                fd_stem_context_t * stem,
    1363          27 :                int *               charge_busy ) {
    1364          27 :   (void)stem;
    1365             :   /* A previous send attempt was overrun.  A corrupt copy of the packet was
    1366             :      placed into an XDP frame, but the frame was not yet submitted to the
    1367             :      TX ring.  Return the tx buffer to the free list. */
    1368             : 
    1369          27 :   if( ctx->tx_op.frame ) {
    1370           0 :     *charge_busy = 1;
    1371           0 :     fd_net_free_ring_t * free      = &ctx->free_tx;
    1372           0 :     ulong                alloc_seq = free->prod;
    1373           0 :     free->queue[ alloc_seq % free->depth ] = (ulong)ctx->tx_op.frame;
    1374           0 :     free->prod = fd_seq_inc( alloc_seq, 1UL );
    1375           0 :     ctx->tx_op.frame = NULL;
    1376           0 :   }
    1377             : 
    1378             :   /* Check if new packets are available or if TX frames are free again
    1379             :      (Round-robin through sockets) */
    1380             : 
    1381          27 :   uint       rr_idx = ctx->rr_idx;
    1382          27 :   fd_xsk_t * rr_xsk = &ctx->xsk[ rr_idx ];
    1383             : 
    1384          27 :   if( FD_LIKELY( !rr_xsk->prefbusy_poll_enabled ) ) {
    1385             :     /* Default poll mode which relies on irqs and wakeups */
    1386          27 :     before_credit_softirq( ctx, charge_busy, rr_idx, rr_xsk );
    1387          27 :   } else {
    1388           0 :     before_credit_prefbusy( ctx, charge_busy, rr_idx, rr_xsk );
    1389           0 :   }
    1390             : 
    1391             :   /* Fire comp event if we have comp desc avail */
    1392          27 :   if( !fd_xdp_ring_empty( &rr_xsk->ring_cr, FD_XDP_RING_ROLE_CONS ) ) {
    1393           0 :     *charge_busy = 1;
    1394           0 :     net_comp_event( ctx, rr_xsk, rr_xsk->ring_cr.cached_cons );
    1395           0 :   }
    1396          27 : }
    1397             : 
    1398             : /* net_xsk_bootstrap assigns UMEM frames to the FILL ring. */
    1399             : 
    1400             : static ulong
    1401             : net_xsk_bootstrap( fd_net_ctx_t * ctx,
    1402             :                    uint           xsk_idx,
    1403           0 :                    ulong          frame_off ) {
    1404           0 :   fd_xsk_t * xsk = &ctx->xsk[ xsk_idx ];
    1405             : 
    1406           0 :   ulong const frame_sz  = FD_NET_MTU;
    1407           0 :   ulong const fr_depth  = ctx->xsk[ xsk_idx ].ring_fr.depth/2UL;
    1408             : 
    1409           0 :   fd_xdp_ring_t * fill      = &xsk->ring_fr;
    1410           0 :   uint            fill_prod = fill->cached_prod;
    1411           0 :   for( ulong j=0UL; j<fr_depth; j++ ) {
    1412           0 :     fill->frame_ring[ j ] = frame_off;
    1413           0 :     frame_off += frame_sz;
    1414           0 :   }
    1415           0 :   fill->cached_prod = fill_prod + (uint)fr_depth;
    1416           0 :   __atomic_store_n( fill->prod, fill->cached_prod, __ATOMIC_RELEASE );
    1417             : 
    1418           0 :   return frame_off;
    1419           0 : }
    1420             : 
    1421             : /* FIXME source MAC address from netlnk tile instead */
    1422             : 
    1423             : static void
    1424             : interface_addrs( const char * interface,
    1425             :                  uchar *      mac,
    1426           0 :                  uint *       ip4_addr ) {
    1427           0 :   int fd = socket( AF_INET, SOCK_DGRAM, 0 );
    1428           0 :   struct ifreq ifr;
    1429           0 :   ifr.ifr_addr.sa_family = AF_INET;
    1430             : 
    1431           0 :   strncpy( ifr.ifr_name, interface, IFNAMSIZ );
    1432           0 :   if( FD_UNLIKELY( ioctl( fd, SIOCGIFHWADDR, &ifr ) ) )
    1433           0 :     FD_LOG_ERR(( "could not get MAC address of interface `%s`: (%i-%s)", interface, errno, fd_io_strerror( errno ) ));
    1434           0 :   fd_memcpy( mac, ifr.ifr_hwaddr.sa_data, 6 );
    1435             : 
    1436           0 :   if( FD_UNLIKELY( ioctl( fd, SIOCGIFADDR, &ifr ) ) )
    1437           0 :     FD_LOG_ERR(( "could not get IP address of interface `%s`: (%i-%s)", interface, errno, fd_io_strerror( errno ) ));
    1438           0 :   *ip4_addr = ((struct sockaddr_in *)fd_type_pun( &ifr.ifr_addr ))->sin_addr.s_addr;
    1439             : 
    1440           0 :   if( FD_UNLIKELY( close(fd) ) )
    1441           0 :     FD_LOG_ERR(( "could not close socket (%i-%s)", errno, fd_io_strerror( errno ) ));
    1442           0 : }
    1443             : 
    1444             : /* privileged_init does the following initialization steps:
    1445             : 
    1446             :    - Create an AF_XDP socket
    1447             :    - Map XDP metadata rings
    1448             :    - Register UMEM data region with socket
    1449             :    - Insert AF_XDP socket into xsk_map
    1450             : 
    1451             :    Net tile 0 also runs fd_xdp_install and repeats the above step for
    1452             :    the loopback device.  (Unless the main interface is already loopback)
    1453             : 
    1454             :    Kernel object references:
    1455             : 
    1456             :      BPF_LINK file descriptor
    1457             :       |
    1458             :       +-> XDP program installation on NIC
    1459             :       |    |
    1460             :       |    +-> XDP program <-- BPF_PROG file descriptor (prog_fd)
    1461             :       |
    1462             :       +-> XSKMAP object <-- BPF_MAP file descriptor (xsk_map) */
    1463             : 
    1464             : FD_FN_UNUSED static void
    1465             : privileged_init( fd_topo_t const *      topo,
    1466           0 :                  fd_topo_tile_t const * tile ) {
    1467           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1468             : 
    1469           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1470           0 :   fd_net_ctx_t * ctx     = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_net_ctx_t), sizeof(fd_net_ctx_t) );
    1471           0 :   ulong *        free_tx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), tile->xdp.free_ring_depth * sizeof(ulong) );;
    1472             : 
    1473           0 :   fd_memset( ctx, 0, sizeof(fd_net_ctx_t) );
    1474             : 
    1475           0 :   interface_addrs( tile->xdp.if_virt, ctx->src_mac_addr, &ctx->default_address );
    1476           0 :   ctx->if_virt = if_nametoindex( tile->xdp.if_virt ); FD_TEST( ctx->if_virt );
    1477             : 
    1478             :   /* Load up dcache containing UMEM */
    1479             : 
    1480           0 :   void * const dcache_mem          = fd_topo_obj_laddr( topo, tile->net.umem_dcache_obj_id );
    1481           0 :   void * const umem                = fd_dcache_join( dcache_mem );
    1482           0 :   ulong  const umem_dcache_data_sz = fd_dcache_data_sz( umem );
    1483           0 :   ulong  const umem_frame_sz       = 2048UL;
    1484           0 :   ulong  const umem_sz             = fd_ulong_align_dn( umem_dcache_data_sz, umem_frame_sz );
    1485             : 
    1486             :   /* Derive chunk bounds */
    1487             : 
    1488           0 :   void * const umem_base   = fd_wksp_containing( dcache_mem );
    1489           0 :   ulong  const umem_chunk0 = ( (ulong)umem - (ulong)umem_base )>>FD_CHUNK_LG_SZ;
    1490           0 :   ulong  const umem_wmark  = umem_chunk0 + ( ( umem_sz-umem_frame_sz )>>FD_CHUNK_LG_SZ );
    1491             : 
    1492           0 :   if( FD_UNLIKELY( umem_chunk0>UINT_MAX || umem_wmark>UINT_MAX || umem_chunk0>umem_wmark ) ) {
    1493           0 :     FD_LOG_ERR(( "Calculated invalid UMEM bounds [%lu,%lu]", umem_chunk0, umem_wmark ));
    1494           0 :   }
    1495             : 
    1496           0 :   if( FD_UNLIKELY( !umem_base ) ) FD_LOG_ERR(( "UMEM dcache is not in a workspace" ));
    1497             : 
    1498           0 :   ctx->umem        = umem;
    1499           0 :   ctx->umem_sz     = umem_sz;
    1500           0 :   ctx->umem_chunk0 = (uint)umem_chunk0;
    1501             : 
    1502           0 :   ctx->free_tx.queue = free_tx;
    1503           0 :   ctx->free_tx.depth = tile->xdp.xdp_tx_queue_size;
    1504             : 
    1505             :   /* Create and install XSKs */
    1506             : 
    1507           0 :   uint if_phys_if_idx = if_nametoindex( tile->xdp.if_phys );
    1508           0 :   if( FD_UNLIKELY( !if_phys_if_idx ) ) FD_LOG_ERR(( "if_nametoindex(%s) failed", tile->xdp.if_phys ));
    1509             : 
    1510           0 :   fd_xsk_params_t params0 = {
    1511           0 :     .if_idx      = if_phys_if_idx,
    1512           0 :     .if_queue_id = tile->xdp.if_queue,
    1513             : 
    1514             :     /* Some kernels produce EOPNOTSUP errors on sendto calls when
    1515             :        starting up without either XDP_ZEROCOPY or XDP_COPY
    1516             :        (e.g. 5.14.0-503.23.1.el9_5 with i40e) */
    1517           0 :     .bind_flags  = tile->xdp.zero_copy ? XDP_ZEROCOPY : XDP_COPY,
    1518             : 
    1519           0 :     .prefbusy_time_budget_micros = PREFBUSY_TIME_BUDGET_MICROS,
    1520             : 
    1521           0 :     .prefbusy_rx_budget = PREFBUSY_RX_BUDGET,
    1522             : 
    1523           0 :     .fr_depth  = tile->xdp.xdp_rx_queue_size*2,
    1524           0 :     .rx_depth  = tile->xdp.xdp_rx_queue_size,
    1525           0 :     .cr_depth  = tile->xdp.xdp_tx_queue_size,
    1526           0 :     .tx_depth  = tile->xdp.xdp_tx_queue_size,
    1527             : 
    1528           0 :     .umem_addr = umem,
    1529           0 :     .frame_sz  = umem_frame_sz,
    1530           0 :     .umem_sz   = umem_sz,
    1531             : 
    1532           0 :     .core_dump = tile->xdp.xsk_core_dump,
    1533           0 :   };
    1534             : 
    1535           0 :   fd_cstr_ncpy( params0.poll_mode, tile->xdp.poll_mode, sizeof(params0.poll_mode) );
    1536             : 
    1537             :   /* Re-derive XDP file descriptors */
    1538             : 
    1539           0 :   fd_xdp_fds_t xdp_fds[ FD_TOPO_XDP_FDS_MAX ];
    1540           0 :   uint         xdp_fds_cnt = FD_TOPO_XDP_FDS_MAX;
    1541           0 :   fd_topo_install_xdp( topo, xdp_fds, &xdp_fds_cnt, 0U, /* dry_run */ 1 );
    1542             : 
    1543           0 :   int xsk_map_fd = -1;
    1544           0 :   for( uint i=0U; i<xdp_fds_cnt; i++ ) {
    1545           0 :     if( xdp_fds[ i ].if_idx==if_phys_if_idx ) {
    1546           0 :       xsk_map_fd              = xdp_fds[ i ].xsk_map_fd;
    1547           0 :       ctx->prog_link_fds[ 0 ] = xdp_fds[ i ].prog_link_fd;
    1548           0 :       xdp_fds[ i ].prog_link_fd = -1; /* mark as used */
    1549           0 :       break;
    1550           0 :     }
    1551           0 :   }
    1552           0 :   FD_TEST( xsk_map_fd>=0 );
    1553             : 
    1554             :   /* Init XSK */
    1555           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 ));
    1556           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 ));
    1557           0 :   ctx->xsk_cnt = 1;
    1558             : 
    1559             :   /* Networking tile at index 0 also binds to loopback (only queue 0 available on lo) */
    1560             : 
    1561           0 :   if( FD_UNLIKELY( strcmp( tile->xdp.if_virt, "lo" ) && !tile->kind_id ) ) {
    1562           0 :     ctx->xsk_cnt = 2;
    1563             : 
    1564           0 :     uint lo_idx = if_nametoindex( "lo" );
    1565           0 :     if( FD_UNLIKELY( !lo_idx ) ) FD_LOG_ERR(( "if_nametoindex(lo) failed" ));
    1566             : 
    1567           0 :     int lo_xsk_map_fd = -1;
    1568           0 :     for( uint i=0U; i<xdp_fds_cnt; i++ ) {
    1569           0 :       if( xdp_fds[ i ].if_idx==lo_idx ) {
    1570           0 :         lo_xsk_map_fd           = xdp_fds[ i ].xsk_map_fd;
    1571           0 :         ctx->prog_link_fds[ 1 ] = xdp_fds[ i ].prog_link_fd;
    1572           0 :         xdp_fds[ i ].prog_link_fd = -1; /* mark as used */
    1573           0 :         break;
    1574           0 :       }
    1575           0 :     }
    1576           0 :     FD_TEST( lo_xsk_map_fd>=0 );
    1577             : 
    1578             :     /* init xsk 1 */
    1579           0 :     fd_xsk_params_t params1 = params0;
    1580           0 :     params1.if_idx      = lo_idx; /* probably always 1 */
    1581           0 :     params1.if_queue_id = 0;
    1582           0 :     params1.bind_flags  = 0;
    1583           0 :     if( FD_UNLIKELY( !fd_xsk_init( &ctx->xsk[ 1 ], &params1 ) ) )          FD_LOG_ERR(( "failed to bind lo_xsk" ));
    1584           0 :     if( FD_UNLIKELY( !fd_xsk_activate( &ctx->xsk[ 1 ], lo_xsk_map_fd ) ) ) FD_LOG_ERR(( "failed to activate lo_xsk" ));
    1585           0 :   }
    1586             : 
    1587             :   /* Close unused XDP fds */
    1588             : 
    1589           0 :   if( FD_UNLIKELY( fd_sandbox_gettid()==fd_sandbox_getpid() ) ) {
    1590             :     /* Kind of gross.. in single threaded mode we don't want to close the xsk_map_fd
    1591             :        since it's shared with other net tiles.  Just check for that by seeing if we
    1592             :        are the only thread in the process. */
    1593           0 :     for( uint i=0U; i<xdp_fds_cnt; i++ ) {
    1594           0 :       if( -1==close( xdp_fds[ i ].xsk_map_fd ) ) {
    1595           0 :         FD_LOG_ERR(( "close(%d) failed (%d-%s)", xsk_map_fd, errno, fd_io_strerror( errno ) ));
    1596           0 :       }
    1597           0 :       if( xdp_fds[ i ].prog_link_fd>0 &&
    1598           0 :           -1==close( xdp_fds[ i ].prog_link_fd ) ) {
    1599           0 :         FD_LOG_ERR(( "close(%d) failed (%d-%s)", xsk_map_fd, errno, fd_io_strerror( errno ) ));
    1600           0 :       }
    1601           0 :     }
    1602           0 :   }
    1603             : 
    1604           0 :   double tick_per_ns = fd_tempo_tick_per_ns( NULL );
    1605           0 :   ctx->xdp_stats_interval_ticks = (long)( FD_XDP_STATS_INTERVAL_NS * tick_per_ns );
    1606             : 
    1607           0 :   ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
    1608           0 :   if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
    1609           0 :     FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
    1610           0 : }
    1611             : 
    1612             : static void
    1613             : init_device_table( fd_net_ctx_t * ctx,
    1614             :                    void *         netdev_tbl_shm,
    1615           3 :                    void *         netdev_tbl_local ) {
    1616           3 :   FD_TEST( fd_netdev_tbl_join( &ctx->netdev_shared, netdev_tbl_shm ) );
    1617           3 :   FD_TEST( fd_netdev_tbl_new( netdev_tbl_local, NETDEV_MAX, BOND_MASTER_MAX ) );
    1618           3 :   FD_TEST( fd_netdev_tbl_join( &ctx->netdev_tbl, netdev_tbl_local ) );
    1619           3 : }
    1620             : 
    1621             : FD_FN_UNUSED static void
    1622             : unprivileged_init( fd_topo_t const *      topo,
    1623           0 :                    fd_topo_tile_t const * tile ) {
    1624           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1625             : 
    1626           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1627           0 :   fd_net_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_net_ctx_t), sizeof(fd_net_ctx_t) );
    1628           0 :   FD_TEST( ctx->xsk_cnt!=0 );
    1629           0 :   FD_TEST( ctx->free_tx.queue!=NULL );
    1630           0 :   (void)FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), tile->xdp.free_ring_depth * sizeof(ulong) );
    1631           0 :   void * netdev_tbl_local = FD_SCRATCH_ALLOC_APPEND( l, fd_netdev_tbl_align(), fd_netdev_tbl_footprint( NETDEV_MAX, BOND_MASTER_MAX ) );
    1632           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 ) );
    1633           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 ) );
    1634           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 ) ) );
    1635           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 ) ) );
    1636             : 
    1637           0 :   ctx->net_tile_id  = (uint)tile->kind_id;
    1638           0 :   ctx->net_tile_cnt = (uint)fd_topo_tile_name_cnt( topo, tile->name );
    1639             : 
    1640           0 :   ctx->bind_address                   = tile->net.bind_address;
    1641           0 :   ctx->shred_listen_port              = tile->net.shred_listen_port;
    1642           0 :   ctx->quic_transaction_listen_port   = tile->net.quic_transaction_listen_port;
    1643           0 :   ctx->legacy_transaction_listen_port = tile->net.legacy_transaction_listen_port;
    1644           0 :   ctx->gossip_listen_port             = tile->net.gossip_listen_port;
    1645           0 :   ctx->repair_client_listen_port      = tile->net.repair_client_listen_port;
    1646           0 :   ctx->repair_serve_listen_port       = tile->net.repair_serve_listen_port;
    1647           0 :   ctx->txsend_src_port                = tile->net.txsend_src_port;
    1648           0 :   ctx->votor_quic_client_listen_port  = tile->net.votor_quic_client_listen_port;
    1649           0 :   ctx->votor_quic_server_listen_port  = tile->net.votor_quic_server_listen_port;
    1650             : 
    1651             :   /* Put a bound on chunks we read from the input, to make sure they
    1652             :      are within in the data region of the workspace. */
    1653             : 
    1654           0 :   if( FD_UNLIKELY( !tile->in_cnt ) ) FD_LOG_ERR(( "net tile in link cnt is zero" ));
    1655           0 :   if( FD_UNLIKELY( tile->in_cnt>MAX_NET_INS ) ) FD_LOG_ERR(( "net tile in link cnt %lu exceeds MAX_NET_INS %lu", tile->in_cnt, MAX_NET_INS ));
    1656           0 :   FD_TEST( tile->in_cnt<=32 );
    1657           0 :   for( ulong i=0UL; i<tile->in_cnt; i++ ) {
    1658           0 :     fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
    1659           0 :     if( !strcmp( link->name, "iproute_out" ) ) ctx->in_kind[i] = IN_KIND_IPROUTE;
    1660           0 :     else {
    1661           0 :       ctx->in_kind[i] = IN_KIND_NET;
    1662           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 ));
    1663           0 :     }
    1664             : 
    1665           0 :     ctx->in[ i ].mem    = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
    1666           0 :     ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
    1667           0 :     ctx->in[ i ].wmark  = fd_dcache_compact_wmark( ctx->in[ i ].mem, link->dcache, link->mtu );
    1668           0 :   }
    1669             : 
    1670           0 :   ctx->rserve_enabled = 0;
    1671           0 :   for( ulong i = 0; i < tile->out_cnt; i++ ) {
    1672           0 :     fd_topo_link_t const * out_link = &topo->links[ tile->out_link_id[ i  ] ];
    1673           0 :     if( strcmp( out_link->name, "net_quic" ) == 0 ) {
    1674           0 :       fd_topo_link_t const * quic_out = out_link;
    1675           0 :       ctx->quic_out->mcache = quic_out->mcache;
    1676           0 :       ctx->quic_out->sync   = fd_mcache_seq_laddr( ctx->quic_out->mcache );
    1677           0 :       ctx->quic_out->depth  = fd_mcache_depth( ctx->quic_out->mcache );
    1678           0 :       ctx->quic_out->seq    = fd_mcache_seq_query( ctx->quic_out->sync );
    1679           0 :     } else if( strcmp( out_link->name, "net_shred" ) == 0 ) {
    1680           0 :       fd_topo_link_t const * shred_out = out_link;
    1681           0 :       ctx->shred_out->mcache = shred_out->mcache;
    1682           0 :       ctx->shred_out->sync   = fd_mcache_seq_laddr( ctx->shred_out->mcache );
    1683           0 :       ctx->shred_out->depth  = fd_mcache_depth( ctx->shred_out->mcache );
    1684           0 :       ctx->shred_out->seq    = fd_mcache_seq_query( ctx->shred_out->sync );
    1685           0 :     } else if( strcmp( out_link->name, "net_gossvf" ) == 0 ) {
    1686           0 :       fd_topo_link_t const * gossip_out = out_link;
    1687           0 :       ctx->gossvf_out->mcache = gossip_out->mcache;
    1688           0 :       ctx->gossvf_out->sync   = fd_mcache_seq_laddr( ctx->gossvf_out->mcache );
    1689           0 :       ctx->gossvf_out->depth  = fd_mcache_depth( ctx->gossvf_out->mcache );
    1690           0 :       ctx->gossvf_out->seq    = fd_mcache_seq_query( ctx->gossvf_out->sync );
    1691           0 :     } else if( strcmp( out_link->name, "net_repair" ) == 0 ) {
    1692           0 :       fd_topo_link_t const * repair_out = out_link;
    1693           0 :       ctx->repair_out->mcache = repair_out->mcache;
    1694           0 :       ctx->repair_out->sync   = fd_mcache_seq_laddr( ctx->repair_out->mcache );
    1695           0 :       ctx->repair_out->depth  = fd_mcache_depth( ctx->repair_out->mcache );
    1696           0 :       ctx->repair_out->seq    = fd_mcache_seq_query( ctx->repair_out->sync );
    1697           0 :     } else if( strcmp( out_link->name, "net_netlnk" ) == 0 ) {
    1698           0 :       fd_topo_link_t const * netlink_out = out_link;
    1699           0 :       ctx->neigh4_solicit->mcache = netlink_out->mcache;
    1700           0 :       ctx->neigh4_solicit->depth  = fd_mcache_depth( ctx->neigh4_solicit->mcache );
    1701           0 :       ctx->neigh4_solicit->seq    = fd_mcache_seq_query( fd_mcache_seq_laddr( ctx->neigh4_solicit->mcache ) );
    1702           0 :     } else if( strcmp( out_link->name, "net_txsend" ) == 0 ) {
    1703           0 :       fd_topo_link_t const * txsend_out = out_link;
    1704           0 :       ctx->txsend_out->mcache = txsend_out->mcache;
    1705           0 :       ctx->txsend_out->sync   = fd_mcache_seq_laddr( ctx->txsend_out->mcache );
    1706           0 :       ctx->txsend_out->depth  = fd_mcache_depth( ctx->txsend_out->mcache );
    1707           0 :       ctx->txsend_out->seq    = fd_mcache_seq_query( ctx->txsend_out->sync );
    1708           0 :     } else if( strcmp( out_link->name, "net_rserve" ) == 0 ) {
    1709           0 :       fd_topo_link_t const * rserve_out = out_link;
    1710           0 :       ctx->rserve_out->mcache = rserve_out->mcache;
    1711           0 :       ctx->rserve_out->sync   = fd_mcache_seq_laddr( ctx->rserve_out->mcache );
    1712           0 :       ctx->rserve_out->depth  = fd_mcache_depth( ctx->rserve_out->mcache );
    1713           0 :       ctx->rserve_out->seq    = fd_mcache_seq_query( ctx->rserve_out->sync );
    1714           0 :       ctx->rserve_enabled     = 1;
    1715           0 :     } else if( strcmp( out_link->name, "net_votor" ) == 0 ) {
    1716           0 :       fd_topo_link_t const * votor_out = out_link;
    1717           0 :       ctx->votor_out->mcache = votor_out->mcache;
    1718           0 :       ctx->votor_out->sync   = fd_mcache_seq_laddr( ctx->votor_out->mcache );
    1719           0 :       ctx->votor_out->depth  = fd_mcache_depth( ctx->votor_out->mcache );
    1720           0 :       ctx->votor_out->seq    = fd_mcache_seq_query( ctx->votor_out->sync );
    1721           0 :       ctx->votor_enabled     = 1;
    1722           0 :     } else {
    1723           0 :       FD_LOG_ERR(( "unrecognized out link `%s`", out_link->name ));
    1724           0 :     }
    1725           0 :   }
    1726             : 
    1727             :   /* Check if any of the tiles we set a listen port for do not have an outlink. */
    1728           0 :   if( FD_UNLIKELY( ctx->shred_listen_port!=0 && ctx->shred_out->mcache==NULL ) ) {
    1729           0 :     FD_LOG_ERR(( "shred listen port set but no out link was found" ));
    1730           0 :   } else if( FD_UNLIKELY( ctx->quic_transaction_listen_port!=0 && ctx->quic_out->mcache==NULL ) ) {
    1731           0 :     FD_LOG_ERR(( "quic transaction listen port set but no out link was found" ));
    1732           0 :   } else if( FD_UNLIKELY( ctx->legacy_transaction_listen_port!=0 && ctx->quic_out->mcache==NULL ) ) {
    1733           0 :     FD_LOG_ERR(( "legacy transaction listen port set but no out link was found" ));
    1734           0 :   } else if( FD_UNLIKELY( ctx->gossip_listen_port!=0 && ctx->gossvf_out->mcache==NULL ) ) {
    1735           0 :     FD_LOG_ERR(( "gossip listen port set but no out link was found" ));
    1736           0 :   } else if( FD_UNLIKELY( ctx->repair_client_listen_port!=0 && ctx->repair_out->mcache==NULL ) ) {
    1737           0 :     FD_LOG_ERR(( "repair intake port set but no out link was found" ));
    1738           0 :   } else if( FD_UNLIKELY( ctx->repair_serve_listen_port!=0 && ctx->rserve_out->mcache==NULL ) ) {
    1739           0 :     FD_LOG_ERR(( "repair serve listen port set but no out link was found" ));
    1740           0 :   } else if( FD_UNLIKELY( ctx->neigh4_solicit->mcache==NULL ) ) {
    1741           0 :     FD_LOG_ERR(( "netlink request link not found" ));
    1742           0 :   } else if( FD_UNLIKELY( ctx->txsend_src_port!=0 && ctx->txsend_out->mcache==NULL ) ) {
    1743           0 :     FD_LOG_ERR(( "txsend listen port set but no out link was found" ));
    1744           0 :   } else if( FD_UNLIKELY( ctx->votor_quic_client_listen_port!=0 && ctx->votor_out->mcache==NULL ) ) {
    1745           0 :     FD_LOG_ERR(( "votor client src port set but no out link was found" ));
    1746           0 :   } else if( FD_UNLIKELY( ctx->votor_quic_server_listen_port!=0 && ctx->votor_out->mcache==NULL ) ) {
    1747           0 :     FD_LOG_ERR(( "votor listen port set but no out link was found" ));
    1748           0 :   }
    1749             : 
    1750           0 :   for( uint j=0U; j<2U; j++ ) {
    1751           0 :     ctx->tx_flusher[ j ].pending_wmark         = (ulong)( (double)tile->xdp.xdp_tx_queue_size * 0.7 );
    1752           0 :     ctx->tx_flusher[ j ].tail_flush_backoff    = (long)( (double)tile->xdp.tx_flush_timeout_ns * fd_tempo_tick_per_ns( NULL ) );
    1753           0 :     ctx->tx_flusher[ j ].next_tail_flush_ticks = LONG_MAX;
    1754             : 
    1755           0 :     ctx->tx_flusher[ j ].prefbusy_last_poll_ticks     = 0L;
    1756           0 :     ctx->tx_flusher[ j ].prefbusy_min_interval_ticks  = (long)( PREFBUSY_MIN_INTERVAL_NS * fd_tempo_tick_per_ns( NULL ) );
    1757           0 :     ctx->tx_flusher[ j ].prefbusy_stall_timeout_ticks = (long)( PREFBUSY_STALL_TIMEOUT_NS * fd_tempo_tick_per_ns( NULL ) );
    1758           0 :   }
    1759             : 
    1760           0 :   ulong neigh4_obj_id = tile->xdp.neigh4_obj_id;
    1761           0 :   ulong ele_max   = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.ele_max",   neigh4_obj_id );
    1762           0 :   ulong probe_max = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.probe_max", neigh4_obj_id );
    1763           0 :   ulong seed      = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.seed",      neigh4_obj_id );
    1764           0 :   if( FD_UNLIKELY( (ele_max==ULONG_MAX) | (probe_max==ULONG_MAX) | (seed==ULONG_MAX) ) )
    1765           0 :     FD_LOG_ERR(( "neigh4 hmap properties not set" ));
    1766           0 :   if( FD_UNLIKELY( !fd_neigh4_hmap_join(
    1767           0 :       ctx->neigh4,
    1768           0 :       fd_topo_obj_laddr( topo, neigh4_obj_id ),
    1769           0 :       ele_max,
    1770           0 :       probe_max,
    1771           0 :       seed ) ) ) {
    1772           0 :     FD_LOG_ERR(( "fd_neigh4_hmap_join failed" ));
    1773           0 :   }
    1774             : 
    1775           0 :   init_device_table( ctx, fd_topo_obj_laddr( topo, tile->xdp.netdev_tbl_obj_id ), netdev_tbl_local );
    1776             : 
    1777             :   /* Initialize TX free ring */
    1778             : 
    1779           0 :   ulong const frame_sz  = 2048UL;
    1780           0 :   ulong       frame_off = 0UL;
    1781           0 :   ulong const tx_depth  = ctx->free_tx.depth;
    1782           0 :   for( ulong j=0; j<tx_depth; j++ ) {
    1783           0 :     ctx->free_tx.queue[ j ] = (ulong)ctx->umem + frame_off;
    1784           0 :     frame_off += frame_sz;
    1785           0 :   }
    1786           0 :   ctx->free_tx.prod = tx_depth;
    1787             : 
    1788             :   /* Initialize RX mcache chunks */
    1789             : 
    1790           0 :   for( ulong i=0UL; i<(tile->out_cnt); i++ ) {
    1791           0 :     fd_topo_link_t const * out_link = &topo->links[ tile->out_link_id[ i  ] ];
    1792           0 :     fd_frag_meta_t * mcache = out_link->mcache;
    1793           0 :     for( ulong j=0UL; j<fd_mcache_depth( mcache ); j++ ) {
    1794           0 :       mcache[ j ].chunk = (uint)( ctx->umem_chunk0 + (frame_off>>FD_CHUNK_LG_SZ) );
    1795           0 :       frame_off += frame_sz;
    1796           0 :     }
    1797           0 :   }
    1798             : 
    1799             :   /* Initialize FILL ring */
    1800             : 
    1801           0 :   int _charge_busy = 0;
    1802           0 :   for( uint j=0U; j<ctx->xsk_cnt; j++ ) {
    1803           0 :     frame_off = net_xsk_bootstrap( ctx, j, frame_off );
    1804           0 :     net_rx_wakeup( ctx, &ctx->xsk[ j ], &_charge_busy );
    1805           0 :     net_tx_wakeup( ctx, &ctx->xsk[ j ], &_charge_busy );
    1806           0 :   }
    1807             : 
    1808           0 :   if( FD_UNLIKELY( frame_off > ctx->umem_sz ) ) {
    1809           0 :     FD_LOG_ERR(( "UMEM is too small" ));
    1810           0 :   }
    1811           0 : }
    1812             : 
    1813             : FD_FN_UNUSED static ulong
    1814             : populate_allowed_seccomp( fd_topo_t const *      topo,
    1815             :                           fd_topo_tile_t const * tile,
    1816             :                           ulong                  out_cnt,
    1817           0 :                           struct sock_filter *   out ) {
    1818           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1819           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1820           0 :   fd_net_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_net_ctx_t ), sizeof( fd_net_ctx_t ) );
    1821             : 
    1822             :   /* A bit of a hack, if there is no loopback XSK for this tile, we still need to pass
    1823             :      two "allow" FD arguments to the net policy, so we just make them both the same. */
    1824           0 :   int allow_fd2 = ctx->xsk_cnt>1UL ? ctx->xsk[ 1 ].xsk_fd : ctx->xsk[ 0 ].xsk_fd;
    1825           0 :   FD_TEST( ctx->xsk[ 0 ].xsk_fd >= 0 && allow_fd2 >= 0 );
    1826             : 
    1827           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 );
    1828           0 :   return sock_filter_policy_fd_xdp_tile_instr_cnt;
    1829           0 : }
    1830             : 
    1831             : FD_FN_UNUSED static ulong
    1832             : populate_allowed_fds( fd_topo_t const *      topo,
    1833             :                       fd_topo_tile_t const * tile,
    1834             :                       ulong                  out_fds_cnt,
    1835           0 :                       int *                  out_fds ) {
    1836           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1837           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1838           0 :   fd_net_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_net_ctx_t ), sizeof( fd_net_ctx_t ) );
    1839             : 
    1840           0 :   if( FD_UNLIKELY( out_fds_cnt<6UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
    1841             : 
    1842           0 :   ulong out_cnt = 0UL;
    1843             : 
    1844           0 :   out_fds[ out_cnt++ ] = 2; /* stderr */
    1845           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
    1846           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
    1847             : 
    1848           0 :                                       out_fds[ out_cnt++ ] = ctx->xsk[ 0 ].xsk_fd;
    1849           0 :                                       out_fds[ out_cnt++ ] = ctx->prog_link_fds[ 0 ];
    1850           0 :   if( FD_LIKELY( ctx->xsk_cnt>1UL ) ) out_fds[ out_cnt++ ] = ctx->xsk[ 1 ].xsk_fd;
    1851           0 :   if( FD_LIKELY( ctx->xsk_cnt>1UL ) ) out_fds[ out_cnt++ ] = ctx->prog_link_fds[ 1 ];
    1852           0 :   return out_cnt;
    1853           0 : }
    1854             : 
    1855           0 : #define STEM_BURST (1UL)
    1856           0 : #define STEM_LAZY ((ulong)30e3) /* 30 us */
    1857             : 
    1858           0 : #define STEM_CALLBACK_CONTEXT_TYPE  fd_net_ctx_t
    1859           0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_net_ctx_t)
    1860             : 
    1861           0 : #define STEM_CALLBACK_METRICS_WRITE       metrics_write
    1862           0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
    1863           0 : #define STEM_CALLBACK_BEFORE_CREDIT       before_credit
    1864           0 : #define STEM_CALLBACK_BEFORE_FRAG         before_frag
    1865           0 : #define STEM_CALLBACK_DURING_FRAG         during_frag
    1866           0 : #define STEM_CALLBACK_AFTER_FRAG          after_frag
    1867             : 
    1868             : #include "../../stem/fd_stem.c"
    1869             : 
    1870             : #ifndef FD_TILE_TEST
    1871             : fd_topo_run_tile_t fd_tile_net = {
    1872             :   .name                     = "net",
    1873             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    1874             :   .populate_allowed_fds     = populate_allowed_fds,
    1875             :   .scratch_align            = scratch_align,
    1876             :   .scratch_footprint        = scratch_footprint,
    1877             :   .privileged_init          = privileged_init,
    1878             :   .unprivileged_init        = unprivileged_init,
    1879             :   .run                      = stem_run,
    1880             : };
    1881             : #endif

Generated by: LCOV version 1.14