diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 2d3bb66e395..76a924cec73 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -2147,6 +2147,19 @@ Table=1234
+
+ RapidCommit=
+
+ Takes a boolean. The DHCPv6 client can obtain configuration parameters from a DHCPv6 server
+ through a rapid two-message exchange (solicit and reply). When the rapid commit option is set by
+ both the DHCPv6 client and the DHCPv6 server, the two-message exchange is used. Otherwise, the
+ four-message exchange (solicit, advertise, request, and reply) is used. The two-message exchange
+ provides faster client configuration. See
+ RFC 3315 for details.
+ Defaults to true, and the two-message exchange will be used if the server support it.
+
+
+
diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h
index 176391ebecd..65f6cb057f4 100644
--- a/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libsystemd-network/dhcp6-internal.h
@@ -71,6 +71,7 @@ struct sd_dhcp6_client {
char **vendor_class;
OrderedHashmap *extra_options;
OrderedSet *vendor_options;
+ bool rapid_commit;
struct sd_dhcp6_lease *lease;
diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c
index 4ca51591065..3de10cc1995 100644
--- a/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libsystemd-network/sd-dhcp6-client.c
@@ -491,6 +491,14 @@ int dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transactio
return 0;
}
+int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable) {
+ assert_return(client, -EINVAL);
+ assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+ client->rapid_commit = enable;
+ return 0;
+}
+
int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
assert_return(client, -EINVAL);
@@ -714,9 +722,11 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) {
break;
case DHCP6_STATE_SOLICITATION:
- r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL);
- if (r < 0)
- return r;
+ if (client->rapid_commit) {
+ r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL);
+ if (r < 0)
+ return r;
+ }
r = client_append_common_options_in_managed_mode(client, &opt, &optlen,
&client->ia_na, &client->ia_pd);
@@ -1160,6 +1170,10 @@ static int client_process_advertise_or_rapid_commit_reply(
if (message->type == DHCP6_MESSAGE_REPLY) {
bool rapid_commit;
+ if (!client->rapid_commit)
+ return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
+ "Received unexpected reply message, even we sent a solicit message without the rapid commit option, ignoring.");
+
r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit);
if (r < 0)
return r;
@@ -1467,6 +1481,7 @@ int sd_dhcp6_client_new(sd_dhcp6_client **ret) {
.ifindex = -1,
.request_ia = DHCP6_REQUEST_IA_NA | DHCP6_REQUEST_IA_PD,
.fd = -1,
+ .rapid_commit = true,
};
*ret = TAKE_PTR(client);
diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c
index d6f2c2cca34..7f9f4e12b99 100644
--- a/src/network/networkd-dhcp6.c
+++ b/src/network/networkd-dhcp6.c
@@ -695,6 +695,12 @@ static int dhcp6_configure(Link *link) {
return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set prefix delegation hint: %m");
}
+ r = sd_dhcp6_client_set_rapid_commit(client, link->network->dhcp6_use_rapid_commit);
+ if (r < 0)
+ return log_link_debug_errno(link, r,
+ "DHCPv6 CLIENT: Failed to %s rapid commit: %m",
+ enable_disable(link->network->dhcp6_use_rapid_commit));
+
link->dhcp6_client = TAKE_PTR(client);
return 0;
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 13d521e37a2..a4f038681a4 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -262,6 +262,7 @@ DHCPv6.SendOption, config_parse_dhcp_send_option,
DHCPv6.IAID, config_parse_iaid, AF_INET6, 0
DHCPv6.DUIDType, config_parse_duid_type, 0, offsetof(Network, dhcp6_duid)
DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, dhcp6_duid)
+DHCPv6.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_use_rapid_commit)
IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_gateway)
IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_route_prefix)
IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
@@ -555,12 +556,11 @@ DHCP.RouteMetric, config_parse_dhcp_or_ra_route_metri
DHCP.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET, 0
DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone)
DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port)
-DHCP.RapidCommit, config_parse_warn_compat, DISABLED_LEGACY, 0
+DHCP.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_use_rapid_commit)
DHCP.ForceDHCPv6PDOtherInformation, config_parse_warn_compat, DISABLED_LEGACY, 0
DHCPv4.UseDomainName, config_parse_dhcp_use_domains, AF_INET, 0
DHCPv4.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical)
DHCPv6.RouteMetric, config_parse_dhcp_or_ra_route_metric, AF_INET6, 0
-DHCPv6.RapidCommit, config_parse_warn_compat, DISABLED_LEGACY, 0
DHCPv6.ForceDHCPv6PDOtherInformation, config_parse_warn_compat, DISABLED_LEGACY, 0
DHCPv6PrefixDelegation.SubnetId, config_parse_dhcp_pd_subnet_id, 0, offsetof(Network, dhcp_pd_subnet_id)
DHCPv6PrefixDelegation.Announce, config_parse_bool, 0, offsetof(Network, dhcp_pd_announce)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index e090a78023e..4faa43b3279 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -414,6 +414,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.dhcp6_use_dns = true,
.dhcp6_use_hostname = true,
.dhcp6_use_ntp = true,
+ .dhcp6_use_rapid_commit = true,
.dhcp6_duid.type = _DUID_TYPE_INVALID,
.dhcp6_client_start_mode = _DHCP6_CLIENT_START_MODE_INVALID,
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index c653124a9c6..3df7eee555e 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -164,6 +164,7 @@ struct Network {
bool dhcp6_use_hostname;
bool dhcp6_use_ntp;
bool dhcp6_use_ntp_set;
+ bool dhcp6_use_rapid_commit;
DHCPUseDomains dhcp6_use_domains;
bool dhcp6_use_domains_set;
uint32_t dhcp6_iaid;
diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h
index 7fe60c356c8..2c66c51b78c 100644
--- a/src/systemd/sd-dhcp6-client.h
+++ b/src/systemd/sd-dhcp6-client.h
@@ -262,6 +262,7 @@ int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client,
int request);
int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client,
sd_dhcp6_option *v);
+int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable);
int sd_dhcp6_client_get_lease(
sd_dhcp6_client *client,
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index df8267c62c3..ccde067af93 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -4165,6 +4165,44 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
print(output)
self.assertRegex(output, 'token :: dev veth99')
+ print('## dnsmasq log')
+ output = read_dnsmasq_log_file()
+ print(output)
+ self.assertIn('DHCPSOLICIT(veth-peer)', output)
+ self.assertNotIn('DHCPADVERTISE(veth-peer)', output)
+ self.assertNotIn('DHCPREQUEST(veth-peer)', output)
+ self.assertIn('DHCPREPLY(veth-peer)', output)
+ self.assertIn('sent size: 0 option: 14 rapid-commit', output)
+
+ with open(os.path.join(network_unit_dir, '25-dhcp-client-ipv6-only.network'), mode='a', encoding='utf-8') as f:
+ f.write('\n[DHCPv6]\nRapidCommit=no\n')
+
+ stop_dnsmasq()
+ start_dnsmasq()
+
+ networkctl_reload()
+ self.wait_online(['veth99:routable', 'veth-peer:routable'])
+
+ # checking address
+ output = check_output('ip address show dev veth99 scope global')
+ print(output)
+ self.assertRegex(output, r'inet6 2600::[0-9a-f:]*/128 scope global dynamic noprefixroute')
+ self.assertNotIn('192.168.5', output)
+
+ # checking semi-static route
+ 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')
+
+ print('## dnsmasq log')
+ output = read_dnsmasq_log_file()
+ print(output)
+ self.assertIn('DHCPSOLICIT(veth-peer)', output)
+ self.assertIn('DHCPADVERTISE(veth-peer)', output)
+ self.assertIn('DHCPREQUEST(veth-peer)', output)
+ self.assertIn('DHCPREPLY(veth-peer)', output)
+ self.assertNotIn('rapid-commit', output)
+
def test_dhcp_client_ipv4_only(self):
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network')