Line data Source code
1 : #include <sys/types.h>
2 : #include <sys/socket.h>
3 : #include <sys/ioctl.h>
4 : #include <linux/netlink.h>
5 : #include <linux/rtnetlink.h>
6 : #include <linux/neighbour.h>
7 : #include <arpa/inet.h>
8 : #include <errno.h>
9 : #include <unistd.h>
10 :
11 : #include "fd_netlink.h"
12 : #include "../../util/cstr/fd_cstr.h"
13 :
14 : void
15 : fd_dump_nla_err( struct nlmsghdr * nlh, uint ip_addr, uint ifindex );
16 :
17 : #define FD_NL_DATATYPE(X,...) \
18 : X( FD_NL_IGN , 1 , ignore , __VA_ARGS__ ) \
19 : X( FD_NL_CHAR , 0 , int , __VA_ARGS__ ) \
20 : X( FD_NL_SHORT , 0 , int , __VA_ARGS__ ) \
21 : X( FD_NL_INT , 0 , int , __VA_ARGS__ ) \
22 : X( FD_NL_ADDR , 0 , addr , __VA_ARGS__ )
23 :
24 : #define FD_NL_RTA_TYPE(X,...) \
25 0 : X( RTA_UNSPEC , FD_NL_IGN , "ignored" , __VA_ARGS__ ) \
26 0 : X( RTA_DST , FD_NL_ADDR , "Route destination address" , __VA_ARGS__ ) \
27 0 : X( RTA_SRC , FD_NL_ADDR , "Route source address" , __VA_ARGS__ ) \
28 0 : X( RTA_IIF , FD_NL_INT , "Input interface index" , __VA_ARGS__ ) \
29 0 : X( RTA_OIF , FD_NL_INT , "Output interface index" , __VA_ARGS__ ) \
30 0 : X( RTA_GATEWAY , FD_NL_ADDR , "The gateway of the route" , __VA_ARGS__ ) \
31 0 : X( RTA_PRIORITY , FD_NL_INT , "Priority of route" , __VA_ARGS__ ) \
32 0 : X( RTA_PREFSRC , FD_NL_ADDR , "Preferred source address" , __VA_ARGS__ ) \
33 0 : X( RTA_METRICS , FD_NL_INT , "Route metric" , __VA_ARGS__ ) \
34 0 : X( RTA_MULTIPATH , FD_NL_IGN , "Multipath nexthop data br" , __VA_ARGS__ ) \
35 0 : X( RTA_PROTOINFO , FD_NL_IGN , "RTA_PROTOINFO No longer used" , __VA_ARGS__ ) \
36 0 : X( RTA_FLOW , FD_NL_INT , "Route realm" , __VA_ARGS__ ) \
37 0 : X( RTA_CACHEINFO , FD_NL_IGN , "Cache info" , __VA_ARGS__ ) \
38 0 : X( RTA_SESSION , FD_NL_IGN , "RTA_SESSION No longer used" , __VA_ARGS__ ) \
39 0 : X( RTA_MP_ALGO , FD_NL_IGN , "RTA_MP_ALGO No longer used" , __VA_ARGS__ ) \
40 0 : X( RTA_TABLE , FD_NL_INT , "Routing table ID; if set," , __VA_ARGS__ ) \
41 0 : X( RTA_MARK , FD_NL_INT , "RTA_MARK" , __VA_ARGS__ ) \
42 0 : X( RTA_MFC_STATS , FD_NL_IGN , "RTA_MFC_STATS" , __VA_ARGS__ ) \
43 0 : X( RTA_VIA , FD_NL_IGN , "Gateway in different AF" , __VA_ARGS__ ) \
44 0 : X( RTA_NEWDST , FD_NL_ADDR , "Change packet destination" , __VA_ARGS__ ) \
45 0 : X( RTA_PREF , FD_NL_CHAR , "RFC4191 IPv6 router" , __VA_ARGS__ ) \
46 0 : X( RTA_ENCAP_TYPE , FD_NL_SHORT , "Encapsulation type" , __VA_ARGS__ ) \
47 0 : X( RTA_ENCAP , FD_NL_IGN , "Defined by RTA_ENCAP_TYPE" , __VA_ARGS__ ) \
48 0 : X( RTA_EXPIRES , FD_NL_INT , "Expire time for IPv6" , __VA_ARGS__ )
49 :
50 : char const *
51 0 : fd_rta_type_to_label( uint rta_type ) {
52 0 : # define FD_RTA_MATCH( LABEL, CLASS, DESC, ... ) \
53 0 : if( rta_type == LABEL ) return #LABEL;
54 0 : FD_NL_RTA_TYPE(FD_RTA_MATCH,x)
55 0 : return "RTA_UNKNOWN";
56 0 : # undef FD_RTA_MATCH
57 0 : }
58 :
59 : char const *
60 0 : fd_rta_type_to_class( uint rta_type ) {
61 0 : # define FD_RTA_MATCH( LABEL, CLASS, DESC, ... ) \
62 0 : if( rta_type == LABEL ) return #CLASS;
63 0 : FD_NL_RTA_TYPE(FD_RTA_MATCH,x)
64 0 : return "RTA_UNKNOWN";
65 0 : # undef FD_RTA_MATCH
66 0 : }
67 :
68 : #define FD_NL_RTM_TYPE(X,...) \
69 0 : X( RTN_UNSPEC , "unknown route" , __VA_ARGS__ ) \
70 0 : X( RTN_UNICAST , "a gateway or direct route" , __VA_ARGS__ ) \
71 0 : X( RTN_LOCAL , "a local interface route" , __VA_ARGS__ ) \
72 0 : X( RTN_BROADCAST , "a local broadcast route (sent as a broadcast)" , __VA_ARGS__ ) \
73 0 : X( RTN_ANYCAST , "a local broadcast route (sent as a unicast)" , __VA_ARGS__ ) \
74 0 : X( RTN_MULTICAST , "a multicast route" , __VA_ARGS__ ) \
75 0 : X( RTN_BLACKHOLE , "a packet dropping route" , __VA_ARGS__ ) \
76 0 : X( RTN_UNREACHABLE , "an unreachable destination" , __VA_ARGS__ ) \
77 0 : X( RTN_PROHIBIT , "a packet rejection route" , __VA_ARGS__ ) \
78 0 : X( RTN_THROW , "continue routing lookup in another table" , __VA_ARGS__ ) \
79 0 : X( RTN_NAT , "a network address translation rule" , __VA_ARGS__ ) \
80 0 : X( RTN_XRESOLVE , "refer to an external resolver (not implemented)" , __VA_ARGS__ )
81 :
82 : char const *
83 0 : fd_rtm_type_to_label( uint rtm_type ) {
84 0 : # define FD_RTN_MATCH( LABEL, ... ) \
85 0 : if( rtm_type == LABEL ) return #LABEL;
86 0 : FD_NL_RTM_TYPE(FD_RTN_MATCH,y)
87 0 : return "RTN_UNKNOWN";
88 0 : # undef FD_RTN_MATCH
89 0 : }
90 :
91 : #ifndef NDA_FDB_EXT_ATTRS
92 : /* Some older kernel headers might not have this defined */
93 : #define NDA_FDB_EXT_ATTRS (14)
94 : #endif
95 :
96 : #define FD_NL_NDA_TYPE(X,...) \
97 0 : X( NDA_UNSPEC , __VA_ARGS__ ) \
98 0 : X( NDA_DST , __VA_ARGS__ ) \
99 0 : X( NDA_LLADDR , __VA_ARGS__ ) \
100 0 : X( NDA_CACHEINFO , __VA_ARGS__ ) \
101 0 : X( NDA_PROBES , __VA_ARGS__ ) \
102 0 : X( NDA_VLAN , __VA_ARGS__ ) \
103 0 : X( NDA_PORT , __VA_ARGS__ ) \
104 0 : X( NDA_VNI , __VA_ARGS__ ) \
105 0 : X( NDA_IFINDEX , __VA_ARGS__ ) \
106 0 : X( NDA_MASTER , __VA_ARGS__ ) \
107 0 : X( NDA_LINK_NETNSID , __VA_ARGS__ ) \
108 0 : X( NDA_SRC_VNI , __VA_ARGS__ ) \
109 0 : X( NDA_PROTOCOL , __VA_ARGS__ ) \
110 0 : X( NDA_FDB_EXT_ATTRS , __VA_ARGS__ )
111 :
112 : char const *
113 0 : fd_nda_type_to_label( uint nda_type ) {
114 0 : # define FD_NDA_MATCH( LABEL, ... ) \
115 0 : if( nda_type == LABEL ) return #LABEL;
116 0 : FD_NL_NDA_TYPE(FD_NDA_MATCH,)
117 0 : return "NDA_UNKNOWN";
118 0 : # undef FD_NDA_MATCH
119 0 : }
120 :
121 : /* writes hex from data, length data_sz, into buffer buf, capacity buf_sz.
122 : returns length written in out_len
123 : always leaves a valid nul-terminated string at buf, unless buf_sz == 0,
124 : in which case there isn't space to put a '\0', and the function simply
125 : returns without doing anything
126 : This function is intended for small strings, and so arbitrarily limits
127 : the data to 20 bytes of input, or 40 bytes of hex output characters
128 : plus overhead */
129 : void
130 : fd_nl_write_hex( char * buf,
131 : ulong buf_sz,
132 : ulong * out_len,
133 : uchar * data,
134 0 : ulong data_sz ) {
135 0 : if( FD_UNLIKELY( buf_sz == 0 ) ) {
136 0 : if( FD_LIKELY( out_len ) ) *out_len = 0;
137 0 : return;
138 0 : }
139 :
140 0 : char * p = buf;
141 0 : ulong p_sz = buf_sz;
142 :
143 0 : ulong lcl_out_len = 0;
144 0 : fd_cstr_printf( p, p_sz, &lcl_out_len, "0x" );
145 :
146 0 : p_sz -= lcl_out_len;
147 0 : p += lcl_out_len;
148 :
149 0 : for( ulong j = 0; j < data_sz && p_sz > 0; ++j ) {
150 0 : if( j > 20 ) {
151 0 : fd_cstr_printf( p, p_sz, &lcl_out_len, "..." );
152 0 : p_sz -= lcl_out_len;
153 0 : p += lcl_out_len;
154 :
155 0 : if( FD_LIKELY( out_len ) ) *out_len = (ulong)( p - buf );
156 0 : return;
157 0 : }
158 :
159 0 : fd_cstr_printf( p, p_sz, &lcl_out_len, "%02x", data[j] );
160 0 : p_sz -= lcl_out_len;
161 0 : p += lcl_out_len;
162 0 : }
163 :
164 0 : if( FD_LIKELY( out_len ) ) *out_len = (ulong)( p - buf );
165 0 : return;
166 0 : }
167 :
168 : void
169 0 : fd_nl_dump_rat( struct rtattr * rat, long ratmsglen ) {
170 0 : char buf[2048] = {0};
171 0 : ulong buf_sz = sizeof(buf);
172 :
173 : /* write attributes into a string, and then output */
174 :
175 : /* current pointer and remaining length */
176 0 : char * p = buf;
177 0 : ulong p_sz = buf_sz;
178 :
179 0 : ulong out_len = 0;
180 0 : fd_cstr_printf( buf, buf_sz, &out_len, "netlink diagnostic rtattr: " );
181 :
182 0 : ulong skip = fd_ulong_min( p_sz, out_len );
183 :
184 0 : p_sz -= skip;
185 0 : p += skip;
186 :
187 0 : while( RTA_OK( rat, ratmsglen ) ) {
188 0 : uchar * rta_data = RTA_DATA( rat );
189 0 : ulong rta_data_sz = RTA_PAYLOAD( rat );
190 0 : uint rta_type = (uint)rat->rta_type;
191 :
192 : /* write attribute name */
193 0 : fd_cstr_printf( p, p_sz, &out_len, " %s(%u): ", fd_nda_type_to_label( rta_type ),
194 0 : rta_type );
195 :
196 0 : p += out_len;
197 0 : p_sz -= out_len;
198 :
199 : /* write value in hex */
200 0 : fd_nl_write_hex( p, p_sz, &out_len, rta_data, rta_data_sz );
201 :
202 0 : p += out_len;
203 0 : p_sz -= out_len;
204 :
205 0 : rat = RTA_NEXT( rat, ratmsglen );
206 0 : }
207 :
208 0 : FD_LOG_NOTICE(( "%s", buf ));
209 0 : }
210 :
211 :
212 : int
213 6 : fd_nl_create_socket( void ) {
214 6 : int fd = socket( AF_NETLINK, SOCK_RAW | SOCK_NONBLOCK, NETLINK_ROUTE );
215 :
216 6 : if( fd < 0 ) {
217 0 : FD_LOG_WARNING(( "Unable to create netlink socket. Error: %d %s", errno,
218 0 : strerror( errno ) ));
219 0 : return -1;
220 0 : }
221 :
222 6 : int one = 1;
223 6 : if( setsockopt( fd, SOL_NETLINK, NETLINK_EXT_ACK,
224 6 : &one, sizeof(one) ) < 0 ) {
225 0 : FD_LOG_WARNING(( "Netlink error reporting not supported" ));
226 : /* continue regardless */
227 0 : }
228 :
229 6 : struct sockaddr_nl sa;
230 6 : fd_memset( &sa, 0, sizeof(sa) );
231 6 : sa.nl_family = AF_NETLINK;
232 6 : if( bind( fd, (void*)&sa, sizeof(sa) ) < 0 ) {
233 0 : FD_LOG_WARNING(( "Unable to bind netlink socket. Error: %d %s",
234 0 : errno, strerror( errno ) ));
235 0 : close( fd );
236 0 : return -1;
237 0 : }
238 :
239 6 : return fd;
240 6 : }
241 :
242 :
243 : void
244 3 : fd_nl_close_socket( int fd ) {
245 3 : if( fd >= 0 ) {
246 3 : close( fd );
247 3 : }
248 3 : }
249 :
250 : long
251 12 : fd_nl_read_socket( int fd, uchar * buf, ulong buf_sz ) {
252 : /* netlink is datagram based
253 : once a recv succeeds, any un-received bytes are lost
254 : and the next datagram will be properly aligned in the buffer */
255 12 : long len = -1;
256 12 : do {
257 12 : len = recv( fd, buf, buf_sz, 0 );
258 12 : } while( FD_UNLIKELY( ( len < 0 && errno == EINTR ) || len == 0 ) );
259 :
260 12 : if( FD_UNLIKELY( len < 0 ) ) {
261 0 : if( errno == EAGAIN ) {
262 : /* EAGAIN means no data. We can simply try again later */
263 0 : return -1;
264 0 : }
265 :
266 0 : FD_LOG_WARNING(( "netlink recv failed with %d %s", errno, strerror( errno ) ));
267 0 : return -1;
268 0 : }
269 :
270 12 : return len;
271 12 : }
272 :
273 : int
274 6 : fd_nl_init( fd_nl_t * nl, uint seq ) {
275 6 : nl->seq = seq;
276 6 : nl->fd = fd_nl_create_socket();
277 6 : nl->init = 1;
278 :
279 : /* returns 1 for failure, 0 for success */
280 6 : return nl->fd < 0 ? 1 : 0;
281 6 : }
282 :
283 : void
284 3 : fd_nl_fini( fd_nl_t * nl ) {
285 3 : fd_nl_close_socket( nl->fd );
286 3 : }
287 :
288 :
289 : long
290 : fd_nl_load_route_table( fd_nl_t * nl,
291 : fd_nl_route_entry_t * route_table,
292 3 : ulong route_table_cap ) {
293 3 : int fd = nl->fd;
294 3 : if( fd < 0 ) {
295 0 : FD_LOG_ERR(( "fd_nl_load_route_table called without valid file descriptor" ));
296 0 : return -1;
297 0 : }
298 :
299 : /* format the request */
300 :
301 : /* Request struct */
302 3 : struct {
303 3 : struct nlmsghdr nlh; /* Netlink header */
304 3 : struct rtmsg rtm; /* Payload - route message */
305 3 : } nl_request;
306 :
307 3 : fd_memset( &nl_request, 0, sizeof( nl_request ) );
308 :
309 3 : uint seq = nl->seq++;
310 :
311 3 : nl_request.nlh.nlmsg_type = RTM_GETROUTE; /* We wish to get routes */
312 : #if 0
313 : /* CAP_NET_ADMIN required for NLM_F_ATOMIC */
314 : nl_request.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT | NLM_F_ATOMIC;
315 : #else
316 3 : nl_request.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
317 3 : #endif
318 3 : nl_request.nlh.nlmsg_len = sizeof(nl_request);
319 3 : nl_request.nlh.nlmsg_pid = 0;
320 3 : nl_request.nlh.nlmsg_seq = seq;
321 :
322 : /* request only IPv4 routes */
323 3 : nl_request.rtm.rtm_family = AF_INET;
324 :
325 3 : ulong send_sz = sizeof(nl_request);
326 3 : long sent = send( fd, &nl_request, send_sz, 0 );
327 3 : if( sent == -1 ) {
328 0 : FD_LOG_WARNING(( "Unable to make netlink request. Error: %d %s", errno, strerror( errno ) ));
329 0 : return -1;
330 0 : }
331 :
332 3 : if( sent != (long)send_sz ) {
333 0 : FD_LOG_WARNING(( "netlink send returned unexpected size: %ld expected: %lu", sent, send_sz ));
334 0 : return -1;
335 0 : }
336 :
337 : /* receiving */
338 3 : long len;
339 :
340 : /* set alignment such that the returned data is aligned */
341 3 : uchar __attribute__(( aligned(16) )) ibuf[FD_NL_BUF_SZ] = {0};
342 :
343 : /* Pointer to the messages head */
344 3 : struct nlmsghdr *h = (struct nlmsghdr *)ibuf;
345 :
346 3 : while(1) {
347 3 : len = fd_nl_read_socket( fd, ibuf, sizeof(ibuf) );
348 3 : if( len <= 0 ) {
349 0 : return -1;
350 0 : }
351 :
352 3 : if( h->nlmsg_seq == seq ) break;
353 3 : }
354 :
355 : /* index into route_entries */
356 3 : long route_entry_idx = 0L;
357 :
358 : /* interpret the response */
359 :
360 3 : long msglen = len;
361 :
362 : /* Iterate through all messages in buffer */
363 :
364 33 : for( ; NLMSG_OK( h, msglen ); h = NLMSG_NEXT( h, msglen ) ) {
365 : /* are we still within the bounds of the table? */
366 33 : if( route_entry_idx >= (long)route_table_cap ) {
367 : /* we filled the table */
368 0 : FD_LOG_ERR(( "fd_nl_load_route_table, but table larger than reserved storage" ));
369 0 : return -1; /* return failure */
370 0 : }
371 :
372 : /* current route entry */
373 33 : fd_nl_route_entry_t * entry = route_table + route_entry_idx;
374 :
375 : /* clear current entry */
376 33 : fd_memset( entry, 0, sizeof( *entry ) );
377 :
378 33 : if( h->nlmsg_flags & NLM_F_DUMP_INTR ) {
379 : /* function was interrupted */
380 :
381 0 : return -1; /* return failure */
382 0 : }
383 :
384 33 : if( FD_UNLIKELY( h->nlmsg_type == NLMSG_ERROR ) ) {
385 0 : struct nlmsgerr * err = (struct nlmsgerr*)NLMSG_DATA(h);
386 :
387 : /* acknowledgements have no error */
388 0 : if( FD_LIKELY( !err->error ) ) {
389 0 : continue;
390 0 : }
391 :
392 0 : if( FD_LIKELY( err->error == -EBUSY ) ) {
393 : /* a workaround for some systems which return EBUSY, we will
394 : * not log, but instead retry one more time */
395 0 : return -1;
396 0 : }
397 :
398 0 : FD_LOG_WARNING(( "netlink returned data with error: %d %s", -err->error, strerror( -err->error) ));
399 :
400 : /* error occurred */
401 :
402 0 : return -1; /* return failure */
403 0 : }
404 :
405 33 : struct rtmsg * msg = NLMSG_DATA( h );
406 33 : struct rtattr * rat = RTM_RTA(msg);
407 33 : long ratmsglen = (long)RTM_PAYLOAD(h);
408 :
409 : /* only care about RTN_UNICAST */
410 33 : if( msg->rtm_type == RTN_UNICAST ) {
411 :
412 42 : while( RTA_OK( rat, ratmsglen ) ) {
413 42 : uchar * rta_data = RTA_DATA( rat );
414 42 : ulong rta_data_sz = RTA_PAYLOAD( rat );
415 :
416 42 : switch( rat->rta_type ) {
417 3 : case RTA_GATEWAY:
418 3 : if( rta_data_sz != 4 ) {
419 0 : FD_LOG_WARNING(( "Routing entry has gateway address with other than"
420 0 : " 4 byte address" ));
421 3 : } else {
422 3 : uint nh_ip_addr;
423 3 : memcpy( &nh_ip_addr, rta_data, 4 );
424 :
425 3 : entry->nh_ip_addr = ntohl( nh_ip_addr );
426 :
427 3 : entry->flags |= FD_NL_RT_FLAGS_NH_IP_ADDR;
428 3 : }
429 3 : break;
430 :
431 6 : case RTA_DST:
432 6 : if( rta_data_sz != 4 ) {
433 0 : FD_LOG_WARNING(( "Routing entry has destination address with other than"
434 0 : " 4 byte destination address" ));
435 6 : } else {
436 6 : uint nh_ip_addr;
437 6 : memcpy( &nh_ip_addr, rta_data, 4 );
438 :
439 6 : entry->dst_netmask = (uint)( 0xffffffff00000000LU >> (ulong)msg->rtm_dst_len );
440 6 : entry->dst_netmask_sz = (uint)msg->rtm_dst_len;
441 6 : entry->dst_ip_addr = ntohl( nh_ip_addr ) & entry->dst_netmask;
442 :
443 6 : entry->flags |= FD_NL_RT_FLAGS_DST_IP_ADDR | FD_NL_RT_FLAGS_DST_NETMASK;
444 6 : }
445 6 : break;
446 :
447 9 : case RTA_OIF:
448 9 : if( rta_data_sz != 4 ) {
449 0 : FD_LOG_WARNING(( "Routing entry has output interface with other than"
450 0 : " 4 byte index" ));
451 9 : } else {
452 9 : memcpy( &entry->oif, rta_data, 4 );
453 :
454 9 : entry->flags |= FD_NL_RT_FLAGS_OIF;
455 9 : }
456 9 : break;
457 :
458 9 : case RTA_PREFSRC:
459 9 : if( rta_data_sz != 4 ) {
460 0 : FD_LOG_WARNING(( "Routing entry has destination address with other than"
461 0 : " 4 byte destination address" ));
462 9 : } else {
463 9 : uint src_ip_addr;
464 9 : memcpy( &src_ip_addr, rta_data, 4 );
465 :
466 9 : entry->src_ip_addr = ntohl( src_ip_addr );
467 :
468 9 : entry->flags |= FD_NL_RT_FLAGS_SRC_IP_ADDR;
469 9 : }
470 9 : break;
471 :
472 0 : case RTA_MULTIPATH:
473 0 : case RTA_VIA:
474 : /* not currently supported */
475 0 : FD_LOG_WARNING(( "Routing entry contains an unsupported feature: %s",
476 0 : fd_rta_type_to_label( rat->rta_type ) ));
477 0 : entry->flags |= FD_NL_RT_FLAGS_UNSUPPORTED;
478 0 : break;
479 42 : }
480 :
481 42 : if( entry->flags & FD_NL_RT_FLAGS_UNSUPPORTED ) {
482 0 : break;
483 0 : }
484 :
485 42 : rat = RTA_NEXT( rat, ratmsglen );
486 42 : }
487 9 : }
488 :
489 : /* the FD_NL_RT_FLAGS_UNSUPPORTED flags must not be present */
490 33 : if( ( entry->flags & FD_NL_RT_FLAGS_UNSUPPORTED ) == 0 ) {
491 : /* supported combinations of flags */
492 33 : uint rqd0 = FD_NL_RT_FLAGS_DST_IP_ADDR |
493 33 : FD_NL_RT_FLAGS_DST_NETMASK |
494 33 : FD_NL_RT_FLAGS_OIF;
495 33 : uint rqd1 = FD_NL_RT_FLAGS_NH_IP_ADDR |
496 33 : FD_NL_RT_FLAGS_OIF;
497 33 : uint flags = entry->flags;
498 33 : if( ( flags & rqd0 ) == rqd0 || ( flags & rqd1 ) == rqd1 ) {
499 9 : entry->flags |= FD_NL_RT_FLAGS_USED;
500 9 : route_entry_idx++;
501 9 : }
502 33 : }
503 :
504 33 : }
505 :
506 : /* clear remaining entries */
507 90 : for( long j = route_entry_idx; j < (long)route_table_cap; ++j ) {
508 87 : fd_memset( route_table + j, 0, sizeof( route_table[0] ) );
509 87 : }
510 :
511 3 : return route_entry_idx;
512 3 : }
513 :
514 :
515 : fd_nl_route_entry_t *
516 210 : fd_nl_route_query( fd_nl_route_entry_t * route_table, ulong route_table_sz, uint ip_addr ) {
517 210 : long best_idx = -1;
518 210 : uint best_class = -1U;
519 :
520 1572 : for( long j = 0L; j < (long)route_table_sz; ++j ) {
521 1488 : fd_nl_route_entry_t * entry = route_table + j;
522 :
523 : /* the used entries are always contiguous */
524 1488 : if( ( entry->flags & FD_NL_RT_FLAGS_USED ) == 0 ) break;
525 :
526 1362 : uint netmask = entry->dst_netmask;
527 1362 : uint clazz = entry->dst_netmask_sz;
528 1362 : uint nh_net = entry->dst_ip_addr;
529 1362 : uint dst_net = ip_addr & netmask;
530 :
531 1362 : if( nh_net == dst_net && ( best_idx == -1 || clazz > best_class ) ) {
532 195 : best_idx = j;
533 195 : best_class = clazz;
534 195 : }
535 1362 : }
536 :
537 210 : if( best_idx < 0L ) {
538 15 : return NULL;
539 195 : } else {
540 195 : return route_table + best_idx;
541 195 : }
542 210 : }
543 :
544 :
545 : long
546 : fd_nl_load_arp_table( fd_nl_t * nl,
547 : fd_nl_arp_entry_t * arp_table,
548 3 : ulong arp_table_cap ) {
549 3 : int fd = nl->fd;
550 3 : if( fd < 0 ) return FD_IP_ERROR;
551 :
552 : /* format the request */
553 :
554 : /* Request struct */
555 3 : struct {
556 3 : struct nlmsghdr nlh; /* Netlink header */
557 3 : struct ndmsg ndm; /* Payload - neighbor message */
558 3 : } nl_request;
559 :
560 3 : fd_memset( &nl_request, 0, sizeof( nl_request ) );
561 :
562 3 : uint seq = nl->seq++;
563 :
564 3 : nl_request.nlh.nlmsg_type = RTM_GETNEIGH; /* We wish to get neighbors */
565 3 : nl_request.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
566 3 : nl_request.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
567 3 : nl_request.nlh.nlmsg_pid = 0;
568 3 : nl_request.nlh.nlmsg_seq = seq;
569 :
570 : /* request IPv4 entries */
571 3 : nl_request.ndm.ndm_family = AF_INET;
572 :
573 3 : ulong send_sz = sizeof(nl_request);
574 3 : long sent = send( fd, &nl_request, send_sz, 0 );
575 3 : if( sent == -1 ) {
576 0 : FD_LOG_WARNING(( "Unable to make netlink request. Error: %d %s", errno, strerror( errno ) ));
577 0 : return FD_IP_ERROR;
578 0 : }
579 :
580 3 : if( sent != (long)send_sz ) {
581 0 : FD_LOG_WARNING(( "netlink send returned unexpected size: %ld expected: %lu", sent, send_sz ));
582 0 : return FD_IP_ERROR;
583 0 : }
584 :
585 : /* receiving */
586 3 : long len;
587 :
588 : /* set alignment such that the returned data is aligned */
589 3 : uchar __attribute__(( aligned(16) )) ibuf[FD_NL_BUF_SZ] = {0};
590 :
591 : /* index into arp_table */
592 3 : long arp_entry_idx = 0L;
593 :
594 3 : int done = 0;
595 :
596 9 : while( !done ) {
597 :
598 : /* Pointer to the messages head */
599 6 : struct nlmsghdr *h = (struct nlmsghdr *)ibuf;
600 :
601 9 : while(1) {
602 9 : len = fd_nl_read_socket( fd, ibuf, sizeof(ibuf) );
603 9 : if( len <= 0 ) {
604 0 : return FD_IP_ERROR;
605 0 : }
606 :
607 9 : if( h->nlmsg_seq == seq ) break;
608 9 : }
609 :
610 : /* interpret the response */
611 :
612 6 : long msglen = len;
613 :
614 : /* Iterate through all messages in buffer */
615 :
616 12 : while( NLMSG_OK(h, msglen) ) {
617 : /* are we still within the bounds of the table? */
618 12 : if( arp_entry_idx >= (long)arp_table_cap ) {
619 : /* we filled the table */
620 0 : FD_LOG_ERR(( "fd_nl_load_arp_table, but table larger than reserved storage" ));
621 0 : return FD_IP_ERROR; /* return failure */
622 0 : }
623 :
624 : /* current arp entry */
625 12 : fd_nl_arp_entry_t * entry = arp_table + arp_entry_idx;
626 :
627 : /* clear current entry */
628 12 : fd_memset( entry, 0, sizeof( *entry ) );
629 :
630 12 : if( h->nlmsg_flags & NLM_F_DUMP_INTR ) {
631 : /* function was interrupted - don't switch to new routing table */
632 :
633 0 : return FD_IP_RETRY; /* return transient failure */
634 0 : }
635 :
636 12 : if( h->nlmsg_type == NLMSG_ERROR ) {
637 0 : struct nlmsgerr * err = NLMSG_DATA(h);
638 :
639 0 : FD_LOG_WARNING(( "netlink returned data with error: %d %s", -err->error, strerror( -err->error) ));
640 :
641 : /* error occurred - don't switch to new routing table */
642 :
643 0 : return FD_IP_ERROR; /* return failure */
644 0 : }
645 :
646 : /* we're done if:
647 : * type is NLMSG_DONE
648 : * OR nlmsg_flags does not contain NLM_F_MULTI */
649 12 : done |= ( h->nlmsg_type == NLMSG_DONE ) | !( h->nlmsg_flags & NLM_F_MULTI );
650 :
651 12 : struct ndmsg * msg = NLMSG_DATA( h );
652 12 : struct rtattr * rat = RTM_RTA(msg);
653 12 : long ratmsglen = (long)RTM_PAYLOAD( h );
654 :
655 : /* store the interface */
656 12 : entry->ifindex = (uint)msg->ndm_ifindex;
657 12 : entry->flags |= FD_NL_ARP_FLAGS_IFINDEX;
658 :
659 : /* we want to skip some entries based on state
660 : here are the states:
661 : NUD_INCOMPLETE a currently resolving cache entry
662 : NUD_REACHABLE a confirmed working cache entry
663 : NUD_STALE an expired cache entry
664 : NUD_DELAY an entry waiting for a timer
665 : NUD_PROBE a cache entry that is currently reprobed
666 : NUD_FAILED an invalid cache entry
667 : NUD_NOARP a device with no destination cache
668 : NUD_PERMANENT a static entry
669 :
670 : Keeping:
671 : NUD_REACHABLE valid, so use it
672 : NUD_STALE probably better to use the existing address
673 : than wait or discard packet
674 : NUD_DELAY an entry waiting for a timer
675 : NUD_PROBE being reprobed, so it's probably valid
676 : NUD_PERMANENT a static entry, so use it */
677 :
678 36 : while( RTA_OK( rat, ratmsglen ) ) {
679 36 : uchar * rta_data = RTA_DATA( rat );
680 36 : ulong rta_data_sz = RTA_PAYLOAD( rat );
681 :
682 36 : switch( rat->rta_type ) {
683 9 : case NDA_DST:
684 9 : if( rta_data_sz != 4 ) {
685 : /* we only support IPv4 */
686 0 : entry->flags |= FD_NL_ARP_FLAGS_UNSUPPORTED;
687 9 : } else {
688 9 : uint dst_ip_addr;
689 9 : memcpy( &dst_ip_addr, rta_data, 4 );
690 9 : entry->dst_ip_addr = ntohl( dst_ip_addr );
691 :
692 9 : entry->flags |= FD_NL_ARP_FLAGS_IP_ADDR;
693 9 : }
694 9 : break;
695 :
696 9 : case NDA_LLADDR:
697 9 : if( FD_UNLIKELY( rta_data_sz != 6 ) ) {
698 : /* we only support ethernet, but these entries are found */
699 : /* on some machines, so silently skip them */
700 0 : entry->flags |= FD_NL_ARP_FLAGS_UNSUPPORTED;
701 9 : } else {
702 9 : memcpy( &entry->mac_addr[0], rta_data, 6 );
703 :
704 9 : entry->flags |= FD_NL_ARP_FLAGS_MAC_ADDR;
705 9 : }
706 9 : break;
707 :
708 36 : }
709 :
710 36 : if( entry->flags & FD_NL_ARP_FLAGS_UNSUPPORTED ) {
711 0 : break;
712 0 : }
713 :
714 36 : rat = RTA_NEXT( rat, ratmsglen );
715 36 : }
716 :
717 : /* at present, each entry must have at least the following:
718 : FD_NL_ARP_FLAGS_IP_ADDR
719 : FD_NL_ARP_FLAGS_MAC_ADDR
720 : FD_NL_ARP_FLAGS_IFINDEX */
721 :
722 12 : uint required_flags = FD_NL_ARP_FLAGS_IP_ADDR | FD_NL_ARP_FLAGS_MAC_ADDR | FD_NL_ARP_FLAGS_IFINDEX;
723 :
724 : /* the FD_NL_ARP_FLAGS_UNSUPPORTED flags must not be present */
725 12 : if( ( entry->flags & FD_NL_ARP_FLAGS_UNSUPPORTED ) == 0
726 12 : && ( entry->flags & required_flags ) == required_flags ) {
727 9 : entry->flags |= FD_NL_ARP_FLAGS_USED;
728 9 : entry->state = msg->ndm_state;
729 9 : arp_entry_idx++;
730 9 : }
731 :
732 12 : h = NLMSG_NEXT( h, msglen );
733 12 : }
734 6 : }
735 :
736 : /* clear remaining entries */
737 90 : for( long j = arp_entry_idx; j < (long)arp_table_cap; ++j ) {
738 87 : fd_memset( arp_table + j, 0, sizeof( arp_table[0] ) );
739 87 : }
740 :
741 3 : return arp_entry_idx;
742 3 : }
743 :
744 :
745 : fd_nl_arp_entry_t *
746 : fd_nl_arp_query( fd_nl_arp_entry_t * arp_table,
747 : ulong arp_table_sz,
748 138 : uint ip_addr ) {
749 630 : for( long j = 0L; j < (long)arp_table_sz; ++j ) {
750 600 : fd_nl_arp_entry_t * entry = arp_table + j;
751 600 : if( ( entry->flags & FD_NL_ARP_FLAGS_USED ) == 0 ) break;
752 :
753 600 : if( entry->dst_ip_addr == ip_addr ) {
754 108 : return entry;
755 108 : }
756 600 : }
757 :
758 30 : return NULL;
759 138 : }
760 :
761 :
762 : int
763 : fd_nl_update_arp_table( fd_nl_t * nl,
764 : fd_nl_arp_entry_t * arp_table,
765 : ulong arp_table_cap,
766 : uint ip_addr,
767 0 : uint ifindex ) {
768 0 : int rtn = FD_IP_ERROR;
769 :
770 0 : int fd = nl->fd;
771 0 : if( fd < 0 ) {
772 0 : FD_LOG_ERR(( "fd_nl_update_arp_table called with invalid file descriptor" ));
773 0 : return FD_IP_ERROR;
774 0 : }
775 :
776 : /* find the entry, if one exists */
777 0 : int cur_state = -1; /* -1 indicates no existing entry */
778 0 : for( ulong j = 0; j < arp_table_cap; ++j ) {
779 0 : fd_nl_arp_entry_t * entry = &arp_table[j];
780 :
781 : /* indicates the end */
782 0 : if( ( entry->flags & FD_NL_RT_FLAGS_USED ) == 0 ) break;
783 :
784 0 : if( entry->dst_ip_addr == ip_addr ) {
785 0 : cur_state = (int)entry->state;
786 0 : break;
787 0 : }
788 0 : }
789 :
790 : /* ARP state transitions
791 :
792 : NONE ---> INCOMPLETE ---+
793 : |
794 : DELAY ---> PROBE |
795 : ^ | |
796 : | V |
797 : STALE <--- REACHABLE <---+
798 :
799 : PERMANENT and NOARP do not participate */
800 :
801 : /* nothing to do - either in final state, or kernel handles the transition */
802 0 : int rtn_states = (int)( NUD_NOARP |
803 0 : NUD_PERMANENT |
804 0 : NUD_DELAY |
805 0 : NUD_STALE |
806 0 : NUD_REACHABLE );
807 0 : if( cur_state > 0 && ( cur_state & rtn_states ) ) return FD_IP_SUCCESS;
808 :
809 : /* determine next state */
810 0 : int next_state = 0;
811 0 : switch( cur_state ) {
812 0 : case -1: next_state = NUD_NONE; rtn = FD_IP_RETRY; break;
813 0 : case NUD_NONE: next_state = NUD_INCOMPLETE; rtn = FD_IP_PROBE_RQD; break;
814 0 : case NUD_INCOMPLETE: return FD_IP_PROBE_RQD;
815 0 : case NUD_PROBE: return FD_IP_PROBE_RQD;
816 0 : default:
817 0 : FD_LOG_WARNING(( "Unexpected state: %d", cur_state ));
818 0 : return FD_IP_ERROR;
819 0 : }
820 :
821 : /* TODO For NUD_STALE, the kernel may never see UDP packets from the IP
822 : (We steal them)
823 : In this case, the kernel will push to DELAY and then PROBE resulting
824 : in unnecessary ARP probes to be sent.
825 : What we should do is update the stale ARP entry to NUD_REACHABLE when
826 : we receive a packet from local IPs */
827 :
828 0 : uint net_ip_addr = htonl( ip_addr );
829 :
830 : /* format the request */
831 :
832 0 : # define IP_ADDR_LEN 4
833 0 : # define NLMSG_LEN NLMSG_LENGTH( sizeof(struct rtmsg) )
834 0 : # define TOT_SZ ( NLMSG_LEN + RTA_LENGTH(IP_ADDR_LEN) )
835 0 : # define BUF_OFFS ( sizeof( struct nlmsghdr ) + sizeof( struct ndmsg ) )
836 0 : # define BUF_SZ ( TOT_SZ - BUF_OFFS )
837 :
838 : /* Request struct */
839 0 : struct {
840 0 : struct nlmsghdr nlh; /* Netlink header */
841 0 : struct ndmsg ndm; /* Payload - neighbor message */
842 0 : uchar buf[BUF_SZ]; /* sized to match the request exactly */
843 0 : } nl_request;
844 :
845 0 : fd_memset( &nl_request, 0, sizeof( nl_request ) );
846 :
847 0 : uint seq = nl->seq++;
848 :
849 0 : nl_request.nlh.nlmsg_type = RTM_NEWNEIGH; /* We wish to get neighbors */
850 0 : nl_request.nlh.nlmsg_flags = cur_state == -1 ? ( NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL )
851 0 : : ( NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE );
852 0 : nl_request.nlh.nlmsg_len = (uint)NLMSG_LEN;
853 0 : nl_request.nlh.nlmsg_pid = 0;
854 0 : nl_request.nlh.nlmsg_seq = seq;
855 :
856 : /* request IPv4 entries */
857 0 : nl_request.ndm.ndm_family = AF_INET;
858 0 : nl_request.ndm.ndm_ifindex = (int)ifindex;
859 0 : nl_request.ndm.ndm_state = (ushort)next_state;
860 0 : nl_request.ndm.ndm_flags = 0;
861 0 : nl_request.ndm.ndm_type = RTN_UNICAST;
862 :
863 : /* not necessarily defined! */
864 0 : #ifndef NLMSG_TAIL
865 0 : #define NLMSG_TAIL(nmsg) ((struct rtattr *)(((uchar *)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
866 0 : #endif
867 :
868 : /* write required attributes */
869 0 : struct rtattr * rqs_rat = NLMSG_TAIL(&nl_request.nlh);
870 :
871 0 : rqs_rat->rta_type = NDA_DST;
872 0 : rqs_rat->rta_len = RTA_LENGTH(4);
873 :
874 0 : memcpy( RTA_DATA(rqs_rat), &net_ip_addr, 4 );
875 0 : nl_request.nlh.nlmsg_len += (int)sizeof(struct rtattr) + 4;
876 :
877 0 : ulong nl_request_sz = (ulong)nl_request.nlh.nlmsg_len;
878 :
879 0 : if( nl_request_sz != TOT_SZ ) {
880 0 : FD_LOG_ERR(( "request size does not match expected size" ));
881 0 : return FD_IP_ERROR;
882 0 : }
883 :
884 0 : ulong send_sz = nl_request_sz;
885 0 : long sent = send( fd, &nl_request, send_sz, 0 );
886 0 : if( sent == -1 ) {
887 0 : FD_LOG_WARNING(( "Unable to make netlink request. Error: %d %s", errno, strerror( errno ) ));
888 0 : return FD_IP_ERROR;
889 0 : }
890 :
891 0 : if( sent != (long)send_sz ) {
892 0 : FD_LOG_WARNING(( "netlink send returned unexpected size: %ld expected: %lu", sent, send_sz ));
893 0 : return FD_IP_ERROR;
894 0 : }
895 :
896 : /* receiving */
897 0 : long len;
898 :
899 : /* set alignment such that the returned data is aligned */
900 0 : uchar __attribute__(( aligned(16) )) ibuf[FD_NL_BUF_SZ] = {0};
901 :
902 : /* Pointer to the messages head */
903 0 : struct nlmsghdr *h = (struct nlmsghdr *)ibuf;
904 :
905 0 : while(1) {
906 0 : len = fd_nl_read_socket( fd, ibuf, sizeof(ibuf) );
907 0 : if( len <= 0 ) {
908 0 : return FD_IP_ERROR;
909 0 : }
910 :
911 0 : if( h->nlmsg_seq == seq ) break;
912 0 : }
913 :
914 : /* interpret the response */
915 :
916 0 : long msglen = len;
917 :
918 : /* check for errors */
919 :
920 0 : while( NLMSG_OK(h, msglen) ) {
921 0 : if( h->nlmsg_type == NLMSG_ERROR ) {
922 0 : struct nlmsgerr * err = NLMSG_DATA(h);
923 :
924 : /* we are expecting the ARP entry to sometimes exist
925 : so simply return SUCCESS here */
926 0 : if( err->error != -EEXIST && err->error != 0 ) {
927 : /* all other errors are reported */
928 :
929 0 : fd_dump_nla_err( h, ip_addr, ifindex );
930 :
931 : /* error occurred - don't switch to new routing table */
932 :
933 0 : return FD_IP_ERROR; /* return failure */
934 0 : }
935 0 : }
936 :
937 0 : h = NLMSG_NEXT( h, msglen );
938 0 : }
939 :
940 : /* state has changed, reload table */
941 0 : fd_nl_load_arp_table( nl, arp_table, arp_table_cap );
942 :
943 0 : return rtn;
944 0 : }
945 :
946 : /* dump netlink extended ack error message */
947 : void
948 0 : fd_dump_nla_err( struct nlmsghdr * nlh, uint ip_addr, uint ifindex ) {
949 :
950 0 : #define FD_NLA_ERR_DEFS(X,...) \
951 0 : X( msg , NLMSGERR_ATTR_MSG , CSTRING , __VA_ARGS__ ) \
952 0 : X( offs , NLMSGERR_ATTR_OFFS , UINT , __VA_ARGS__ )
953 :
954 0 : # define FD_NLA_MBR(MBR,ATTR,TYPE,...) FD_NLA_MBR_TYPE_##TYPE MBR;
955 0 : # define FD_NLA_MBR_TYPE_CSTRING char const *
956 0 : # define FD_NLA_MBR_TYPE_UINT uint
957 :
958 0 : # define FD_NLA_CPY(MBR,TYPE,DATA) FD_NLA_CPY_##TYPE(MBR,DATA)
959 0 : # define FD_NLA_CPY_CSTRING(MBR,DATA) (char const *)data
960 0 : # define FD_NLA_CPY_UINT(MBR,DATA) *(uint *)data
961 :
962 0 : struct fd_err_attr {
963 0 : FD_NLA_ERR_DEFS(FD_NLA_MBR,x,y)
964 0 : } err_attr = {0};
965 :
966 0 : struct nlmsgerr * err = NLMSG_DATA( nlh );
967 :
968 : /* no extended info, just report what we have */
969 0 : if( ( nlh->nlmsg_flags & NLM_F_ACK_TLVS ) == 0 ) {
970 0 : # define EXPAND_IP4(ip) (((ip)>>24u)&0xffU), (((ip)>>16u)&0xffU), (((ip)>>8u)&0xffU), ((ip)&0xffU)
971 0 : FD_LOG_WARNING(( "netlink returned data with error: %d %s"
972 0 : " adding ip address: %u.%u.%u.%u on ifindex: %u",
973 0 : -err->error, strerror( -err->error ),
974 0 : EXPAND_IP4( (uint)ip_addr ),
975 0 : (uint)ifindex ));
976 0 : # undef EXPAND_IP4
977 0 : return;
978 0 : }
979 :
980 0 : struct nlattr *attr = NULL;
981 :
982 0 : int hlen = sizeof(*err);
983 :
984 : /* if NLM_F_CAPPED is set then the inner err msg was capped */
985 0 : if( !(nlh->nlmsg_flags & NLM_F_CAPPED) ) {
986 0 : hlen += (int)err->msg.nlmsg_len - (int)NLMSG_HDRLEN;
987 0 : }
988 :
989 0 : attr = (struct nlattr *)( (uchar *)err + hlen );
990 0 : int alen = (int)( (uchar *)nlh + nlh->nlmsg_len - (uchar *)attr );
991 :
992 0 : # ifndef NLA_OK
993 0 : # define NLA_OK(NLA,REM) \
994 0 : ( \
995 0 : (REM) >= (int)sizeof(*(NLA)) && \
996 0 : (NLA)->nla_len >= sizeof(*(NLA)) && \
997 0 : (REM) >= (NLA)->nla_len \
998 0 : )
999 0 : # endif
1000 :
1001 0 : # ifndef NLA_NEXT
1002 0 : # define NLA_NEXT_ALIGN(NLA,REM,TOTLEN) \
1003 0 : ( (*(REM) -= (TOTLEN) ), (struct nlattr *)((uchar *)(NLA) + (TOTLEN)) )
1004 0 : # define NLA_NEXT(NLA,REM) NLA_NEXT_ALIGN(NLA,REM,NLA_ALIGN((NLA)->nla_len))
1005 0 : # endif
1006 :
1007 : /* the NLA_HDRLEN macro in netlink.h causes sign conversion errors
1008 : so replacing with this: */
1009 0 : #define FD_NLA_ALIGN(len) (((long)(len) + (long)(NLA_ALIGNTO) - 1L) & \
1010 0 : ~((long)(NLA_ALIGNTO) - 1L))
1011 0 : #define FD_NLA_HDRLEN ((long)(FD_NLA_ALIGN(sizeof(struct nlattr))))
1012 :
1013 : /* walk thru extended error attributes and populate err_attr */
1014 0 : for( int rem = alen; NLA_OK( attr, rem ); attr = NLA_NEXT(attr,&rem) ) {
1015 : /* get type */
1016 0 : int type = attr->nla_type & NLA_TYPE_MASK;
1017 0 : void * data = (void*)( (long)attr + FD_NLA_HDRLEN );
1018 :
1019 : /* lookup type */
1020 :
1021 0 : # define FD_NLA_POPULATE( MBR, ATTR, TYPE, ATTR_VAR, ... ) \
1022 0 : if( type == ATTR ) { \
1023 0 : (ATTR_VAR).MBR = FD_NLA_CPY(MBR,TYPE,data); \
1024 0 : } else
1025 :
1026 0 : FD_NLA_ERR_DEFS( FD_NLA_POPULATE, err_attr, dummy ) {
1027 : /* ignore */
1028 0 : }
1029 0 : }
1030 :
1031 0 : # define EXPAND_IP4(ip) (((ip)>>24u)&0xffu), (((ip)>>16u)&0xffu), (((ip)>>8u)&0xffu), ((ip)&0xffu)
1032 0 : FD_LOG_WARNING(( "netlink returned data with error: %d %s"
1033 0 : " adding ip address: %u.%u.%u.%u on ifindex: %u"
1034 0 : " extended info: %s offset: %u",
1035 0 : -err->error, strerror( -err->error ),
1036 0 : EXPAND_IP4( (uint)ip_addr ),
1037 0 : (uint)ifindex, (char const *)err_attr.msg,
1038 0 : (uint)err_attr.offs ));
1039 0 : # undef EXPAND_IP4
1040 :
1041 0 : return;
1042 0 : }
|