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