]>
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 | |
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 |
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 | ||
54e6f97b LP |
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 | ||
0b2c0c31 YW |
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, | |
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 | ||
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 | ||
eb1f9ed6 | 115 | return add_local_address_full(list, n_list, ifindex, scope, 0, 0, family, address); |
0b2c0c31 YW |
116 | } |
117 | ||
54e6f97b LP |
118 | int 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 |
231 | static 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 |
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 | ||
54e6f97b LP |
349 | int 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 |
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 | ||
eb1f9ed6 | 515 | return add_local_address_full(list, n_list, ifindex, 0, 0, 0, family, address); |
0b2c0c31 YW |
516 | } |
517 | ||
54e6f97b LP |
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; | |
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 | } |