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 39 : ulong rta_sz ) {
40 39 : 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 39 : hop->if_idx = FD_LOAD( uint, rta ); /* host byte order */
46 39 : }
47 :
48 : static void
49 : fd_fib4_rta_prefsrc( fd_fib4_hop_t * hop,
50 : void const * rta,
51 36 : ulong rta_sz ) {
52 36 : 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 36 : hop->ip4_src = FD_LOAD( uint, rta ); /* big endian */
58 36 : }
59 :
60 : static int
61 : fd_fib4_netlink_translate( fd_fib4_t * fib,
62 : struct nlmsghdr const * msg_hdr,
63 78 : uint table_id ) {
64 78 : uint ip4_dst = 0U;
65 78 : int prefix = -1; /* -1 indicates unset ip4_dst / prefix */
66 78 : uint prio = 0U; /* default metric */
67 :
68 78 : fd_fib4_hop_t hop[1] = {0};
69 :
70 78 : struct rtmsg * msg = NLMSG_DATA( msg_hdr );
71 78 : struct rtattr * rat = RTM_RTA( msg );
72 78 : long rat_sz = (long)(int)RTM_PAYLOAD( msg_hdr );
73 :
74 78 : switch( msg->rtm_type ) {
75 18 : case RTN_UNICAST:
76 18 : hop->rtype = FD_FIB4_RTYPE_UNICAST;
77 18 : break;
78 24 : case RTN_LOCAL:
79 24 : hop->rtype = FD_FIB4_RTYPE_LOCAL;
80 24 : break;
81 36 : case RTN_BROADCAST:
82 36 : hop->rtype = FD_FIB4_RTYPE_BROADCAST;
83 36 : break;
84 0 : case RTN_MULTICAST:
85 0 : hop->rtype = FD_FIB4_RTYPE_MULTICAST;
86 0 : break;
87 0 : case RTN_BLACKHOLE:
88 0 : hop->rtype = FD_FIB4_RTYPE_BLACKHOLE;
89 0 : break;
90 0 : default:
91 0 : FD_LOG_DEBUG(( "Unsupported route type (%u-%s)", msg->rtm_type, fd_netlink_rtm_type_str( msg->rtm_type ) ));
92 0 : hop->rtype = FD_FIB4_RTYPE_BLACKHOLE;
93 0 : hop->flags |= FD_FIB4_FLAG_RTYPE_UNSUPPORTED;
94 0 : break;
95 78 : }
96 :
97 198 : for( ; RTA_OK( rat, rat_sz ); rat=RTA_NEXT( rat, rat_sz ) ) {
98 198 : void * rta = RTA_DATA( rat );
99 198 : ulong rta_sz = RTA_PAYLOAD( rat );
100 :
101 198 : switch( rat->rta_type ) {
102 :
103 3 : case RTA_GATEWAY:
104 3 : fd_fib4_rta_gateway( hop, rta, rta_sz );
105 3 : break;
106 :
107 36 : case RTA_DST:
108 36 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
109 0 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
110 0 : continue;
111 0 : }
112 36 : ip4_dst = FD_LOAD( uint, rta ); /* big endian */
113 36 : prefix = msg->rtm_dst_len;
114 36 : break;
115 :
116 39 : case RTA_OIF:
117 39 : fd_fib4_rta_oif( hop, rta, rta_sz );
118 39 : break;
119 :
120 36 : case RTA_PREFSRC:
121 36 : fd_fib4_rta_prefsrc( hop, rta, rta_sz );
122 36 : break;
123 :
124 6 : case RTA_PRIORITY:
125 6 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
126 0 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
127 0 : continue;
128 0 : }
129 6 : prio = FD_LOAD( uint, rta ); /* host byte order */
130 6 : break;
131 :
132 78 : case RTA_TABLE:
133 : /* Skip routes that aren't in the requested table */
134 78 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
135 0 : hop->flags |= FD_FIB4_FLAG_RTA_PARSE_ERR;
136 0 : continue;
137 0 : }
138 78 : if( FD_LOAD( uint, rta )!=table_id ) return 0;
139 39 : break;
140 :
141 39 : default:
142 0 : FD_LOG_DEBUG(( "Unsupported route table attribute (%u-%s)", rat->rta_type, fd_netlink_rtattr_str( rat->rta_type ) ));
143 0 : hop->flags |= FD_FIB4_FLAG_RTA_UNSUPPORTED;
144 0 : break;
145 198 : }
146 198 : }
147 :
148 39 : if( fd_fib4_free_cnt( fib )==0UL ) return ENOSPC;
149 39 : *fd_fib4_append( fib, ip4_dst, prefix, prio ) = *hop;
150 :
151 39 : return 0;
152 39 : }
153 :
154 : int
155 : fd_fib4_netlink_load_table( fd_fib4_t * fib,
156 : fd_netlink_t * netlink,
157 6 : uint table_id ) {
158 :
159 6 : uint seq = netlink->seq++;
160 :
161 6 : struct {
162 6 : struct nlmsghdr nlh; /* Netlink header */
163 6 : struct rtmsg rtm; /* Payload - route message */
164 6 : struct rtattr rta;
165 6 : uint table_id;
166 6 : } request;
167 6 : request.nlh = (struct nlmsghdr) {
168 6 : .nlmsg_type = RTM_GETROUTE,
169 6 : .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
170 6 : .nlmsg_len = sizeof(request),
171 6 : .nlmsg_seq = seq
172 6 : };
173 6 : request.rtm = (struct rtmsg) {
174 6 : .rtm_family = AF_INET, /* IPv4 */
175 6 : };
176 6 : request.rta = (struct rtattr) {
177 6 : .rta_type = RTA_TABLE,
178 6 : .rta_len = RTA_LENGTH( sizeof(uint) )
179 6 : };
180 6 : request.table_id = table_id;
181 :
182 6 : long send_res = sendto( netlink->fd, &request, sizeof(request), 0, NULL, 0 );
183 6 : if( FD_UNLIKELY( send_res<0 ) ) {
184 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 ) ));
185 0 : return errno;
186 0 : }
187 6 : if( FD_UNLIKELY( send_res!=sizeof(request) ) ) {
188 0 : FD_LOG_WARNING(( "netlink send(%d,RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP) failed (short write)", netlink->fd ));
189 0 : return EPIPE;
190 0 : }
191 :
192 6 : fd_fib4_clear( fib );
193 :
194 6 : int dump_intr = 0;
195 6 : int no_space = 0;
196 6 : ulong route_cnt = 0UL;
197 :
198 6 : uchar buf[ 4096 ];
199 6 : fd_netlink_iter_t iter[1];
200 6 : for( fd_netlink_iter_init( iter, netlink, buf, sizeof(buf) );
201 84 : !fd_netlink_iter_done( iter );
202 78 : fd_netlink_iter_next( iter, netlink ) ) {
203 78 : struct nlmsghdr const * nlh = fd_netlink_iter_msg( iter );
204 78 : if( FD_UNLIKELY( nlh->nlmsg_flags & NLM_F_DUMP_INTR ) ) dump_intr = 1;
205 78 : if( FD_UNLIKELY( nlh->nlmsg_type==NLMSG_ERROR ) ) {
206 0 : struct nlmsgerr * err = NLMSG_DATA( nlh );
207 0 : int nl_err = -err->error;
208 0 : FD_LOG_WARNING(( "netlink RTM_GETROUTE,NLM_F_REQUEST|NLM_F_DUMP failed (%d-%s)", nl_err, fd_io_strerror( nl_err ) ));
209 0 : return nl_err;
210 0 : }
211 78 : if( FD_UNLIKELY( nlh->nlmsg_type!=RTM_NEWROUTE ) ) {
212 0 : FD_LOG_DEBUG(( "unexpected nlmsg_type %u", nlh->nlmsg_type ));
213 0 : continue;
214 0 : }
215 78 : route_cnt++;
216 :
217 78 : int translate_err = fd_fib4_netlink_translate( fib, nlh, table_id );
218 78 : if( FD_UNLIKELY( translate_err==ENOSPC ) ) {
219 0 : no_space = 1;
220 0 : break;
221 0 : }
222 78 : }
223 6 : if( FD_UNLIKELY( iter->err > 0 ) ) return FD_FIB_NETLINK_ERR_IO;
224 6 : ulong drain_cnt = fd_netlink_iter_drain( iter, netlink );
225 :
226 6 : if( no_space ) {
227 0 : FD_LOG_WARNING(( "Routing table is too small! `ip route show table %u` returned %lu entries, which exceeds the configured maximum of %lu",
228 0 : table_id, route_cnt+drain_cnt, fd_fib4_max( fib ) ));
229 0 : fd_fib4_clear( fib );
230 0 : return FD_FIB_NETLINK_ERR_SPACE;
231 0 : }
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 : FD_FN_CONST char const *
247 0 : fd_fib4_netlink_strerror( int err ) {
248 0 : switch( err ) {
249 0 : case FD_FIB_NETLINK_SUCCESS:
250 0 : return "success";
251 0 : case FD_FIB_NETLINK_ERR_OOPS:
252 0 : return "oops";
253 0 : case FD_FIB_NETLINK_ERR_IO:
254 0 : return "io";
255 0 : case FD_FIB_NETLINK_ERR_INTR:
256 0 : return "interrupt";
257 0 : case FD_FIB_NETLINK_ERR_SPACE:
258 0 : return "out of space";
259 0 : default:
260 0 : return "unknown";
261 0 : }
262 0 : }
|