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 21 : ulong rta_sz ) {
27 21 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
28 15 : FD_LOG_HEXDUMP_DEBUG(( "Failed to parse RTA_GATEWAY", rta, rta_sz ));
29 15 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
30 15 : return;
31 15 : }
32 6 : uint ip_addr = FD_LOAD( uint, rta ); /* big endian */
33 6 : hop->ip4_gw = ip_addr;
34 6 : }
35 :
36 : static void
37 : fd_fib4_rta_oif( fd_fib4_hop_t * hop,
38 : void const * rta,
39 48 : ulong rta_sz ) {
40 48 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
41 15 : FD_LOG_HEXDUMP_DEBUG(( "Failed to parse RTA_OIF", rta, rta_sz ));
42 15 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
43 15 : return;
44 15 : }
45 33 : hop->if_idx = FD_LOAD( uint, rta ); /* host byte order */
46 33 : }
47 :
48 : static void
49 : fd_fib4_rta_prefsrc( fd_fib4_hop_t * hop,
50 : void const * rta,
51 45 : ulong rta_sz ) {
52 45 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
53 15 : FD_LOG_HEXDUMP_DEBUG(( "Failed to parse RTA_PREFSRC", rta, rta_sz ));
54 15 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
55 15 : return;
56 15 : }
57 30 : hop->ip4_src = FD_LOAD( uint, rta ); /* big endian */
58 30 : }
59 :
60 : int
61 : fd_fib4_netlink_translate( struct nlmsghdr const * msg_hdr,
62 : uint table_id,
63 198 : fd_iproute_msg_t * route ) {
64 198 : uint ip4_dst = 0U;
65 198 : int prefix = -1; /* -1 indicates unset ip4_dst / prefix */
66 198 : uint prio = 0U; /* default metric */
67 :
68 198 : fd_fib4_hop_t hop[1] = {0};
69 :
70 198 : struct rtmsg * msg = NLMSG_DATA( msg_hdr );
71 198 : struct rtattr * rat = RTM_RTA( msg );
72 198 : long rat_sz = (long)(int)RTM_PAYLOAD( msg_hdr );
73 :
74 198 : if( FD_UNLIKELY(msg->rtm_flags & RTM_F_CLONED) ) {
75 0 : return 0;
76 0 : }
77 :
78 198 : if( FD_UNLIKELY(msg->rtm_table != RT_TABLE_UNSPEC &&
79 198 : msg->rtm_table != table_id ) ) {
80 33 : return 0;
81 33 : }
82 :
83 165 : switch( msg->rtm_type ) {
84 129 : case RTN_UNICAST:
85 129 : hop->rtype = FD_FIB4_RTYPE_UNICAST;
86 129 : break;
87 12 : case RTN_LOCAL:
88 12 : hop->rtype = FD_FIB4_RTYPE_LOCAL;
89 12 : break;
90 12 : case RTN_BROADCAST:
91 12 : hop->rtype = FD_FIB4_RTYPE_BROADCAST;
92 12 : break;
93 3 : case RTN_MULTICAST:
94 3 : hop->rtype = FD_FIB4_RTYPE_MULTICAST;
95 3 : break;
96 3 : case RTN_BLACKHOLE:
97 3 : hop->rtype = FD_FIB4_RTYPE_BLACKHOLE;
98 3 : break;
99 3 : case RTN_THROW:
100 3 : hop->rtype = FD_FIB4_RTYPE_THROW;
101 3 : break;
102 3 : default:
103 3 : FD_LOG_DEBUG(( "Unsupported route type (%u-%s)", msg->rtm_type, fd_netlink_rtm_type_str( msg->rtm_type ) ));
104 3 : hop->rtype = FD_FIB4_RTYPE_BLACKHOLE;
105 3 : hop->flags |= FD_FIB4_FLAG_RTYPE_UNSUPPORTED;
106 3 : break;
107 165 : }
108 :
109 255 : for( ; RTA_OK( rat, rat_sz ); rat=RTA_NEXT( rat, rat_sz ) ) {
110 255 : void * rta = RTA_DATA( rat );
111 255 : ulong rta_sz = RTA_PAYLOAD( rat );
112 :
113 255 : switch( rat->rta_type ) {
114 :
115 21 : case RTA_GATEWAY:
116 21 : fd_fib4_rta_gateway( hop, rta, rta_sz );
117 21 : break;
118 :
119 63 : case RTA_DST:
120 63 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
121 15 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
122 15 : continue;
123 15 : }
124 48 : ip4_dst = FD_LOAD( uint, rta ); /* big endian */
125 48 : prefix = msg->rtm_dst_len;
126 48 : break;
127 :
128 48 : case RTA_OIF:
129 48 : fd_fib4_rta_oif( hop, rta, rta_sz );
130 48 : break;
131 :
132 45 : case RTA_PREFSRC:
133 45 : fd_fib4_rta_prefsrc( hop, rta, rta_sz );
134 45 : break;
135 :
136 24 : case RTA_PRIORITY:
137 24 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
138 15 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
139 15 : continue;
140 15 : }
141 9 : prio = FD_LOAD( uint, rta ); /* host byte order */
142 9 : break;
143 :
144 54 : case RTA_TABLE:
145 : /* Skip routes that aren't in the requested table */
146 54 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
147 15 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
148 15 : continue;
149 15 : }
150 39 : if( FD_LOAD( uint, rta )!=table_id ) return 0;
151 36 : break;
152 :
153 36 : 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 255 : }
158 255 : }
159 :
160 162 : if( FD_UNLIKELY( hop->flags & FD_FIB4_FLAG_RTA_PARSE_ERR ) ) return 0;
161 :
162 72 : if( FD_UNLIKELY( prefix<0 ) ) prefix = 0; /* default route omits RTA_DST */
163 72 : *route = (fd_iproute_msg_t) {
164 72 : .hop = *hop,
165 72 : .dst_addr = ip4_dst,
166 72 : .prio = prio,
167 72 : .table_id = table_id,
168 72 : .op = msg_hdr->nlmsg_type==RTM_DELROUTE ? FD_IPROUTE_OP_DELETE : FD_IPROUTE_OP_UPSERT,
169 72 : .prefix = (uchar)prefix,
170 72 : };
171 72 : return 1;
172 162 : }
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 6 : void * arg ) {
179 :
180 6 : uint seq = netlink->seq++;
181 :
182 6 : struct {
183 6 : struct nlmsghdr nlh; /* Netlink header */
184 6 : struct rtmsg rtm; /* Payload - route message */
185 6 : struct rtattr rta;
186 6 : uint table_id;
187 6 : } request;
188 6 : request.nlh = (struct nlmsghdr) {
189 6 : .nlmsg_type = RTM_GETROUTE,
190 6 : .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
191 6 : .nlmsg_len = sizeof(request),
192 6 : .nlmsg_seq = seq
193 6 : };
194 6 : request.rtm = (struct rtmsg) {
195 6 : .rtm_family = AF_INET, /* IPv4 */
196 6 : };
197 6 : request.rta = (struct rtattr) {
198 6 : .rta_type = RTA_TABLE,
199 6 : .rta_len = RTA_LENGTH( sizeof(uint) )
200 6 : };
201 6 : request.table_id = table_id;
202 :
203 6 : long send_res = sendto( netlink->fd, &request, sizeof(request), 0, NULL, 0 );
204 6 : 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 6 : 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 6 : int dump_intr = 0;
214 :
215 6 : uchar buf[ 4096 ];
216 6 : fd_netlink_iter_t iter[1];
217 6 : for( fd_netlink_iter_init( iter, netlink, buf, sizeof(buf) );
218 66 : !fd_netlink_iter_done( iter );
219 60 : fd_netlink_iter_next( iter, netlink ) ) {
220 60 : struct nlmsghdr const * nlh = fd_netlink_iter_msg( iter );
221 60 : if( FD_UNLIKELY( nlh->nlmsg_flags & NLM_F_DUMP_INTR ) ) dump_intr = 1;
222 60 : 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 60 : 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 60 : fd_iproute_msg_t route;
233 60 : if( fd_fib4_netlink_translate( nlh, table_id, &route ) && FD_UNLIKELY( fn( arg, &route ) ) ) break;
234 60 : }
235 6 : if( FD_UNLIKELY( iter->err > 0 ) ) return FD_FIB_NETLINK_ERR_IO;
236 6 : ulong drain_cnt = fd_netlink_iter_drain( iter, netlink );
237 :
238 6 : 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 6 : 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 6 : return 0;
249 6 : }
250 :
251 : typedef struct {
252 : fd_fib4_t * fib;
253 : int no_space;
254 : } fd_fib4_load_ctx_t;
255 :
256 : static int
257 30 : fd_fib4_load_route( void * arg, fd_iproute_msg_t const * route ) {
258 30 : fd_fib4_load_ctx_t * ctx = arg;
259 30 : 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 30 : return 0;
264 30 : }
265 :
266 : int
267 : fd_fib4_netlink_load_table( fd_fib4_t * fib,
268 : fd_netlink_t * netlink,
269 6 : uint table_id ) {
270 6 : fd_fib4_clear( fib );
271 6 : fd_fib4_load_ctx_t ctx = { .fib = fib };
272 6 : int err = fd_fib4_netlink_dump_table( netlink, table_id, fd_fib4_load_route, &ctx );
273 6 : 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 6 : if( FD_UNLIKELY( err ) ) fd_fib4_clear( fib );
279 6 : return err;
280 6 : }
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 : }
|