Discussion:
[Dnsmasq-discuss] [PATCH] DHCPv4 Rapid Commit support
Ashram Method
2018-03-22 20:29:04 UTC
Permalink
RFC 4039 defines DHCPv4 Rapid Commit option, which allows for obtaining an
IP address and configuration information using a 2-message exchange.
This is particularly useful for mobile clients to quickly establish IP
connectivity when changing network, e.g. from cellular to home WiFi.

If the DHCP client indicates Rapid Commit support in DHCPDISCOVER, the
server can respond directly with DHCPACK. Rapid Commit should only be
enabled on the server if it is the only server for the subnet, or if
multiple servers are present they each commit a binding for all clients.

The patch below was tested against dhcpcd client with Rapid Commit enabled.

Thanks
Ashram

dnsmasq.conf.example | 8 ++++++++
src/dhcp-common.c | 1 +
src/dhcp-protocol.h | 1 +
src/dnsmasq.h | 3 ++-
src/option.c | 3 +++
src/rfc2131.c | 29 +++++++++++++++++++++++++++--
6 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/dnsmasq.conf.example b/dnsmasq.conf.example
index 574b053..7e89ef8 100644
--- a/dnsmasq.conf.example
+++ b/dnsmasq.conf.example
@@ -547,6 +547,14 @@
# http://www.isc.org/files/auth.html
#dhcp-authoritative

