network: Support interface-bound ECMP routes in MultiPathRoute=
MultiPathRoute= can now specify device-only nexthops without a gateway address, e.g. MultiPathRoute=@wg0. This enables ECMP configurations over interfaces that don't use gateway addresses, such as WireGuard tunnels. The syntax is extended from "address[@device] [weight]" to "[address]@device [weight]". The address is now optional, but at least one of gateway or device must be specified. The @ symbol must still be present for device-only routes, making the syntax unambiguous: @wg0 specifies a device, while a bare IP address specifies a gateway. Device-only nexthops are only available for IPv4 routes. Device-only multipath routes for IPv6 are not supported by the kernel's netlink interface and will be rejected with a warning. This change is fully backwards compatible. All existing configurations continue to work unchanged, as they always included a gateway address. Closes #39699.
This commit is contained in:
+12
-5
@@ -2325,13 +2325,20 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>MultiPathRoute=<replaceable>address</replaceable>[@<replaceable>name</replaceable>] [<replaceable>weight</replaceable>]</varname></term>
|
||||
<term><varname>MultiPathRoute=</varname></term>
|
||||
<listitem>
|
||||
<para>Configures multipath route. Multipath routing is the technique of using multiple
|
||||
alternative paths through a network. Takes gateway address. Optionally, takes a network
|
||||
interface name or index separated with <literal>@</literal>, and a weight in 1..256 for this
|
||||
multipath route separated with whitespace. This setting can be specified multiple times. If
|
||||
an empty string is assigned, then the all previous assignments are cleared.</para>
|
||||
alternative paths through a network. Takes a gateway address and/or a network interface
|
||||
name or index (prefixed with <literal>@</literal>). At least one of these must be specified.
|
||||
Optionally, a weight in 1..256 can be specified, separated with whitespace. This setting
|
||||
can be specified multiple times. If an empty string is assigned, then all previous
|
||||
assignments are cleared.</para>
|
||||
|
||||
<para>Examples:</para>
|
||||
<programlisting>MultiPathRoute=10.0.0.1@eth0 20
|
||||
MultiPathRoute=192.168.1.1 50
|
||||
MultiPathRoute=@wg0 15
|
||||
MultiPathRoute=2001:db8::1@eth0</programlisting>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v245"/>
|
||||
</listitem>
|
||||
|
||||
@@ -94,10 +94,12 @@ static void route_nexthop_hash_func_full(const RouteNextHop *nh, struct siphash
|
||||
/* See nh_comp() in net/ipv4/fib_semantics.c of the kernel. */
|
||||
|
||||
siphash24_compress_typesafe(nh->family, state);
|
||||
if (!IN_SET(nh->family, AF_INET, AF_INET6))
|
||||
return;
|
||||
|
||||
in_addr_hash_func(&nh->gw, nh->family, state);
|
||||
/* For device-only nexthops parsed from config, family is AF_UNSPEC until verification.
|
||||
* We still need to hash weight/ifindex/ifname to distinguish different device-only entries. */
|
||||
if (IN_SET(nh->family, AF_INET, AF_INET6))
|
||||
in_addr_hash_func(&nh->gw, nh->family, state);
|
||||
|
||||
if (with_weight)
|
||||
siphash24_compress_typesafe(nh->weight, state);
|
||||
siphash24_compress_typesafe(nh->ifindex, state);
|
||||
@@ -115,12 +117,13 @@ static int route_nexthop_compare_func_full(const RouteNextHop *a, const RouteNex
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
if (!IN_SET(a->family, AF_INET, AF_INET6))
|
||||
return 0;
|
||||
|
||||
r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family));
|
||||
if (r != 0)
|
||||
return r;
|
||||
/* For device-only nexthops parsed from config, family is AF_UNSPEC until verification.
|
||||
* We still need to compare weight/ifindex/ifname to distinguish different device-only entries. */
|
||||
if (IN_SET(a->family, AF_INET, AF_INET6)) {
|
||||
r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family));
|
||||
if (r != 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (with_weight) {
|
||||
r = CMP(a->weight, b->weight);
|
||||
@@ -553,30 +556,34 @@ static int append_nexthop_one(const Route *route, const RouteNextHop *nh, struct
|
||||
|
||||
(*rta)->rta_len += sizeof(struct rtnexthop);
|
||||
|
||||
if (nh->family == route->family) {
|
||||
r = rtattr_append_attribute(rta, RTA_GATEWAY, &nh->gw, FAMILY_ADDRESS_SIZE(nh->family));
|
||||
if (r < 0)
|
||||
goto clear;
|
||||
/* For device-only nexthops, skip RTA_GATEWAY entirely. The kernel will use the
|
||||
* interface specified in rtnh_ifindex without requiring a gateway address. */
|
||||
if (in_addr_is_set(nh->family, &nh->gw)) {
|
||||
if (nh->family == route->family) {
|
||||
r = rtattr_append_attribute(rta, RTA_GATEWAY, &nh->gw, FAMILY_ADDRESS_SIZE(nh->family));
|
||||
if (r < 0)
|
||||
goto clear;
|
||||
|
||||
rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
|
||||
rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(nh->family));
|
||||
rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
|
||||
rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(nh->family));
|
||||
|
||||
} else if (nh->family == AF_INET6) {
|
||||
assert(route->family == AF_INET);
|
||||
} else if (nh->family == AF_INET6) {
|
||||
assert(route->family == AF_INET);
|
||||
|
||||
r = rtattr_append_attribute(rta, RTA_VIA,
|
||||
&(RouteVia) {
|
||||
.family = nh->family,
|
||||
.address = nh->gw,
|
||||
}, sizeof(RouteVia));
|
||||
if (r < 0)
|
||||
goto clear;
|
||||
r = rtattr_append_attribute(rta, RTA_VIA,
|
||||
&(RouteVia) {
|
||||
.family = nh->family,
|
||||
.address = nh->gw,
|
||||
}, sizeof(RouteVia));
|
||||
if (r < 0)
|
||||
goto clear;
|
||||
|
||||
rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
|
||||
rtnh->rtnh_len += RTA_SPACE(sizeof(RouteVia));
|
||||
rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
|
||||
rtnh->rtnh_len += RTA_SPACE(sizeof(RouteVia));
|
||||
|
||||
} else if (nh->family == AF_INET)
|
||||
assert_not_reached();
|
||||
} else if (nh->family == AF_INET)
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1080,11 +1087,16 @@ int config_parse_multipath_route(
|
||||
}
|
||||
}
|
||||
|
||||
r = in_addr_from_string_auto(word, &nh->family, &nh->gw);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r,
|
||||
"Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue);
|
||||
return 0;
|
||||
if (isempty(word)) {
|
||||
if (!dev)
|
||||
return log_syntax_parse_error(unit, filename, line, SYNTHETIC_ERRNO(EINVAL), lvalue, rvalue);
|
||||
} else {
|
||||
r = in_addr_from_string_auto(word, &nh->family, &nh->gw);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r,
|
||||
"Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isempty(p)) {
|
||||
|
||||
Reference in New Issue
Block a user