mirror of
https://github.com/systemd/systemd.git
synced 2025-02-24 17:57:34 +03:00
Merge pull request #18699 from yuwata/network-route-add-nexthop-setting
network: add NextHop= setting in [Route] section
This commit is contained in:
commit
7f37ecf7e5
@ -1554,6 +1554,13 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
|
||||
times. If an empty string is assigned, then the all previous assignments are cleared.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><varname>NextHop=</varname></term>
|
||||
<listitem>
|
||||
<para>Specifies the nexthop id. Takes an unsigned integer in the range 1…4294967295.
|
||||
If set, the corresponding [NextHop] section must be configured. Defaults to unset.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
|
@ -894,6 +894,8 @@ Manager* manager_free(Manager *m) {
|
||||
m->routes = set_free(m->routes);
|
||||
m->routes_foreign = set_free(m->routes_foreign);
|
||||
|
||||
m->nexthops_by_id = hashmap_free(m->nexthops_by_id);
|
||||
|
||||
sd_event_source_unref(m->speed_meter_event_source);
|
||||
sd_event_unref(m->event);
|
||||
|
||||
|
@ -61,6 +61,9 @@ struct Manager {
|
||||
Set *rules;
|
||||
Set *rules_foreign;
|
||||
|
||||
/* Manage nexthops by id. */
|
||||
Hashmap *nexthops_by_id;
|
||||
|
||||
/* Manager stores routes without RTA_OIF attribute. */
|
||||
Set *routes;
|
||||
Set *routes_foreign;
|
||||
|
@ -185,6 +185,7 @@ Route.QuickAck, config_parse_route_boolean,
|
||||
Route.FastOpenNoCookie, config_parse_route_boolean, 0, 0
|
||||
Route.TTLPropagate, config_parse_route_boolean, 0, 0
|
||||
Route.MultiPathRoute, config_parse_multipath_route, 0, 0
|
||||
Route.NextHop, config_parse_route_nexthop, 0, 0
|
||||
NextHop.Id, config_parse_nexthop_id, 0, 0
|
||||
NextHop.Gateway, config_parse_nexthop_gateway, 0, 0
|
||||
NextHop.Family, config_parse_nexthop_family, 0, 0
|
||||
|
@ -28,6 +28,9 @@ NextHop *nexthop_free(NextHop *nexthop) {
|
||||
if (nexthop->link) {
|
||||
set_remove(nexthop->link->nexthops, nexthop);
|
||||
set_remove(nexthop->link->nexthops_foreign, nexthop);
|
||||
|
||||
if (nexthop->link->manager && nexthop->id > 0)
|
||||
hashmap_remove(nexthop->link->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id));
|
||||
}
|
||||
|
||||
return mfree(nexthop);
|
||||
@ -129,6 +132,23 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
|
||||
nexthop_compare_func,
|
||||
nexthop_free);
|
||||
|
||||
int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret) {
|
||||
NextHop *nh;
|
||||
|
||||
assert(manager);
|
||||
|
||||
if (id == 0)
|
||||
return -EINVAL;
|
||||
|
||||
nh = hashmap_get(manager->nexthops_by_id, UINT32_TO_PTR(id));
|
||||
if (!nh)
|
||||
return -ENOENT;
|
||||
|
||||
if (ret)
|
||||
*ret = nh;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nexthop_get(Link *link, const NextHop *in, NextHop **ret) {
|
||||
NextHop *existing;
|
||||
|
||||
@ -221,14 +241,18 @@ static int nexthop_update(Link *link, NextHop *nexthop, const NextHop *in) {
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(link->manager);
|
||||
assert(nexthop);
|
||||
assert(in);
|
||||
assert(in->id > 0);
|
||||
|
||||
/* Currently, this only updates ID. */
|
||||
/* This updates nexthop ID if necessary, and register the nexthop to Manager. */
|
||||
|
||||
if (nexthop->id > 0)
|
||||
return nexthop->id == in->id ? 0 : -EINVAL;
|
||||
if (nexthop->id > 0) {
|
||||
if (nexthop->id == in->id)
|
||||
goto set_manager;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
nexthop = set_remove(link->nexthops, nexthop);
|
||||
if (!nexthop)
|
||||
@ -251,7 +275,8 @@ static int nexthop_update(Link *link, NextHop *nexthop, const NextHop *in) {
|
||||
return r < 0 ? r : -EEXIST;
|
||||
}
|
||||
|
||||
return 0;
|
||||
set_manager:
|
||||
return hashmap_ensure_put(&link->manager->nexthops_by_id, NULL, UINT32_TO_PTR(nexthop->id), nexthop);
|
||||
}
|
||||
|
||||
static void log_nexthop_debug(const NextHop *nexthop, uint32_t id, const char *str, const Link *link) {
|
||||
|
@ -35,6 +35,7 @@ void network_drop_invalid_nexthops(Network *network);
|
||||
|
||||
int link_set_nexthops(Link *link);
|
||||
|
||||
int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret);
|
||||
int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m);
|
||||
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_id);
|
||||
|
@ -328,6 +328,7 @@ void route_hash_func(const Route *route, struct siphash *state) {
|
||||
siphash24_compress(&route->initrwnd, sizeof(route->initrwnd), state);
|
||||
|
||||
siphash24_compress(&route->advmss, sizeof(route->advmss), state);
|
||||
siphash24_compress(&route->nexthop_id, sizeof(route->nexthop_id), state);
|
||||
|
||||
break;
|
||||
default:
|
||||
@ -416,6 +417,10 @@ int route_compare_func(const Route *a, const Route *b) {
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
r = CMP(a->nexthop_id, b->nexthop_id);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
default:
|
||||
/* treat any other address family as AF_UNSPEC */
|
||||
@ -479,7 +484,7 @@ static int route_get(const Manager *manager, const Link *link, const Route *in,
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static void route_copy(Route *dest, const Route *src, const MultipathRoute *m) {
|
||||
static void route_copy(Route *dest, const Route *src, const MultipathRoute *m, const NextHop *nh) {
|
||||
assert(dest);
|
||||
assert(src);
|
||||
|
||||
@ -498,9 +503,14 @@ static void route_copy(Route *dest, const Route *src, const MultipathRoute *m) {
|
||||
dest->initcwnd = src->initcwnd;
|
||||
dest->initrwnd = src->initrwnd;
|
||||
dest->lifetime = src->lifetime;
|
||||
dest->advmss= src->advmss;
|
||||
dest->advmss = src->advmss;
|
||||
dest->nexthop_id = src->nexthop_id;
|
||||
|
||||
if (m) {
|
||||
if (nh) {
|
||||
dest->gw_family = nh->family;
|
||||
dest->gw = nh->gw;
|
||||
dest->gw_weight = src->gw_weight;
|
||||
} else if (m) {
|
||||
dest->gw_family = m->gateway.family;
|
||||
dest->gw = m->gateway.address;
|
||||
dest->gw_weight = m->weight;
|
||||
@ -523,7 +533,7 @@ static int route_add_internal(Manager *manager, Link *link, Set **routes, const
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
route_copy(route, in, NULL);
|
||||
route_copy(route, in, NULL, NULL);
|
||||
|
||||
r = set_ensure_put(routes, &route_hash_ops, route);
|
||||
if (r < 0)
|
||||
@ -547,7 +557,7 @@ static int route_add_foreign(Manager *manager, Link *link, const Route *in, Rout
|
||||
return route_add_internal(manager, link, link ? &link->routes_foreign : &manager->routes_foreign, in, ret);
|
||||
}
|
||||
|
||||
static int route_add(Manager *manager, Link *link, const Route *in, const MultipathRoute *m, Route **ret) {
|
||||
static int route_add(Manager *manager, Link *link, const Route *in, const MultipathRoute *m, const NextHop *nh, Route **ret) {
|
||||
_cleanup_(route_freep) Route *tmp = NULL;
|
||||
bool is_new = false;
|
||||
Route *route;
|
||||
@ -556,14 +566,21 @@ static int route_add(Manager *manager, Link *link, const Route *in, const Multip
|
||||
assert(manager || link);
|
||||
assert(in);
|
||||
|
||||
if (m) {
|
||||
if (nh) {
|
||||
r = route_new(&tmp);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
route_copy(tmp, in, NULL, nh);
|
||||
in = tmp;
|
||||
} else if (m) {
|
||||
assert(link && (m->ifindex == 0 || m->ifindex == link->ifindex));
|
||||
|
||||
r = route_new(&tmp);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
route_copy(tmp, in, m);
|
||||
route_copy(tmp, in, m, NULL);
|
||||
in = tmp;
|
||||
}
|
||||
|
||||
@ -630,10 +647,11 @@ static void log_route_debug(const Route *route, const char *str, const Link *lin
|
||||
(void) route_protocol_full_to_string_alloc(route->protocol, &proto);
|
||||
|
||||
log_link_debug(link,
|
||||
"%s route: dst: %s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s",
|
||||
"%s route: dst: %s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s, nexthop: %"PRIu32,
|
||||
str, strna(dst), strna(src), strna(gw), strna(prefsrc),
|
||||
strna(scope), strna(table), strna(proto),
|
||||
strna(route_type_to_string(route->type)));
|
||||
strna(route_type_to_string(route->type)),
|
||||
route->nexthop_id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -722,7 +740,7 @@ static int route_set_netlink_message(const Route *route, sd_netlink_message *req
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Could not set route type: %m");
|
||||
|
||||
if (!route_type_is_reject(route)) {
|
||||
if (!route_type_is_reject(route) && route->nexthop_id == 0) {
|
||||
assert(link); /* Those routes must be attached to a specific link */
|
||||
|
||||
r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex);
|
||||
@ -730,6 +748,12 @@ static int route_set_netlink_message(const Route *route, sd_netlink_message *req
|
||||
return log_link_error_errno(link, r, "Could not append RTA_OIF attribute: %m");
|
||||
}
|
||||
|
||||
if (route->nexthop_id > 0) {
|
||||
r = sd_netlink_message_append_u32(req, RTA_NH_ID, route->nexthop_id);
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Could not append RTA_NH_ID attribute: %m");
|
||||
}
|
||||
|
||||
r = sd_netlink_message_append_u8(req, RTA_PREF, route->pref);
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Could not append RTA_PREF attribute: %m");
|
||||
@ -893,7 +917,7 @@ int link_drop_foreign_routes(Link *link) {
|
||||
continue;
|
||||
|
||||
if (link_has_route(link, route))
|
||||
k = route_add(NULL, link, route, NULL, NULL);
|
||||
k = route_add(NULL, link, route, NULL, NULL, NULL);
|
||||
else
|
||||
k = route_remove(route, NULL, link, NULL);
|
||||
if (k < 0 && r >= 0)
|
||||
@ -947,16 +971,20 @@ static int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdat
|
||||
|
||||
static int route_add_and_setup_timer(Link *link, const Route *route, const MultipathRoute *m, Route **ret) {
|
||||
_cleanup_(sd_event_source_unrefp) sd_event_source *expire = NULL;
|
||||
NextHop *nh = NULL;
|
||||
Route *nr;
|
||||
int r, k;
|
||||
|
||||
assert(link);
|
||||
assert(link->manager);
|
||||
assert(route);
|
||||
|
||||
(void) manager_get_nexthop_by_id(link->manager, route->nexthop_id, &nh);
|
||||
|
||||
if (route_type_is_reject(route))
|
||||
k = route_add(link->manager, NULL, route, NULL, &nr);
|
||||
k = route_add(link->manager, NULL, route, NULL, NULL, &nr);
|
||||
else if (!m || m->ifindex == 0 || m->ifindex == link->ifindex)
|
||||
k = route_add(NULL, link, route, m, &nr);
|
||||
k = route_add(NULL, link, route, m, nh, &nr);
|
||||
else {
|
||||
Link *link_gw;
|
||||
|
||||
@ -964,7 +992,7 @@ static int route_add_and_setup_timer(Link *link, const Route *route, const Multi
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Failed to get link with ifindex %d: %m", m->ifindex);
|
||||
|
||||
k = route_add(NULL, link_gw, route, m, &nr);
|
||||
k = route_add(NULL, link_gw, route, m, NULL, &nr);
|
||||
}
|
||||
if (k < 0)
|
||||
return log_link_error_errno(link, k, "Could not add route: %m");
|
||||
@ -1258,6 +1286,9 @@ static bool route_has_gateway(const Route *route) {
|
||||
if (!ordered_set_isempty(route->multipath_routes))
|
||||
return true;
|
||||
|
||||
if (route->nexthop_id > 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1356,13 +1387,30 @@ int link_set_routes(Link *link) {
|
||||
static int process_route_one(Manager *manager, Link *link, uint16_t type, const Route *tmp, const MultipathRoute *m) {
|
||||
_cleanup_(route_freep) Route *nr = NULL;
|
||||
Route *route = NULL;
|
||||
NextHop *nh = NULL;
|
||||
int r;
|
||||
|
||||
assert(manager);
|
||||
assert(tmp);
|
||||
assert(IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE));
|
||||
|
||||
if (m) {
|
||||
(void) manager_get_nexthop_by_id(manager, tmp->nexthop_id, &nh);
|
||||
|
||||
if (nh) {
|
||||
if (link && link != nh->link)
|
||||
return log_link_warning_errno(link, SYNTHETIC_ERRNO(EINVAL),
|
||||
"rtnl: received RTA_OIF and ifindex of nexthop corresponding to RTA_NH_ID do not match, ignoring.");
|
||||
|
||||
link = nh->link;
|
||||
|
||||
r = route_new(&nr);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
route_copy(nr, tmp, NULL, nh);
|
||||
|
||||
tmp = nr;
|
||||
} else if (m) {
|
||||
if (link)
|
||||
return log_link_warning_errno(link, SYNTHETIC_ERRNO(EINVAL),
|
||||
"rtnl: received route contains both RTA_OIF and RTA_MULTIPATH, ignoring.");
|
||||
@ -1381,7 +1429,7 @@ static int process_route_one(Manager *manager, Link *link, uint16_t type, const
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
route_copy(nr, tmp, m);
|
||||
route_copy(nr, tmp, m, NULL);
|
||||
|
||||
tmp = nr;
|
||||
}
|
||||
@ -1573,6 +1621,12 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = sd_netlink_message_read_u32(message, RTA_NH_ID, &tmp->nexthop_id);
|
||||
if (r < 0 && r != -ENODATA) {
|
||||
log_link_warning_errno(link, r, "rtnl: received route message with invalid nexthop id, ignoring: %m");
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = sd_netlink_message_enter_container(message, RTA_METRICS);
|
||||
if (r < 0 && r != -ENODATA) {
|
||||
log_link_error_errno(link, r, "rtnl: Could not enter RTA_METRICS container: %m");
|
||||
@ -1966,6 +2020,59 @@ int config_parse_route_scope(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_route_nexthop(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
unsigned section_line,
|
||||
const char *lvalue,
|
||||
int ltype,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
Network *network = userdata;
|
||||
_cleanup_(route_free_or_set_invalidp) Route *n = NULL;
|
||||
uint32_t id;
|
||||
int r;
|
||||
|
||||
assert(filename);
|
||||
assert(section);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(data);
|
||||
|
||||
r = route_new_static(network, filename, section_line, &n);
|
||||
if (r == -ENOMEM)
|
||||
return log_oom();
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r,
|
||||
"Failed to allocate route, ignoring assignment: %m");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isempty(rvalue)) {
|
||||
n->nexthop_id = 0;
|
||||
TAKE_PTR(n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = safe_atou32(rvalue, &id);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse nexthop ID, ignoring assignment: %s", rvalue);
|
||||
return 0;
|
||||
}
|
||||
if (id == 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid nexthop ID, ignoring assignment: %s", rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
n->nexthop_id = id;
|
||||
TAKE_PTR(n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_route_table(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
@ -2656,6 +2763,14 @@ static int route_section_verify(Route *route, Network *network) {
|
||||
route->section->filename, route->section->line);
|
||||
}
|
||||
|
||||
if (route->nexthop_id > 0 &&
|
||||
(in_addr_is_set(route->gw_family, &route->gw) ||
|
||||
!ordered_set_isempty(route->multipath_routes)))
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"%s: NextHopId= cannot be specified with Gateway= or MultiPathRoute=. "
|
||||
"Ignoring [Route] section from line %u.",
|
||||
route->section->filename, route->section->line);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,7 @@ typedef struct Route {
|
||||
unsigned char pref;
|
||||
unsigned flags;
|
||||
int gateway_onlink;
|
||||
uint32_t nexthop_id;
|
||||
|
||||
bool scope_set:1;
|
||||
bool table_set:1;
|
||||
@ -105,3 +106,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_route_mtu);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_tcp_advmss);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_route_table_names);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_route_nexthop);
|
||||
|
@ -166,6 +166,7 @@ Metric=
|
||||
TTLPropagate=
|
||||
MultiPathRoute=
|
||||
TCPAdvertisedMaximumSegmentSize=
|
||||
NextHop=
|
||||
[Network]
|
||||
IPv6DuplicateAddressDetection=
|
||||
IPMasquerade=
|
||||
|
@ -25,3 +25,15 @@ Family=ipv4
|
||||
|
||||
[NextHop]
|
||||
Gateway=192.168.5.2
|
||||
|
||||
[Route]
|
||||
NextHop=1
|
||||
Destination=10.10.10.10
|
||||
|
||||
[Route]
|
||||
NextHop=2
|
||||
Destination=10.10.10.11
|
||||
|
||||
[Route]
|
||||
NextHop=2
|
||||
Destination=2001:1234:5:8f62::1
|
||||
|
@ -2805,6 +2805,18 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
|
||||
self.assertIn('id 4 dev veth99', output)
|
||||
self.assertRegex(output, r'id [0-9]* via 192.168.5.2 dev veth99')
|
||||
|
||||
output = check_output('ip route show dev veth99 10.10.10.10')
|
||||
print(output)
|
||||
self.assertEqual('10.10.10.10 nhid 1 via 192.168.5.1 proto static', output)
|
||||
|
||||
output = check_output('ip route show dev veth99 10.10.10.11')
|
||||
print(output)
|
||||
self.assertEqual('10.10.10.11 nhid 2 via inet6 2001:1234:5:8f63::2 proto static', output)
|
||||
|
||||
output = check_output('ip -6 route show dev veth99 2001:1234:5:8f62::1')
|
||||
print(output)
|
||||
self.assertEqual('2001:1234:5:8f62::1 nhid 2 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output)
|
||||
|
||||
def test_qdisc(self):
|
||||
copy_unit_to_networkd_unit_path('25-qdisc-clsact-and-htb.network', '12-dummy.netdev',
|
||||
'25-qdisc-ingress-netem-compat.network', '11-dummy.netdev')
|
||||
|
Loading…
x
Reference in New Issue
Block a user