]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
8041b5ba | 2 | |
54e6f97b LP |
3 | #include <net/if_arp.h> |
4 | ||
1c4baffc | 5 | #include "sd-netlink.h" |
b5efdb8a LP |
6 | |
7 | #include "alloc-util.h" | |
54e6f97b | 8 | #include "fd-util.h" |
e80af1bd | 9 | #include "local-addresses.h" |
cf0fbc49 TA |
10 | #include "macro.h" |
11 | #include "netlink-util.h" | |
760877e9 | 12 | #include "sort-util.h" |
8041b5ba | 13 | |
93bab288 YW |
14 | static int address_compare(const struct local_address *a, const struct local_address *b) { |
15 | int r; | |
5502f0d9 LP |
16 | |
17 | /* Order lowest scope first, IPv4 before IPv6, lowest interface index first */ | |
18 | ||
e9140aff LP |
19 | if (a->family == AF_INET && b->family == AF_INET6) |
20 | return -1; | |
21 | if (a->family == AF_INET6 && b->family == AF_INET) | |
22 | return 1; | |
23 | ||
93bab288 YW |
24 | r = CMP(a->scope, b->scope); |
25 | if (r != 0) | |
26 | return r; | |
5502f0d9 | 27 | |
93bab288 YW |
28 | r = CMP(a->metric, b->metric); |
29 | if (r != 0) | |
30 | return r; | |
5502f0d9 | 31 | |
93bab288 YW |
32 | r = CMP(a->ifindex, b->ifindex); |
33 | if (r != 0) | |
34 | return r; | |
5502f0d9 | 35 | |
00d75e57 | 36 | return memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(a->family)); |
5502f0d9 LP |
37 | } |
38 | ||
54e6f97b LP |
39 | static void suppress_duplicates(struct local_address *list, size_t *n_list) { |
40 | size_t old_size, new_size; | |
41 | ||
42 | /* Removes duplicate entries, assumes the list of addresses is already sorted. Updates in-place. */ | |
43 | ||
44 | if (*n_list < 2) /* list with less than two entries can't have duplicates */ | |
45 | return; | |
46 | ||
47 | old_size = *n_list; | |
48 | new_size = 1; | |
49 | ||
50 | for (size_t i = 1; i < old_size; i++) { | |
51 | ||
52 | if (address_compare(list + i, list + new_size - 1) == 0) | |
53 | continue; | |
54 | ||
55 | list[new_size++] = list[i]; | |
56 | } | |
57 | ||
58 | *n_list = new_size; | |
59 | } | |
60 | ||
61 | int local_addresses( | |
62 | sd_netlink *context, | |
63 | int ifindex, | |
64 | int af, | |
65 | struct local_address **ret) { | |
66 | ||
4afd3348 LP |
67 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; |
68 | _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; | |
e80af1bd | 69 | _cleanup_free_ struct local_address *list = NULL; |
319a4f4b | 70 | size_t n_list = 0; |
d1ca51b1 | 71 | int r; |
d73c3269 | 72 | |
ee8c4568 | 73 | if (context) |
1c4baffc | 74 | rtnl = sd_netlink_ref(context); |
ee8c4568 | 75 | else { |
1c4baffc | 76 | r = sd_netlink_open(&rtnl); |
ee8c4568 LP |
77 | if (r < 0) |
78 | return r; | |
79 | } | |
8041b5ba | 80 | |
6a28b78f | 81 | r = sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, ifindex, af); |
d1ca51b1 TG |
82 | if (r < 0) |
83 | return r; | |
8041b5ba | 84 | |
24c0f385 | 85 | r = sd_netlink_message_set_request_dump(req, true); |
f318f643 YW |
86 | if (r < 0) |
87 | return r; | |
88 | ||
1c4baffc | 89 | r = sd_netlink_call(rtnl, req, 0, &reply); |
d1ca51b1 TG |
90 | if (r < 0) |
91 | return r; | |
d1ca51b1 | 92 | |
d856e1a7 | 93 | for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { |
e80af1bd | 94 | struct local_address *a; |
d1ca51b1 | 95 | unsigned char flags; |
5502f0d9 | 96 | uint16_t type; |
1d050e1e | 97 | int ifi, family; |
d1ca51b1 | 98 | |
1c4baffc | 99 | r = sd_netlink_message_get_errno(m); |
d1ca51b1 TG |
100 | if (r < 0) |
101 | return r; | |
102 | ||
1c4baffc | 103 | r = sd_netlink_message_get_type(m, &type); |
d1ca51b1 TG |
104 | if (r < 0) |
105 | return r; | |
d1ca51b1 | 106 | if (type != RTM_NEWADDR) |
8041b5ba LP |
107 | continue; |
108 | ||
ee8c4568 LP |
109 | r = sd_rtnl_message_addr_get_ifindex(m, &ifi); |
110 | if (r < 0) | |
111 | return r; | |
1d050e1e LP |
112 | if (ifindex > 0 && ifi != ifindex) |
113 | continue; | |
ee8c4568 | 114 | |
1d050e1e LP |
115 | r = sd_rtnl_message_addr_get_family(m, &family); |
116 | if (r < 0) | |
117 | return r; | |
118 | if (af != AF_UNSPEC && af != family) | |
ee8c4568 LP |
119 | continue; |
120 | ||
5502f0d9 | 121 | r = sd_rtnl_message_addr_get_flags(m, &flags); |
d1ca51b1 TG |
122 | if (r < 0) |
123 | return r; | |
5502f0d9 | 124 | if (flags & IFA_F_DEPRECATED) |
d73c3269 | 125 | continue; |
8041b5ba | 126 | |
319a4f4b | 127 | if (!GREEDY_REALLOC0(list, n_list+1)) |
5502f0d9 LP |
128 | return -ENOMEM; |
129 | ||
130 | a = list + n_list; | |
131 | ||
132 | r = sd_rtnl_message_addr_get_scope(m, &a->scope); | |
d1ca51b1 TG |
133 | if (r < 0) |
134 | return r; | |
8041b5ba | 135 | |
945c2931 | 136 | if (ifindex == 0 && IN_SET(a->scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE)) |
d73c3269 | 137 | continue; |
8041b5ba | 138 | |
1d050e1e | 139 | switch (family) { |
5502f0d9 | 140 | |
d1ca51b1 | 141 | case AF_INET: |
1c4baffc | 142 | r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a->address.in); |
d1ca51b1 | 143 | if (r < 0) { |
1c4baffc | 144 | r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a->address.in); |
d1ca51b1 TG |
145 | if (r < 0) |
146 | continue; | |
147 | } | |
148 | break; | |
5502f0d9 | 149 | |
d1ca51b1 | 150 | case AF_INET6: |
1c4baffc | 151 | r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a->address.in6); |
d1ca51b1 | 152 | if (r < 0) { |
1c4baffc | 153 | r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a->address.in6); |
d1ca51b1 TG |
154 | if (r < 0) |
155 | continue; | |
156 | } | |
157 | break; | |
5502f0d9 | 158 | |
d1ca51b1 | 159 | default: |
d73c3269 | 160 | continue; |
d73c3269 | 161 | } |
8041b5ba | 162 | |
ee8c4568 | 163 | a->ifindex = ifi; |
1d050e1e | 164 | a->family = family; |
8041b5ba | 165 | |
d1ca51b1 | 166 | n_list++; |
5502f0d9 | 167 | }; |
8041b5ba | 168 | |
c3a8c6aa LP |
169 | if (ret) { |
170 | typesafe_qsort(list, n_list, address_compare); | |
54e6f97b | 171 | suppress_duplicates(list, &n_list); |
c3a8c6aa LP |
172 | *ret = TAKE_PTR(list); |
173 | } | |
e9140aff LP |
174 | |
175 | return (int) n_list; | |
176 | } | |
177 | ||
bff94a84 YW |
178 | static int add_local_gateway( |
179 | struct local_address **list, | |
180 | size_t *n_list, | |
bff94a84 YW |
181 | int af, |
182 | int ifindex, | |
183 | uint32_t metric, | |
184 | const RouteVia *via) { | |
185 | ||
186 | assert(list); | |
187 | assert(n_list); | |
bff94a84 YW |
188 | assert(via); |
189 | ||
190 | if (af != AF_UNSPEC && af != via->family) | |
191 | return 0; | |
192 | ||
319a4f4b | 193 | if (!GREEDY_REALLOC(*list, *n_list + 1)) |
bff94a84 YW |
194 | return -ENOMEM; |
195 | ||
196 | (*list)[(*n_list)++] = (struct local_address) { | |
197 | .ifindex = ifindex, | |
198 | .metric = metric, | |
199 | .family = via->family, | |
200 | .address = via->address, | |
201 | }; | |
202 | ||
203 | return 0; | |
204 | } | |
205 | ||
54e6f97b LP |
206 | int local_gateways( |
207 | sd_netlink *context, | |
208 | int ifindex, | |
209 | int af, | |
210 | struct local_address **ret) { | |
211 | ||
4afd3348 LP |
212 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; |
213 | _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; | |
e9140aff | 214 | _cleanup_free_ struct local_address *list = NULL; |
319a4f4b | 215 | size_t n_list = 0; |
e9140aff LP |
216 | int r; |
217 | ||
e9140aff | 218 | if (context) |
1c4baffc | 219 | rtnl = sd_netlink_ref(context); |
e9140aff | 220 | else { |
1c4baffc | 221 | r = sd_netlink_open(&rtnl); |
e9140aff LP |
222 | if (r < 0) |
223 | return r; | |
224 | } | |
225 | ||
1d050e1e | 226 | r = sd_rtnl_message_new_route(rtnl, &req, RTM_GETROUTE, af, RTPROT_UNSPEC); |
e9140aff LP |
227 | if (r < 0) |
228 | return r; | |
229 | ||
3e0eeb8e YW |
230 | r = sd_rtnl_message_route_set_type(req, RTN_UNICAST); |
231 | if (r < 0) | |
232 | return r; | |
233 | ||
234 | r = sd_rtnl_message_route_set_table(req, RT_TABLE_MAIN); | |
235 | if (r < 0) | |
236 | return r; | |
237 | ||
24c0f385 | 238 | r = sd_netlink_message_set_request_dump(req, true); |
e9140aff LP |
239 | if (r < 0) |
240 | return r; | |
241 | ||
1c4baffc | 242 | r = sd_netlink_call(rtnl, req, 0, &reply); |
e9140aff LP |
243 | if (r < 0) |
244 | return r; | |
245 | ||
bff94a84 YW |
246 | for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { |
247 | _cleanup_ordered_set_free_free_ OrderedSet *multipath_routes = NULL; | |
248 | _cleanup_free_ void *rta_multipath = NULL; | |
249 | union in_addr_union gateway; | |
e9140aff | 250 | uint16_t type; |
d1b014df | 251 | unsigned char dst_len, src_len, table; |
d2f4a948 | 252 | uint32_t ifi = 0, metric = 0; |
bff94a84 | 253 | size_t rta_len; |
1d050e1e | 254 | int family; |
bff94a84 | 255 | RouteVia via; |
e9140aff | 256 | |
1c4baffc | 257 | r = sd_netlink_message_get_errno(m); |
e9140aff LP |
258 | if (r < 0) |
259 | return r; | |
260 | ||
1c4baffc | 261 | r = sd_netlink_message_get_type(m, &type); |
e9140aff LP |
262 | if (r < 0) |
263 | return r; | |
e9140aff LP |
264 | if (type != RTM_NEWROUTE) |
265 | continue; | |
266 | ||
a98433c0 | 267 | /* We only care for default routes */ |
584d0d2a | 268 | r = sd_rtnl_message_route_get_dst_prefixlen(m, &dst_len); |
e9140aff LP |
269 | if (r < 0) |
270 | return r; | |
e9140aff LP |
271 | if (dst_len != 0) |
272 | continue; | |
273 | ||
584d0d2a | 274 | r = sd_rtnl_message_route_get_src_prefixlen(m, &src_len); |
a98433c0 LP |
275 | if (r < 0) |
276 | return r; | |
277 | if (src_len != 0) | |
278 | continue; | |
279 | ||
d1b014df LP |
280 | r = sd_rtnl_message_route_get_table(m, &table); |
281 | if (r < 0) | |
282 | return r; | |
283 | if (table != RT_TABLE_MAIN) | |
284 | continue; | |
285 | ||
bff94a84 YW |
286 | r = sd_netlink_message_read_u32(m, RTA_PRIORITY, &metric); |
287 | if (r < 0 && r != -ENODATA) | |
e9140aff | 288 | return r; |
e9140aff | 289 | |
1d050e1e LP |
290 | r = sd_rtnl_message_route_get_family(m, &family); |
291 | if (r < 0) | |
292 | return r; | |
bff94a84 | 293 | if (!IN_SET(family, AF_INET, AF_INET6)) |
1d050e1e LP |
294 | continue; |
295 | ||
bff94a84 YW |
296 | r = sd_netlink_message_read_u32(m, RTA_OIF, &ifi); |
297 | if (r < 0 && r != -ENODATA) | |
298 | return r; | |
299 | if (r >= 0) { | |
300 | if (ifi <= 0) | |
301 | return -EINVAL; | |
302 | if (ifindex > 0 && (int) ifi != ifindex) | |
303 | continue; | |
e9140aff | 304 | |
bff94a84 YW |
305 | r = netlink_message_read_in_addr_union(m, RTA_GATEWAY, family, &gateway); |
306 | if (r < 0 && r != -ENODATA) | |
307 | return r; | |
308 | if (r >= 0) { | |
309 | via.family = family; | |
310 | via.address = gateway; | |
319a4f4b | 311 | r = add_local_gateway(&list, &n_list, af, ifi, metric, &via); |
bff94a84 YW |
312 | if (r < 0) |
313 | return r; | |
e9140aff | 314 | |
e9140aff | 315 | continue; |
bff94a84 | 316 | } |
e9140aff | 317 | |
bff94a84 | 318 | if (family != AF_INET) |
e9140aff LP |
319 | continue; |
320 | ||
bff94a84 YW |
321 | r = sd_netlink_message_read(m, RTA_VIA, sizeof(via), &via); |
322 | if (r < 0 && r != -ENODATA) | |
323 | return r; | |
324 | if (r >= 0) { | |
319a4f4b | 325 | r = add_local_gateway(&list, &n_list, af, ifi, metric, &via); |
bff94a84 YW |
326 | if (r < 0) |
327 | return r; | |
328 | ||
329 | continue; | |
330 | } | |
e9140aff LP |
331 | } |
332 | ||
bff94a84 YW |
333 | r = sd_netlink_message_read_data(m, RTA_MULTIPATH, &rta_len, &rta_multipath); |
334 | if (r < 0 && r != -ENODATA) | |
335 | return r; | |
336 | if (r >= 0) { | |
337 | MultipathRoute *mr; | |
e9140aff | 338 | |
bff94a84 YW |
339 | r = rtattr_read_nexthop(rta_multipath, rta_len, family, &multipath_routes); |
340 | if (r < 0) | |
341 | return r; | |
e9140aff | 342 | |
bff94a84 YW |
343 | ORDERED_SET_FOREACH(mr, multipath_routes) { |
344 | if (ifindex > 0 && mr->ifindex != ifindex) | |
345 | continue; | |
346 | ||
319a4f4b | 347 | r = add_local_gateway(&list, &n_list, af, ifi, metric, &mr->gateway); |
bff94a84 YW |
348 | if (r < 0) |
349 | return r; | |
350 | } | |
351 | } | |
e9140aff LP |
352 | } |
353 | ||
c3a8c6aa LP |
354 | if (ret) { |
355 | typesafe_qsort(list, n_list, address_compare); | |
54e6f97b LP |
356 | suppress_duplicates(list, &n_list); |
357 | *ret = TAKE_PTR(list); | |
358 | } | |
359 | ||
360 | return (int) n_list; | |
361 | } | |
362 | ||
363 | int local_outbounds( | |
364 | sd_netlink *context, | |
365 | int ifindex, | |
366 | int af, | |
367 | struct local_address **ret) { | |
368 | ||
369 | _cleanup_free_ struct local_address *list = NULL, *gateways = NULL; | |
319a4f4b | 370 | size_t n_list = 0; |
54e6f97b LP |
371 | int r, n_gateways; |
372 | ||
373 | /* Determines our default outbound addresses, i.e. the "primary" local addresses we use to talk to IP | |
374 | * addresses behind the default routes. This is still an address of the local host (i.e. this doesn't | |
375 | * resolve NAT or so), but it's the set of addresses the local IP stack most likely uses to talk to | |
376 | * other hosts. | |
377 | * | |
378 | * This works by connect()ing a SOCK_DGRAM socket to the local gateways, and then reading the IP | |
379 | * address off the socket that was chosen for the routing decision. */ | |
380 | ||
381 | n_gateways = local_gateways(context, ifindex, af, &gateways); | |
382 | if (n_gateways < 0) | |
383 | return n_gateways; | |
384 | if (n_gateways == 0) { | |
385 | /* No gateways? Then we have no outbound addresses either. */ | |
386 | if (ret) | |
387 | *ret = NULL; | |
388 | ||
389 | return 0; | |
390 | } | |
391 | ||
392 | for (int i = 0; i < n_gateways; i++) { | |
393 | _cleanup_close_ int fd = -1; | |
394 | union sockaddr_union sa; | |
395 | socklen_t salen; | |
396 | ||
397 | fd = socket(gateways[i].family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); | |
398 | if (fd < 0) | |
399 | return -errno; | |
400 | ||
401 | switch (gateways[i].family) { | |
402 | ||
403 | case AF_INET: | |
404 | sa.in = (struct sockaddr_in) { | |
405 | .sin_family = AF_INET, | |
406 | .sin_addr = gateways[i].address.in, | |
098d42b6 YW |
407 | .sin_port = htobe16(53), /* doesn't really matter which port we pick — |
408 | * we just care about the routing decision */ | |
54e6f97b LP |
409 | }; |
410 | ||
411 | break; | |
412 | ||
413 | case AF_INET6: | |
414 | sa.in6 = (struct sockaddr_in6) { | |
415 | .sin6_family = AF_INET6, | |
416 | .sin6_addr = gateways[i].address.in6, | |
417 | .sin6_port = htobe16(53), | |
418 | .sin6_scope_id = gateways[i].ifindex, | |
419 | }; | |
420 | ||
421 | break; | |
422 | ||
423 | default: | |
04499a70 | 424 | assert_not_reached(); |
54e6f97b LP |
425 | } |
426 | ||
427 | /* So ideally we'd just use IP_UNICAST_IF here to pass the ifindex info to the kernel before | |
428 | * connect()ing, sot that it influences the routing decision. However, on current kernels | |
429 | * IP_UNICAST_IF doesn't actually influence the routing decision for UDP — which I think | |
430 | * should probably just be considered a bug. Once that bug is fixed this is the best API to | |
431 | * use, since it is the most lightweight. */ | |
432 | r = socket_set_unicast_if(fd, gateways[i].family, gateways[i].ifindex); | |
433 | if (r < 0) | |
434 | log_debug_errno(r, "Failed to set unicast interface index %i, ignoring: %m", gateways[i].ifindex); | |
435 | ||
436 | /* We'll also use SO_BINDTOINDEX. This requires CAP_NET_RAW on old kernels, hence there's a | |
437 | * good chance this fails. Since 5.7 this restriction was dropped and the first | |
438 | * SO_BINDTOINDEX on a socket may be done without privileges. This one has the benefit of | |
439 | * really influencing the routing decision, i.e. this one definitely works for us — as long | |
098d42b6 | 440 | * as we have the privileges for it. */ |
54e6f97b LP |
441 | r = socket_bind_to_ifindex(fd, gateways[i].ifindex); |
442 | if (r < 0) | |
443 | log_debug_errno(r, "Failed to bind socket to interface %i, ignoring: %m", gateways[i].ifindex); | |
444 | ||
445 | /* Let's now connect() to the UDP socket, forcing the kernel to make a routing decision and | |
446 | * auto-bind the socket. We ignore failures on this, since that failure might happen for a | |
447 | * multitude of reasons (policy/firewall issues, who knows?) and some of them might be | |
448 | * *after* the routing decision and the auto-binding already took place. If so we can still | |
449 | * make use of the binding and return it. Hence, let's not unnecessarily fail early here: we | |
450 | * can still easily detect if the auto-binding worked or not, by comparing the bound IP | |
098d42b6 | 451 | * address with zero — which we do below. */ |
54e6f97b LP |
452 | if (connect(fd, &sa.sa, SOCKADDR_LEN(sa)) < 0) |
453 | log_debug_errno(errno, "Failed to connect SOCK_DGRAM socket to gateway, ignoring: %m"); | |
454 | ||
455 | /* Let's now read the socket address of the socket. A routing decision should have been | |
456 | * made. Let's verify that and use the data. */ | |
457 | salen = SOCKADDR_LEN(sa); | |
458 | if (getsockname(fd, &sa.sa, &salen) < 0) | |
459 | return -errno; | |
460 | assert(sa.sa.sa_family == gateways[i].family); | |
461 | assert(salen == SOCKADDR_LEN(sa)); | |
462 | ||
463 | switch (gateways[i].family) { | |
464 | ||
465 | case AF_INET: | |
466 | if (in4_addr_is_null(&sa.in.sin_addr)) /* Auto-binding didn't work. :-( */ | |
467 | continue; | |
468 | ||
319a4f4b | 469 | if (!GREEDY_REALLOC(list, n_list+1)) |
54e6f97b LP |
470 | return -ENOMEM; |
471 | ||
472 | list[n_list++] = (struct local_address) { | |
473 | .family = gateways[i].family, | |
474 | .ifindex = gateways[i].ifindex, | |
475 | .address.in = sa.in.sin_addr, | |
476 | }; | |
477 | ||
478 | break; | |
479 | ||
480 | case AF_INET6: | |
481 | if (in6_addr_is_null(&sa.in6.sin6_addr)) | |
482 | continue; | |
483 | ||
319a4f4b | 484 | if (!GREEDY_REALLOC(list, n_list+1)) |
54e6f97b LP |
485 | return -ENOMEM; |
486 | ||
487 | list[n_list++] = (struct local_address) { | |
488 | .family = gateways[i].family, | |
489 | .ifindex = gateways[i].ifindex, | |
490 | .address.in6 = sa.in6.sin6_addr, | |
491 | }; | |
492 | break; | |
493 | ||
494 | default: | |
04499a70 | 495 | assert_not_reached(); |
54e6f97b LP |
496 | } |
497 | } | |
498 | ||
499 | if (ret) { | |
500 | typesafe_qsort(list, n_list, address_compare); | |
501 | suppress_duplicates(list, &n_list); | |
c3a8c6aa LP |
502 | *ret = TAKE_PTR(list); |
503 | } | |
d73c3269 | 504 | |
e80af1bd | 505 | return (int) n_list; |
8041b5ba | 506 | } |