diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-11-12 11:23:24 -0700 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-11-12 11:23:24 -0700 |
commit | e9d1860b0a106e8088e365a9baa912acf98e0c2f (patch) | |
tree | c8df3b8f2fc1686968b0da730c6b9792851bf292 | |
parent | f399364f1b16fbf99c82f1a845b4c3ca3f0e7464 (diff) |
libdhcp: Reduce stack usage
-rw-r--r-- | libdhcp/dhcp_client.c | 274 |
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. */ |