diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 015fcf01f9..4741e87318 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -1340,6 +1340,15 @@ IPv6Token=prefixstable:2002:da8:1::
to ipv4.
+
+ OnLink=
+
+ Takes a boolean. If set to true, the kernel does not have to check if the gateway is
+ reachable directly by the current machine (i.e., attached to the local network), so that we
+ can insert the nexthop in the kernel table without it being complained about. Defaults to
+ no.
+
+
@@ -1361,9 +1370,9 @@ IPv6Token=prefixstable:2002:da8:1::
GatewayOnLink=
Takes a boolean. If set to true, the kernel does not have to check if the gateway is
- reachable directly by the current machine (i.e., the kernel does not need to check if the
- gateway is attached to the local network), so that we can insert the route in the kernel
- table without it being complained about. Defaults to no.
+ reachable directly by the current machine (i.e., attached to the local network), so that we
+ can insert the route in the kernel table without it being complained about. Defaults to
+ no.
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index e74a44b2a6..6e70e97989 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -189,6 +189,7 @@ Route.NextHop, config_parse_route_nexthop,
NextHop.Id, config_parse_nexthop_id, 0, 0
NextHop.Gateway, config_parse_nexthop_gateway, 0, 0
NextHop.Family, config_parse_nexthop_family, 0, 0
+NextHop.OnLink, config_parse_nexthop_onlink, 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 07842ae608..f98adf468e 100644
--- a/src/network/networkd-nexthop.c
+++ b/src/network/networkd-nexthop.c
@@ -47,6 +47,7 @@ static int nexthop_new(NextHop **ret) {
*nexthop = (NextHop) {
.family = AF_UNSPEC,
+ .onlink = -1,
};
*ret = TAKE_PTR(nexthop);
@@ -360,6 +361,12 @@ static int nexthop_configure(const NextHop *nexthop, Link *link) {
r = netlink_message_append_in_addr_union(req, NHA_GATEWAY, nexthop->family, &nexthop->gw);
if (r < 0)
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");
+ }
}
r = netlink_call_async(link->manager->rtnl, NULL, req, nexthop_handler,
@@ -549,6 +556,16 @@ static int nexthop_section_verify(NextHop *nh) {
/* When no Gateway= is specified, assume IPv4. */
nh->family = AF_INET;
+ 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.
+ * TODO: we may need to improve the condition above. */
+ log_warning("%s: Gateway= without static address configured. "
+ "Enabling OnLink= option.",
+ nh->section->filename);
+ nh->onlink = true;
+ }
+
return 0;
}
@@ -722,3 +739,48 @@ int config_parse_nexthop_family(
TAKE_PTR(n);
return 0;
}
+
+int config_parse_nexthop_onlink(
+ 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();
+
+ if (isempty(rvalue)) {
+ n->onlink = -1;
+ TAKE_PTR(n);
+ return 0;
+ }
+
+ 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->onlink = r;
+
+ TAKE_PTR(n);
+ return 0;
+}
diff --git a/src/network/networkd-nexthop.h b/src/network/networkd-nexthop.h
index 06673d4369..0356e997ec 100644
--- a/src/network/networkd-nexthop.h
+++ b/src/network/networkd-nexthop.h
@@ -27,6 +27,7 @@ typedef struct NextHop {
uint32_t id;
int family;
union in_addr_union gw;
+ int onlink;
} NextHop;
NextHop *nexthop_free(NextHop *nexthop);
@@ -41,3 +42,4 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message,
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);
diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c
index ddfa4d4df7..aa744d5470 100644
--- a/src/network/networkd-route.c
+++ b/src/network/networkd-route.c
@@ -2743,9 +2743,10 @@ static int route_section_verify(Route *route, Network *network) {
if (route->family == AF_INET6 && route->priority == 0)
route->priority = IP6_RT_PRIO_USER;
- if (ordered_hashmap_isempty(network->addresses_by_section) &&
- in_addr_is_set(route->gw_family, &route->gw) &&
- route->gateway_onlink < 0) {
+ if (route->gateway_onlink < 0 && in_addr_is_set(route->gw_family, &route->gw) &&
+ ordered_hashmap_isempty(network->addresses_by_section)) {
+ /* If no address is configured, in most cases the gateway cannot be reachable.
+ * TODO: we may need to improve the condition above. */
log_warning("%s: Gateway= without static address configured. "
"Enabling GatewayOnLink= option.",
network->filename);
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index 90420f42b5..04a2a4c9c1 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -352,6 +352,7 @@ SendVendorOption=
Id=
Gateway=
Family=
+OnLink=
[QDisc]
Parent=
Handle=
diff --git a/test/test-network/conf/25-nexthop.network b/test/test-network/conf/25-nexthop.network
index d4c7aa606c..a0b220f918 100644
--- a/test/test-network/conf/25-nexthop.network
+++ b/test/test-network/conf/25-nexthop.network
@@ -23,6 +23,11 @@ Family=ipv6
Id=4
Family=ipv4
+[NextHop]
+Id=5
+Gateway=192.168.10.1
+OnLink=yes
+
[NextHop]
Gateway=192.168.5.2
@@ -37,3 +42,7 @@ Destination=10.10.10.11
[Route]
NextHop=2
Destination=2001:1234:5:8f62::1
+
+[Route]
+NextHop=5
+Destination=10.10.10.12
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index a5942ec4ea..38480e1ce5 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -2812,6 +2812,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
self.assertIn('id 2 via 2001:1234:5:8f63::2 dev veth99', output)
self.assertIn('id 3 dev veth99', output)
self.assertIn('id 4 dev veth99', output)
+ 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')
output = check_output('ip route show dev veth99 10.10.10.10')
@@ -2822,6 +2823,10 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
print(output)
self.assertEqual('10.10.10.11 nhid 2 via inet6 2001:1234:5:8f63::2 proto static', output)
+ output = check_output('ip route show dev veth99 10.10.10.12')
+ print(output)
+ self.assertEqual('10.10.10.12 nhid 5 via 192.168.10.1 proto static onlink', 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)