[prev in list] [next in list] [prev in thread] [next in thread] 

List:       busybox
Subject:    [PATCH v2] udhcpc: add support for sending DHCPINFORM packets
From:       bluca () debian ! org
Date:       2022-08-30 21:41:51
Message-ID: 20220830214151.1923924-1-bluca () debian ! org
[Download RAW message or body]

From: Luca Boccassi <bluca@debian.org>

It is useful for applications to be able to query DHCP options
without renewing IP address. Instead of a full DHCP handshake,
using -I will cause a single DHCPINFORM packet to be sent, and
the server response (including DHCP options received) to be
printed and terminate. No configuration will be changed.

This is useful for clients that want to query additional information
from a server, that might not be normally processed, like custom
server options. Also useful for checking specific options via -O.

As per RFC 2131, allow targeting the already-known DHCP server via
unicast instead of broadcast, via new -e option.

Tested by running isc-dhcp-server with the following configuration:

option domain-name "example.org";
option domain-name-servers 1.1.1.1, 8.8.8.8;
subnet 192.168.11.0 netmask 255.255.255.0 {
  range 192.168.11.1 192.168.11.100;
  authoritative;
  option default-url "default.url";
}

$ busybox udhcpc -I -i host0 -O 114 -r 192.168.11.1
udhcpc: started, v1.36.0.git
udhcpc: broadcasting inform for 192.168.11.1, server 0.0.0.0
udhcpc: lease of 0.0.0.0 obtained from 0.0.0.0, lease time 3600 (default)
udhcpc: option: opt53=0x05
udhcpc: option: serverid=192.168.11.101
udhcpc: option: subnet=255.255.255.0
udhcpc: option: dns=1.1.1.1 8.8.8.8
udhcpc: option: domain=example.org
udhcpc: option: opt114=0x64656661756c742e75726c

$ busybox udhcpc -e 192.168.11.101 -I -i host0 -O 114 -r 192.168.11.1
udhcpc: started, v1.36.0.git
udhcpc: unicasting inform for 192.168.11.1, server 192.168.11.101
udhcpc: lease of 0.0.0.0 obtained from 192.168.11.101, lease time 3600 (default)
udhcpc: option: opt53=0x05
udhcpc: option: serverid=192.168.11.101
udhcpc: option: subnet=255.255.255.0
udhcpc: option: dns=1.1.1.1 8.8.8.8
udhcpc: option: domain=example.org
udhcpc: option: opt114=0x64656661756c742e75726c

Co-authored-by: Sinan Kaya <okaya@kernel.org>
Signed-off-by: Luca Boccassi <bluca@debian.org>
---
v2: updated commit message and comments
    applied all review comments
    print received DHCP options and exit

 networking/udhcp/dhcpc.c | 116 ++++++++++++++++++++++++++++++++++-----
 1 file changed, 103 insertions(+), 13 deletions(-)

diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c
index c757fb37c..5762a69ca 100644
--- a/networking/udhcp/dhcpc.c
+++ b/networking/udhcp/dhcpc.c
@@ -75,6 +75,8 @@ static const char udhcpc_longopts[] ALIGN1 =
 	"background\0"     No_argument       "b"
 	)
 	"broadcast\0"      No_argument       "B"
+	"inform\0"         No_argument       "I"
+	"server\0"         Required_argument "e"
 	IF_FEATURE_UDHCPC_ARPING("arping\0"	Optional_argument "a")
 	IF_FEATURE_UDHCP_PORT("client-port\0"	Required_argument "P")
 	;
@@ -100,8 +102,10 @@ enum {
 	OPT_x = 1 << 16,
 	OPT_f = 1 << 17,
 	OPT_B = 1 << 18,
+	OPT_I = 1 << 19,
+	OPT_e = 1 << 20,
 /* The rest has variable bit positions, need to be clever */
-	OPTBIT_B = 18,
+	OPTBIT_B = 20,
 	USE_FOR_MMU(             OPTBIT_b,)
 	IF_FEATURE_UDHCPC_ARPING(OPTBIT_a,)
 	IF_FEATURE_UDHCP_PORT(   OPTBIT_P,)
@@ -740,16 +744,24 @@ static NOINLINE int send_discover(uint32_t requested)
 	return raw_bcast_from_client_data_ifindex(&packet, INADDR_ANY);
 }
 
-/* Broadcast a DHCP request message */
+/* Broadcast a DHCP request message or broadcast/unicast a DHCP inform message */
 /* RFC 2131 3.1 paragraph 3:
  * "The client _broadcasts_ a DHCPREQUEST message..."
+ *
+ * RFC 2131 4.4.3 Initialization with an externally assigned network address
+ *
+ * The client then unicasts the DHCPINFORM to the DHCP server if it
+ * knows the server's address, otherwise it broadcasts the message to
+ * the limited (all 1s) broadcast address.
  */
 /* NOINLINE: limit stack usage in caller */
