]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/local-addresses.c
hwdb: Add mapping for Xiaomi Mipad 2 bottom bezel capacitive buttons
[thirdparty/systemd.git] / src / shared / local-addresses.c
CommitLineData
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
14static 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
37359b1c 28 r = CMP(a->priority, b->priority);
93bab288
YW
29 if (r != 0)
30 return r;
5502f0d9 31
eb1f9ed6
YW
32 r = CMP(a->weight, b->weight);
33 if (r != 0)
34 return r;
35
93bab288
YW
36 r = CMP(a->ifindex, b->ifindex);
37 if (r != 0)
38 return r;
5502f0d9 39
00d75e57 40 return memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(a->family));
5502f0d9
LP
41}
42
e5ee6453
YW
43bool has_local_address(const struct local_address *addresses, size_t n_addresses, const struct local_address *needle) {
44 assert(addresses || n_addresses == 0);
45 assert(needle);
46
47 for (size_t i = 0; i < n_addresses; i++)
48 if (address_compare(addresses + i, needle) == 0)
49 return true;
50
51 return false;
52}
53
54e6f97b
LP
54static void suppress_duplicates(struct local_address *list, size_t *n_list) {
55 size_t old_size, new_size;
56
57 /* Removes duplicate entries, assumes the list of addresses is already sorted. Updates in-place. */
58
59 if (*n_list < 2) /* list with less than two entries can't have duplicates */
60 return;
61
62 old_size = *n_list;
63 new_size = 1;
64
65 for (size_t i = 1; i < old_size; i++) {
66
67 if (address_compare(list + i, list + new_size - 1) == 0)
68 continue;
69
70 list[new_size++] = list[i];
71 }
72
73 *n_list = new_size;
74}
75
0b2c0c31
YW
76static int add_local_address_full(
77 struct local_address **list,
78 size_t *n_list,
79 int ifindex,
80 unsigned char scope,
81 uint32_t priority,
eb1f9ed6 82 uint32_t weight,
0b2c0c31
YW
83 int family,
84 const union in_addr_union *address) {
85
86 assert(list);
87 assert(n_list);
88 assert(ifindex > 0);
89 assert(IN_SET(family, AF_INET, AF_INET6));
90 assert(address);
91
92 if (!GREEDY_REALLOC(*list, *n_list + 1))
93 return -ENOMEM;
94
95 (*list)[(*n_list)++] = (struct local_address) {
96 .ifindex = ifindex,
97 .scope = scope,
98 .priority = priority,
eb1f9ed6 99 .weight = weight,
0b2c0c31
YW
100 .family = family,
101 .address = *address,
102 };
103
104 return 1;
105}
106
107static int add_local_address(
108 struct local_address **list,
109 size_t *n_list,
110 int ifindex,
111 unsigned char scope,
112 int family,
113 const union in_addr_union *address) {
114
eb1f9ed6 115 return add_local_address_full(list, n_list, ifindex, scope, 0, 0, family, address);
0b2c0c31
YW
116}
117
54e6f97b
LP
118int local_addresses(
119 sd_netlink *context,
120 int ifindex,
121 int af,
122 struct local_address **ret) {
123
4afd3348
LP
124 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
125 _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
e80af1bd 126 _cleanup_free_ struct local_address *list = NULL;
319a4f4b 127 size_t n_list = 0;
d1ca51b1 128 int r;
d73c3269 129
ee8c4568 130 if (context)
1c4baffc 131 rtnl = sd_netlink_ref(context);
ee8c4568 132 else {
1c4baffc 133 r = sd_netlink_open(&rtnl);
ee8c4568
LP
134 if (r < 0)
135 return r;
136 }
8041b5ba 137
6a28b78f 138 r = sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, ifindex, af);
d1ca51b1
TG
139 if (r < 0)
140 return r;
8041b5ba 141
24c0f385 142 r = sd_netlink_message_set_request_dump(req, true);
f318f643
YW
143 if (r < 0)
144 return r;
145
1c4baffc 146 r = sd_netlink_call(rtnl, req, 0, &reply);
d1ca51b1
TG
147 if (r < 0)
148 return r;
d1ca51b1 149
d856e1a7 150 for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) {
0b2c0c31
YW
151 union in_addr_union a;
152 unsigned char flags, scope;
5502f0d9 153 uint16_t type;
1d050e1e 154 int ifi, family;
d1ca51b1 155
1c4baffc 156 r = sd_netlink_message_get_errno(m);
d1ca51b1
TG
157 if (r < 0)
158 return r;
159
1c4baffc 160 r = sd_netlink_message_get_type(m, &type);
d1ca51b1
TG
161 if (r < 0)
162 return r;
d1ca51b1 163 if (type != RTM_NEWADDR)
8041b5ba
LP
164 continue;
165
ee8c4568
LP
166 r = sd_rtnl_message_addr_get_ifindex(m, &ifi);
167 if (r < 0)
168 return r;
1d050e1e
LP
169 if (ifindex > 0 && ifi != ifindex)
170 continue;
ee8c4568 171
1d050e1e
LP
172 r = sd_rtnl_message_addr_get_family(m, &family);
173 if (r < 0)
174 return r;
5cb56068
YW
175 if (!IN_SET(family, AF_INET, AF_INET6))
176 continue;
1d050e1e 177 if (af != AF_UNSPEC && af != family)
ee8c4568
LP
178 continue;
179
5502f0d9 180 r = sd_rtnl_message_addr_get_flags(m, &flags);
d1ca51b1
TG
181 if (r < 0)
182 return r;
e90863f2 183 if ((flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) != 0)
d73c3269 184 continue;
8041b5ba 185
0b2c0c31 186 r = sd_rtnl_message_addr_get_scope(m, &scope);
d1ca51b1
TG
187 if (r < 0)
188 return r;
8041b5ba 189
0b2c0c31 190 if (ifindex == 0 && IN_SET(scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE))
d73c3269 191 continue;
8041b5ba 192
1d050e1e 193 switch (family) {
5502f0d9 194
d1ca51b1 195 case AF_INET:
0b2c0c31 196 r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a.in);
d1ca51b1 197 if (r < 0) {
0b2c0c31 198 r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a.in);
d1ca51b1
TG
199 if (r < 0)
200 continue;
201 }
202 break;
5502f0d9 203
d1ca51b1 204 case AF_INET6:
0b2c0c31 205 r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a.in6);
d1ca51b1 206 if (r < 0) {
0b2c0c31 207 r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a.in6);
d1ca51b1
TG
208 if (r < 0)
209 continue;
210 }
211 break;
5502f0d9 212
d1ca51b1 213 default:
7d08e235 214 assert_not_reached();
d73c3269 215 }
8041b5ba 216
0b2c0c31
YW
217 r = add_local_address(&list, &n_list, ifi, scope, family, &a);
218 if (r < 0)
219 return r;
5502f0d9 220 };
8041b5ba 221
a64f6041
YW
222 typesafe_qsort(list, n_list, address_compare);
223 suppress_duplicates(list, &n_list);
224
225 if (ret)
c3a8c6aa 226 *ret = TAKE_PTR(list);
e9140aff
LP
227
228 return (int) n_list;
229}
230
bff94a84
YW
231static int add_local_gateway(
232 struct local_address **list,
233 size_t *n_list,
bff94a84 234 int ifindex,
37359b1c 235 uint32_t priority,
eb1f9ed6 236 uint32_t weight,
0b2c0c31
YW
237 int family,
238 const union in_addr_union *address) {
bff94a84 239
eb1f9ed6 240 return add_local_address_full(list, n_list, ifindex, 0, priority, weight, family, address);
bff94a84
YW
241}
242
16d95d6f
YW
243static int parse_nexthop_one(
244 struct local_address **list,
245 size_t *n_list,
246 bool allow_via,
247 int family,
248 uint32_t priority,
249 const struct rtnexthop *rtnh) {
250
251 bool has_gw = false;
252 int r;
253
254 assert(rtnh);
255
256 size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop);
257 for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len))
258
259 switch (attr->rta_type) {
260 case RTA_GATEWAY:
261 if (has_gw)
262 return -EBADMSG;
263
264 has_gw = true;
265
266 if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(family)))
267 return -EBADMSG;
268
269 union in_addr_union a;
270 memcpy(&a, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(family));
271 r = add_local_gateway(list, n_list, rtnh->rtnh_ifindex, priority, rtnh->rtnh_hops, family, &a);
272 if (r < 0)
273 return r;
274
275 break;
276
277 case RTA_VIA:
278 if (has_gw)
279 return -EBADMSG;
280
281 has_gw = true;
282
283 if (!allow_via)
284 continue;
285
286 if (family != AF_INET)
287 return -EBADMSG; /* RTA_VIA is only supported for IPv4 routes. */
288
289 if (attr->rta_len != RTA_LENGTH(sizeof(RouteVia)))
290 return -EBADMSG;
291
292 RouteVia *via = RTA_DATA(attr);
293 if (via->family != AF_INET6)
294 return -EBADMSG; /* gateway address should be always IPv6. */
295
296 r = add_local_gateway(list, n_list, rtnh->rtnh_ifindex, priority, rtnh->rtnh_hops, via->family,
297 &(union in_addr_union) { .in6 = via->address.in6 });
298 if (r < 0)
299 return r;
300
301 break;
302 }
303
304 return 0;
305}
306
307static int parse_nexthops(
308 struct local_address **list,
309 size_t *n_list,
310 int ifindex,
311 bool allow_via,
312 int family,
313 uint32_t priority,
314 const struct rtnexthop *rtnh,
315 size_t size) {
316
317 int r;
318
319 assert(list);
320 assert(n_list);
321 assert(IN_SET(family, AF_INET, AF_INET6));
322 assert(rtnh || size == 0);
323
324 if (size < sizeof(struct rtnexthop))
325 return -EBADMSG;
326
327 for (; size >= sizeof(struct rtnexthop); ) {
328 if (NLMSG_ALIGN(rtnh->rtnh_len) > size)
329 return -EBADMSG;
330
331 if (rtnh->rtnh_len < sizeof(struct rtnexthop))
332 return -EBADMSG;
333
334 if (ifindex > 0 && rtnh->rtnh_ifindex != ifindex)
335 goto next_nexthop;
336
337 r = parse_nexthop_one(list, n_list, allow_via, family, priority, rtnh);
338 if (r < 0)
339 return r;
340
341 next_nexthop:
342 size -= NLMSG_ALIGN(rtnh->rtnh_len);
343 rtnh = RTNH_NEXT(rtnh);
344 }
345
346 return 0;
347}
348
54e6f97b
LP
349int local_gateways(
350 sd_netlink *context,
351 int ifindex,
352 int af,
353 struct local_address **ret) {
354
4afd3348
LP
355 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
356 _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
e9140aff 357 _cleanup_free_ struct local_address *list = NULL;
319a4f4b 358 size_t n_list = 0;
e9140aff
LP
359 int r;
360
5cb56068 361 /* The RTA_VIA attribute is used only for IPv4 routes with an IPv6 gateway. If IPv4 gateways are
14f95de8 362 * requested (af == AF_INET), then we do not return IPv6 gateway addresses. Similarly, if IPv6
5cb56068
YW
363 * gateways are requested (af == AF_INET6), then we do not return gateway addresses for IPv4 routes.
364 * So, the RTA_VIA attribute is only parsed when af == AF_UNSPEC. */
365 bool allow_via = af == AF_UNSPEC;
366
e9140aff 367 if (context)
1c4baffc 368 rtnl = sd_netlink_ref(context);
e9140aff 369 else {
1c4baffc 370 r = sd_netlink_open(&rtnl);
e9140aff
LP
371 if (r < 0)
372 return r;
373 }
374
1d050e1e 375 r = sd_rtnl_message_new_route(rtnl, &req, RTM_GETROUTE, af, RTPROT_UNSPEC);
e9140aff
LP
376 if (r < 0)
377 return r;
378
3e0eeb8e
YW
379 r = sd_rtnl_message_route_set_type(req, RTN_UNICAST);
380 if (r < 0)
381 return r;
382
383 r = sd_rtnl_message_route_set_table(req, RT_TABLE_MAIN);
384 if (r < 0)
385 return r;
386
24c0f385 387 r = sd_netlink_message_set_request_dump(req, true);
e9140aff
LP
388 if (r < 0)
389 return r;
390
1c4baffc 391 r = sd_netlink_call(rtnl, req, 0, &reply);
e9140aff
LP
392 if (r < 0)
393 return r;
394
bff94a84 395 for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) {
e9140aff 396 uint16_t type;
d1b014df 397 unsigned char dst_len, src_len, table;
37359b1c 398 uint32_t ifi = 0, priority = 0;
1d050e1e 399 int family;
e9140aff 400
1c4baffc 401 r = sd_netlink_message_get_errno(m);
e9140aff
LP
402 if (r < 0)
403 return r;
404
1c4baffc 405 r = sd_netlink_message_get_type(m, &type);
e9140aff
LP
406 if (r < 0)
407 return r;
e9140aff
LP
408 if (type != RTM_NEWROUTE)
409 continue;
410
a98433c0 411 /* We only care for default routes */
584d0d2a 412 r = sd_rtnl_message_route_get_dst_prefixlen(m, &dst_len);
e9140aff
LP
413 if (r < 0)
414 return r;
e9140aff
LP
415 if (dst_len != 0)
416 continue;
417
584d0d2a 418 r = sd_rtnl_message_route_get_src_prefixlen(m, &src_len);
a98433c0
LP
419 if (r < 0)
420 return r;
421 if (src_len != 0)
422 continue;
423
d1b014df
LP
424 r = sd_rtnl_message_route_get_table(m, &table);
425 if (r < 0)
426 return r;
427 if (table != RT_TABLE_MAIN)
428 continue;
429
37359b1c 430 r = sd_netlink_message_read_u32(m, RTA_PRIORITY, &priority);
bff94a84 431 if (r < 0 && r != -ENODATA)
e9140aff 432 return r;
e9140aff 433
1d050e1e
LP
434 r = sd_rtnl_message_route_get_family(m, &family);
435 if (r < 0)
436 return r;
bff94a84 437 if (!IN_SET(family, AF_INET, AF_INET6))
1d050e1e 438 continue;
5cb56068
YW
439 if (af != AF_UNSPEC && af != family)
440 continue;
1d050e1e 441
bff94a84
YW
442 r = sd_netlink_message_read_u32(m, RTA_OIF, &ifi);
443 if (r < 0 && r != -ENODATA)
444 return r;
445 if (r >= 0) {
446 if (ifi <= 0)
447 return -EINVAL;
448 if (ifindex > 0 && (int) ifi != ifindex)
449 continue;
e9140aff 450
4019bec8 451 union in_addr_union gateway;
bff94a84
YW
452 r = netlink_message_read_in_addr_union(m, RTA_GATEWAY, family, &gateway);
453 if (r < 0 && r != -ENODATA)
454 return r;
455 if (r >= 0) {
eb1f9ed6 456 r = add_local_gateway(&list, &n_list, ifi, priority, 0, family, &gateway);
bff94a84
YW
457 if (r < 0)
458 return r;
e9140aff 459
e9140aff 460 continue;
bff94a84 461 }
e9140aff 462
5cb56068
YW
463 if (!allow_via)
464 continue;
465
bff94a84 466 if (family != AF_INET)
e9140aff
LP
467 continue;
468
4019bec8 469 RouteVia via;
bff94a84
YW
470 r = sd_netlink_message_read(m, RTA_VIA, sizeof(via), &via);
471 if (r < 0 && r != -ENODATA)
472 return r;
473 if (r >= 0) {
5cb56068
YW
474 if (via.family != AF_INET6)
475 return -EBADMSG;
476
eb1f9ed6 477 r = add_local_gateway(&list, &n_list, ifi, priority, 0, via.family,
0b2c0c31 478 &(union in_addr_union) { .in6 = via.address.in6 });
bff94a84
YW
479 if (r < 0)
480 return r;
bff94a84 481 }
1305fe4e
YW
482
483 /* If the route has RTA_OIF, it does not have RTA_MULTIPATH. */
484 continue;
e9140aff
LP
485 }
486
4019bec8
YW
487 size_t rta_len;
488 _cleanup_free_ void *rta_multipath = NULL;
bff94a84
YW
489 r = sd_netlink_message_read_data(m, RTA_MULTIPATH, &rta_len, &rta_multipath);
490 if (r < 0 && r != -ENODATA)
491 return r;
492 if (r >= 0) {
16d95d6f 493 r = parse_nexthops(&list, &n_list, ifindex, allow_via, family, priority, rta_multipath, rta_len);
bff94a84
YW
494 if (r < 0)
495 return r;
bff94a84 496 }
e9140aff
LP
497 }
498
a64f6041
YW
499 typesafe_qsort(list, n_list, address_compare);
500 suppress_duplicates(list, &n_list);
501
502 if (ret)
54e6f97b 503 *ret = TAKE_PTR(list);
54e6f97b
LP
504
505 return (int) n_list;
506}
507
0b2c0c31
YW
508static int add_local_outbound(
509 struct local_address **list,
510 size_t *n_list,
511 int ifindex,
512 int family,
513 const union in_addr_union *address) {
514
eb1f9ed6 515 return add_local_address_full(list, n_list, ifindex, 0, 0, 0, family, address);
0b2c0c31
YW
516}
517
54e6f97b
LP
518int local_outbounds(
519 sd_netlink *context,
520 int ifindex,
521 int af,
522 struct local_address **ret) {
523
524 _cleanup_free_ struct local_address *list = NULL, *gateways = NULL;
319a4f4b 525 size_t n_list = 0;
54e6f97b
LP
526 int r, n_gateways;
527
528 /* Determines our default outbound addresses, i.e. the "primary" local addresses we use to talk to IP
529 * addresses behind the default routes. This is still an address of the local host (i.e. this doesn't
530 * resolve NAT or so), but it's the set of addresses the local IP stack most likely uses to talk to
531 * other hosts.
532 *
533 * This works by connect()ing a SOCK_DGRAM socket to the local gateways, and then reading the IP
534 * address off the socket that was chosen for the routing decision. */
535
536 n_gateways = local_gateways(context, ifindex, af, &gateways);
537 if (n_gateways < 0)
538 return n_gateways;
539 if (n_gateways == 0) {
540 /* No gateways? Then we have no outbound addresses either. */
541 if (ret)
542 *ret = NULL;
543
544 return 0;
545 }
546
547 for (int i = 0; i < n_gateways; i++) {
254d1313 548 _cleanup_close_ int fd = -EBADF;
54e6f97b
LP
549 union sockaddr_union sa;
550 socklen_t salen;
551
552 fd = socket(gateways[i].family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
553 if (fd < 0)
554 return -errno;
555
556 switch (gateways[i].family) {
557
558 case AF_INET:
559 sa.in = (struct sockaddr_in) {
560 .sin_family = AF_INET,
561 .sin_addr = gateways[i].address.in,
098d42b6
YW
562 .sin_port = htobe16(53), /* doesn't really matter which port we pick —
563 * we just care about the routing decision */
54e6f97b
LP
564 };
565
566 break;
567
568 case AF_INET6:
569 sa.in6 = (struct sockaddr_in6) {
570 .sin6_family = AF_INET6,
571 .sin6_addr = gateways[i].address.in6,
572 .sin6_port = htobe16(53),
573 .sin6_scope_id = gateways[i].ifindex,
574 };
575
576 break;
577
578 default:
04499a70 579 assert_not_reached();
54e6f97b
LP
580 }
581
582 /* So ideally we'd just use IP_UNICAST_IF here to pass the ifindex info to the kernel before
583 * connect()ing, sot that it influences the routing decision. However, on current kernels
584 * IP_UNICAST_IF doesn't actually influence the routing decision for UDP — which I think
585 * should probably just be considered a bug. Once that bug is fixed this is the best API to
586 * use, since it is the most lightweight. */
587 r = socket_set_unicast_if(fd, gateways[i].family, gateways[i].ifindex);
588 if (r < 0)
589 log_debug_errno(r, "Failed to set unicast interface index %i, ignoring: %m", gateways[i].ifindex);
590
591 /* We'll also use SO_BINDTOINDEX. This requires CAP_NET_RAW on old kernels, hence there's a
592 * good chance this fails. Since 5.7 this restriction was dropped and the first
593 * SO_BINDTOINDEX on a socket may be done without privileges. This one has the benefit of
594 * really influencing the routing decision, i.e. this one definitely works for us — as long
098d42b6 595 * as we have the privileges for it. */
54e6f97b
LP
596 r = socket_bind_to_ifindex(fd, gateways[i].ifindex);
597 if (r < 0)
598 log_debug_errno(r, "Failed to bind socket to interface %i, ignoring: %m", gateways[i].ifindex);
599
600 /* Let's now connect() to the UDP socket, forcing the kernel to make a routing decision and
601 * auto-bind the socket. We ignore failures on this, since that failure might happen for a
602 * multitude of reasons (policy/firewall issues, who knows?) and some of them might be
603 * *after* the routing decision and the auto-binding already took place. If so we can still
604 * make use of the binding and return it. Hence, let's not unnecessarily fail early here: we
605 * can still easily detect if the auto-binding worked or not, by comparing the bound IP
098d42b6 606 * address with zero — which we do below. */
54e6f97b
LP
607 if (connect(fd, &sa.sa, SOCKADDR_LEN(sa)) < 0)
608 log_debug_errno(errno, "Failed to connect SOCK_DGRAM socket to gateway, ignoring: %m");
609
610 /* Let's now read the socket address of the socket. A routing decision should have been
611 * made. Let's verify that and use the data. */
612 salen = SOCKADDR_LEN(sa);
613 if (getsockname(fd, &sa.sa, &salen) < 0)
614 return -errno;
615 assert(sa.sa.sa_family == gateways[i].family);
616 assert(salen == SOCKADDR_LEN(sa));
617
618 switch (gateways[i].family) {
619
620 case AF_INET:
621 if (in4_addr_is_null(&sa.in.sin_addr)) /* Auto-binding didn't work. :-( */
622 continue;
623
0b2c0c31
YW
624 r = add_local_outbound(&list, &n_list, gateways[i].ifindex, gateways[i].family,
625 &(union in_addr_union) { .in = sa.in.sin_addr });
626 if (r < 0)
627 return r;
54e6f97b
LP
628 break;
629
630 case AF_INET6:
631 if (in6_addr_is_null(&sa.in6.sin6_addr))
632 continue;
633
0b2c0c31
YW
634 r = add_local_outbound(&list, &n_list, gateways[i].ifindex, gateways[i].family,
635 &(union in_addr_union) { .in6 = sa.in6.sin6_addr });
636 if (r < 0)
637 return r;
54e6f97b
LP
638 break;
639
640 default:
04499a70 641 assert_not_reached();
54e6f97b
LP
642 }
643 }
644
a64f6041
YW
645 typesafe_qsort(list, n_list, address_compare);
646 suppress_duplicates(list, &n_list);
647
648 if (ret)
c3a8c6aa 649 *ret = TAKE_PTR(list);
d73c3269 650
e80af1bd 651 return (int) n_list;
8041b5ba 652}