From: Luca Boccassi <bl...@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 <ok...@kernel.org> Signed-off-by: Luca Boccassi <bl...@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