diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 4741e873181..2df79106906 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -1349,6 +1349,14 @@ IPv6Token=prefixstable:2002:da8:1::
no.
+
+ Blackhole=
+
+ Takes a boolean. If enabled, packets to the corresponding routes are discarded
+ silently, and Gateway= cannot be specified. Defaults to
+ no.
+
+
diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c
index 8f8aff602a0..08da9120ad3 100644
--- a/src/network/networkd-manager.c
+++ b/src/network/networkd-manager.c
@@ -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 = set_free(m->nexthops);
+ m->nexthops_foreign = set_free(m->nexthops_foreign);
m->nexthops_by_id = hashmap_free(m->nexthops_by_id);
sd_event_source_unref(m->speed_meter_event_source);
diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h
index 3075b8f7076..c0cd4275170 100644
--- a/src/network/networkd-manager.h
+++ b/src/network/networkd-manager.h
@@ -64,6 +64,10 @@ struct Manager {
/* Manage nexthops by id. */
Hashmap *nexthops_by_id;
+ /* Manager stores nexthops without RTA_OIF attribute. */
+ Set *nexthops;
+ Set *nexthops_foreign;
+
/* Manager stores routes without RTA_OIF attribute. */
Set *routes;
Set *routes_foreign;
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 6e70e979891..b31224413ee 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -190,6 +190,7 @@ NextHop.Id, config_parse_nexthop_id,
NextHop.Gateway, config_parse_nexthop_gateway, 0, 0
NextHop.Family, config_parse_nexthop_family, 0, 0
NextHop.OnLink, config_parse_nexthop_onlink, 0, 0
+NextHop.Blackhole, config_parse_nexthop_blackhole, 0, 0
DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
DHCPv4.UseDNS, config_parse_dhcp_use_dns, 0, 0
DHCPv4.RoutesToDNS, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_dns)
diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c
index f98adf468e9..0b598823f65 100644
--- a/src/network/networkd-nexthop.c
+++ b/src/network/networkd-nexthop.c
@@ -33,6 +33,14 @@ NextHop *nexthop_free(NextHop *nexthop) {
hashmap_remove(nexthop->link->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id));
}
+ if (nexthop->manager) {
+ set_remove(nexthop->manager->nexthops, nexthop);
+ set_remove(nexthop->manager->nexthops_foreign, nexthop);
+
+ if (nexthop->id > 0)
+ hashmap_remove(nexthop->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id));
+ }
+
return mfree(nexthop);
}
@@ -95,6 +103,7 @@ static void nexthop_hash_func(const NextHop *nexthop, struct siphash *state) {
assert(nexthop);
siphash24_compress(&nexthop->id, sizeof(nexthop->id), state);
+ siphash24_compress(&nexthop->blackhole, sizeof(nexthop->blackhole), state);
siphash24_compress(&nexthop->family, sizeof(nexthop->family), state);
switch (nexthop->family) {
@@ -116,6 +125,10 @@ static int nexthop_compare_func(const NextHop *a, const NextHop *b) {
if (r != 0)
return r;
+ r = CMP(a->blackhole, b->blackhole);
+ if (r != 0)
+ return r;
+
r = CMP(a->family, b->family);
if (r != 0)
return r;
@@ -133,6 +146,18 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
nexthop_compare_func,
nexthop_free);
+static void nexthop_copy(NextHop *dest, const NextHop *src) {
+ assert(dest);
+ assert(src);
+
+ /* This only copies entries used in the above hash and compare functions. */
+
+ dest->id = src->id;
+ dest->blackhole = src->blackhole;
+ dest->family = src->family;
+ dest->gw = src->gw;
+}
+
int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret) {
NextHop *nh;
@@ -150,20 +175,20 @@ int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret) {
return 0;
}
-static int nexthop_get(Link *link, const NextHop *in, NextHop **ret) {
+static int nexthop_get(Manager *manager, Link *link, const NextHop *in, NextHop **ret) {
NextHop *existing;
- assert(link);
+ assert(manager || link);
assert(in);
- existing = set_get(link->nexthops, in);
+ existing = set_get(link ? link->nexthops : manager->nexthops, in);
if (existing) {
if (ret)
*ret = existing;
return 1;
}
- existing = set_get(link->nexthops_foreign, in);
+ existing = set_get(link ? link->nexthops_foreign : manager->nexthops_foreign, in);
if (existing) {
if (ret)
*ret = existing;
@@ -173,11 +198,11 @@ static int nexthop_get(Link *link, const NextHop *in, NextHop **ret) {
return -ENOENT;
}
-static int nexthop_add_internal(Link *link, Set **nexthops, const NextHop *in, NextHop **ret) {
+static int nexthop_add_internal(Manager *manager, Link *link, Set **nexthops, const NextHop *in, NextHop **ret) {
_cleanup_(nexthop_freep) NextHop *nexthop = NULL;
int r;
- assert(link);
+ assert(manager || link);
assert(nexthops);
assert(in);
@@ -185,9 +210,7 @@ static int nexthop_add_internal(Link *link, Set **nexthops, const NextHop *in, N
if (r < 0)
return r;
- nexthop->id = in->id;
- nexthop->family = in->family;
- nexthop->gw = in->gw;
+ nexthop_copy(nexthop, in);
r = set_ensure_put(nexthops, &nexthop_hash_ops, nexthop);
if (r < 0)
@@ -196,6 +219,7 @@ static int nexthop_add_internal(Link *link, Set **nexthops, const NextHop *in, N
return -EEXIST;
nexthop->link = link;
+ nexthop->manager = manager;
if (ret)
*ret = nexthop;
@@ -204,8 +228,9 @@ static int nexthop_add_internal(Link *link, Set **nexthops, const NextHop *in, N
return 0;
}
-static int nexthop_add_foreign(Link *link, const NextHop *in, NextHop **ret) {
- return nexthop_add_internal(link, &link->nexthops_foreign, in, ret);
+static int nexthop_add_foreign(Manager *manager, Link *link, const NextHop *in, NextHop **ret) {
+ assert(manager || link);
+ return nexthop_add_internal(manager, link, link ? &link->nexthops_foreign : &manager->nexthops_foreign, in, ret);
}
static int nexthop_add(Link *link, const NextHop *in, NextHop **ret) {
@@ -213,20 +238,30 @@ static int nexthop_add(Link *link, const NextHop *in, NextHop **ret) {
NextHop *nexthop;
int r;
- r = nexthop_get(link, in, &nexthop);
+ assert(link);
+ assert(in);
+
+ if (in->blackhole)
+ r = nexthop_get(link->manager, NULL, in, &nexthop);
+ else
+ r = nexthop_get(NULL, link, in, &nexthop);
if (r == -ENOENT) {
/* NextHop does not exist, create a new one */
- r = nexthop_add_internal(link, &link->nexthops, in, &nexthop);
+ r = nexthop_add_internal(link->manager,
+ in->blackhole ? NULL : link,
+ in->blackhole ? &link->manager->nexthops : &link->nexthops,
+ in, &nexthop);
if (r < 0)
return r;
is_new = true;
} else if (r == 0) {
/* Take over a foreign nexthop */
- r = set_ensure_put(&link->nexthops, &nexthop_hash_ops, nexthop);
+ r = set_ensure_put(in->blackhole ? &link->manager->nexthops : &link->nexthops,
+ &nexthop_hash_ops, nexthop);
if (r < 0)
return r;
- set_remove(link->nexthops_foreign, nexthop);
+ set_remove(in->blackhole ? link->manager->nexthops_foreign : link->nexthops_foreign, nexthop);
} else if (r == 1) {
/* NextHop exists, do nothing */
;
@@ -238,11 +273,13 @@ static int nexthop_add(Link *link, const NextHop *in, NextHop **ret) {
return is_new;
}
-static int nexthop_update(Link *link, NextHop *nexthop, const NextHop *in) {
+static int nexthop_update(Manager *manager, Link *link, NextHop *nexthop, const NextHop *in) {
+ Set *nexthops;
int r;
- assert(link);
- assert(link->manager);
+ /* link may be NULL. */
+
+ assert(manager);
assert(nexthop);
assert(in);
assert(in->id > 0);
@@ -255,19 +292,21 @@ static int nexthop_update(Link *link, NextHop *nexthop, const NextHop *in) {
return -EINVAL;
}
- nexthop = set_remove(link->nexthops, nexthop);
+ nexthops = link ? link->nexthops : manager->nexthops;
+
+ nexthop = set_remove(nexthops, nexthop);
if (!nexthop)
return -ENOENT;
nexthop->id = in->id;
- r = set_put(link->nexthops, nexthop);
+ r = set_put(nexthops, nexthop);
if (r <= 0) {
int k;
/* On failure, revert the change. */
nexthop->id = 0;
- k = set_put(link->nexthops, nexthop);
+ k = set_put(nexthops, nexthop);
if (k <= 0) {
nexthop_free(nexthop);
return k < 0 ? k : -EEXIST;
@@ -277,13 +316,14 @@ static int nexthop_update(Link *link, NextHop *nexthop, const NextHop *in) {
}
set_manager:
- return hashmap_ensure_put(&link->manager->nexthops_by_id, NULL, UINT32_TO_PTR(nexthop->id), nexthop);
+ return hashmap_ensure_put(&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) {
assert(nexthop);
assert(str);
- assert(link);
+
+ /* link may be NULL. */
if (DEBUG_LOGGING) {
_cleanup_free_ char *gw = NULL;
@@ -291,11 +331,11 @@ static void log_nexthop_debug(const NextHop *nexthop, uint32_t id, const char *s
(void) in_addr_to_string(nexthop->family, &nexthop->gw, &gw);
if (nexthop->id == id)
- log_link_debug(link, "%s nexthop: id: %"PRIu32", gw: %s",
- str, nexthop->id, strna(gw));
+ log_link_debug(link, "%s nexthop: id: %"PRIu32", gw: %s, blackhole: %s",
+ str, nexthop->id, strna(gw), yes_no(nexthop->blackhole));
else
- log_link_debug(link, "%s nexthop: id: %"PRIu32"→%"PRIu32", gw: %s",
- str, nexthop->id, id, strna(gw));
+ log_link_debug(link, "%s nexthop: id: %"PRIu32"→%"PRIu32", gw: %s, blackhole: %s",
+ str, nexthop->id, id, strna(gw), yes_no(nexthop->blackhole));
}
}
@@ -353,19 +393,25 @@ static int nexthop_configure(const NextHop *nexthop, Link *link) {
return log_link_error_errno(link, r, "Could not append NHA_ID attribute: %m");
}
- r = sd_netlink_message_append_u32(req, NHA_OIF, link->ifindex);
- if (r < 0)
- return log_link_error_errno(link, r, "Could not append NHA_OIF attribute: %m");
-
- if (in_addr_is_set(nexthop->family, &nexthop->gw)) {
- r = netlink_message_append_in_addr_union(req, NHA_GATEWAY, nexthop->family, &nexthop->gw);
+ if (nexthop->blackhole) {
+ r = sd_netlink_message_append_flag(req, NHA_BLACKHOLE);
if (r < 0)
- return log_link_error_errno(link, r, "Could not append NHA_GATEWAY attribute: %m");
+ return log_link_error_errno(link, r, "Could not append NHA_BLACKHOLE attribute: %m");
+ } else {
+ r = sd_netlink_message_append_u32(req, NHA_OIF, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append NHA_OIF attribute: %m");
- if (nexthop->onlink > 0) {
- r = sd_rtnl_message_nexthop_set_flags(req, RTNH_F_ONLINK);
+ if (in_addr_is_set(nexthop->family, &nexthop->gw)) {
+ r = netlink_message_append_in_addr_union(req, NHA_GATEWAY, nexthop->family, &nexthop->gw);
if (r < 0)
- return log_link_error_errno(link, r, "Failed to set RTNH_F_ONLINK flag: %m");
+ return log_link_error_errno(link, r, "Could not append NHA_GATEWAY attribute: %m");
+
+ if (nexthop->onlink > 0) {
+ r = sd_rtnl_message_nexthop_set_flags(req, RTNH_F_ONLINK);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to set RTNH_F_ONLINK flag: %m");
+ }
}
}
@@ -431,7 +477,7 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
NextHop *nexthop = NULL;
uint32_t ifindex;
uint16_t type;
- Link *link;
+ Link *link = NULL;
int r;
assert(rtnl);
@@ -456,22 +502,21 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
}
r = sd_netlink_message_read_u32(message, NHA_OIF, &ifindex);
- if (r == -ENODATA) {
- log_warning_errno(r, "rtnl: received nexthop message without NHA_OIF attribute, ignoring: %m");
- return 0;
- } else if (r < 0) {
+ if (r < 0 && r != -ENODATA) {
log_warning_errno(r, "rtnl: could not get NHA_OIF attribute, ignoring: %m");
return 0;
- } else if (ifindex <= 0) {
- log_warning("rtnl: received nexthop message with invalid ifindex %"PRIu32", ignoring.", ifindex);
- return 0;
- }
+ } else if (r >= 0) {
+ if (ifindex <= 0) {
+ log_warning("rtnl: received nexthop message with invalid ifindex %"PRIu32", ignoring.", ifindex);
+ return 0;
+ }
- r = link_get(m, ifindex, &link);
- if (r < 0 || !link) {
- if (!m->enumerating)
- log_warning("rtnl: received nexthop message for link (%"PRIu32") we do not know about, ignoring", ifindex);
- return 0;
+ r = link_get(m, ifindex, &link);
+ if (r < 0 || !link) {
+ if (!m->enumerating)
+ log_warning("rtnl: received nexthop message for link (%"PRIu32") we do not know about, ignoring", ifindex);
+ return 0;
+ }
}
r = nexthop_new(&tmp);
@@ -491,6 +536,13 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
return 0;
}
+ r = sd_netlink_message_has_flag(message, NHA_BLACKHOLE);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: could not get NHA_BLACKHOLE attribute, ignoring: %m");
+ return 0;
+ }
+ tmp->blackhole = r;
+
r = sd_netlink_message_read_u32(message, NHA_ID, &tmp->id);
if (r == -ENODATA) {
log_link_warning_errno(link, r, "rtnl: received nexthop message without NHA_ID attribute, ignoring: %m");
@@ -503,7 +555,12 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
return 0;
}
- r = nexthop_get(link, tmp, &nexthop);
+ /* All blackhole nexthops are managed by Manager. Note that the linux kernel does not set
+ * NHA_OID attribute when NHA_BLACKHOLE is set. Just for safety. */
+ if (tmp->blackhole)
+ link = NULL;
+
+ r = nexthop_get(m, link, tmp, &nexthop);
if (r < 0) {
uint32_t id;
@@ -512,7 +569,7 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
id = tmp->id;
tmp->id = 0;
- (void) nexthop_get(link, tmp, &nexthop);
+ (void) nexthop_get(m, link, tmp, &nexthop);
tmp->id = id;
}
@@ -523,14 +580,14 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
log_nexthop_debug(nexthop, tmp->id, "Received remembered", link);
else {
log_nexthop_debug(tmp, tmp->id, "Remembering foreign", link);
- r = nexthop_add_foreign(link, tmp, &nexthop);
+ r = nexthop_add_foreign(m, link, tmp, &nexthop);
if (r < 0) {
log_link_warning_errno(link, r, "Could not remember foreign nexthop, ignoring: %m");
return 0;
}
}
- r = nexthop_update(link, nexthop, tmp);
+ r = nexthop_update(m, link, nexthop, tmp);
if (r < 0) {
log_link_warning_errno(link, r, "Could not update nexthop, ignoring: %m");
return 0;
@@ -556,6 +613,12 @@ static int nexthop_section_verify(NextHop *nh) {
/* When no Gateway= is specified, assume IPv4. */
nh->family = AF_INET;
+ if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw))
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: blackhole nexthop cannot have gateway address. "
+ "Ignoring [NextHop] section from line %u.",
+ nh->section->filename, nh->section->line);
+
if (nh->onlink < 0 && in_addr_is_set(nh->family, &nh->gw) &&
ordered_hashmap_isempty(nh->network->addresses_by_section)) {
/* If no address is configured, in most cases the gateway cannot be reachable.
@@ -784,3 +847,42 @@ int config_parse_nexthop_onlink(
TAKE_PTR(n);
return 0;
}
+
+int config_parse_nexthop_blackhole(
+ 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) {
+
+ _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
+ Network *network = userdata;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = nexthop_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return log_oom();
+
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ n->blackhole = r;
+
+ TAKE_PTR(n);
+ return 0;
+}
diff --git a/src/network/networkd-nexthop.h b/src/network/networkd-nexthop.h
index 0356e997ecb..9ead5fc95ca 100644
--- a/src/network/networkd-nexthop.h
+++ b/src/network/networkd-nexthop.h
@@ -20,11 +20,13 @@ typedef struct NextHop {
Network *network;
NetworkConfigSection *section;
+ Manager *manager;
Link *link;
unsigned char protocol;
uint32_t id;
+ bool blackhole;
int family;
union in_addr_union gw;
int onlink;
@@ -43,3 +45,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_id);
CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_gateway);
CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_family);
CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_onlink);
+CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_blackhole);
diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c
index aa744d54700..a74541a6c95 100644
--- a/src/network/networkd-route.c
+++ b/src/network/networkd-route.c
@@ -451,34 +451,18 @@ static int route_get(const Manager *manager, const Link *link, const Route *in,
assert(manager || link);
assert(in);
- if (link) {
- existing = set_get(link->routes, in);
- if (existing) {
- if (ret)
- *ret = existing;
- return 1;
- }
+ existing = set_get(link ? link->routes : manager->routes, in);
+ if (existing) {
+ if (ret)
+ *ret = existing;
+ return 1;
+ }
- existing = set_get(link->routes_foreign, in);
- if (existing) {
- if (ret)
- *ret = existing;
- return 0;
- }
- } else {
- existing = set_get(manager->routes, in);
- if (existing) {
- if (ret)
- *ret = existing;
- return 1;
- }
-
- existing = set_get(manager->routes_foreign, in);
- if (existing) {
- if (ret)
- *ret = existing;
- return 0;
- }
+ existing = set_get(link ? link->routes_foreign : manager->routes_foreign, in);
+ if (existing) {
+ if (ret)
+ *ret = existing;
+ return 0;
}
return -ENOENT;
@@ -488,6 +472,8 @@ static void route_copy(Route *dest, const Route *src, const MultipathRoute *m, c
assert(dest);
assert(src);
+ /* This only copies entries used by the above hash and compare functions. */
+
dest->family = src->family;
dest->src = src->src;
dest->src_prefixlen = src->src_prefixlen;
@@ -496,7 +482,10 @@ static void route_copy(Route *dest, const Route *src, const MultipathRoute *m, c
dest->prefsrc = src->prefsrc;
dest->scope = src->scope;
dest->protocol = src->protocol;
- dest->type = src->type;
+ if (nh && nh->blackhole)
+ dest->type = RTN_BLACKHOLE;
+ else
+ dest->type = src->type;
dest->tos = src->tos;
dest->priority = src->priority;
dest->table = src->table;
@@ -593,19 +582,11 @@ static int route_add(Manager *manager, Link *link, const Route *in, const Multip
is_new = true;
} else if (r == 0) {
/* Take over a foreign route */
- if (link) {
- r = set_ensure_put(&link->routes, &route_hash_ops, route);
- if (r < 0)
- return r;
+ r = set_ensure_put(link ? &link->routes : &manager->routes, &route_hash_ops, route);
+ if (r < 0)
+ return r;
- set_remove(link->routes_foreign, route);
- } else {
- r = set_ensure_put(&manager->routes, &route_hash_ops, route);
- if (r < 0)
- return r;
-
- set_remove(manager->routes_foreign, route);
- }
+ set_remove(link ? link->routes_foreign : manager->routes_foreign, route);
} else if (r == 1) {
/* Route exists, do nothing */
;
@@ -981,8 +962,8 @@ static int route_add_and_setup_timer(Link *link, const Route *route, const Multi
(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, NULL, &nr);
+ if (route_type_is_reject(route) || (nh && nh->blackhole))
+ k = route_add(link->manager, NULL, route, NULL, nh, &nr);
else if (!m || m->ifindex == 0 || m->ifindex == link->ifindex)
k = route_add(NULL, link, route, m, nh, &nr);
else {
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index 04a2a4c9c19..48f0ca89515 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -353,6 +353,7 @@ Id=
Gateway=
Family=
OnLink=
+Blackhole=
[QDisc]
Parent=
Handle=
diff --git a/test/test-network/conf/25-nexthop.network b/test/test-network/conf/25-nexthop.network
index a0b220f9184..8b3e4b65c56 100644
--- a/test/test-network/conf/25-nexthop.network
+++ b/test/test-network/conf/25-nexthop.network
@@ -28,9 +28,27 @@ Id=5
Gateway=192.168.10.1
OnLink=yes
+[NextHop]
+Id=6
+Family=ipv4
+Blackhole=yes
+
+[NextHop]
+Id=7
+Family=ipv6
+Blackhole=yes
+
[NextHop]
Gateway=192.168.5.2
+[NextHop]
+Family=ipv4
+Blackhole=yes
+
+[NextHop]
+Family=ipv6
+Blackhole=yes
+
[Route]
NextHop=1
Destination=10.10.10.10
@@ -46,3 +64,11 @@ Destination=2001:1234:5:8f62::1
[Route]
NextHop=5
Destination=10.10.10.12
+
+[Route]
+NextHop=6
+Destination=10.10.10.13
+
+[Route]
+NextHop=7
+Destination=2001:1234:5:8f62::2
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index 2effa42e16d..6c17b204a11 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -404,6 +404,13 @@ def remove_routes(routes):
for route_type, addr in routes:
call('ip route del', route_type, addr, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+def remove_blackhole_nexthops():
+ ret = run('ip nexthop show dev lo', stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
+ if ret.returncode == 0:
+ for line in ret.stdout.rstrip().splitlines():
+ id = line.split()[1]
+ call(f'ip nexthop del id {id}')
+
def remove_l2tp_tunnels(tunnel_ids):
output = check_output('ip l2tp show tunnel')
for tid in tunnel_ids:
@@ -1831,12 +1838,14 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
routes = [['blackhole', '202.54.1.2'], ['unreachable', '202.54.1.3'], ['prohibit', '202.54.1.4']]
def setUp(self):
+ remove_blackhole_nexthops()
remove_routing_policy_rule_tables(self.routing_policy_rule_tables)
remove_routes(self.routes)
remove_links(self.links)
stop_networkd(show_logs=False)
def tearDown(self):
+ remove_blackhole_nexthops()
remove_routing_policy_rule_tables(self.routing_policy_rule_tables)
remove_routes(self.routes)
remove_links(self.links)
@@ -2829,6 +2838,12 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
self.assertRegex(output, 'id 5 via 192.168.10.1 dev veth99 .*onlink')
self.assertRegex(output, r'id [0-9]* via 192.168.5.2 dev veth99')
+ # kernel manages blackhole nexthops on lo
+ output = check_output('ip nexthop list dev lo')
+ print(output)
+ self.assertIn('id 6 blackhole', output)
+ self.assertIn('id 7 blackhole', output)
+
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)
@@ -2845,6 +2860,14 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
print(output)
self.assertEqual('2001:1234:5:8f62::1 nhid 2 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output)
+ output = check_output('ip route show 10.10.10.13')
+ print(output)
+ self.assertEqual('blackhole 10.10.10.13 nhid 6 dev lo proto static', output)
+
+ output = check_output('ip -6 route show 2001:1234:5:8f62::2')
+ print(output)
+ self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 7 dev lo 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')