Line data Source code
1 : #include "fd_netdev_netlink.h"
2 : #include "../../util/fd_util.h"
3 : #include "../../util/net/fd_ip4.h"
4 : #include "fd_netdev_tbl.h"
5 : #include <linux/if_link.h>
6 :
7 : #if !defined(__linux__)
8 : #error "fd_netdev_netlink.c requires a Linux system with kernel headers"
9 : #endif
10 :
11 : #include <errno.h>
12 : #include <linux/if.h> /* IFNAMSIZ */
13 : #include <linux/if_arp.h> /* ARPHRD_NETROM */
14 : #include <linux/rtnetlink.h> /* RTM_{...}, NLM_{...} */
15 : #include <linux/ip.h>
16 : #include <linux/ipv6.h>
17 : #include <linux/if_tunnel.h>
18 :
19 : static fd_netdev_t *
20 21 : fd_netdev_init( fd_netdev_t * netdev ) {
21 21 : *netdev = (fd_netdev_t) {
22 21 : .mtu = 1500,
23 21 : .if_idx = 0,
24 21 : .slave_tbl_idx = -1,
25 21 : .master_idx = -1,
26 21 : .oper_status = FD_OPER_STATUS_INVALID,
27 21 : .dev_type = ARPHRD_NONE,
28 21 : .gre_dst_ip = 0,
29 21 : .gre_src_ip = 0
30 21 : };
31 21 : return netdev;
32 21 : }
33 :
34 : FD_FN_CONST static uchar
35 21 : ifoper_to_oper_status( uint if_oper ) {
36 : /* Linux uses different enum values than RFC 2863 */
37 21 : switch( if_oper ) {
38 3 : case IF_OPER_UNKNOWN:
39 3 : return FD_OPER_STATUS_UNKNOWN;
40 0 : case IF_OPER_NOTPRESENT:
41 0 : return FD_OPER_STATUS_NOT_PRESENT;
42 9 : case IF_OPER_DOWN:
43 9 : return FD_OPER_STATUS_DOWN;
44 0 : case IF_OPER_LOWERLAYERDOWN:
45 0 : return FD_OPER_STATUS_LOWER_LAYER_DOWN;
46 0 : case IF_OPER_TESTING:
47 0 : return FD_OPER_STATUS_TESTING;
48 0 : case IF_OPER_DORMANT:
49 0 : return FD_OPER_STATUS_DORMANT;
50 9 : case IF_OPER_UP:
51 9 : return FD_OPER_STATUS_UP;
52 0 : default:
53 0 : return FD_OPER_STATUS_INVALID;
54 21 : }
55 21 : }
56 :
57 : int
58 : fd_netdev_netlink_load_table( fd_netdev_tbl_join_t * tbl,
59 3 : fd_netlink_t * netlink ) {
60 3 : fd_netdev_tbl_reset( tbl );
61 :
62 3 : uint seq = netlink->seq++;
63 :
64 3 : struct {
65 3 : struct nlmsghdr nlh;
66 3 : struct ifinfomsg ifi;
67 3 : } request;
68 3 : request.nlh = (struct nlmsghdr) {
69 3 : .nlmsg_type = RTM_GETLINK,
70 3 : .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
71 3 : .nlmsg_len = sizeof(request),
72 3 : .nlmsg_seq = seq
73 3 : };
74 3 : request.ifi = (struct ifinfomsg) {
75 3 : .ifi_family = AF_PACKET,
76 3 : .ifi_type = ARPHRD_NETROM,
77 3 : };
78 :
79 3 : long send_res = sendto( netlink->fd, &request, sizeof(request), 0, NULL, 0 );
80 3 : if( FD_UNLIKELY( send_res<0 ) ) {
81 0 : FD_LOG_WARNING(( "netlink send(%d,RTM_GETLINK,NLM_F_REQUEST|NLM_F_DUMP) failed (%i-%s)", netlink->fd, errno, fd_io_strerror( errno ) ));
82 0 : return errno;
83 0 : }
84 3 : if( FD_UNLIKELY( send_res!=sizeof(request) ) ) {
85 0 : FD_LOG_WARNING(( "netlink send(%d,RTM_GETLINK,NLM_F_REQUEST|NLM_F_DUMP) failed (short write)", netlink->fd ));
86 0 : return EPIPE;
87 0 : }
88 :
89 3 : int err = 0;
90 :
91 3 : uchar buf[ 4096 ];
92 3 : fd_netlink_iter_t iter[1];
93 3 : for( fd_netlink_iter_init( iter, netlink, buf, sizeof(buf) );
94 24 : !fd_netlink_iter_done( iter );
95 21 : fd_netlink_iter_next( iter, netlink ) ) {
96 21 : struct nlmsghdr const * nlh = fd_netlink_iter_msg( iter );
97 21 : if( FD_UNLIKELY( nlh->nlmsg_type==NLMSG_ERROR ) ) {
98 0 : struct nlmsgerr * err = NLMSG_DATA( nlh );
99 0 : int nl_err = -err->error;
100 0 : FD_LOG_WARNING(( "netlink RTM_GETLINK,NLM_F_REQUEST|NLM_F_DUMP failed (%d-%s)", nl_err, fd_io_strerror( nl_err ) ));
101 0 : return nl_err;
102 0 : }
103 21 : if( FD_UNLIKELY( nlh->nlmsg_type!=RTM_NEWLINK ) ) {
104 0 : FD_LOG_DEBUG(( "unexpected nlmsg_type %u", nlh->nlmsg_type ));
105 0 : continue;
106 0 : }
107 21 : struct ifinfomsg const * ifi = NLMSG_DATA( nlh );
108 :
109 21 : if( FD_UNLIKELY( ifi->ifi_index<0 ) ) {
110 0 : FD_LOG_WARNING(( "Error reading interface table: interface %d has invalid index", ifi->ifi_index ));
111 0 : err = EPROTO;
112 0 : break;
113 0 : }
114 :
115 21 : ushort ifi_type = ifi->ifi_type;
116 21 : if( ifi_type!=ARPHRD_ETHER && ifi_type!=ARPHRD_LOOPBACK && ifi_type!=ARPHRD_IPGRE ) continue;
117 21 : if( ifi_type==ARPHRD_IPGRE && !(ifi->ifi_flags & IFF_UP) ) continue;
118 :
119 21 : struct ifinfomsg * msg = NLMSG_DATA( nlh );
120 21 : struct rtattr * rat = (void *)( (ulong)msg + NLMSG_ALIGN( sizeof(struct ifinfomsg) ) );
121 21 : long rat_sz = (long)NLMSG_PAYLOAD( nlh, sizeof(struct ifinfomsg) );
122 :
123 21 : fd_netdev_t netdev[1];
124 21 : fd_netdev_init( netdev );
125 :
126 813 : for( ; RTA_OK( rat, rat_sz ); rat=RTA_NEXT( rat, rat_sz ) ) {
127 813 : void * rta = RTA_DATA( rat );
128 813 : ulong rta_sz = RTA_PAYLOAD( rat );
129 :
130 813 : switch( rat->rta_type ) {
131 :
132 21 : case IFLA_IFNAME:
133 : /* Includes trailing zero */
134 21 : if( FD_UNLIKELY( rta_sz==0 || rta_sz>IFNAMSIZ ) ) {
135 0 : FD_LOG_WARNING(( "Error reading interface table: IFLA_IFNAME has unsupported size %lu", rta_sz ));
136 0 : err = EPROTO;
137 0 : goto fail;
138 0 : }
139 21 : memcpy( netdev->name, rta, rta_sz );
140 21 : netdev->name[ rta_sz-1 ] = '\0';
141 21 : break;
142 :
143 21 : case IFLA_ADDRESS:
144 21 : if( FD_UNLIKELY( rta_sz==6UL ) ) {
145 21 : memcpy( netdev->mac_addr, rta, 6 );
146 21 : }
147 21 : break;
148 :
149 21 : case IFLA_OPERSTATE:
150 21 : if( FD_UNLIKELY( rta_sz!=1UL ) ) {
151 0 : FD_LOG_WARNING(( "Error reading interface table: IFLA_OPERSTATE has unexpected size %lu", rta_sz ));
152 0 : err = EPROTO;
153 0 : goto fail;
154 0 : }
155 21 : netdev->oper_status = (uchar)ifoper_to_oper_status( FD_LOAD( uchar, rta ) );
156 21 : break;
157 :
158 21 : case IFLA_MTU:
159 21 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
160 0 : FD_LOG_WARNING(( "Error reading interface table: IFLA_MTU has unexpected size %lu", rta_sz ));
161 0 : err = EPROTO;
162 0 : goto fail;
163 0 : }
164 21 : netdev->mtu = (ushort)fd_uint_min( FD_LOAD( uint, rta ), USHORT_MAX );
165 21 : break;
166 :
167 6 : case IFLA_MASTER: {
168 6 : if( FD_UNLIKELY( rta_sz!=4UL ) ) {
169 0 : FD_LOG_WARNING(( "Error reading interface table: IFLA_MASTER has unexpected size %lu", rta_sz ));
170 0 : err = EPROTO;
171 0 : goto fail;
172 0 : }
173 6 : int master_idx = FD_LOAD( int, rta );
174 6 : if( FD_UNLIKELY( master_idx<0 ) ) {
175 0 : FD_LOG_WARNING(( "Error reading interface table: IFLA_MASTER has invalid ifindex %d", master_idx ));
176 0 : err = EPROTO;
177 0 : goto fail;
178 0 : }
179 6 : netdev->master_idx = master_idx;
180 6 : break;
181 6 : } /* IFLA_MASTER */
182 :
183 12 : case IFLA_LINKINFO : {
184 12 : if( ifi->ifi_type!=ARPHRD_IPGRE ) continue;
185 :
186 0 : struct rtattr * info_rat = rta;
187 0 : long info_rat_sz = (long)rta_sz;
188 :
189 0 : for( ; RTA_OK( info_rat, info_rat_sz ); info_rat = RTA_NEXT( info_rat, info_rat_sz ) ) {
190 0 : void * info_rta = RTA_DATA( info_rat );
191 0 : ulong info_rta_sz = RTA_PAYLOAD( info_rat );
192 :
193 0 : switch( info_rat->rta_type ) {
194 0 : case IFLA_INFO_DATA: {
195 0 : struct rtattr * gre_rat = (struct rtattr *)info_rta;
196 0 : long gre_rat_sz = (long)info_rta_sz;
197 :
198 0 : for( ; RTA_OK( gre_rat, gre_rat_sz ); gre_rat = RTA_NEXT( gre_rat, gre_rat_sz ) ) {
199 0 : void * gre_rta = RTA_DATA( gre_rat );
200 0 : uint gre_dst_ip;
201 0 : uint gre_src_ip;
202 :
203 0 : switch( gre_rat->rta_type ) {
204 0 : case IFLA_GRE_REMOTE: {
205 0 : gre_dst_ip = FD_LOAD( uint, gre_rta );
206 0 : if( gre_dst_ip != 0 ) netdev->gre_dst_ip = gre_dst_ip;
207 0 : break;
208 0 : } /* IFLA_GRE_REMOTE */
209 0 : case IFLA_GRE_LOCAL: {
210 0 : gre_src_ip = FD_LOAD( uint, gre_rta );
211 0 : if( gre_src_ip != 0 ) netdev->gre_src_ip = gre_src_ip;
212 0 : break;
213 0 : } /* IFLA_GRE_LOCAL */
214 0 : }
215 0 : }
216 0 : break;
217 0 : } /* IFLA_INFO_DATA */
218 0 : } /* info_rat->rta_type */
219 0 : } /* for each info_rta */
220 0 : } /* IFLA_LINKINFO */
221 :
222 813 : } /* switch( rat->rta_type ) */
223 813 : } /* for each RTA */
224 :
225 21 : if( ifi->ifi_type==ARPHRD_LOOPBACK ) {
226 3 : netdev->oper_status = FD_OPER_STATUS_UP;
227 3 : }
228 :
229 21 : netdev->dev_type = ifi_type;
230 21 : netdev->if_idx = (uint)ifi->ifi_index;
231 21 : fd_netdev_t * dst = fd_netdev_tbl_query( tbl, (uint)ifi->ifi_index );
232 21 : if( FD_UNLIKELY( !dst ) ) {
233 21 : if( FD_UNLIKELY( tbl->hdr->dev_cnt >= tbl->hdr->dev_max ) ) {
234 0 : FD_LOG_WARNING(( "Error reading interface table: tracked interface limit %u reached (dropping ifindex %i)",
235 0 : tbl->hdr->dev_max, ifi->ifi_index ));
236 0 : err = ENOSPC;
237 0 : break;
238 0 : }
239 21 : dst = &tbl->dev_tbl[ tbl->hdr->dev_cnt ]; // alloc new entry
240 21 : tbl->hdr->dev_cnt = (ushort)(tbl->hdr->dev_cnt + 1);
241 21 : }
242 21 : *dst = *netdev;
243 21 : }
244 :
245 : /* Walk the table again to index the bond master => slave mapping */
246 :
247 24 : for( ulong j=0UL; j<(tbl->hdr->dev_cnt); j++ ) {
248 : /* Only consider UP slaves */
249 21 : if( tbl->dev_tbl[ j ].oper_status!=FD_OPER_STATUS_UP ) continue;
250 :
251 : /* Find master */
252 12 : int master_idx = tbl->dev_tbl[ j ].master_idx;
253 12 : if( master_idx<0 ) continue;
254 6 : fd_netdev_t * master = fd_netdev_tbl_query( tbl, (uint)master_idx );
255 6 : if( FD_UNLIKELY( !master ) ) {
256 0 : FD_LOG_WARNING(( "Error reading interface table: Bond device references missing master %d", master_idx ));
257 0 : continue;
258 0 : }
259 :
260 : /* Allocate a new bond slave table if needed */
261 6 : if( master->slave_tbl_idx<0 ) {
262 3 : if( FD_UNLIKELY( tbl->hdr->bond_cnt>=tbl->hdr->bond_max ) ) {
263 0 : FD_LOG_WARNING(( "Error reading interface table: Found %u bond devices but max is %u", tbl->hdr->bond_cnt, tbl->hdr->bond_max ));
264 0 : continue;
265 0 : }
266 :
267 3 : master->slave_tbl_idx = (short)tbl->hdr->bond_cnt;
268 3 : tbl->hdr->bond_cnt = (ushort)( tbl->hdr->bond_cnt+1U );
269 : /* Assume that this table is empty */
270 3 : }
271 :
272 6 : fd_netdev_bond_t * bond = &tbl->bond_tbl[ master->slave_tbl_idx ];
273 6 : if( FD_UNLIKELY( bond->slave_cnt>=FD_NETDEV_BOND_SLAVE_MAX ) ) {
274 0 : FD_LOG_WARNING(( "Error reading interface table: Bond device %d has %u slaves but max is %d", master_idx, bond->slave_cnt, FD_NETDEV_BOND_SLAVE_MAX ));
275 0 : continue;
276 0 : }
277 6 : bond->slave_idx[ bond->slave_cnt ] = (ushort)j;
278 6 : bond->slave_cnt = (uchar)( bond->slave_cnt+1U );
279 6 : }
280 :
281 3 : return 0;
282 :
283 0 : fail:
284 0 : fd_netlink_iter_drain( iter, netlink );
285 0 : return err;
286 3 : }
|