diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 4cd2520173..4299583fe7 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -1216,7 +1216,9 @@
Gateway=
- As in the [Network] section.
+ Takes the gateway address or special value dhcp. If
+ dhcp, then the gateway address provided by DHCP (or in the IPv6 case,
+ provided by IPv6 RA) is used.
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index 66d83e76bf..70377ad6bb 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -377,6 +377,23 @@ static int link_set_dhcp_routes(Link *link) {
return log_link_error_errno(link, r, "Could not set router: %m");
}
+ Route *rt;
+ LIST_FOREACH(routes, rt, link->network->static_routes) {
+ if (!rt->gateway_from_dhcp)
+ continue;
+
+ if (rt->family != AF_INET)
+ continue;
+
+ rt->gw.in = router[0];
+
+ r = route_configure(rt, link, dhcp4_route_handler);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set gateway: %m");
+ if (r > 0)
+ link->dhcp4_messages++;
+ }
+
return link_set_dns_routes(link, &address);
}
@@ -480,6 +497,20 @@ static int dhcp_remove_router(Link *link, sd_dhcp_lease *lease, const struct in_
if (remove_all || !set_contains(link->dhcp_routes, route))
(void) route_remove(route, link, NULL);
+ Route *rt;
+ LIST_FOREACH(routes, rt, link->network->static_routes) {
+ if (!rt->gateway_from_dhcp)
+ continue;
+
+ if (rt->family != AF_INET)
+ continue;
+
+ if (!remove_all && in4_addr_equal(router, &rt->gw.in))
+ continue;
+
+ (void) route_remove(rt, link, NULL);
+ }
+
return 0;
}
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 23d0ee675b..f2027057c4 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -1039,6 +1039,8 @@ int link_request_set_routes(Link *link) {
/* First add the routes that enable us to talk to gateways, then add in the others that need a gateway. */
for (phase = 0; phase < _PHASE_MAX; phase++)
LIST_FOREACH(routes, rt, link->network->static_routes) {
+ if (rt->gateway_from_dhcp)
+ continue;
if ((in_addr_is_null(rt->family, &rt->gw) && ordered_set_isempty(rt->multipath_routes)) != (phase == PHASE_NON_GATEWAY))
continue;
diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c
index d1db9e4931..fb3d6f2a84 100644
--- a/src/network/networkd-ndisc.c
+++ b/src/network/networkd-ndisc.c
@@ -169,6 +169,26 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
if (r > 0)
link->ndisc_messages++;
+ Route *route_gw;
+ LIST_FOREACH(routes, route_gw, link->network->static_routes) {
+ if (!route_gw->gateway_from_dhcp)
+ continue;
+
+ if (route_gw->family != AF_INET6)
+ continue;
+
+ route_gw->gw = gateway;
+
+ r = route_configure(route_gw, link, ndisc_netlink_route_message_handler);
+ if (r < 0) {
+ log_link_error_errno(link, r, "Could not set gateway: %m");
+ link_enter_failed(link);
+ return r;
+ }
+ if (r > 0)
+ link->ndisc_messages++;
+ }
+
return 0;
}
diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c
index ecc8d219b7..4e90fdef18 100644
--- a/src/network/networkd-route.c
+++ b/src/network/networkd-route.c
@@ -998,10 +998,19 @@ int config_parse_gateway(
/* we are not in an Route section, so treat
* this as the special '0' section */
r = route_new_static(network, NULL, 0, &n);
- } else
+ if (r < 0)
+ return r;
+ } else {
r = route_new_static(network, filename, section_line, &n);
- if (r < 0)
- return r;
+ if (r < 0)
+ return r;
+
+ if (streq(rvalue, "dhcp")) {
+ n->gateway_from_dhcp = true;
+ TAKE_PTR(n);
+ return 0;
+ }
+ }
if (n->family == AF_UNSPEC)
r = in_addr_from_string_auto(rvalue, &n->family, &n->gw);
diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h
index 91bba368ee..067c65f2f7 100644
--- a/src/network/networkd-route.h
+++ b/src/network/networkd-route.h
@@ -48,6 +48,7 @@ struct Route {
unsigned char pref;
unsigned flags;
int gateway_onlink;
+ bool gateway_from_dhcp;
union in_addr_union gw;
union in_addr_union dst;
diff --git a/test/test-network/conf/dhcp-client-gateway-ipv4.network b/test/test-network/conf/dhcp-client-gateway-ipv4.network
new file mode 100644
index 0000000000..1b8a3751a4
--- /dev/null
+++ b/test/test-network/conf/dhcp-client-gateway-ipv4.network
@@ -0,0 +1,10 @@
+[Match]
+Name=veth99
+
+[Network]
+DHCP=ipv4
+IPv6AcceptRA=no
+
+[Route]
+Gateway=dhcp
+Destination=10.0.0.0/8
diff --git a/test/test-network/conf/dhcp-client-gateway-ipv6.network b/test/test-network/conf/dhcp-client-gateway-ipv6.network
new file mode 100644
index 0000000000..058cb33080
--- /dev/null
+++ b/test/test-network/conf/dhcp-client-gateway-ipv6.network
@@ -0,0 +1,9 @@
+[Match]
+Name=veth99
+
+[Network]
+DHCP=ipv6
+
+[Route]
+Gateway=dhcp
+Destination=2001:1234:5:9fff:ff:ff:ff:ff/128
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index 90b1a8b140..d9b5df18df 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -2659,6 +2659,8 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
'25-vrf.network',
'dhcp-client-anonymize.network',
'dhcp-client-decline.network',
+ 'dhcp-client-gateway-ipv4.network',
+ 'dhcp-client-gateway-ipv6.network',
'dhcp-client-gateway-onlink-implicit.network',
'dhcp-client-ipv4-dhcp-settings.network',
'dhcp-client-ipv4-only-ipv6-disabled.network',
@@ -3145,6 +3147,30 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
print(output)
self.assertEqual(output, '')
+ def test_dhcp_client_gateway_ipv4(self):
+ copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network',
+ 'dhcp-client-gateway-ipv4.network')
+ start_networkd()
+ self.wait_online(['veth-peer:carrier'])
+ start_dnsmasq()
+ self.wait_online(['veth99:routable', 'veth-peer:routable'])
+
+ output = check_output('ip route list dev veth99 10.0.0.0/8')
+ print(output)
+ self.assertRegex(output, '10.0.0.0/8 via 192.168.5.1 proto static')
+
+ def test_dhcp_client_gateway_ipv6(self):
+ copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network',
+ 'dhcp-client-gateway-ipv6.network')
+ start_networkd()
+ self.wait_online(['veth-peer:carrier'])
+ start_dnsmasq()
+ self.wait_online(['veth99:routable', 'veth-peer:routable'])
+
+ output = check_output('ip -6 route list dev veth99 2001:1234:5:9fff:ff:ff:ff:ff')
+ print(output)
+ self.assertRegex(output, 'via fe80::1034:56ff:fe78:9abd')
+
def test_dhcp_client_gateway_onlink_implicit(self):
copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network',
'dhcp-client-gateway-onlink-implicit.network')