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 33 : ulong rta_sz ) {
40 33 : 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 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 33 : ulong rta_sz ) {
52 33 : 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 33 : hop->ip4_src = FD_LOAD( uint, rta ); /* big endian */
58 33 : }
59 :
60 : static int
61 : fd_fib4_netlink_translate( fd_fib4_t * fib,
62 : struct nlmsghdr const * msg_hdr,
63 66 : uint table_id ) {
64 66 : uint ip4_dst = 0U;
65 66 : int prefix = -1; /* -1 indicates unset ip4_dst / prefix */
66 66 : uint prio = 0U; /* default metric */
67 :
68 66 : fd_fib4_hop_t hop[1] = {0};
69 :
70 66 : struct rtmsg * msg = NLMSG_DATA( msg_hdr );
71 66 : struct rtattr * rat = RTM_RTA( msg );
72 66 : long rat_sz = (long)(int)RTM_PAYLOAD( msg_hdr );
73 :
74 66 : if( FD_UNLIKELY(msg->rtm_flags & RTM_F_CLONED) ) {
75 0 : return 0;
76 0 : }
77 :
78 66 : if( FD_UNLIKELY(msg->rtm_table != RT_TABLE_UNSPEC &&
79 66 : msg->rtm_table != table_id ) ) {
80 33 : return 0;
81 33 : }
82 :
83 33 : 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 12 : case RTN_BROADCAST:
91 12 : hop->rtype = FD_FIB4_RTYPE_BROADCAST;
92 12 : 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 33 : }
105 :
106 138 : for( ; RTA_OK( rat, rat_sz ); rat=RTA_NEXT( rat, rat_sz ) ) {
107 138 : void * rta = RTA_DATA( rat );
108 138 : ulong rta_sz = RTA_PAYLOAD( rat );
109 :
110 138 : 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 30 : case RTA_DST:
117 30 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
118 0 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
119 0 : continue;
120 0 : }
121 30 : ip4_dst = FD_LOAD( uint, rta ); /* big endian */
122 30 : prefix = msg->rtm_dst_len;
123 30 : break;
124 :
125 33 : case RTA_OIF:
126 33 : fd_fib4_rta_oif( hop, rta, rta_sz );
127 33 : break;
128 :
129 33 : case RTA_PREFSRC:
130 33 : fd_fib4_rta_prefsrc( hop, rta, rta_sz );
131 33 : 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 33 : case RTA_TABLE:
142 : /* Skip routes that aren't in the requested table */
143 33 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
144 0 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
145 0 : continue;
146 0 : }
147 33 : if( FD_LOAD( uint, rta )!=table_id ) return 0;
148 33 : break;
149 :
150 33 : 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 138 : }
155 138 : }
156 :
157 33 : if( fd_fib4_free_cnt( fib )==0UL ) return ENOSPC;
158 33 : *fd_fib4_append( fib, ip4_dst, prefix, prio ) = *hop;
159 :
160 33 : return 0;
161 33 : }
162 :
163 : int
164 : fd_fib4_netlink_load_table( fd_fib4_t * fib,
165 : fd_netlink_t * netlink,
166 6 : uint table_id ) {
167 :
168 6 : uint seq = netlink->seq++;
169 :
170 6 : struct {
171 6 : struct nlmsghdr nlh; /* Netlink header */
172 6 : struct rtmsg rtm; /* Payload - route message */
173 6 : struct rtattr rta;
174 6 : uint table_id;
175 6 : } request;
176 6 : request.nlh = (struct nlmsghdr) {
177 6 : .nlmsg_type = RTM_GETROUTE,
178 6 : .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
179 6 : .nlmsg_len = sizeof(request),
180 6 : .nlmsg_seq = seq
181 6 : };
182 6 : request.rtm = (struct rtmsg) {
183 6 : .rtm_family = AF_INET, /* IPv4 */
184 6 : };
185 6 : request.rta = (struct rtattr) {
186 6 : .rta_type = RTA_TABLE,
187 6 : .rta_len = RTA_LENGTH( sizeof(uint) )
188 6 : };
189 6 : request.table_id = table_id;
190 :
191 6 : long send_res = sendto( netlink->fd, &request, sizeof(request), 0, NULL, 0 );
192 6 : if( FD_UNLIKELY( send_res<0 ) ) {
193 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 ) ));
194 0 : return errno;
195 0 : }
196 6 : if( FD_UNLIKELY( send_res!=sizeof(request) ) ) {
197 0 : FD_LOG_WARNING(( "netlink send(%d,RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP) failed (short write)", netlink->fd ));
198 0 : return EPIPE;
199 0 : }
200 :
201 6 : fd_fib4_clear( fib );
202 :
203 6 : int dump_intr = 0;
204 6 : int no_space = 0;
205 6 : ulong route_cnt = 0UL;
206 :
207 6 : uchar buf[ 4096 ];
208 6 : fd_netlink_iter_t iter[1];
209 6 : for( fd_netlink_iter_init( iter, netlink, buf, sizeof(buf) );
210 72 : !fd_netlink_iter_done( iter );
211 66 : fd_netlink_iter_next( iter, netlink ) ) {
212 66 : struct nlmsghdr const * nlh = fd_netlink_iter_msg( iter );
213 66 : if( FD_UNLIKELY( nlh->nlmsg_flags & NLM_F_DUMP_INTR ) ) dump_intr = 1;
214 66 : if( FD_UNLIKELY( nlh->nlmsg_type==NLMSG_ERROR ) ) {
215 0 : struct nlmsgerr * err = NLMSG_DATA( nlh );
216 0 : int nl_err = -err->error;
217 0 : FD_LOG_WARNING(( "netlink RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP failed (%d-%s)", nl_err, fd_io_strerror( nl_err ) ));
218 0 : return nl_err;
219 0 : }
220 66 : if( FD_UNLIKELY( nlh->nlmsg_type!=RTM_NEWROUTE ) ) {
221 0 : FD_LOG_DEBUG(( "unexpected nlmsg_type %u", nlh->nlmsg_type ));
222 0 : continue;
223 0 : }
224 66 : route_cnt++;
225 :
226 66 : int translate_err = fd_fib4_netlink_translate( fib, nlh, table_id );
227 66 : if( FD_UNLIKELY( translate_err==ENOSPC ) ) {
228 0 : no_space = 1;
229 0 : break;
230 0 : }
231 66 : }
232 6 : if( FD_UNLIKELY( iter->err > 0 ) ) return FD_FIB_NETLINK_ERR_IO;
233 6 : ulong drain_cnt = fd_netlink_iter_drain( iter, netlink );
234 :
235 6 : if( no_space ) {
236 0 : FD_LOG_WARNING(( "Routing table is too small! `ip route show table %u` returned %lu entries, which exceeds the configured maximum of %lu",
237 0 : table_id, route_cnt+drain_cnt, fd_fib4_max( fib ) ));
238 0 : fd_fib4_clear( fib );
239 0 : return FD_FIB_NETLINK_ERR_SPACE;
240 0 : }
241 :
242 6 : if( dump_intr ) {
243 0 : FD_LOG_DEBUG(( "received NLM_F_DUMP_INTR (our read of the routing table was overrun by a concurrent write)" ));
244 0 : return FD_FIB_NETLINK_ERR_INTR;
245 0 : }
246 :
247 6 : if( FD_UNLIKELY( drain_cnt ) ) {
248 0 : FD_LOG_WARNING(( "Unexpectedly skipped %lu routes. This is a bug!", drain_cnt ));
249 0 : return FD_FIB_NETLINK_ERR_OOPS;
250 0 : }
251 :
252 6 : return 0;
253 6 : }
254 :
255 : FD_FN_CONST char const *
256 0 : fd_fib4_netlink_strerror( int err ) {
257 0 : switch( err ) {
258 0 : case FD_FIB_NETLINK_SUCCESS:
259 0 : return "success";
260 0 : case FD_FIB_NETLINK_ERR_OOPS:
261 0 : return "oops";
262 0 : case FD_FIB_NETLINK_ERR_IO:
263 0 : return "io";
264 0 : case FD_FIB_NETLINK_ERR_INTR:
265 0 : return "interrupt";
266 0 : case FD_FIB_NETLINK_ERR_SPACE:
267 0 : return "out of space";
268 0 : default:
269 0 : return "unknown";
270 0 : }
271 0 : }
|