Line data Source code
1 : #include "fd_fib4_netlink.h"
2 : #include "fd_fib4.h"
3 : #include "fd_netlink1.h"
4 :
5 : #if !defined(__linux__)
6 : #error "fd_fib4_netlink.c requires a Linux system with kernel headers"
7 : #endif
8 :
9 : #include <errno.h>
10 : #include <linux/netlink.h>
11 : #include <linux/rtnetlink.h>
12 : #include <arpa/inet.h>
13 : #include "../../util/fd_util.h"
14 :
15 : FD_STATIC_ASSERT( FD_FIB4_RTYPE_UNSPEC ==RTN_UNSPEC, linux );
16 : FD_STATIC_ASSERT( FD_FIB4_RTYPE_UNICAST ==RTN_UNICAST, linux );
17 : FD_STATIC_ASSERT( FD_FIB4_RTYPE_LOCAL ==RTN_LOCAL, linux );
18 : FD_STATIC_ASSERT( FD_FIB4_RTYPE_BROADCAST==RTN_BROADCAST, linux );
19 : FD_STATIC_ASSERT( FD_FIB4_RTYPE_MULTICAST==RTN_MULTICAST, linux );
20 : FD_STATIC_ASSERT( FD_FIB4_RTYPE_BLACKHOLE==RTN_BLACKHOLE, linux );
21 : FD_STATIC_ASSERT( FD_FIB4_RTYPE_THROW ==RTN_THROW, linux );
22 :
23 : static void
24 : fd_fib4_rta_gateway( fd_fib4_hop_t * hop,
25 : void const * rta,
26 3 : ulong rta_sz ) {
27 3 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
28 0 : FD_LOG_HEXDUMP_DEBUG(( "Failed to parse RTA_GATEWAY", rta, rta_sz ));
29 0 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
30 0 : return;
31 0 : }
32 3 : uint ip_addr = FD_LOAD( uint, rta ); /* big endian */
33 3 : hop->ip4_gw = ip_addr;
34 3 : }
35 :
36 : static void
37 : fd_fib4_rta_oif( fd_fib4_hop_t * hop,
38 : void const * rta,
39 27 : ulong rta_sz ) {
40 27 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
41 0 : FD_LOG_HEXDUMP_DEBUG(( "Failed to parse RTA_OIF", rta, rta_sz ));
42 0 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
43 0 : return;
44 0 : }
45 27 : hop->if_idx = FD_LOAD( uint, rta ); /* host byte order */
46 27 : }
47 :
48 : static void
49 : fd_fib4_rta_prefsrc( fd_fib4_hop_t * hop,
50 : void const * rta,
51 27 : ulong rta_sz ) {
52 27 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
53 0 : FD_LOG_HEXDUMP_DEBUG(( "Failed to parse RTA_PREFSRC", rta, rta_sz ));
54 0 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
55 0 : return;
56 0 : }
57 27 : hop->ip4_src = FD_LOAD( uint, rta ); /* big endian */
58 27 : }
59 :
60 : int
61 : fd_fib4_netlink_translate( struct nlmsghdr const * msg_hdr,
62 : uint table_id,
63 54 : fd_iproute_msg_t * route ) {
64 54 : uint ip4_dst = 0U;
65 54 : int prefix = -1; /* -1 indicates unset ip4_dst / prefix */
66 54 : uint prio = 0U; /* default metric */
67 :
68 54 : fd_fib4_hop_t hop[1] = {0};
69 :
70 54 : struct rtmsg * msg = NLMSG_DATA( msg_hdr );
71 54 : struct rtattr * rat = RTM_RTA( msg );
72 54 : long rat_sz = (long)(int)RTM_PAYLOAD( msg_hdr );
73 :
74 54 : if( FD_UNLIKELY(msg->rtm_flags & RTM_F_CLONED) ) {
75 0 : return 0;
76 0 : }
77 :
78 54 : if( FD_UNLIKELY(msg->rtm_table != RT_TABLE_UNSPEC &&
79 54 : msg->rtm_table != table_id ) ) {
80 27 : return 0;
81 27 : }
82 :
83 27 : switch( msg->rtm_type ) {
84 9 : case RTN_UNICAST:
85 9 : hop->rtype = FD_FIB4_RTYPE_UNICAST;
86 9 : break;
87 12 : case RTN_LOCAL:
88 12 : hop->rtype = FD_FIB4_RTYPE_LOCAL;
89 12 : break;
90 6 : case RTN_BROADCAST:
91 6 : hop->rtype = FD_FIB4_RTYPE_BROADCAST;
92 6 : break;
93 0 : case RTN_MULTICAST:
94 0 : hop->rtype = FD_FIB4_RTYPE_MULTICAST;
95 0 : break;
96 0 : case RTN_BLACKHOLE:
97 0 : hop->rtype = FD_FIB4_RTYPE_BLACKHOLE;
98 0 : break;
99 0 : default:
100 0 : FD_LOG_DEBUG(( "Unsupported route type (%u-%s)", msg->rtm_type, fd_netlink_rtm_type_str( msg->rtm_type ) ));
101 0 : hop->rtype = FD_FIB4_RTYPE_BLACKHOLE;
102 0 : hop->flags |= FD_FIB4_FLAG_RTYPE_UNSUPPORTED;
103 0 : break;
104 27 : }
105 :
106 114 : for( ; RTA_OK( rat, rat_sz ); rat=RTA_NEXT( rat, rat_sz ) ) {
107 114 : void * rta = RTA_DATA( rat );
108 114 : ulong rta_sz = RTA_PAYLOAD( rat );
109 :
110 114 : switch( rat->rta_type ) {
111 :
112 3 : case RTA_GATEWAY:
113 3 : fd_fib4_rta_gateway( hop, rta, rta_sz );
114 3 : break;
115 :
116 24 : case RTA_DST:
117 24 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
118 0 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
119 0 : continue;
120 0 : }
121 24 : ip4_dst = FD_LOAD( uint, rta ); /* big endian */
122 24 : prefix = msg->rtm_dst_len;
123 24 : break;
124 :
125 27 : case RTA_OIF:
126 27 : fd_fib4_rta_oif( hop, rta, rta_sz );
127 27 : break;
128 :
129 27 : case RTA_PREFSRC:
130 27 : fd_fib4_rta_prefsrc( hop, rta, rta_sz );
131 27 : break;
132 :
133 6 : case RTA_PRIORITY:
134 6 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
135 0 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
136 0 : continue;
137 0 : }
138 6 : prio = FD_LOAD( uint, rta ); /* host byte order */
139 6 : break;
140 :
141 27 : case RTA_TABLE:
142 : /* Skip routes that aren't in the requested table */
143 27 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
144 0 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
145 0 : continue;
146 0 : }
147 27 : if( FD_LOAD( uint, rta )!=table_id ) return 0;
148 27 : break;
149 :
150 27 : default:
151 0 : FD_LOG_DEBUG(( "Unsupported route table attribute (%u-%s)", rat->rta_type, fd_netlink_rtattr_str( rat->rta_type ) ));
152 0 : hop->flags |= FD_FIB4_FLAG_RTA_UNSUPPORTED;
153 0 : break;
154 114 : }
155 114 : }
156 :
157 27 : if( FD_UNLIKELY( prefix<0 ) ) prefix = 0; /* default route omits RTA_DST */
158 27 : *route = (fd_iproute_msg_t) {
159 27 : .hop = *hop,
160 27 : .dst_addr = ip4_dst,
161 27 : .prio = prio,
162 27 : .table_id = table_id,
163 27 : .op = msg_hdr->nlmsg_type==RTM_DELROUTE ? FD_IPROUTE_OP_DELETE : FD_IPROUTE_OP_UPSERT,
164 27 : .prefix = (uchar)prefix,
165 27 : };
166 27 : return 1;
167 27 : }
168 :
169 : int
170 : fd_fib4_netlink_dump_table( fd_netlink_t * netlink,
171 : uint table_id,
172 : fd_fib4_netlink_route_fn_t fn,
173 6 : void * arg ) {
174 :
175 6 : uint seq = netlink->seq++;
176 :
177 6 : struct {
178 6 : struct nlmsghdr nlh; /* Netlink header */
179 6 : struct rtmsg rtm; /* Payload - route message */
180 6 : struct rtattr rta;
181 6 : uint table_id;
182 6 : } request;
183 6 : request.nlh = (struct nlmsghdr) {
184 6 : .nlmsg_type = RTM_GETROUTE,
185 6 : .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
186 6 : .nlmsg_len = sizeof(request),
187 6 : .nlmsg_seq = seq
188 6 : };
189 6 : request.rtm = (struct rtmsg) {
190 6 : .rtm_family = AF_INET, /* IPv4 */
191 6 : };
192 6 : request.rta = (struct rtattr) {
193 6 : .rta_type = RTA_TABLE,
194 6 : .rta_len = RTA_LENGTH( sizeof(uint) )
195 6 : };
196 6 : request.table_id = table_id;
197 :
198 6 : long send_res = sendto( netlink->fd, &request, sizeof(request), 0, NULL, 0 );
199 6 : if( FD_UNLIKELY( send_res<0 ) ) {
200 0 : FD_LOG_WARNING(( "netlink send(%d,RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP) failed (%d-%s)", netlink->fd, errno, fd_io_strerror( errno ) ));
201 0 : return errno;
202 0 : }
203 6 : if( FD_UNLIKELY( send_res!=sizeof(request) ) ) {
204 0 : FD_LOG_WARNING(( "netlink send(%d,RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP) failed (short write)", netlink->fd ));
205 0 : return EPIPE;
206 0 : }
207 :
208 6 : int dump_intr = 0;
209 :
210 6 : uchar buf[ 4096 ];
211 6 : fd_netlink_iter_t iter[1];
212 6 : for( fd_netlink_iter_init( iter, netlink, buf, sizeof(buf) );
213 60 : !fd_netlink_iter_done( iter );
214 54 : fd_netlink_iter_next( iter, netlink ) ) {
215 54 : struct nlmsghdr const * nlh = fd_netlink_iter_msg( iter );
216 54 : if( FD_UNLIKELY( nlh->nlmsg_flags & NLM_F_DUMP_INTR ) ) dump_intr = 1;
217 54 : if( FD_UNLIKELY( nlh->nlmsg_type==NLMSG_ERROR ) ) {
218 0 : struct nlmsgerr * err = NLMSG_DATA( nlh );
219 0 : int nl_err = -err->error;
220 0 : FD_LOG_WARNING(( "netlink RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP failed (%d-%s)", nl_err, fd_io_strerror( nl_err ) ));
221 0 : return nl_err;
222 0 : }
223 54 : if( FD_UNLIKELY( nlh->nlmsg_type!=RTM_NEWROUTE ) ) {
224 0 : FD_LOG_DEBUG(( "unexpected nlmsg_type %u", nlh->nlmsg_type ));
225 0 : continue;
226 0 : }
227 54 : fd_iproute_msg_t route;
228 54 : if( fd_fib4_netlink_translate( nlh, table_id, &route ) && FD_UNLIKELY( fn( arg, &route ) ) ) break;
229 54 : }
230 6 : if( FD_UNLIKELY( iter->err > 0 ) ) return FD_FIB_NETLINK_ERR_IO;
231 6 : ulong drain_cnt = fd_netlink_iter_drain( iter, netlink );
232 :
233 6 : if( dump_intr ) {
234 0 : FD_LOG_DEBUG(( "received NLM_F_DUMP_INTR (our read of the routing table was overrun by a concurrent write)" ));
235 0 : return FD_FIB_NETLINK_ERR_INTR;
236 0 : }
237 :
238 6 : if( FD_UNLIKELY( drain_cnt ) ) {
239 0 : FD_LOG_WARNING(( "Unexpectedly skipped %lu routes. This is a bug!", drain_cnt ));
240 0 : return FD_FIB_NETLINK_ERR_OOPS;
241 0 : }
242 :
243 6 : return 0;
244 6 : }
245 :
246 : typedef struct {
247 : fd_fib4_t * fib;
248 : int no_space;
249 : } fd_fib4_load_ctx_t;
250 :
251 : static int
252 27 : fd_fib4_load_route( void * arg, fd_iproute_msg_t const * route ) {
253 27 : fd_fib4_load_ctx_t * ctx = arg;
254 27 : if( FD_UNLIKELY( !fd_fib4_insert( ctx->fib, route->dst_addr, route->prefix, route->prio, &route->hop ) ) ) {
255 0 : ctx->no_space = 1;
256 0 : return 1;
257 0 : }
258 27 : return 0;
259 27 : }
260 :
261 : int
262 : fd_fib4_netlink_load_table( fd_fib4_t * fib,
263 : fd_netlink_t * netlink,
264 6 : uint table_id ) {
265 6 : fd_fib4_clear( fib );
266 6 : fd_fib4_load_ctx_t ctx = { .fib = fib };
267 6 : int err = fd_fib4_netlink_dump_table( netlink, table_id, fd_fib4_load_route, &ctx );
268 6 : if( FD_UNLIKELY( ctx.no_space ) ) {
269 0 : FD_LOG_WARNING(( "Routing table is too small for table %u (max %lu)", table_id, fd_fib4_max( fib ) ));
270 0 : fd_fib4_clear( fib );
271 0 : return FD_FIB_NETLINK_ERR_SPACE;
272 0 : }
273 6 : if( FD_UNLIKELY( err ) ) fd_fib4_clear( fib );
274 6 : return err;
275 6 : }
276 :
277 : FD_FN_CONST char const *
278 0 : fd_fib4_netlink_strerror( int err ) {
279 0 : switch( err ) {
280 0 : case FD_FIB_NETLINK_SUCCESS:
281 0 : return "success";
282 0 : case FD_FIB_NETLINK_ERR_OOPS:
283 0 : return "oops";
284 0 : case FD_FIB_NETLINK_ERR_IO:
285 0 : return "io";
286 0 : case FD_FIB_NETLINK_ERR_INTR:
287 0 : return "interrupt";
288 0 : case FD_FIB_NETLINK_ERR_SPACE:
289 0 : return "out of space";
290 0 : default:
291 0 : return "unknown";
292 0 : }
293 0 : }
|