-static NOINLINE int send_select(uint32_t server, uint32_t requested)
+static NOINLINE int send_select(uint32_t server, uint32_t requested, int inform)
 {
 	struct dhcp_packet packet;
 	struct in_addr temp_addr;
 	char server_str[sizeof("255.255.255.255")];
+	const char *direction = (inform && server) ? "unicasting" : "broadcasting";
+	const char *type = inform ? "inform" : "select";
 
 /*
  * RFC 2131 4.3.2 DHCPREQUEST message
@@ -766,7 +778,19 @@ static NOINLINE int send_select(uint32_t server, uint32_t \
requested)  /* Fill in: op, htype, hlen, cookie, chaddr fields,
 	 * xid field, message type option:
 	 */
-	init_packet(&packet, DHCPREQUEST);
+	init_packet(&packet, inform ? DHCPINFORM: DHCPREQUEST);
+
+/*
+ * RFC 2131 4.4.3 Initialization with an externally assigned network address
+ *
+ * The client sends a DHCPINFORM message. The client may request
+ * specific configuration parameters by including the 'parameter request
+ * list' option. The client generates and records a random transaction
+ * identifier and inserts that identifier into the 'xid' field. The
+ * client places its own network address in the 'ciaddr' field.
+ */
+	if (inform)
+		packet.ciaddr = requested;
 
 	udhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, requested);
 
@@ -780,10 +804,25 @@ static NOINLINE int send_select(uint32_t server, uint32_t \
requested)  temp_addr.s_addr = server;
 	strcpy(server_str, inet_ntoa(temp_addr));
 	temp_addr.s_addr = requested;
-	bb_info_msg("broadcasting select for %s, server %s",
+	bb_info_msg("%s %s for %s, server %s",
+			direction,
+			type,
 			inet_ntoa(temp_addr),
 			server_str
 	);
+
+/*
+ * RFC 2131 4.4.3 Initialization with an externally assigned network address
+ *
+ * The client then unicasts the DHCPINFORM to the DHCP server if it
+ * knows the server's address, otherwise it broadcasts the message to
+ * the limited (all 1s) broadcast address.  DHCPINFORM messages MUST be
+ * directed to the 'DHCP server' UDP port.
+ */
+
+	if (inform && server)
+		return bcast_or_ucast(&packet, requested, server);
+
 	return raw_bcast_from_client_data_ifindex(&packet, INADDR_ANY);
 }
 
@@ -1161,9 +1200,9 @@ static void client_background(void)
 //usage:# define IF_UDHCP_VERBOSE(...)
 //usage:#endif
 //usage:#define udhcpc_trivial_usage
-//usage:       "[-fbq"IF_UDHCP_VERBOSE("v")"RB]"IF_FEATURE_UDHCPC_ARPING(" \
[-a[MSEC]]")" [-t N] [-T SEC] [-A SEC|-n]\n" +//usage:       \
"[-fbq"IF_UDHCP_VERBOSE("v")"RBI]"IF_FEATURE_UDHCPC_ARPING(" [-a[MSEC]]")" [-t N] [-T \
SEC] [-A SEC|-n]\n"  //usage:       "	[-i IFACE]"IF_FEATURE_UDHCP_PORT(" [-P PORT]")" \
                [-s PROG] [-p PIDFILE]\n"
-//usage:       "	[-oC] [-r IP] [-V VENDOR] [-F NAME] [-x OPT:VAL]... [-O OPT]..."
+//usage:       "	[-oC] [-r IP] [-e SERVER_IP] [-V VENDOR] [-F NAME] [-x OPT:VAL]... \
[-O OPT]..."  //usage:#define udhcpc_full_usage "\n"
 //usage:     "\n	-i IFACE	Interface to use (default \
"CONFIG_UDHCPC_DEFAULT_INTERFACE")"  //usage:	IF_FEATURE_UDHCP_PORT(
@@ -1172,6 +1211,7 @@ static void client_background(void)
 //usage:     "\n	-s PROG		Run PROG at DHCP events (default \
"CONFIG_UDHCPC_DEFAULT_SCRIPT")"  //usage:     "\n	-p FILE		Create pidfile"
 //usage:     "\n	-B		Request broadcast replies"
+//usage:     "\n	-I		Send DHCPINFORM, print received options and exit, instead of \
full DHCP handshake"  //usage:     "\n	-t N		Send up to N discover packets (default \
3)"  //usage:     "\n	-T SEC		Pause between packets (default 3)"
 //usage:     "\n	-A SEC		Wait if lease is not obtained (default 20)"
@@ -1187,6 +1227,7 @@ static void client_background(void)
 //usage:     "\n	-a[MSEC]	Validate offered address with ARP ping"
 //usage:	)
 //usage:     "\n	-r IP		Request this IP address"
+//usage:     "\n	-e IP		Send to this server IP address when sending DHCPINFORM \
packets"  //usage:     "\n	-o		Don't request any options (unless -O is given)"
 //usage:     "\n	-O OPT		Request option OPT from server (cumulative)"
 //usage:     "\n	-x OPT:VAL	Include option OPT in sent packets (cumulative)"
