Line data Source code
1 : #include "fd_ip.h"
2 :
3 : #include <arpa/inet.h>
4 :
5 : ulong
6 6 : fd_ip_align( void ) {
7 6 : return FD_IP_ALIGN;
8 6 : }
9 :
10 :
11 : ulong
12 : fd_ip_footprint( ulong arp_entries,
13 6 : ulong route_entries ) {
14 :
15 : /* use 256 as a default */
16 6 : if( arp_entries == 0 ) arp_entries = 256;
17 6 : if( route_entries == 0 ) route_entries = 256;
18 :
19 6 : ulong l;
20 :
21 6 : l = FD_LAYOUT_INIT;
22 6 : l = FD_LAYOUT_APPEND( l, FD_IP_ALIGN, sizeof(fd_ip_t) );
23 6 : l = FD_LAYOUT_APPEND( l, FD_IP_ALIGN, sizeof(fd_nl_t) );
24 : /* allocate enough space for two of each kind of table */
25 6 : l = FD_LAYOUT_APPEND( l, FD_IP_ALIGN, 2 * arp_entries * sizeof(fd_nl_arp_entry_t) );
26 6 : l = FD_LAYOUT_APPEND( l, FD_IP_ALIGN, 2 * route_entries * sizeof(fd_nl_route_entry_t) );
27 :
28 6 : return FD_LAYOUT_FINI( l, FD_IP_ALIGN );
29 6 : }
30 :
31 :
32 : void *
33 : fd_ip_new( void * shmem,
34 : ulong arp_entries,
35 3 : ulong route_entries ) {
36 3 : if( !fd_ulong_is_aligned( (ulong)shmem, FD_IP_ALIGN ) ) {
37 0 : FD_LOG_ERR(( "Attempt to fd_ip_new with unaligned memory" ));
38 0 : return NULL;
39 0 : }
40 :
41 : /* use 256 as a default */
42 3 : if( arp_entries == 0 ) arp_entries = 256;
43 3 : if( route_entries == 0 ) route_entries = 256;
44 :
45 3 : ulong l;
46 3 : uchar * mem = (uchar*)shmem;
47 :
48 3 : l = FD_LAYOUT_INIT;
49 :
50 3 : fd_ip_t * ip = (fd_ip_t*)mem;
51 3 : l = FD_LAYOUT_APPEND( l, FD_IP_ALIGN, sizeof(fd_ip_t) );
52 :
53 3 : ulong ofs_netlink = l;
54 3 : l = FD_LAYOUT_APPEND( l, FD_IP_ALIGN, sizeof(fd_nl_t) );
55 :
56 3 : ulong ofs_arp_table = FD_ULONG_ALIGN_UP( l, FD_IP_ALIGN );
57 3 : l = FD_LAYOUT_APPEND( l, FD_IP_ALIGN, 2 * arp_entries * sizeof(fd_nl_arp_entry_t) );
58 :
59 3 : ulong ofs_route_table = FD_ULONG_ALIGN_UP( l, FD_IP_ALIGN );
60 3 : l = FD_LAYOUT_APPEND( l, FD_IP_ALIGN, 2 * route_entries * sizeof(fd_nl_route_entry_t) );
61 :
62 3 : ulong mem_sz = FD_LAYOUT_FINI( l, FD_IP_ALIGN );
63 :
64 : /* clear all to zero */
65 3 : fd_memset( ip, 0, mem_sz );
66 :
67 : /* set values in ip */
68 3 : ip->num_arp_entries = arp_entries;
69 3 : ip->num_route_entries = route_entries;
70 3 : ip->ofs_netlink = ofs_netlink;
71 3 : ip->ofs_arp_table = ofs_arp_table;
72 3 : ip->ofs_route_table = ofs_route_table;
73 3 : ip->arp_table_idx = 0;
74 3 : ip->route_table_idx = 0;
75 :
76 : /* set magic last, after a fence */
77 3 : FD_COMPILER_MFENCE();
78 3 : ip->magic = FD_IP_MAGIC;
79 :
80 3 : return (void*)ip;
81 3 : }
82 :
83 :
84 : fd_ip_t *
85 3 : fd_ip_join( void * mem ) {
86 3 : if( !mem ) {
87 0 : FD_LOG_ERR(( "Attempt to fd_ip_join a NULL" ));
88 0 : return NULL;
89 0 : }
90 :
91 3 : if( !fd_ulong_is_aligned( (ulong)mem, FD_IP_ALIGN ) ) {
92 0 : FD_LOG_ERR(( "Attempt to fd_ip_join with unaligned memory" ));
93 0 : return NULL;
94 0 : }
95 :
96 3 : fd_ip_t * ip = (fd_ip_t*)mem;
97 :
98 3 : if( ip->magic != FD_IP_MAGIC ) {
99 0 : FD_LOG_ERR(( "Failed to fd_ip_join. Possibly memory corrupt" ));
100 0 : return NULL;
101 0 : }
102 :
103 : /* initialize netlink */
104 3 : fd_nl_t * netlink = fd_ip_netlink_get( ip );
105 3 : if( fd_nl_init( netlink, 0 ) ) {
106 0 : FD_LOG_ERR(( "Failed to initialize fd_netlink." ));
107 0 : return NULL;
108 0 : }
109 :
110 3 : return ip;
111 3 : }
112 :
113 :
114 : void *
115 3 : fd_ip_leave( fd_ip_t * ip ) {
116 3 : if( !ip ) {
117 0 : return NULL;
118 0 : }
119 :
120 : /* clear out the magic first */
121 3 : ip->magic = 0;
122 :
123 : /* then fence */
124 3 : FD_COMPILER_MFENCE();
125 :
126 : /* finalize the netlink */
127 3 : fd_nl_t * netlink = fd_ip_netlink_get( ip );
128 3 : fd_nl_fini( netlink );
129 :
130 3 : fd_memset( ip, 0, sizeof( *ip ) );
131 :
132 3 : return (void*)ip;
133 3 : }
134 :
135 :
136 : /* get pointer to fd_nl_t */
137 : fd_nl_t *
138 6 : fd_ip_netlink_get( fd_ip_t * ip ) {
139 6 : ulong mem = (ulong)ip;
140 :
141 6 : return (fd_nl_t*)( mem + ip->ofs_netlink );
142 6 : }
143 :
144 :
145 : /* get pointer to start of current routing table */
146 : fd_ip_route_entry_t *
147 129 : fd_ip_route_table_get( fd_ip_t * ip ) {
148 129 : ulong mem = (ulong)ip;
149 :
150 : /* find the first table from the offset */
151 129 : fd_ip_route_entry_t * first_table = (fd_ip_route_entry_t*)( mem + ip->ofs_route_table );
152 :
153 : /* find the table index */
154 129 : uint idx = ip->route_table_idx;
155 :
156 : /* offset to the current table */
157 129 : return first_table + ( idx * ip->num_route_entries );
158 129 : }
159 :
160 :
161 : /* get pointer to start of alternate routing table */
162 : fd_ip_route_entry_t *
163 0 : fd_ip_route_table_get_alt( fd_ip_t * ip ) {
164 0 : ulong mem = (ulong)ip;
165 :
166 : /* find the first table from the offset */
167 0 : fd_ip_route_entry_t * first_table = (fd_ip_route_entry_t*)( mem + ip->ofs_route_table );
168 :
169 : /* find the table index of the alternate table */
170 0 : uint idx = !ip->route_table_idx;
171 :
172 : /* offset to the alternate table */
173 0 : return first_table + ( idx * ip->num_route_entries );
174 0 : }
175 :
176 :
177 : /* get pointer to start of current arp table */
178 : fd_ip_arp_entry_t *
179 117 : fd_ip_arp_table_get( fd_ip_t * ip ) {
180 117 : ulong mem = (ulong)ip;
181 :
182 : /* find the table from the offset */
183 117 : fd_ip_arp_entry_t * first_table = (fd_ip_arp_entry_t*)( mem + ip->ofs_arp_table );
184 :
185 : /* find the table index */
186 117 : uint idx = ip->arp_table_idx;
187 :
188 : /* offset to the current table */
189 117 : return first_table + ( idx * ip->num_arp_entries );
190 117 : }
191 :
192 :
193 : /* get pointer to start of alternate arp table */
194 : fd_ip_arp_entry_t *
195 0 : fd_ip_arp_table_get_alt( fd_ip_t * ip ) {
196 0 : ulong mem = (ulong)ip;
197 :
198 : /* find the table from the offset */
199 0 : fd_ip_arp_entry_t * first_table = (fd_ip_arp_entry_t*)( mem + ip->ofs_arp_table );
200 :
201 : /* find the table index */
202 0 : uint idx = !ip->arp_table_idx;
203 :
204 : /* offset to the alternate table */
205 0 : return first_table + ( idx * ip->num_arp_entries );
206 0 : }
207 :
208 :
209 : void
210 0 : fd_ip_arp_fetch( fd_ip_t * ip ) {
211 : /* we fetch into a temp space. If it is successful we switch temp and current */
212 :
213 : /* get pointer to alt table */
214 0 : fd_ip_arp_entry_t * alt_arp_table = fd_ip_arp_table_get_alt( ip );
215 0 : ulong arp_table_cap = ip->num_arp_entries;
216 0 : fd_nl_t * netlink = fd_ip_netlink_get( ip );
217 :
218 0 : long num_entries = fd_nl_load_arp_table( netlink, alt_arp_table, arp_table_cap );
219 :
220 0 : if( num_entries <= 0L ) {
221 : /* don't switch */
222 0 : return;
223 0 : }
224 :
225 : /* success - switch to other table */
226 0 : ip->arp_table_idx ^= 1;
227 0 : ip->cur_num_arp_entries = (ulong)num_entries;
228 0 : }
229 :
230 :
231 : /* query an arp entry
232 :
233 : searches for an IP address in the table
234 :
235 : if found, the resulting data is written into the destination and the function
236 : returns FD_IP_SUCCESS
237 :
238 : otherwise, the function returns FD_IP_ERROR */
239 :
240 : int
241 : fd_ip_arp_query( fd_ip_t * ip,
242 : fd_ip_arp_entry_t ** arp,
243 114 : uint ip_addr ) {
244 114 : fd_ip_arp_entry_t * arp_table = fd_ip_arp_table_get( ip );
245 114 : ulong arp_table_sz = ip->cur_num_arp_entries;
246 :
247 114 : fd_ip_arp_entry_t * entry = fd_nl_arp_query( arp_table, arp_table_sz, ip_addr );
248 114 : if( FD_UNLIKELY( !entry ) ) return FD_IP_ERROR;
249 :
250 96 : *arp = entry;
251 :
252 96 : return FD_IP_SUCCESS;
253 114 : }
254 :
255 :
256 : /* generate a raw ARP packet
257 :
258 : used for caller to generate an ARP packet to send in the event
259 : we don't have an existing ARP entry
260 :
261 : writes ARP packet into dest
262 :
263 : if successful, returns FD_IP_SUCCESS
264 :
265 : if unable to generate ARP, if the dest capacity (dest_cap) is not enough space
266 : then the function returns FD_IP_ERROR */
267 :
268 : int
269 : fd_ip_arp_gen_arp_probe( uchar * buf,
270 : ulong buf_cap,
271 : ulong * arp_len,
272 : uint dst_ip_addr,
273 : uint src_ip_addr,
274 0 : uchar const * src_mac_addr ) {
275 0 : if( buf_cap < sizeof( fd_ip_arp_t ) ) {
276 0 : return FD_IP_ERROR;
277 0 : }
278 :
279 : /* convert ip_addr */
280 0 : uint net_dst_ip_addr = htonl( dst_ip_addr );
281 0 : uint net_src_ip_addr = htonl( src_ip_addr );
282 :
283 0 : fd_ip_arp_t * arp = (fd_ip_arp_t*)buf;
284 :
285 0 : fd_memset( arp->dst_mac_addr, 0xff, 6 ); /* set broadcast */
286 0 : fd_memcpy( arp->src_mac_addr, src_mac_addr, 6 ); /* source mac address */
287 :
288 0 : arp->ethtype = htons( 0x0806 ); /* Ethertype - ARP is 0x0806 */
289 0 : arp->hw_type = htons( 1 ); /* Ethernet is 1 */
290 0 : arp->proto_type = htons( 0x0800 ); /* IP is 0x0800 */
291 0 : arp->hw_addr_len = 6; /* hardware address length - ethernet is 6 */
292 0 : arp->proto_addr_len = 4; /* protocol address length - IPv4 is 4 */
293 0 : arp->op = htons( 1 ); /* operation - request is 1 */
294 :
295 0 : fd_memcpy( arp->sender_hw_addr, src_mac_addr, 6 ); /* sender hardware address */
296 0 : fd_memcpy( arp->sender_proto_addr, &net_src_ip_addr, 4 ); /* sender protocol (IPv4) address */
297 :
298 0 : fd_memset( arp->target_hw_addr, 0, 6 ); /* target hardware address - ignored for request */
299 0 : fd_memcpy( arp->target_proto_addr, &net_dst_ip_addr, 4 ); /* target protocol (IPv4) address - ignored for request */
300 :
301 0 : if( arp_len ) *arp_len = sizeof( *arp );
302 :
303 0 : return FD_IP_SUCCESS;
304 0 : }
305 :
306 :
307 : /* fetch the routing table from the kernel
308 :
309 : the routing table will be written into the workspace, completely replacing
310 : any existing routing entries */
311 :
312 : void
313 0 : fd_ip_route_fetch( fd_ip_t * ip ) {
314 0 : fd_ip_route_entry_t * alt_route_table = fd_ip_route_table_get_alt( ip );
315 0 : ulong route_table_cap = ip->num_route_entries;
316 0 : fd_nl_t * netlink = fd_ip_netlink_get( ip );
317 :
318 0 : long num_entries = fd_nl_load_route_table( netlink, alt_route_table, route_table_cap );
319 :
320 0 : if( FD_UNLIKELY( num_entries < 0 ) ) {
321 : /* as a workaround for some systems that return EBUSY, but succeed
322 : * immediately after, we will retry once */
323 0 : num_entries = fd_nl_load_route_table( netlink, alt_route_table, route_table_cap );
324 0 : }
325 :
326 0 : if( FD_UNLIKELY( num_entries <= 0L ) ) {
327 : /* don't switch */
328 0 : return;
329 0 : }
330 :
331 : /* switch to new table */
332 0 : ip->route_table_idx ^= 1U;
333 0 : ip->cur_num_route_entries = (ulong)num_entries;
334 0 : }
335 :
336 : /* query the routing table
337 :
338 : the provided IP address is looked up in the routing table
339 :
340 : if an appropriate entry is found, the details are written into
341 : the destination and FD_IP_SUCCESS is returned
342 :
343 : otherwise, FD_IP_ERROR is returned */
344 :
345 : int
346 : fd_ip_route_query( fd_ip_t * ip,
347 : fd_ip_route_entry_t ** route,
348 126 : uint ip_addr ) {
349 126 : fd_ip_route_entry_t * route_table = fd_ip_route_table_get( ip );
350 126 : ulong route_table_cap = ip->num_route_entries;
351 :
352 126 : if( FD_UNLIKELY( route_table_cap == 0 ) ) return FD_IP_RETRY;
353 :
354 126 : fd_ip_route_entry_t * entry = fd_nl_route_query( route_table, route_table_cap, ip_addr );
355 :
356 126 : if( FD_UNLIKELY( !entry ) ) return FD_IP_NO_ROUTE;
357 :
358 114 : *route = entry;
359 :
360 114 : return FD_IP_SUCCESS;
361 126 : }
362 :
363 :
364 : int
365 : fd_ip_route_ip_addr( uchar * out_dst_mac,
366 : uint * out_next_ip_addr,
367 : uint * out_ifindex,
368 : fd_ip_t * ip,
369 132 : uint ip_addr ) {
370 : /* handle broadcasts and multicast */
371 :
372 : /* multicast is 224.0.0.0/4 */
373 132 : if( ( ip_addr & 0xf0000000 ) == 0xe0000000 ) {
374 : /* multicast */
375 :
376 : /* map to ethernet space */
377 6 : out_dst_mac[0] = 0x01;
378 6 : out_dst_mac[1] = 0x00;
379 6 : out_dst_mac[2] = 0x5e;
380 6 : out_dst_mac[3] = (uchar)( ( ip_addr >> 020 ) & 0x7fU );
381 6 : out_dst_mac[4] = (uchar)( ( ip_addr >> 010 ) & 0xffU );
382 6 : out_dst_mac[5] = (uchar)( ( ip_addr >> 000 ) & 0xffU );
383 :
384 6 : return FD_IP_MULTICAST;
385 6 : }
386 :
387 126 : if( ip_addr == 0xffffffff ) {
388 : /* broadcast */
389 0 : out_dst_mac[0] = 0xffU;
390 0 : out_dst_mac[1] = 0xffU;
391 0 : out_dst_mac[2] = 0xffU;
392 0 : out_dst_mac[3] = 0xffU;
393 0 : out_dst_mac[4] = 0xffU;
394 0 : out_dst_mac[5] = 0xffU;
395 :
396 0 : return FD_IP_BROADCAST;
397 0 : }
398 :
399 : /* query routing table */
400 126 : fd_ip_route_entry_t * route_entry = NULL;
401 126 : int route_rtn = fd_ip_route_query( ip, &route_entry, ip_addr );
402 126 : if( route_rtn != FD_IP_SUCCESS ) {
403 12 : return route_rtn; /* no routing entry */
404 12 : }
405 :
406 : /* routing entry found */
407 :
408 114 : uint next_ip_addr = ip_addr; /* assume local */
409 :
410 : /* which ip address to use?
411 : if the routing entry has a gateway, use that */
412 114 : if( route_entry->nh_ip_addr ) {
413 78 : next_ip_addr = route_entry->nh_ip_addr; /* use next hop */
414 78 : } else {
415 36 : next_ip_addr = ip_addr;
416 36 : }
417 :
418 : /* have a next IP address, so look up ARP table */
419 :
420 : /* set out_next_ip_addr and out_ifindex */
421 114 : *out_next_ip_addr = next_ip_addr;
422 114 : *out_ifindex = route_entry->oif;
423 :
424 : /* query ARP table */
425 114 : fd_ip_arp_entry_t * arp_entry = NULL;
426 114 : int arp_rtn = fd_ip_arp_query( ip, &arp_entry, next_ip_addr );
427 114 : if( arp_rtn != FD_IP_SUCCESS ) {
428 18 : return FD_IP_PROBE_RQD; /* no entry, so send probe */
429 18 : }
430 :
431 : /* arp entry found - store mac addr in out_dst_mac */
432 96 : fd_memcpy( out_dst_mac, arp_entry->mac_addr, 6 );
433 :
434 : /* check the status */
435 : /* both NUD_REACHABLE and NUD_PERMANENT are acceptable */
436 96 : uint accept_states = (uint)NUD_REACHABLE | (uint)NUD_PERMANENT;
437 96 : if( FD_LIKELY( (uint)arp_entry->state & accept_states ) ) return FD_IP_SUCCESS;
438 :
439 : /* all other statutes, try probing */
440 0 : return FD_IP_PROBE_RQD;
441 96 : }
442 :
443 :
444 : int
445 : fd_ip_update_arp_table( fd_ip_t * ip,
446 : uint ip_addr,
447 0 : uint ifindex ) {
448 0 : fd_nl_t * netlink = fd_ip_netlink_get( ip );
449 :
450 : /* ensure the table is up-to-date */
451 0 : fd_ip_arp_fetch( ip );
452 :
453 : /* query the table */
454 0 : fd_ip_arp_entry_t * arp = NULL;
455 :
456 : /* if entry already exists, return success */
457 0 : if( fd_ip_arp_query( ip, &arp, ip_addr ) == FD_IP_SUCCESS ) return FD_IP_SUCCESS;
458 :
459 0 : return fd_nl_update_arp_table( netlink,
460 0 : fd_ip_arp_table_get(ip),
461 0 : ip->num_arp_entries,
462 0 : ip_addr,
463 0 : ifindex );
464 0 : }
|