]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <net/if_arp.h> | |
4 | ||
5 | #include "sd-netlink.h" | |
6 | ||
7 | #include "alloc-util.h" | |
8 | #include "fd-util.h" | |
9 | #include "local-addresses.h" | |
10 | #include "macro.h" | |
11 | #include "netlink-util.h" | |
12 | #include "sort-util.h" | |
13 | ||
14 | static int address_compare(const struct local_address *a, const struct local_address *b) { | |
15 | int r; | |
16 | ||
17 | /* Order lowest scope first, IPv4 before IPv6, lowest interface index first */ | |
18 | ||
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 | ||
24 | r = CMP(a->scope, b->scope); | |
25 | if (r != 0) | |
26 | return r; | |
27 | ||
28 | r = CMP(a->priority, b->priority); | |
29 | if (r != 0) | |
30 | return r; | |
31 | ||
32 | r = CMP(a->weight, b->weight); | |
33 | if (r != 0) | |
34 | return r; | |
35 | ||
36 | r = CMP(a->ifindex, b->ifindex); | |
37 | if (r != 0) | |
38 | return r; | |
39 | ||
40 | return memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(a->family)); | |
41 | } | |
42 | ||
43 | bool 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 | ||
54 | static 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 | ||
76 | static 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, | |
82 | uint32_t weight, | |
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, | |
99 | .weight = weight, | |
100 | .family = family, | |
101 | .address = *address, | |
102 | }; | |
103 | ||
104 | return 1; | |
105 | } | |
106 | ||
107 | static 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 | ||
115 | return add_local_address_full(list, n_list, ifindex, scope, 0, 0, family, address); | |
116 | } | |
117 | ||
118 | int local_addresses( | |
119 | sd_netlink *context, | |
120 | int ifindex, | |
121 | int af, | |
122 | struct local_address **ret) { | |
123 | ||
124 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; | |
125 | _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; | |
126 | _cleanup_free_ struct local_address *list = NULL; | |
127 | size_t n_list = 0; | |
128 | int r; | |
129 | ||
130 | if (context) | |
131 | rtnl = sd_netlink_ref(context); | |
132 | else { | |
133 | r = sd_netlink_open(&rtnl); | |
134 | if (r < 0) | |
135 | return r; | |
136 | } | |
137 | ||
138 | r = sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, ifindex, af); | |
139 | if (r < 0) | |
140 | return r; | |
141 | ||
142 | r = sd_netlink_message_set_request_dump(req, true); | |
143 | if (r < 0) | |
144 | return r; | |
145 | ||
146 | r = sd_netlink_call(rtnl, req, 0, &reply); | |
147 | if (r < 0) | |
148 | return r; | |
149 | ||
150 | for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { | |
151 | union in_addr_union a; | |
152 | unsigned char flags, scope; | |
153 | uint16_t type; | |
154 | int ifi, family; | |
155 | ||
156 | r = sd_netlink_message_get_errno(m); | |
157 | if (r < 0) | |
158 | return r; | |
159 | ||
160 | r = sd_netlink_message_get_type(m, &type); | |
161 | if (r < 0) | |
162 | return r; | |
163 | if (type != RTM_NEWADDR) | |
164 | continue; | |
165 | ||
166 | r = sd_rtnl_message_addr_get_ifindex(m, &ifi); | |
167 | if (r < 0) | |
168 | return r; | |
169 | if (ifindex > 0 && ifi != ifindex) | |
170 | continue; | |
171 | ||
172 | r = sd_rtnl_message_addr_get_family(m, &family); | |
173 | if (r < 0) | |
174 | return r; | |
175 | if (!IN_SET(family, AF_INET, AF_INET6)) | |
176 | continue; | |
177 | if (af != AF_UNSPEC && af != family) | |
178 | continue; | |
179 | ||
180 | r = sd_rtnl_message_addr_get_flags(m, &flags); | |
181 | if (r < 0) | |
182 | return r; | |
183 | if ((flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) != 0) | |
184 | continue; | |
185 | ||
186 | r = sd_rtnl_message_addr_get_scope(m, &scope); | |
187 | if (r < 0) | |
188 | return r; | |
189 | ||
190 | if (ifindex == 0 && IN_SET(scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE)) | |
191 | continue; | |
192 | ||
193 | switch (family) { | |
194 | ||
195 | case AF_INET: | |
196 | r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a.in); | |
197 | if (r < 0) { | |
198 | r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a.in); | |
199 | if (r < 0) | |
200 | continue; | |
201 | } | |
202 | break; | |
203 | ||
204 | case AF_INET6: | |
205 | r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a.in6); | |
206 | if (r < 0) { | |
207 | r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a.in6); | |
208 | if (r < 0) | |
209 | continue; | |
210 | } | |
211 | break; | |
212 | ||
213 | default: | |
214 | assert_not_reached(); | |
215 | } | |
216 | ||
217 | r = add_local_address(&list, &n_list, ifi, scope, family, &a); | |
218 | if (r < 0) | |
219 | return r; | |
220 | }; | |
221 | ||
222 | typesafe_qsort(list, n_list, address_compare); | |
223 | suppress_duplicates(list, &n_list); | |
224 | ||
225 | if (ret) | |
226 | *ret = TAKE_PTR(list); | |
227 | ||
228 | return (int) n_list; | |
229 | } | |
230 | ||
231 | static int add_local_gateway( | |
232 | struct local_address **list, | |
233 | size_t *n_list, | |
234 | int ifindex, | |
235 | uint32_t priority, | |
236 | uint32_t weight, | |
237 | int family, | |
238 | const union in_addr_union *address) { | |
239 | ||
240 | return add_local_address_full(list, n_list, ifindex, 0, priority, weight, family, address); | |
241 | } | |
242 | ||
243 | static 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 | ||
307 | static 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 | ||
349 | int local_gateways( | |
350 | sd_netlink *context, | |
351 | int ifindex, | |
352 | int af, | |
353 | struct local_address **ret) { | |
354 | ||
355 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; | |
356 | _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; | |
357 | _cleanup_free_ struct local_address *list = NULL; | |
358 | size_t n_list = 0; | |
359 | int r; | |
360 | ||
361 | /* The RTA_VIA attribute is used only for IPv4 routes with an IPv6 gateway. If IPv4 gateways are | |
362 | * requested (af == AF_INET), then we do not return IPv6 gateway addresses. Similarly, if IPv6 | |
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 | ||
367 | if (context) | |
368 | rtnl = sd_netlink_ref(context); | |
369 | else { | |
370 | r = sd_netlink_open(&rtnl); | |
371 | if (r < 0) | |
372 | return r; | |
373 | } | |
374 | ||
375 | r = sd_rtnl_message_new_route(rtnl, &req, RTM_GETROUTE, af, RTPROT_UNSPEC); | |
376 | if (r < 0) | |
377 | return r; | |
378 | ||
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 | ||
387 | r = sd_netlink_message_set_request_dump(req, true); | |
388 | if (r < 0) | |
389 | return r; | |
390 | ||
391 | r = sd_netlink_call(rtnl, req, 0, &reply); | |
392 | if (r < 0) | |
393 | return r; | |
394 | ||
395 | for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { | |
396 | uint16_t type; | |
397 | unsigned char dst_len, src_len, table; | |
398 | uint32_t ifi = 0, priority = 0; | |
399 | int family; | |
400 | ||
401 | r = sd_netlink_message_get_errno(m); | |
402 | if (r < 0) | |
403 | return r; | |
404 | ||
405 | r = sd_netlink_message_get_type(m, &type); | |
406 | if (r < 0) | |
407 | return r; | |
408 | if (type != RTM_NEWROUTE) | |
409 | continue; | |
410 | ||
411 | /* We only care for default routes */ | |
412 | r = sd_rtnl_message_route_get_dst_prefixlen(m, &dst_len); | |
413 | if (r < 0) | |
414 | return r; | |
415 | if (dst_len != 0) | |
416 | continue; | |
417 | ||
418 | r = sd_rtnl_message_route_get_src_prefixlen(m, &src_len); | |
419 | if (r < 0) | |
420 | return r; | |
421 | if (src_len != 0) | |
422 | continue; | |
423 | ||
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 | ||
430 | r = sd_netlink_message_read_u32(m, RTA_PRIORITY, &priority); | |
431 | if (r < 0 && r != -ENODATA) | |
432 | return r; | |
433 | ||
434 | r = sd_rtnl_message_route_get_family(m, &family); | |
435 | if (r < 0) | |
436 | return r; | |
437 | if (!IN_SET(family, AF_INET, AF_INET6)) | |
438 | continue; | |
439 | if (af != AF_UNSPEC && af != family) | |
440 | continue; | |
441 | ||
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; | |
450 | ||
451 | union in_addr_union gateway; | |
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) { | |
456 | r = add_local_gateway(&list, &n_list, ifi, priority, 0, family, &gateway); | |
457 | if (r < 0) | |
458 | return r; | |
459 | ||
460 | continue; | |
461 | } | |
462 | ||
463 | if (!allow_via) | |
464 | continue; | |
465 | ||
466 | if (family != AF_INET) | |
467 | continue; | |
468 | ||
469 | RouteVia via; | |
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) { | |
474 | if (via.family != AF_INET6) | |
475 | return -EBADMSG; | |
476 | ||
477 | r = add_local_gateway(&list, &n_list, ifi, priority, 0, via.family, | |
478 | &(union in_addr_union) { .in6 = via.address.in6 }); | |
479 | if (r < 0) | |
480 | return r; | |
481 | } | |
482 | ||
483 | /* If the route has RTA_OIF, it does not have RTA_MULTIPATH. */ | |
484 | continue; | |
485 | } | |
486 | ||
487 | size_t rta_len; | |
488 | _cleanup_free_ void *rta_multipath = NULL; | |
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) { | |
493 | r = parse_nexthops(&list, &n_list, ifindex, allow_via, family, priority, rta_multipath, rta_len); | |
494 | if (r < 0) | |
495 | return r; | |
496 | } | |
497 | } | |
498 | ||
499 | typesafe_qsort(list, n_list, address_compare); | |
500 | suppress_duplicates(list, &n_list); | |
501 | ||
502 | if (ret) | |
503 | *ret = TAKE_PTR(list); | |
504 | ||
505 | return (int) n_list; | |
506 | } | |
507 | ||
508 | static 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 | ||
515 | return add_local_address_full(list, n_list, ifindex, 0, 0, 0, family, address); | |
516 | } | |
517 | ||
518 | int 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; | |
525 | size_t n_list = 0; | |
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++) { | |
548 | _cleanup_close_ int fd = -EBADF; | |
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, | |
562 | .sin_port = htobe16(53), /* doesn't really matter which port we pick — | |
563 | * we just care about the routing decision */ | |
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: | |
579 | assert_not_reached(); | |
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 | |
595 | * as we have the privileges for it. */ | |
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 | |
606 | * address with zero — which we do below. */ | |
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 | ||
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; | |
628 | break; | |
629 | ||
630 | case AF_INET6: | |
631 | if (in6_addr_is_null(&sa.in6.sin6_addr)) | |
632 | continue; | |
633 | ||
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; | |
638 | break; | |
639 | ||
640 | default: | |
641 | assert_not_reached(); | |
642 | } | |
643 | } | |
644 | ||
645 | typesafe_qsort(list, n_list, address_compare); | |
646 | suppress_duplicates(list, &n_list); | |
647 | ||
648 | if (ret) | |
649 | *ret = TAKE_PTR(list); | |
650 | ||
651 | return (int) n_list; | |
652 | } |