summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2024-11-12 11:23:24 -0700
committerLuke T. Shumaker <lukeshu@lukeshu.com>2024-11-12 11:23:24 -0700
commite9d1860b0a106e8088e365a9baa912acf98e0c2f (patch)
treec8df3b8f2fc1686968b0da730c6b9792851bf292
parentf399364f1b16fbf99c82f1a845b4c3ca3f0e7464 (diff)
libdhcp: Reduce stack usage
-rw-r--r--libdhcp/dhcp_client.c274
1 files changed, 157 insertions, 117 deletions
diff --git a/libdhcp/dhcp_client.c b/libdhcp/dhcp_client.c
index 3c6ed8d..bb21564 100644
--- a/libdhcp/dhcp_client.c
+++ b/libdhcp/dhcp_client.c
@@ -172,6 +172,71 @@ struct dhcp_client {
};
/**
+ * For convenience in switch blocks, a list of the states that, when
+ * msgtyp==DHCP_MSGTYP_REQUEST, dhcp_client_send() has assert()ed the state is
+ * not.
+ */
+#define IMPOSSIBLE_REQUEST_STATES \
+ STATE_INIT: \
+ case STATE_REQUESTING: \
+ case STATE_REBINDING: \
+ case STATE_REBOOTING
+
+static inline enum requirement dhcp_table5(typeof((struct dhcp_client){}.state) state,
+ uint8_t msgtyp,
+ uint8_t opt) {
+ /* Encode "Table 5: Fields and options used by DHCP clients"
+ * from
+ * https://datatracker.ietf.org/doc/html/rfc2131#page-38. */
+#define INC_ADDR ({ \
+ enum requirement req; \
+ switch (state) { \
+ case STATE_SELECTING: case STATE_INIT_REBOOT: req = MUST; break; \
+ case STATE_BOUND: case STATE_RENEWING: req = MUST_NOT; break; \
+ case IMPOSSIBLE_REQUEST_STATES: default: req = _SHOULD_NOT_HAPPEN; \
+ } \
+ req; \
+ })
+#define INC_SERVER ({ \
+ enum requirement req; \
+ switch (state) { \
+ case STATE_SELECTING: req = MUST; break; \
+ case STATE_INIT_REBOOT: case STATE_BOUND: case STATE_RENEWING: req = MUST_NOT; break; \
+ case IMPOSSIBLE_REQUEST_STATES: default: req = _SHOULD_NOT_HAPPEN; \
+ } \
+ req; \
+ })
+ struct { enum requirement cols[5]; } row;
+#define ROW(...) row = (typeof(row)){{ __VA_ARGS__ }}
+ switch (opt) {
+ /* Option DISCOVER INFORM REQUEST DECLINE RELEASE */
+ /* ------ ---------- ---------- ---------- -------- -------- */
+ case DHCP_OPT_ADDRESS_REQUEST: ROW(MAY, MUST_NOT, INC_ADDR, MUST, MUST_NOT); break;
+ case DHCP_OPT_ADDRESS_TIME: ROW(MAY, MUST_NOT, MAY, MUST_NOT, MUST_NOT); break;
+ case DHCP_OPT_OVERLOAD: ROW(MAY, MAY, MAY, MAY, MAY ); break;
+ case DHCP_OPT_DHCP_MSG_TYPE: ROW(MUST, MUST, MUST, MUST, MUST ); break;
+ case DHCP_OPT_CLIENT_ID: ROW(MAY, MAY, MAY, MAY, MAY ); break;
+ case DHCP_OPT_CLASS_ID: ROW(MAY, MAY, MAY, MUST_NOT, MUST_NOT); break;
+ case DHCP_OPT_DHCP_SERVER_ID: ROW(MUST_NOT, MUST_NOT, INC_SERVER, MUST, MUST ); break;
+ case DHCP_OPT_PARAMETER_LIST: ROW(MAY, MAY, MAY, MUST_NOT, MUST_NOT); break;
+ case DHCP_OPT_DHCP_MAX_MSG_SIZE: ROW(MAY, MAY, MAY, MUST_NOT, MUST_NOT); break;
+ case DHCP_OPT_DHCP_MESSAGE: ROW(SHOULD_NOT, SHOULD_NOT, SHOULD_NOT, SHOULD, SHOULD ); break;
+ default: ROW(MAY, MAY, MAY, MUST_NOT, MUST_NOT);
+ }
+#undef ROW
+#undef INC_SERVER
+#undef INC_ADDR
+ switch (msgtyp) {
+ case DHCP_MSGTYP_DISCOVER: return row.cols[0];
+ case DHCP_MSGTYP_INFORM: return row.cols[1];
+ case DHCP_MSGTYP_REQUEST: return row.cols[2];
+ case DHCP_MSGTYP_DECLINE: return row.cols[3];
+ case DHCP_MSGTYP_RELEASE: return row.cols[4];
+ default: return _SHOULD_NOT_HAPPEN;
+ }
+}
+
+/**
* @param client->state
* @param client->self_eth_addr
* @param client->xid
@@ -200,14 +265,6 @@ static bool dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const c
client->state == STATE_BOUND || /* T1 expired, start renew */
client->state == STATE_RENEWING ); /* T2 expired, start rebind */
- /* For convenience in switch blocks, a list of the states that
- * are not in the above assert(). */
-#define IMPOSSIBLE_REQUEST_STATES \
- STATE_INIT: \
- case STATE_REQUESTING: \
- case STATE_REBINDING: \
- case STATE_REBOOTING
-
/**********************************************************************\
* Setup *
\**********************************************************************/
@@ -315,94 +372,74 @@ static bool dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const c
msg.options[optlen++] = dhcp_magic_cookie[2];
msg.options[optlen++] = dhcp_magic_cookie[3];
- /* Options.
- * https://datatracker.ietf.org/doc/html/rfc2131#page-38 */
- static uint8_t parameter_request_list[] = {
- DHCP_OPT_SUBNET_MASK,
- DHCP_OPT_ROUTER,
- DHCP_OPT_RENEWAL_TIME,
- DHCP_OPT_REBINDING_TIME,
- };
- const struct {
- bool have_cols;
- enum requirement cols[5];
- size_t val_len;
- const void *val_ptr;
- } options[0x100] = {
-#define V(x) sizeof(x), &(x)
-#define NONE 0, NULL
- /* Encode the table from
- * https://datatracker.ietf.org/doc/html/rfc2131#page-38 */
-#define INC_ADDR ({ \
- enum requirement req; \
- switch (client->state) { \
- case STATE_SELECTING: case STATE_INIT_REBOOT: req = MUST; break; \
- case STATE_BOUND: case STATE_RENEWING: req = MUST_NOT; break; \
- case IMPOSSIBLE_REQUEST_STATES: default: req = _SHOULD_NOT_HAPPEN; \
- } \
- req; \
- })
-#define INC_SERVER ({ \
- enum requirement req; \
- switch (client->state) { \
- case STATE_SELECTING: req = MUST; break; \
- case STATE_INIT_REBOOT: case STATE_BOUND: case STATE_RENEWING: req = MUST_NOT; break; \
- case IMPOSSIBLE_REQUEST_STATES: default: req = _SHOULD_NOT_HAPPEN; \
- } \
- req; \
- })
- /* Option DISCOVER INFORM REQUEST DECLINE RELEASE */
- /* ------ ---------- ---------- ---------- -------- -------- */
- [DHCP_OPT_ADDRESS_REQUEST] = { 1, { MAY, MUST_NOT, INC_ADDR, MUST, MUST_NOT }, V(client->lease_client_addr) },
- [DHCP_OPT_ADDRESS_TIME] = { 1, { MAY, MUST_NOT, MAY, MUST_NOT, MUST_NOT }, NONE },
- [DHCP_OPT_OVERLOAD] = { 1, { MAY, MAY, MAY, MAY, MAY }, NONE },
- [DHCP_OPT_DHCP_MSG_TYPE] = { 1, { MUST, MUST, MUST, MUST, MUST }, V(msgtyp) },
- [DHCP_OPT_CLIENT_ID] = { 1, { MAY, MAY, MAY, MAY, MAY }, client->self_id_len, client->self_id_dat },
- [DHCP_OPT_CLASS_ID] = { 1, { MAY, MAY, MAY, MUST_NOT, MUST_NOT }, NONE },
- [DHCP_OPT_DHCP_SERVER_ID] = { 1, { MUST_NOT, MUST_NOT, INC_SERVER, MUST, MUST }, V(client->lease_server_id) },
- [DHCP_OPT_PARAMETER_LIST] = { 1, { MAY, MAY, MAY, MUST_NOT, MUST_NOT }, V(parameter_request_list) },
- [DHCP_OPT_DHCP_MAX_MSG_SIZE] = { 1, { MAY, MAY, MAY, MUST_NOT, MUST_NOT },
- CONFIG_DHCP_OPT_SIZE > DHCP_MSG_MIN_MAX_OPT_SIZE ? 2 : 0,
- CONFIG_DHCP_OPT_SIZE > DHCP_MSG_MIN_MAX_OPT_SIZE ? &(struct{uint16be_t;}){uint16be_marshal(/*IP*/20+/*UDP*/8+sizeof(msg))} : NULL },
- [DHCP_OPT_DHCP_MESSAGE] = { 1, { SHOULD_NOT, SHOULD_NOT, SHOULD_NOT, SHOULD, SHOULD }, errstr ? strlen(errstr) : 0, errstr },
- [0]/* All others */ = { 0, { MAY, MAY, MAY, MUST_NOT, MUST_NOT }, NONE },
-
- [DHCP_OPT_HOSTNAME] = { 0, {0},
- strnlen(client->self_hostname, sizeof(client->self_hostname)),
- client->self_hostname },
- };
- int col;
- switch (msgtyp) {
- case DHCP_MSGTYP_DISCOVER: col = 0; break;
- case DHCP_MSGTYP_INFORM: col = 1; break;
- case DHCP_MSGTYP_REQUEST: col = 2; break;
- case DHCP_MSGTYP_DECLINE: col = 3; break;
- case DHCP_MSGTYP_RELEASE: col = 4; break;
- default: assert_notreached("invalid message type for client to send");
- }
+ /* Options. */
for (uint8_t opt = 1; opt < 255; opt++) {
- enum requirement req = options[opt].have_cols
- ? options[opt].cols[col]
- : options[0].cols[col];
+ enum requirement req = dhcp_table5(client->state, msgtyp, opt);
switch (req) {
case MUST_NOT:
/* Do nothing. */
break;
case MUST:
- assert(options[opt].val_len);
- /* fall-through */
case SHOULD:
case SHOULD_NOT:
case MAY:
- if (options[opt].val_len) {
- assert(options[opt].val_len <= UINT16_MAX);
- for (size_t done = 0; done < options[opt].val_len;) {
- uint8_t len = options[opt].val_len - done > UINT8_MAX
+ struct { size_t len; const void *ptr; } val;
+ uint8_t _val_prl[] = {
+ DHCP_OPT_SUBNET_MASK,
+ DHCP_OPT_ROUTER,
+ DHCP_OPT_RENEWAL_TIME,
+ DHCP_OPT_REBINDING_TIME,
+ };
+ uint16be_t _val16 ;
+#define V_RAW(len, ptr) val = (typeof(val)){ len, ptr }
+#define V_OBJ(x) V_RAW(sizeof(x), &x)
+#define V_STR(x) V_RAW(x ? strlen(x) : 0, x)
+#define V_STRN(x) V_RAW(strnlen(x, sizeof(x)), x)
+#define V_NONE() V_RAW(0, NULL)
+ switch (opt) {
+ /* For clarity, list all options mentioned in "Table 5:
+ * Fields and options used by DHCP clients" from
+ * https://datatracker.ietf.org/doc/html/rfc2131#page-38,
+ * even if we don't have a value for them. */
+ case DHCP_OPT_ADDRESS_REQUEST: V_OBJ(client->lease_client_addr); break;
+ case DHCP_OPT_ADDRESS_TIME: V_NONE(); break;
+ case DHCP_OPT_OVERLOAD: V_NONE(); break;
+ case DHCP_OPT_DHCP_MSG_TYPE: V_OBJ(msgtyp); break;
+ case DHCP_OPT_CLIENT_ID: V_RAW(client->self_id_len, client->self_id_dat); break;
+ case DHCP_OPT_CLASS_ID: V_NONE(); break;
+ case DHCP_OPT_DHCP_SERVER_ID: V_OBJ(client->lease_server_id); break;
+ case DHCP_OPT_PARAMETER_LIST: V_OBJ(_val_prl); break;
+ case DHCP_OPT_DHCP_MAX_MSG_SIZE:
+ if (CONFIG_DHCP_OPT_SIZE <= DHCP_MSG_MIN_MAX_OPT_SIZE)
+ V_NONE();
+ else {
+ _val16 = uint16be_marshal(20 /* IP header */ +
+ 8 /* UDP header */ +
+ sizeof(msg));
+ V_OBJ(_val16);
+ }
+ break;
+ case DHCP_OPT_DHCP_MESSAGE: V_STR(errstr); break;
+ /* "Site-specific" and "All others". */
+ case DHCP_OPT_HOSTNAME: V_STRN(client->self_hostname); break;
+ default: V_NONE();
+ };
+#undef V_NONE
+#undef V_STRN
+#undef V_STR
+#undef V_OBJ
+#undef V_RAW
+ if (req == MUST)
+ assert(val.len);
+ if (val.len) {
+ assert(val.len <= UINT16_MAX);
+ for (size_t done = 0; done < val.len;) {
+ uint8_t len = val.len - done > UINT8_MAX
? UINT8_MAX
- : options[opt].val_len - done;
+ : val.len - done;
msg.options[optlen++] = opt;
msg.options[optlen++] = len;
- memcpy(&msg.options[optlen], &((char*)options[opt].val_ptr)[done], len);
+ memcpy(&msg.options[optlen], &((char*)val.ptr)[done], len);
optlen += len;
done += len;
}
@@ -484,6 +521,38 @@ static inline void _dhcp_client_recv_consolidate_opts(struct dhcp_recv_msg *ret,
}
}
+static inline enum requirement dhcp_table3(uint8_t req_msgtyp, uint8_t resp_msgtyp, uint8_t resp_opt) {
+ /* Encode "Table 3: Fields and options used by DHCP servers"
+ * from https://datatracker.ietf.org/doc/html/rfc2131#page-29,
+ * with modifications from
+ * https://datatracker.ietf.org/doc/html/rfc6842#page-4. */
+ struct { enum requirement cols[3]; } row;
+#define ROW(...) row = (typeof(row)){{ __VA_ARGS__ }}
+ switch (resp_opt) {
+ /* Option DHCPOFFER DHCPACK DHCPNAK */
+ /* -------------------------- -------- ---------- -------- */
+ case DHCP_OPT_ADDRESS_REQUEST: ROW(MUST_NOT, MUST_NOT, MUST_NOT); break;
+ case DHCP_OPT_ADDRESS_TIME: ROW(MUST, req_msgtyp == DHCP_MSGTYP_REQUEST ? MUST : MUST_NOT,
+ MUST_NOT); break;
+ case DHCP_OPT_OVERLOAD: ROW(MAY, MAY, MUST_NOT); break;
+ case DHCP_OPT_DHCP_MSG_TYPE: ROW(MUST, MUST, MUST ); break;
+ case DHCP_OPT_PARAMETER_LIST: ROW(MUST_NOT, MUST_NOT, MUST_NOT); break;
+ case DHCP_OPT_DHCP_MESSAGE: ROW(SHOULD, SHOULD, SHOULD ); break;
+ case DHCP_OPT_CLIENT_ID: ROW(MAY, MAY, MAY ); /* MUST_NOTs changed to MAY, per RFC 6842 */ break;
+ case DHCP_OPT_CLASS_ID: ROW(MAY, MAY, MAY ); break;
+ case DHCP_OPT_DHCP_SERVER_ID: ROW(MUST, MUST, MUST ); break;
+ case DHCP_OPT_DHCP_MAX_MSG_SIZE: ROW(MUST_NOT, MUST_NOT, MUST_NOT); break;
+ default: ROW(MAY, MAY, MUST_NOT);
+ }
+#undef ROW
+ switch (resp_msgtyp) {
+ case DHCP_MSGTYP_OFFER: return row.cols[0];
+ case DHCP_MSGTYP_ACK: return row.cols[1];
+ case DHCP_MSGTYP_NAK: return row.cols[2];
+ default: return _SHOULD_NOT_HAPPEN;
+ }
+}
+
/**
* @param client->sock
* @param client->self_eth_addr
@@ -589,41 +658,12 @@ static ssize_t dhcp_client_recv(struct dhcp_client *client, struct dhcp_recv_msg
if (optoverload & 2u)
_dhcp_client_recv_consolidate_opts(ret, ret->raw.sname, sizeof(ret->raw.sname));
/* Validate presence of options. */
- const struct {
- bool have_cols;
- enum requirement cols[3];
- } option_req[0x100] = {
- /* Enocde the table from
- * https://datatracker.ietf.org/doc/html/rfc2131#page-29, with modifications from
- * https://datatracker.ietf.org/doc/html/rfc6842#page-4 */
-#define REQ_TIME client->last_sent_msgtyp == DHCP_MSGTYP_REQUEST ? MUST : MUST_NOT
- /* Option DHCPOFFER DHCPACK DHCPNAK */
- /* ------ -------- ---------- -------- */
- [DHCP_OPT_ADDRESS_REQUEST] = { 1, { MUST_NOT, MUST_NOT, MUST_NOT } },
- [DHCP_OPT_ADDRESS_TIME] = { 1, { MUST, REQ_TIME, MUST_NOT } },
- [DHCP_OPT_OVERLOAD] = { 1, { MAY, MAY, MUST_NOT } },
- [DHCP_OPT_DHCP_MSG_TYPE] = { 1, { MUST, MUST, MUST } },
- [DHCP_OPT_PARAMETER_LIST] = { 1, { MUST_NOT, MUST_NOT, MUST_NOT } },
- [DHCP_OPT_DHCP_MESSAGE] = { 1, { SHOULD, SHOULD, SHOULD } },
- [DHCP_OPT_CLIENT_ID] = { 1, { MAY, MAY, MAY } }, /* MUST_NOTs changed to MAY, per RFC 6842 */
- [DHCP_OPT_CLASS_ID] = { 1, { MAY, MAY, MAY } },
- [DHCP_OPT_DHCP_SERVER_ID] = { 1, { MUST, MUST, MUST } },
- [DHCP_OPT_DHCP_MAX_MSG_SIZE] = { 1, { MUST_NOT, MUST_NOT, MUST_NOT } },
- [0]/* All others */ = { 0, { MAY, MAY, MUST_NOT } },
- };
if (!ret->options[DHCP_OPT_DHCP_MSG_TYPE].len)
goto ignore;
- int col;
- switch (ret->options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) {
- case DHCP_MSGTYP_OFFER: col = 0; break;
- case DHCP_MSGTYP_ACK: col = 1; break;
- case DHCP_MSGTYP_NAK: col = 2; break;
- default: goto ignore;
- }
for (uint8_t opt = 1; opt < 255; opt++) {
- enum requirement req = option_req[opt].have_cols
- ? option_req[opt].cols[col]
- : option_req[0].cols[col];
+ enum requirement req = dhcp_table3(client->last_sent_msgtyp,
+ ret->options[DHCP_OPT_DHCP_MSG_TYPE].dat[0],
+ opt);
switch (req) {
case MUST:
if (!ret->options[opt].len)
@@ -639,7 +679,7 @@ static ssize_t dhcp_client_recv(struct dhcp_client *client, struct dhcp_recv_msg
/* Do nothing. */
break;
case _SHOULD_NOT_HAPPEN:
- assert_notreached("bad table");
+ goto ignore;
}
}
/* Validate values of options. */