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