Line data Source code
1 : #ifndef HEADER_fd_src_waltz_neigh_fd_neigh4_probe_h 2 : #define HEADER_fd_src_waltz_neigh_fd_neigh4_probe_h 3 : 4 : /* fd_neigh4_probe.h is a hack to indirectly trigger ARP requests in 5 : Linux. 6 : 7 : ### Background 8 : 9 : When sending an IP packet via the Firedancer network stack, it is 10 : the net tile's responsibility to pick the network interface to send 11 : the packet out on, as well as the destination MAC address. 12 : 13 : The dst MAC address is taken from a neighbor table entry given the 14 : "next hop" (an output of a previously done route table lookiup). 15 : The neighbor table is directly mirrored from the Linux kernel. 16 : 17 : If no matching neighbor table entry exists, the system should send 18 : broadcast an ARP request (e.g. "who is 192.168.12.13? tell 19 : 192.168.12.4"). ARP replies to this request will then go to the 20 : kernel. The kernel also needs to be told that it should expect an 21 : ARP reply to avoid drops. 22 : 23 : ### Possible Solutions 24 : 25 : 1. Add a neighbor table entry, send out the ARP request via XDP: 26 : `ip neigh add IP_ADDR nud incomplete` 27 : Requires CAP_NET_ADMIN (to send RTM_NEWNEIGH) 28 : 29 : 2. Add a neighbor table entry, make the kernel issue the ARP 30 : request: `ip neigh add IP_ADDR nud incomplete use` 31 : Requires CAP_NET_ADMIN (to send RTM_NEWNEIGH) 32 : 33 : 3. Send a UDP datagram which indirectly makes the kernel do an ARP 34 : request: `echo "hello" | nc -u IP_ADDR:65535` 35 : Does not require privileges 36 : 37 : 4. Send an IP packet (ICMP echo, invalid ICMP, invalid next proto...) 38 : which indirectly makes the kernel do an ARP request 39 : `ping IP_ADDR -c 1` 40 : Requires CAP_NET_RAW to create a SOCK_RAW socket 41 : 42 : Solution 2 is theoretically ideal. Unfortunately, it requires the 43 : netlink API caller to be in the root user namespace, which would 44 : break assumptions made in fd_sandbox. 45 : 46 : fd_neigh4_probe implements solution 3 because it requires the least 47 : amount of privileges. */ 48 : 49 : #include "fd_neigh4_map.h" 50 : #include "../fd_token_bucket.h" 51 : 52 : /* The fd_neigh4_prober_t class provides "neighbor probing" 53 : functionality as described above using empty UDP/IP packets. */ 54 : 55 : struct fd_neigh4_prober { 56 : int sock_fd; /* UDP socket with IP_TTL 0 */ 57 : 58 : /* probe_delay specifies the delay in ticks for successive ARP 59 : requests to the same IP address (see fd_tickcount()) */ 60 : long probe_delay; 61 : 62 : /* Token bucket rate limiter on any outgoing ARP probes */ 63 : fd_token_bucket_t rate_limit; 64 : 65 : /* Metric counter for probes suppressed by local rate limit */ 66 : ulong local_rate_limited_cnt; 67 : 68 : /* Metric counter for probes suppressed by global rate limit */ 69 : ulong global_rate_limited_cnt; 70 : }; 71 : 72 : typedef struct fd_neigh4_prober fd_neigh4_prober_t; 73 : 74 : FD_PROTOTYPES_BEGIN 75 : 76 : /* fd_neigh4_prober_init initializes a neigh4_prober object. Creates a 77 : new unbound UDP socket (socket(2)) with an IPv4 TTL of zero 78 : (setsockopt(2)). max_probes_per_second and max_probe_burst configure 79 : token bucket rate limit parameters for outgoing probe packets. 80 : probe_delay_seconds sets the min wait time between two probe packet 81 : sends for the same dst IP. */ 82 : 83 : void 84 : fd_neigh4_prober_init( fd_neigh4_prober_t * prober, 85 : float max_probes_per_second, 86 : ulong max_probe_burst, 87 : float probe_delay_seconds ); 88 : 89 : /* fd_neigh4_prober_fini closes the neigh4_prober socket. */ 90 : 91 : void 92 : fd_neigh4_prober_fini( fd_neigh4_prober_t * prober ); 93 : 94 : /* fd_neigh4_probe sends out an empty UDP packet to port 65535 with the 95 : IP time-to-live field set to 0. ip4_addr is an IP address on a 96 : neighboring subnet for which the neighbor discovery process should 97 : be started. ip4_addr is big endian. now is a recent fd_tickcount() 98 : value. Returns the errno value produced by sendto(2) or 0 on success. */ 99 : 100 : int 101 : fd_neigh4_probe( fd_neigh4_prober_t * prober, 102 : fd_neigh4_entry_t * entry, 103 : uint ip4_addr, 104 : long now ); 105 : 106 : /* fd_neigh4_probe_rate_limited calls fd_neigh4_probe unless that would 107 : violate rate limits. Returns 0 if a probe was sent out. Returns 108 : positive errno on probe failure. Returns -1 if rate limit was hit. */ 109 : 110 : static inline int 111 : fd_neigh4_probe_rate_limited( 112 : fd_neigh4_prober_t * prober, 113 : fd_neigh4_entry_t * entry, 114 : uint ip4_addr, 115 : long now 116 0 : ) { 117 : /* Local rate limit */ 118 0 : if( now < entry->probe_suppress_until ) { 119 0 : prober->local_rate_limited_cnt++; 120 0 : return -1; 121 0 : } 122 0 : entry->probe_suppress_until = now + prober->probe_delay; 123 : 124 : /* Global rate limit */ 125 0 : if( !fd_token_bucket_consume( &prober->rate_limit, 1.0f, now ) ) { 126 0 : prober->global_rate_limited_cnt++; 127 0 : return -1; 128 0 : } 129 : 130 0 : return fd_neigh4_probe( prober, entry, ip4_addr, now ); 131 0 : } 132 : 133 : FD_PROTOTYPES_END 134 : 135 : #endif /* HEADER_fd_src_waltz_neigh_fd_neigh4_probe_h */