uint32_t priority,
uint32_t weight,
int family,
- const union in_addr_union *address) {
+ const union in_addr_union *address,
+ const union in_addr_union *prefsrc) {
assert(list);
assert(n_list);
.weight = weight,
.family = family,
.address = *address,
+ .prefsrc = prefsrc ? *prefsrc : IN_ADDR_NULL,
};
return 1;
int family,
const union in_addr_union *address) {
- return add_local_address_full(list, n_list, ifindex, scope, 0, 0, family, address);
+ return add_local_address_full(
+ list, n_list, ifindex,
+ scope, /* priority = */ 0, /* weight = */ 0,
+ family, address, /* prefsrc = */ NULL);
}
int local_addresses(
uint32_t priority,
uint32_t weight,
int family,
- const union in_addr_union *address) {
-
- return add_local_address_full(list, n_list, ifindex, 0, priority, weight, family, address);
+ const union in_addr_union *address,
+ const union in_addr_union *prefsrc) {
+
+ return add_local_address_full(
+ list, n_list,
+ ifindex,
+ /* scope = */ 0, priority, weight,
+ family, address, prefsrc);
}
static int parse_nexthop_one(
bool allow_via,
int family,
uint32_t priority,
+ const union in_addr_union *prefsrc,
const struct rtnexthop *rtnh) {
bool has_gw = false;
union in_addr_union a;
memcpy(&a, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(family));
- r = add_local_gateway(list, n_list, rtnh->rtnh_ifindex, priority, rtnh->rtnh_hops, family, &a);
+ r = add_local_gateway(list, n_list, rtnh->rtnh_ifindex, priority, rtnh->rtnh_hops, family, &a, prefsrc);
if (r < 0)
return r;
return -EBADMSG; /* gateway address should be always IPv6. */
r = add_local_gateway(list, n_list, rtnh->rtnh_ifindex, priority, rtnh->rtnh_hops, via->family,
- &(union in_addr_union) { .in6 = via->address.in6 });
+ &(union in_addr_union) { .in6 = via->address.in6 },
+ /* prefsrc = */ NULL);
if (r < 0)
return r;
bool allow_via,
int family,
uint32_t priority,
+ const union in_addr_union *prefsrc,
const struct rtnexthop *rtnh,
size_t size) {
if (ifindex > 0 && rtnh->rtnh_ifindex != ifindex)
goto next_nexthop;
- r = parse_nexthop_one(list, n_list, allow_via, family, priority, rtnh);
+ r = parse_nexthop_one(list, n_list, allow_via, family, priority, prefsrc, rtnh);
if (r < 0)
return r;
return r;
for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) {
+ union in_addr_union prefsrc = IN_ADDR_NULL;
uint16_t type;
unsigned char dst_len, src_len, table;
uint32_t ifi = 0, priority = 0;
if (af != AF_UNSPEC && af != family)
continue;
+ r = netlink_message_read_in_addr_union(m, RTA_PREFSRC, family, &prefsrc);
+ if (r < 0 && r != -ENODATA)
+ return r;
+
r = sd_netlink_message_read_u32(m, RTA_OIF, &ifi);
if (r < 0 && r != -ENODATA)
return r;
if (r < 0 && r != -ENODATA)
return r;
if (r >= 0) {
- r = add_local_gateway(&list, &n_list, ifi, priority, 0, family, &gateway);
+ r = add_local_gateway(&list, &n_list, ifi, priority, 0, family, &gateway, &prefsrc);
if (r < 0)
return r;
if (via.family != AF_INET6)
return -EBADMSG;
+ /* Ignore prefsrc, and let's take the source address by socket command, if necessary. */
r = add_local_gateway(&list, &n_list, ifi, priority, 0, via.family,
- &(union in_addr_union) { .in6 = via.address.in6 });
+ &(union in_addr_union) { .in6 = via.address.in6 },
+ /* prefsrc = */ NULL);
if (r < 0)
return r;
}
if (r < 0 && r != -ENODATA)
return r;
if (r >= 0) {
- r = parse_nexthops(&list, &n_list, ifindex, allow_via, family, priority, rta_multipath, rta_len);
+ r = parse_nexthops(&list, &n_list, ifindex, allow_via, family, priority, &prefsrc, rta_multipath, rta_len);
if (r < 0)
return r;
}
int family,
const union in_addr_union *address) {
- return add_local_address_full(list, n_list, ifindex, 0, 0, 0, family, address);
+ return add_local_address_full(
+ list, n_list, ifindex,
+ /* scope = */ 0, /* priority = */ 0, /* weight = */ 0,
+ family, address, /* prefsrc = */ NULL);
+}
+
+static int add_local_outbound_by_prefsrc(
+ struct local_address **list,
+ size_t *n_list,
+ const struct local_address *gateway,
+ const struct local_address *addresses,
+ size_t n_addresses) {
+
+ int r;
+
+ assert(list);
+ assert(n_list);
+ assert(gateway);
+
+ if (!in_addr_is_set(gateway->family, &gateway->prefsrc))
+ return 0;
+
+ /* If the gateway has prefsrc, then let's honor the field. But, check if the address is assigned to
+ * the same interface, like we do with SO_BINDTOINDEX. */
+
+ bool found = false;
+ FOREACH_ARRAY(a, addresses, n_addresses) {
+ if (a->ifindex != gateway->ifindex)
+ continue;
+ if (a->family != gateway->family)
+ continue;
+ if (in_addr_equal(a->family, &a->address, &gateway->prefsrc) <= 0)
+ continue;
+
+ found = true;
+ break;
+ }
+ if (!found)
+ return -EHOSTUNREACH;
+
+ r = add_local_outbound(list, n_list, gateway->ifindex, gateway->family, &gateway->prefsrc);
+ if (r < 0)
+ return r;
+
+ return 1;
}
int local_outbounds(
int af,
struct local_address **ret) {
- _cleanup_free_ struct local_address *list = NULL, *gateways = NULL;
+ _cleanup_free_ struct local_address *list = NULL, *gateways = NULL, *addresses = NULL;
size_t n_list = 0;
- int r, n_gateways;
+ int r, n_gateways, n_addresses;
/* Determines our default outbound addresses, i.e. the "primary" local addresses we use to talk to IP
* addresses behind the default routes. This is still an address of the local host (i.e. this doesn't
return 0;
}
+ n_addresses = local_addresses(context, ifindex, af, &addresses);
+ if (n_addresses < 0)
+ return n_addresses;
+
FOREACH_ARRAY(i, gateways, n_gateways) {
_cleanup_close_ int fd = -EBADF;
union sockaddr_union sa;
socklen_t salen;
+ r = add_local_outbound_by_prefsrc(&list, &n_list, i, addresses, n_addresses);
+ if (r > 0 || r == -EHOSTUNREACH)
+ continue;
+ if (r < 0)
+ return r;
+
fd = socket(i->family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (fd < 0)
return -errno;