@@ -1209,7 +1250,7 @@ int udhcpc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int udhcpc_main(int argc UNUSED_PARAM, char **argv)
 {
 	uint8_t *message;
-	const char *str_V, *str_F, *str_r;
+	const char *str_V, *str_F, *str_r, *str_e;
 	IF_FEATURE_UDHCPC_ARPING(const char *str_a = "2000";)
 	IF_FEATURE_UDHCP_PORT(char *str_P;)
 	uint8_t *clientid_mac_ptr;
@@ -1218,7 +1259,7 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
 	int tryagain_timeout = 20;
 	int discover_timeout = 3;
 	int discover_retries = 3;
-	uint32_t server_id = server_id; /* for compiler */
+	uint32_t server_id = 0;
 	uint32_t requested_ip = 0;
 	int packet_num;
 	int timeout; /* must be signed */
@@ -1244,7 +1285,7 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
 	/* Parse command line */
 	opt = getopt32long(argv, "^"
 		/* O,x: list; -T,-t,-A take numeric param */
-		"CV:F:i:np:qRr:s:T:+t:+SA:+O:*ox:*fB"
+		"CV:F:i:np:qRr:s:T:+t:+SA:+O:*ox:*fBIe:"
 		USE_FOR_MMU("b")
 		IF_FEATURE_UDHCPC_ARPING("a::")
 		IF_FEATURE_UDHCP_PORT("P:")
@@ -1258,6 +1299,7 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
 		, &discover_timeout, &discover_retries, &tryagain_timeout /* T,t,A */
 		, &list_O
 		, &list_x
+		, &str_e /* e */
 		IF_FEATURE_UDHCPC_ARPING(, &str_a)
 		IF_FEATURE_UDHCP_PORT(, &str_P)
 		IF_UDHCP_VERBOSE(, &dhcp_verbose)
@@ -1283,6 +1325,10 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
 		/*p[OPT_DATA + 2] = 0; */
 		memcpy(p + OPT_DATA + 3, str_F, len); /* do not store NUL byte */
 	}
+	/* Allow DHCPINFORM to target a particular server as per RFC 2131 4.4.3 */
+	if (opt & OPT_e && opt & OPT_I)
+		if (!inet_aton(str_e, (void*)&server_id))
+			bb_show_usage();
 	if (opt & OPT_r)
 		if (!inet_aton(str_r, (void*)&requested_ip))
 			bb_show_usage();
@@ -1368,7 +1414,17 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
 	/* We want random_xid to be random... */
 	srand(monotonic_us());
 
-	client_data.state = INIT_SELECTING;
+	if (opt & OPT_I) {
+		/* As per RFC 2131 4.4.3, a DHCPINFORM packet must set 'ciaddr' */
+		if (!(opt & OPT_r))
+			bb_error_msg_and_die("-I requires -r");
+		client_data.state = REQUESTING;
+		change_listen_mode(LISTEN_RAW);
+		client_data.xid = random_xid();
+	} else {
+		client_data.state = INIT_SELECTING;
+	}
+
 	d4_run_script_deconfig();
 	packet_num = 0;
 	timeout = 0;
@@ -1481,8 +1537,8 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
 				continue;
 			case REQUESTING:
 				if (packet_num < 3) {
-					/* send broadcast select packet */
-					send_select(server_id, requested_ip);
+					/* send broadcast/unicast select packet */
+					send_select(server_id, requested_ip, opt & OPT_I);
 					timeout = discover_timeout;
 					packet_num++;
 					continue;
@@ -1736,6 +1792,40 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
 					inet_ntoa(temp_addr), server_str, (unsigned)lease_remaining,
 					temp ? "" : " (default)"
 				);
+
+				/* If INFORM was selected, print the returned DHCP options and exit */
+				if (opt & OPT_I) {
+					uint8_t *optptr;
+					struct dhcp_scan_state scan_state;
+					char *new_opt;
+
+					init_scan_state(&packet, &scan_state);
+
+					while ((optptr = udhcp_scan_options(&packet, &scan_state)) != NULL) {
+						const struct dhcp_optflag *dh;
+						const char *opt_name;
+						struct dhcp_optitem *opt_item;
+						uint8_t code = optptr[OPT_CODE];
+						uint8_t len = optptr[OPT_LEN];
+						uint8_t *data = optptr + OPT_DATA;
+
+						opt_item = concat_option(data, len, code);
+						opt_name = get_optname(code, &dh);
+						if (opt_name)
+							new_opt = xmalloc_optname_optval(opt_item, dh, opt_name);
+						else {
+							unsigned ofs;
+							new_opt = xmalloc(sizeof("optNNN=0x") + 1 + opt_item->len*2);
+							ofs = sprintf(new_opt, "opt%u=0x", opt_item->code);
+							bin2hex(new_opt + ofs, (char *)opt_item->data, opt_item->len)[0] = '\0';
+						}
+						bb_info_msg("option: %s", new_opt);
+						free(new_opt);
+					}
+
+					return 0;
+				}
+
 				/* paranoia: must not be too small and not prone to overflows */
 				/* NB: 60s leases _are_ used in real world
 				 * (temporary IPs while ISP modem initializes)
-- 
2.34.1

_______________________________________________
busybox mailing list
busybox@busybox.net
http://lists.busybox.net/mailman/listinfo/busybox


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic