udhcp: add option scanner

Added an option scanner to udhcp to enable iteration over packet options.

Signed-off-by: Martin Lewis <martin.lewis.x84@gmail.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Martin Lewis 2020-06-23 15:25:08 -05:00 committed by Denys Vlasenko
parent fc2ce04a38
commit acdc8eed89
2 changed files with 63 additions and 43 deletions

View File

@ -15,7 +15,7 @@ const uint8_t MAC_BCAST_ADDR[6] ALIGN2 = {
};
#if ENABLE_UDHCPC || ENABLE_UDHCPD
/* Supported options are easily added here.
/* Supported options are easily added here, they need to be sorted.
* See RFC2132 for more options.
* OPTION_REQ: these options are requested by udhcpc (unless -o).
*/
@ -222,79 +222,91 @@ unsigned FAST_FUNC udhcp_option_idx(const char *name, const char *option_strings
}
}
/* Get an option with bounds checking (warning, result is not aligned) */
uint8_t* FAST_FUNC udhcp_get_option(struct dhcp_packet *packet, int code)
/* Initialize state to be used between subsequent udhcp_scan_options calls */
void FAST_FUNC init_scan_state(struct dhcp_packet *packet, struct dhcp_scan_state *scan_state)
{
scan_state->overload = 0;
scan_state->rem = sizeof(packet->options);
scan_state->optionptr = packet->options;
}
/* Iterate over packet's options, each call returning the next option.
* scan_state needs to be initialized with init_scan_state beforehand.
* Warning, result is not aligned. */
uint8_t* FAST_FUNC udhcp_scan_options(struct dhcp_packet *packet, struct dhcp_scan_state *scan_state)
{
uint8_t *optionptr;
int len;
int rem;
int overload = 0;
enum {
FILE_FIELD101 = FILE_FIELD * 0x101,
SNAME_FIELD101 = SNAME_FIELD * 0x101,
};
/* option bytes: [code][len][data1][data2]..[dataLEN] */
optionptr = packet->options;
rem = sizeof(packet->options);
while (1) {
if (rem <= 0) {
if (scan_state->rem <= 0) {
complain:
bb_simple_error_msg("bad packet, malformed option field");
return NULL;
}
/* DHCP_PADDING and DHCP_END have no [len] byte */
if (optionptr[OPT_CODE] == DHCP_PADDING) {
rem--;
optionptr++;
if (scan_state->optionptr[OPT_CODE] == DHCP_PADDING) {
scan_state->rem--;
scan_state->optionptr++;
continue;
}
if (optionptr[OPT_CODE] == DHCP_END) {
if ((overload & FILE_FIELD101) == FILE_FIELD) {
if (scan_state->optionptr[OPT_CODE] == DHCP_END) {
if ((scan_state->overload & FILE_FIELD101) == FILE_FIELD) {
/* can use packet->file, and didn't look at it yet */
overload |= FILE_FIELD101; /* "we looked at it" */
optionptr = packet->file;
rem = sizeof(packet->file);
scan_state->overload |= FILE_FIELD101; /* "we looked at it" */
scan_state->optionptr = packet->file;
scan_state->rem = sizeof(packet->file);
continue;
}
if ((overload & SNAME_FIELD101) == SNAME_FIELD) {
if ((scan_state->overload & SNAME_FIELD101) == SNAME_FIELD) {
/* can use packet->sname, and didn't look at it yet */
overload |= SNAME_FIELD101; /* "we looked at it" */
optionptr = packet->sname;
rem = sizeof(packet->sname);
scan_state->overload |= SNAME_FIELD101; /* "we looked at it" */
scan_state->optionptr = packet->sname;
scan_state->rem = sizeof(packet->sname);
continue;
}
break;
}
if (rem <= OPT_LEN)
if (scan_state->rem <= OPT_LEN)
goto complain; /* complain and return NULL */
len = 2 + optionptr[OPT_LEN];
rem -= len;
if (rem < 0)
len = 2 + scan_state->optionptr[OPT_LEN];
scan_state->rem -= len;
/* So far no valid option with length 0 known. */
if (scan_state->rem < 0 || scan_state->optionptr[OPT_LEN] == 0)
goto complain; /* complain and return NULL */
if (optionptr[OPT_CODE] == code) {
if (optionptr[OPT_LEN] == 0) {
/* So far no valid option with length 0 known.
* Having this check means that searching
* for DHCP_MESSAGE_TYPE need not worry
* that returned pointer might be unsafe
* to dereference.
*/
goto complain; /* complain and return NULL */
}
log_option("option found", optionptr);
return optionptr + OPT_DATA;
}
if (optionptr[OPT_CODE] == DHCP_OPTION_OVERLOAD) {
if (scan_state->optionptr[OPT_CODE] == DHCP_OPTION_OVERLOAD) {
if (len >= 3)
overload |= optionptr[OPT_DATA];
/* fall through */
scan_state->overload |= scan_state->optionptr[OPT_DATA];
} else {
uint8_t *return_ptr = scan_state->optionptr;
scan_state->optionptr += len;
return return_ptr;
}
scan_state->optionptr += len;
}
return NULL;
}
/* Get an option with bounds checking (warning, result is not aligned) */
uint8_t* FAST_FUNC udhcp_get_option(struct dhcp_packet *packet, int code)
{
uint8_t *optptr;
struct dhcp_scan_state scan_state;
init_scan_state(packet, &scan_state);
while ((optptr = udhcp_scan_options(packet, &scan_state)) != NULL) {
if (optptr[OPT_CODE] == code) {
log_option("option found", optptr);
return optptr + OPT_DATA;
}
optionptr += len;
}
/* log3 because udhcpc uses it a lot - very noisy */

View File

@ -107,6 +107,12 @@ enum {
OPTION_LIST = 0x20,
};
struct dhcp_scan_state {
int overload;
int rem;
uint8_t *optionptr;
};
/* DHCP option codes (partial list). See RFC 2132 and
* http://www.iana.org/assignments/bootp-dhcp-parameters/
* Commented out options are handled by common option machinery,
@ -206,6 +212,8 @@ extern const uint8_t dhcp_option_lengths[] ALIGN1;
unsigned FAST_FUNC udhcp_option_idx(const char *name, const char *option_strings);
void init_scan_state(struct dhcp_packet *packet, struct dhcp_scan_state *scan_state) FAST_FUNC;
uint8_t *udhcp_scan_options(struct dhcp_packet *packet, struct dhcp_scan_state *scan_state) FAST_FUNC;
uint8_t *udhcp_get_option(struct dhcp_packet *packet, int code) FAST_FUNC;
/* Same as above + ensures that option length is 4 bytes
* (returns NULL if size is different)