diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 2ce7c7096e..dd0535a06e 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -347,6 +347,15 @@
+
+ DefaultRouteOnDevice=
+
+ Takes a boolean. If set to true, sets up the default route bound to the interface.
+ Defaults to false. This is useful when creating routes on point-to-point interfaces.
+ This is equivalent to e.g. the following.
+ ip route add default dev veth99
+
+
IPv6Token=
diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c
index 8fb8a64e6d..9df5646a7e 100644
--- a/src/network/networkd-ipv4ll.c
+++ b/src/network/networkd-ipv4ll.c
@@ -34,7 +34,9 @@ static int ipv4ll_address_lost(Link *link) {
address->prefixlen = 16;
address->scope = RT_SCOPE_LINK;
- address_remove(address, link, NULL);
+ r = address_remove(address, link, NULL);
+ if (r < 0)
+ return r;
r = route_new(&route);
if (r < 0)
@@ -44,7 +46,9 @@ static int ipv4ll_address_lost(Link *link) {
route->scope = RT_SCOPE_LINK;
route->priority = IPV4LL_ROUTE_METRIC;
- route_remove(route, link, NULL);
+ r = route_remove(route, link, NULL);
+ if (r < 0)
+ return r;
link_check_ready(link);
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index f87d7def44..3c6b5ac100 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -53,6 +53,7 @@ Network.DHCP, config_parse_dhcp,
Network.DHCPServer, config_parse_bool, 0, offsetof(Network, dhcp_server)
Network.LinkLocalAddressing, config_parse_link_local_address_family_boolean, 0, offsetof(Network, link_local)
Network.IPv4LLRoute, config_parse_bool, 0, offsetof(Network, ipv4ll_route)
+Network.DefaultRouteOnDevice, config_parse_bool, 0, offsetof(Network, default_route_on_device)
Network.IPv6Token, config_parse_ipv6token, 0, offsetof(Network, ipv6_token)
Network.LLDP, config_parse_lldp_mode, 0, offsetof(Network, lldp_mode)
Network.EmitLLDP, config_parse_lldp_emit, 0, offsetof(Network, lldp_emit)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 56a45379c1..755cd604dc 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -454,6 +454,11 @@ int network_load_one(Manager *manager, const char *filename) {
if (r < 0)
log_warning_errno(r, "%s: Failed to add IPv4LL route, ignoring: %m", network->filename);
+ r = network_add_default_route_on_device(network);
+ if (r < 0)
+ log_warning_errno(r, "%s: Failed to add default route on device, ignoring: %m",
+ network->filename);
+
r = ordered_hashmap_ensure_allocated(&manager->networks, &string_hash_ops);
if (r < 0)
return r;
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index d00062fe59..22c7dc5afd 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -151,6 +151,8 @@ struct Network {
AddressFamilyBoolean link_local;
bool ipv4ll_route;
+ bool default_route_on_device;
+
/* IPv6 prefix delegation support */
RADVPrefixDelegation router_prefix_delegation;
usec_t router_lifetime_usec;
diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c
index 179eeac017..24ef5b46bc 100644
--- a/src/network/networkd-route.c
+++ b/src/network/networkd-route.c
@@ -712,6 +712,30 @@ int network_add_ipv4ll_route(Network *network) {
return 0;
}
+int network_add_default_route_on_device(Network *network) {
+ _cleanup_(route_free_or_set_invalidp) Route *n = NULL;
+ int r;
+
+ assert(network);
+
+ if (!network->default_route_on_device)
+ return 0;
+
+ /* DefaultRouteOnDevice= is in [Network] section. */
+ r = route_new_static(network, NULL, 0, &n);
+ if (r < 0)
+ return r;
+
+ r = in_addr_from_string(AF_INET, "169.254.0.0", &n->dst);
+ if (r < 0)
+ return r;
+
+ n->family = AF_INET;
+
+ TAKE_PTR(n);
+ return 0;
+}
+
int config_parse_gateway(
const char *unit,
const char *filename,
diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h
index 892da25bd5..4643f77854 100644
--- a/src/network/networkd-route.h
+++ b/src/network/networkd-route.h
@@ -62,6 +62,7 @@ int route_section_verify(Route *route, Network *network);
DEFINE_NETWORK_SECTION_FUNCTIONS(Route, route_free);
int network_add_ipv4ll_route(Network *network);
+int network_add_default_route_on_device(Network *network);
CONFIG_PARSER_PROTOTYPE(config_parse_gateway);
CONFIG_PARSER_PROTOTYPE(config_parse_preferred_src);
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index 06813c29fe..ef29c7951f 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -89,6 +89,7 @@ IPMasquerade=
ProxyARP=
PrimarySlave=
IPv4LLRoute=
+DefaultRouteOnDevice=
Address=
IPv6ProxyNDPAddress=
IPv6AcceptRA=
diff --git a/test/test-network/conf/25-route-static.network b/test/test-network/conf/25-route-static.network
index 50b2ce0c3b..9e12480a65 100644
--- a/test/test-network/conf/25-route-static.network
+++ b/test/test-network/conf/25-route-static.network
@@ -5,6 +5,8 @@ Name=dummy98
IPv6AcceptRA=no
Address=2001:1234:5:8f63::1/128
Address=149.10.124.58/28
+DefaultRouteOnDevice=yes
+IPv4LLRoute=yes
[Route]
Destination=2001:1234:5:8fff:ff:ff:ff:ff/128
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index e65897561b..9c665d9f93 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -1203,12 +1203,14 @@ class NetworkdNetWorkTests(unittest.TestCase, Utilities):
print(output)
self.assertRegex(output, '149.10.124.48/28 proto kernel scope link src 149.10.124.58')
self.assertRegex(output, '149.10.124.64 proto static scope link')
+ self.assertRegex(output, '169.254.0.0/16 proto static scope link metric 2048')
self.assertRegex(output, '192.168.1.1 proto static initcwnd 20')
self.assertRegex(output, '192.168.1.2 proto static initrwnd 30')
output = subprocess.check_output(['ip', '-4', 'route', 'show', 'dev', 'dummy98', 'default']).rstrip().decode('utf-8')
self.assertRegex(output, 'default via 149.10.125.65 proto static onlink')
self.assertRegex(output, 'default via 149.10.124.64 proto static')
+ self.assertRegex(output, 'default proto static')
output = subprocess.check_output(['ip', 'route', 'show', 'type', 'blackhole']).rstrip().decode('utf-8')
print(output)