Rewrite DHCP options parsing to conform to RFC 3396: options concatenation is

now fully supported.
This commit is contained in:
Nicholas J. Kain 2011-07-27 07:39:45 -04:00
parent d7103c887d
commit 13f5ab56e2
3 changed files with 88 additions and 84 deletions

View File

@ -219,18 +219,18 @@ static size_t send_client_ip(char *out, size_t olen, struct dhcpmsg *packet)
static size_t send_cmd(char *out, size_t olen, struct dhcpmsg *packet, static size_t send_cmd(char *out, size_t olen, struct dhcpmsg *packet,
uint8_t code) uint8_t code)
{ {
char buf[256]; char buf[2048];
uint8_t *optdata, *olddata; uint8_t optdata[MAX_DOPT_SIZE], olddata[MAX_DOPT_SIZE];
ssize_t optlen, oldlen; ssize_t optlen, oldlen;
if (!packet) if (!packet)
return 0; return 0;
memset(buf, '\0', sizeof buf); memset(buf, '\0', sizeof buf);
optdata = get_option_data(packet, code, &optlen); optlen = get_dhcp_opt(packet, code, optdata, sizeof optdata);
if (!optlen) if (!optlen)
return 0; return 0;
olddata = get_option_data(&cfg_packet, code, &oldlen); oldlen = get_dhcp_opt(&cfg_packet, code, olddata, sizeof olddata);
if (oldlen == optlen && !memcmp(optdata, olddata, optlen)) if (oldlen == optlen && !memcmp(optdata, olddata, optlen))
return 0; return 0;
if (ifchd_cmd(buf, sizeof buf, optdata, optlen, code) == -1) if (ifchd_cmd(buf, sizeof buf, optdata, optlen, code) == -1)

View File

@ -34,84 +34,83 @@
#include "log.h" #include "log.h"
#include "ifch_proto.h" #include "ifch_proto.h"
// Worker function for get_option_data(). Optlen will be set to the length static int do_overload_value(uint8_t *buf, ssize_t blen, int overload)
// of the option data.
static uint8_t *do_get_option_data(uint8_t *buf, ssize_t buflen, int code,
char *overload, ssize_t *optlen)
{ {
// option bytes: [code][len]([data1][data2]..[dataLEN]) ssize_t i = 0;
*overload = 0; while (i < blen) {
while (buflen > 0) { if (buf[i] == DCODE_PADDING) {
// Advance over padding. ++i;
if (buf[0] == DCODE_PADDING) {
buflen--;
buf++;
continue; continue;
} }
if (buf[i] == DCODE_END)
// We hit the end. break;
if (buf[0] == DCODE_END) { if (i >= blen - 2)
*optlen = 0; break;
return NULL; if (buf[i] == DCODE_OVERLOAD) {
if (buf[i+1] == 1) {
overload |= buf[i+2];
i += 3;
continue;
}
} }
i += buf[i+1] + 2;
buflen -= buf[1] + 2;
if (buflen < 0) {
log_warning("Bad option data: length would exceed options field size.");
*optlen = 0;
return NULL;
}
if (buf[0] == code) {
*optlen = buf[1];
return buf + 2;
}
if (buf[0] == DCODE_OVERLOAD) {
if (buf[1] == 1)
*overload |= buf[2];
// fall through
}
buf += buf[1] + 2;
} }
// End of options field was unmarked: no option data return overload;
*optlen = 0;
return NULL;
} }
// XXX: Never concatenates options. If this is added, refer to RFC3396. static int overload_value(struct dhcpmsg *packet)
// Get an option with bounds checking (warning, result is not aligned)
// optlen will be equal to the length of the option data.
uint8_t *get_option_data(struct dhcpmsg *packet, int code, ssize_t *optlen)
{ {
uint8_t *option, *buf; int ol = do_overload_value(packet->options, sizeof packet->options, 0);
ssize_t buflen; if (ol & 1 && ol & 2)
char overload, parsed_ff = 0; return ol;
if (ol & 1) {
buf = packet->options; ol |= do_overload_value(packet->file, sizeof packet->file, ol);
buflen = sizeof packet->options; return ol;
option = do_get_option_data(buf, buflen, code, &overload, optlen);
if (option)
return option;
if (overload & 1) {
parsed_ff = 1;
option = do_get_option_data(packet->file, sizeof packet->file,
code, &overload, optlen);
if (option)
return option;
} }
if (overload & 2) { if (ol & 2) {
option = do_get_option_data(packet->sname, sizeof packet->sname, ol |= do_overload_value(packet->sname, sizeof packet->sname, ol);
code, &overload, optlen); return ol;
if (option)
return option;
if (!parsed_ff && overload & 1)
option = do_get_option_data(packet->file, sizeof packet->file,
code, &overload, optlen);
} }
return option; return ol; // ol == 0
}
static ssize_t do_get_dhcp_opt(uint8_t *sbuf, ssize_t slen, uint8_t code,
uint8_t *dbuf, ssize_t dlen, ssize_t didx)
{
ssize_t i = 0;
while (i < slen) {
if (sbuf[i] == DCODE_PADDING) {
++i;
continue;
}
if (sbuf[i] == DCODE_END)
break;
if (i >= slen - 2)
break;
if (sbuf[i] == code) {
if (dlen - didx < sbuf[i+1])
return didx;
memcpy(dbuf+didx, sbuf+i+2, sbuf[i+1]);
didx += sbuf[i+1];
}
i += sbuf[i+1] + 2;
}
return didx;
}
ssize_t get_dhcp_opt(struct dhcpmsg *packet, uint8_t code, uint8_t *dbuf,
ssize_t dlen)
{
int ol = overload_value(packet);
ssize_t didx = do_get_dhcp_opt(packet->options, sizeof packet->options,
code, dbuf, dlen, 0);
if (ol & 1)
didx += do_get_dhcp_opt(packet->file, sizeof packet->file, code,
dbuf, dlen, didx);
if (ol & 2)
didx += do_get_dhcp_opt(packet->sname, sizeof packet->sname, code,
dbuf, dlen, didx);
return didx;
} }
// return the position of the 'end' option // return the position of the 'end' option
@ -297,9 +296,10 @@ uint32_t get_option_router(struct dhcpmsg *packet)
{ {
ssize_t ol; ssize_t ol;
uint32_t ret = 0; uint32_t ret = 0;
uint8_t *od = get_option_data(packet, DCODE_ROUTER, &ol); uint8_t buf[MAX_DOPT_SIZE];
ol = get_dhcp_opt(packet, DCODE_ROUTER, buf, sizeof buf);
if (ol == sizeof ret) if (ol == sizeof ret)
memcpy(&ret, od, sizeof ret); memcpy(&ret, buf, sizeof ret);
return ret; return ret;
} }
@ -307,22 +307,23 @@ uint8_t get_option_msgtype(struct dhcpmsg *packet)
{ {
ssize_t ol; ssize_t ol;
uint8_t ret = 0; uint8_t ret = 0;
uint8_t *t = get_option_data(packet, DCODE_MSGTYPE, &ol); uint8_t buf[MAX_DOPT_SIZE];
if (t) ol = get_dhcp_opt(packet, DCODE_MSGTYPE, buf, sizeof buf);
ret = *t; if (ol == sizeof ret)
ret = buf[0];
return ret; return ret;
} }
uint32_t get_option_serverid(struct dhcpmsg *packet, int *found) uint32_t get_option_serverid(struct dhcpmsg *packet, int *found)
{ {
ssize_t ol; ssize_t ol;
uint8_t *t;
uint32_t ret = 0; uint32_t ret = 0;
uint8_t buf[MAX_DOPT_SIZE];
*found = 0; *found = 0;
t = get_option_data(packet, DCODE_SERVER_ID, &ol); ol = get_dhcp_opt(packet, DCODE_SERVER_ID, buf, sizeof buf);
if (ol == sizeof ret) { if (ol == sizeof ret) {
*found = 1; *found = 1;
memcpy(&ret, t, sizeof ret); memcpy(&ret, buf, sizeof ret);
} }
return ret; return ret;
} }
@ -330,11 +331,11 @@ uint32_t get_option_serverid(struct dhcpmsg *packet, int *found)
uint32_t get_option_leasetime(struct dhcpmsg *packet) uint32_t get_option_leasetime(struct dhcpmsg *packet)
{ {
ssize_t ol; ssize_t ol;
uint8_t *t;
uint32_t ret = 0; uint32_t ret = 0;
t = get_option_data(packet, DCODE_LEASET, &ol); uint8_t buf[MAX_DOPT_SIZE];
ol = get_dhcp_opt(packet, DCODE_LEASET, buf, sizeof buf);
if (ol == sizeof ret) { if (ol == sizeof ret) {
memcpy(&ret, t, sizeof ret); memcpy(&ret, buf, sizeof ret);
ret = ntohl(ret); ret = ntohl(ret);
} }
return ret; return ret;

View File

@ -54,7 +54,10 @@
#define DCODE_CLIENT_ID 0x3d #define DCODE_CLIENT_ID 0x3d
#define DCODE_END 0xff #define DCODE_END 0xff
uint8_t *get_option_data(struct dhcpmsg *packet, int code, ssize_t *optlen); #define MAX_DOPT_SIZE 500
ssize_t get_dhcp_opt(struct dhcpmsg *packet, uint8_t code, uint8_t *dbuf,
ssize_t dlen);
ssize_t get_end_option_idx(struct dhcpmsg *packet); ssize_t get_end_option_idx(struct dhcpmsg *packet);
size_t add_option_request_list(struct dhcpmsg *packet); size_t add_option_request_list(struct dhcpmsg *packet);