udhcpc6: improvements

Several small improvements to udhcpc6.

- Remove usage text for the nonexistent -B option.
- Fix a segfault when renewing an IA_PD lease without IA_NA (which means
the client hasn't been assigned an ip, so we cannot locally bind to it).
- Fix NAK management: check the option length, and print the status code
and status message
- Add a -m option to always send renew requests as multicast.

 These last two changes are useful to deal with hopelessly broken DHCPv6
servers such as the one from the Orange Livebox (one of the main French
ISPs) which I'm currently having the displeasure to have to talk to,
hence the patch.

function                                             old     new   delta
static.send_d6_renew                                   -     126    +126
.rodata                                           105598  105649     +51
udhcpc6_main                                        2607    2650     +43
packed_usage                                       34933   34953     +20
d6_send_kernel_packet_from_client_data_ifindex       266     282     +16
send_d6_renew                                        174       -    -174
------------------------------------------------------------------------------
(add/remove: 1/1 grow/shrink: 4/0 up/down: 256/-174)           Total: 82 bytes

Signed-off-by: Laurent Bercot <ska-dietlibc@skarnet.org>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Laurent Bercot 2025-02-09 12:50:41 +01:00 committed by Denys Vlasenko
parent 14f57f5357
commit ef21820dc9
2 changed files with 72 additions and 32 deletions

View file

@ -148,10 +148,11 @@ enum {
OPT_o = 1 << 12,
OPT_x = 1 << 13,
OPT_f = 1 << 14,
OPT_l = 1 << 15,
OPT_d = 1 << 16,
OPT_m = 1 << 15,
OPT_l = 1 << 16,
OPT_d = 1 << 17,
/* The rest has variable bit positions, need to be clever */
OPTBIT_d = 16,
OPTBIT_d = 17,
USE_FOR_MMU( OPTBIT_b,)
///IF_FEATURE_UDHCPC_ARPING(OPTBIT_a,)
IF_FEATURE_UDHCP_PORT( OPTBIT_P,)
@ -268,6 +269,23 @@ static void option_to_env(const uint8_t *option, const uint8_t *option_end)
//case D6_OPT_SERVERID:
case D6_OPT_IA_NA:
case D6_OPT_IA_PD:
/* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | OPTION_IA_PD | option-length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | IAID (4 octets) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | T1 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | T2 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* . .
* . IA_PD-options .
* . .
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
/* recurse to handle "IA_PD-options" field */
option_to_env(option + 16, option + 4 + option[3]);
break;
//case D6_OPT_IA_TA:
@ -1131,12 +1149,11 @@ static void client_background(void)
//usage:#endif
//usage:#define udhcpc6_trivial_usage
//usage: "[-fbq"IF_UDHCP_VERBOSE("v")"R] [-t N] [-T SEC] [-A SEC|-n] [-i IFACE] [-s PROG]\n"
//usage: " [-p PIDFILE]"IF_FEATURE_UDHCP_PORT(" [-P PORT]")" [-ldo] [-r IPv6] [-x OPT:VAL]... [-O OPT]..."
//usage: " [-p PIDFILE]"IF_FEATURE_UDHCP_PORT(" [-P PORT]")" [-mldo] [-r IPv6] [-x OPT:VAL]... [-O OPT]..."
//usage:#define udhcpc6_full_usage "\n"
//usage: "\n -i IFACE Interface to use (default "CONFIG_UDHCPC_DEFAULT_INTERFACE")"
//usage: "\n -p FILE Create pidfile"
//usage: "\n -s PROG Run PROG at DHCP events (default "CONFIG_UDHCPC6_DEFAULT_SCRIPT")"
//usage: "\n -B Request broadcast replies"
//usage: "\n -t N Send up to N discover packets"
//usage: "\n -T SEC Pause between packets (default 3)"
//usage: "\n -A SEC Wait if lease is not obtained (default 20)"
@ -1154,6 +1171,7 @@ static void client_background(void)
////usage: IF_FEATURE_UDHCPC_ARPING(
////usage: "\n -a Use arping to validate offered address"
////usage: )
//usage: "\n -m Send multicast renew requests rather than unicast ones"
//usage: "\n -l Send 'information request' instead of 'solicit'"
//usage: "\n (used for servers which do not assign IPv6 addresses)"
//usage: "\n -r IPv6 Request this address ('no' to not request any IP)"
@ -1211,7 +1229,7 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
/* Parse command line */
opt = getopt32long(argv, "^"
/* O,x: list; -T,-t,-A take numeric param */
"i:np:qRr:s:T:+t:+SA:+O:*ox:*fld"
"i:np:qRr:s:T:+t:+SA:+O:*ox:*fmld"
USE_FOR_MMU("b")
///IF_FEATURE_UDHCPC_ARPING("a")
IF_FEATURE_UDHCP_PORT("P:")
@ -1464,7 +1482,7 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
if (opt & OPT_l)
send_d6_info_request();
else
send_d6_renew(&srv6_buf, requested_ipv6);
send_d6_renew(OPT_m ? NULL : &srv6_buf, requested_ipv6);
timeout = discover_timeout;
packet_num++;
continue;
@ -1617,8 +1635,7 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
address_timeout = 0;
prefix_timeout = 0;
option = d6_find_option(packet.d6_options, packet_end, D6_OPT_STATUS_CODE);
if (option && (option->data[0] | option->data[1]) != 0) {
///FIXME:
if (option) {
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | OPTION_STATUS_CODE | option-len |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@ -1626,21 +1643,28 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
// . status-message .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// so why do we think it's NAK if data[0] is zero but data[1] is not? That's wrong...
// we should also check that option->len is ok (i.e. not 0), right?
/* return to init state */
bb_info_msg("received DHCP NAK (%u)", option->data[4]);
d6_run_script(packet.d6_options,
packet_end, "nak");
if (client_data.state != REQUESTING)
d6_run_script_no_option("deconfig");
sleep(3); /* avoid excessive network traffic */
client_data.state = INIT_SELECTING;
client_data.first_secs = 0; /* make secs field count from 0 */
requested_ipv6 = NULL;
timeout = 0;
packet_num = 0;
continue;
unsigned len, status;
len = ((unsigned)option->len_hi << 8) + option->len;
if (len < 2) {
bb_simple_error_msg("invalid OPTION_STATUS_CODE, ignoring packet");
continue;
}
status = ((unsigned)option->data[0] << 8) + option->data[1];
if (status != 0) {
//TODO: handle status == 5 (UseMulticast)?
/* return to init state */
bb_info_msg("received DHCP NAK: %u '%.*s'", status, len - 2, option->data + 2);
d6_run_script(packet.d6_options, packet_end, "nak");
if (client_data.state != REQUESTING)
d6_run_script_no_option("deconfig");
sleep(3); /* avoid excessive network traffic */
client_data.state = INIT_SELECTING;
client_data.first_secs = 0; /* make secs field count from 0 */
requested_ipv6 = NULL;
timeout = 0;
packet_num = 0;
continue;
}
}
option = d6_copy_option(packet.d6_options, packet_end, D6_OPT_SERVERID);
if (!option) {
@ -1661,7 +1685,6 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
packet_num = 0;
continue;
}
/* It's a D6_MSG_REPLY */
/*
* RFC 3315 18.1.8. Receipt of Reply Messages
*
@ -1790,6 +1813,21 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
free(client6_data.ia_pd);
client6_data.ia_pd = d6_copy_option(packet.d6_options, packet_end, D6_OPT_IA_PD);
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | OPTION_IA_PD | option-length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | IAID (4 octets) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | T1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | T2 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// . .
// . IA_PD-options .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
if (!client6_data.ia_pd) {
bb_info_msg("no %s option%s", "IA_PD", ", ignoring packet");
continue;

View file

@ -153,13 +153,15 @@ int FAST_FUNC d6_send_kernel_packet_from_client_data_ifindex(
}
setsockopt_reuseaddr(fd);
memset(&sa, 0, sizeof(sa));
sa.sin6_family = AF_INET6;
sa.sin6_port = htons(source_port);
sa.sin6_addr = *src_ipv6; /* struct copy */
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
msg = "bind(%s)";
goto ret_close;
if (src_ipv6) {
memset(&sa, 0, sizeof(sa));
sa.sin6_family = AF_INET6;
sa.sin6_port = htons(source_port);
sa.sin6_addr = *src_ipv6; /* struct copy */
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
msg = "bind(%s)";
goto ret_close;
}
}
memset(&sa, 0, sizeof(sa));