+# Set the DHCP server to enable DHCPv4 Rapid Commit Option per RFC 4039.
+# In this mode it will respond to a DHCPDISCOVER message including Rapid
Commit
+# option with a DHCPACK including Rapid Commit option and fully committed
addre$
+# and configuration information. This must only be enabled if either the
server$
+# the only server for the subnet, or multiple servers are present and they
each
+# commit a binding for all clients.
+#rapid-commit
+
# Run an executable when a DHCP lease is created or destroyed.
# The arguments sent to the script are "add" or "del",
# then the MAC address, the IP address and finally the hostname
diff --git a/src/dhcp-common.c b/src/dhcp-common.c
index d9719d1..8ff0f0d 100644
--- a/src/dhcp-common.c
+++ b/src/dhcp-common.c
@@ -556,6 +556,7 @@ static const struct opttab_t {
{ "nntp-server", 71, OT_ADDR_LIST },
{ "irc-server", 74, OT_ADDR_LIST },
{ "user-class", 77, 0 },
+ { "rapid-commit", 80, 0 },
{ "FQDN", 81, OT_INTERNAL },
{ "agent-id", 82, OT_INTERNAL },
{ "client-arch", 93, 2 | OT_DEC },
diff --git a/src/dhcp-protocol.h b/src/dhcp-protocol.h
index a4a3535..389c85e 100644
--- a/src/dhcp-protocol.h
+++ b/src/dhcp-protocol.h
@@ -54,6 +54,7 @@
#define OPTION_SNAME 66
#define OPTION_FILENAME 67
#define OPTION_USER_CLASS 77
+#define OPTION_RAPID_COMMIT 80
#define OPTION_CLIENT_FQDN 81
#define OPTION_AGENT_ID 82
#define OPTION_ARCH 93
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 6773b69..30c4519 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -250,7 +250,8 @@ struct event_desc {
#define OPT_MAC_B64 54
#define OPT_MAC_HEX 55
#define OPT_TFTP_APREF_MAC 56
-#define OPT_LAST 57
+#define OPT_RAPID_COMMIT 57
+#define OPT_LAST 58

/* extra flags for my_syslog, we use a couple of facilities since they are
known
not to occupy the same bits as priorities, no matter how syslog.h is
set up. */
diff --git a/src/option.c b/src/option.c
index d358d99..3691944 100644
--- a/src/option.c
+++ b/src/option.c
@@ -160,6 +160,7 @@ struct myoption {
#define LOPT_DHCPTTL 348
#define LOPT_TFTP_MTU 349
#define LOPT_REPLY_DELAY 350
+#define LOPT_RAPID_COMMIT 351

#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -325,6 +326,7 @@ static const struct myoption opts[] =
{ "script-arp", 0, 0, LOPT_SCRIPT_ARP },
{ "dhcp-ttl", 1, 0 , LOPT_DHCPTTL },
{ "dhcp-reply-delay", 1, 0, LOPT_REPLY_DELAY },
+ { "rapid-commit", 0, 0, LOPT_RAPID_COMMIT },
{ NULL, 0, 0, 0 }
};

@@ -497,6 +499,7 @@ static struct {
{ LOPT_IGNORE_ADDR, ARG_DUP, "<ipaddr>", gettext_noop("Ignore DNS
responses containing ipaddr."), NULL },
{ LOPT_DHCPTTL, ARG_ONE, "<ttl>", gettext_noop("Set TTL in DNS responses
with DHCP-derived addresses."), NULL },
{ LOPT_REPLY_DELAY, ARG_ONE, "<integer>", gettext_noop("Delay DHCP
replies for at least number of seconds."), NULL },
+ { LOPT_RAPID_COMMIT, OPT_RAPID_COMMIT, NULL, gettext_noop("Enables
DHCPv4 Rapid Commit option."), NULL },
{ 0, 0, NULL, NULL, NULL }
};

diff --git a/src/rfc2131.c b/src/rfc2131.c
index c08a8ab..64b56e7 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -1073,6 +1073,9 @@ size_t dhcp_reply(struct dhcp_context *context, char
*iface_name, int int_index,

log_tags(tagif_netid, ntohl(mess->xid));
apply_delay(mess->xid, recvtime, tagif_netid);
+
+ if (!option_bool(OPT_RAPID_COMMIT) || !(opt = option_find(mess, sz,
OPTION_RAPID_COMMIT, 0)))
+ {
log_packet("DHCPOFFER" , &mess->yiaddr, emac, emac_len, iface_name,
NULL, NULL, mess->xid);

time = calc_time(context, config, option_find(mess, sz,
OPTION_LEASE_TIME, 4));
@@ -1085,8 +1088,13 @@ size_t dhcp_reply(struct dhcp_context *context, char
*iface_name, int int_index,
netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid,
vendor_class_len, now, time, fuzz);

return dhcp_packet_size(mess, agent_id, real_end);
-
+ }
+
+ /* rapid commit case falls through to send DHCPACK */
+ __attribute__((fallthrough));
case DHCPREQUEST:
+ if (!option_bool(OPT_RAPID_COMMIT) || !(opt = option_find(mess, sz,
OPTION_RAPID_COMMIT, 0)))
+ {
if (ignore || have_config(config, CONFIG_DISABLE))
return 0;
if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))
@@ -1185,6 +1193,7 @@ size_t dhcp_reply(struct dhcp_context *context, char
*iface_name, int int_index,
}

log_packet("DHCPREQUEST", &mess->yiaddr, emac, emac_len, iface_name,
NULL, NULL, mess->xid);
+ }

if (!message)
{
@@ -1256,6 +1265,8 @@ size_t dhcp_reply(struct dhcp_context *context, char
*iface_name, int int_index,

if (message)
{
+ if (!option_bool(OPT_RAPID_COMMIT) || !(opt = option_find(mess, sz,
OPTION_RAPID_COMMIT, 0)))
+ {
log_packet("DHCPNAK", &mess->yiaddr, emac, emac_len, iface_name, NULL,
message, mess->xid);

mess->yiaddr.s_addr = 0;
@@ -1271,6 +1282,12 @@ size_t dhcp_reply(struct dhcp_context *context, char
*iface_name, int int_index,
mess->flags |= htons(0x8000); /* broadcast */
mess->ciaddr.s_addr = 0;
}
+ }
+ else
+ {
+ /* rapid commit case: lease allocate failed but don't send DHCPNAK */
+ return 0;
+ }
}
else
{
@@ -1415,7 +1432,15 @@ size_t dhcp_reply(struct dhcp_context *context, char
*iface_name, int int_index,

log_packet("DHCPACK", &mess->yiaddr, emac, emac_len, iface_name,
hostname, NULL, mess->xid);

- clear_packet(mess, end);
+ if (option_bool(OPT_RAPID_COMMIT) && (opt = option_find(mess, sz,
OPTION_RAPID_COMMIT, 0)))
+ {
+ clear_packet(mess, end);
+ option_put(mess, end, OPTION_RAPID_COMMIT, 0, 0);
+ }
+ else
+ {
+ clear_packet(mess, end);
+ }
option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ,
ntohl(server_id(context, override, fallback).s_addr));
option_put(mess, end, OPTION_LEASE_TIME, 4, time);
Simon Kelley
2018-03-23 23:42:59 UTC
Permalink
Thanks for this.

I've done the following:

1) changed option to dhcp-rapid-commit, for consistency.
2) heavily re-formatted the code, to suit my tastes.
3) Added man page entry.
4) Added logging of the reason a rapid-commit DHCPDISCOVER is ignored.

and committed the changes.

Any new bugs are mine, but please check that I didn't break anything.

http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=734d53176f571d9c0023f0de8791d223b7766f6b

Cheers,

Simon.
Post by Ashram Method
RFC 4039 defines DHCPv4 Rapid Commit option, which allows for obtaining
an IP address and configuration information using a 2-message exchange.
This is particularly useful for mobile clients to quickly establish IP
connectivity when changing network, e.g. from cellular to home WiFi.
If the DHCP client indicates Rapid Commit support in DHCPDISCOVER, the
server can respond directly with DHCPACK. Rapid Commit should only be
enabled on the server if it is the only server for the subnet, or if
multiple servers are present they each commit a binding for all clients.
The patch below was tested against dhcpcd client with Rapid Commit enabled.
Loading...