From: Nicolas BESNARD Date: Mon, 28 Oct 2024 14:41:51 +0000 (+0000) Subject: dhcpv6: refactor dhcp_request Function X-Git-Url: http://git.openwrt.org/?a=commitdiff_plain;h=1df65f0caf4647288972b637c89aa4f226c92a7a;p=project%2Fodhcp6c.git dhcpv6: refactor dhcp_request Function Problem: The current implementation of the dhcp_request function handles both sending and receiving messages within the same function. This makes the function a blocking one, as it waits for a valid response before proceeding, causing potential delays. Solution: The dhcp_request function is refactored into two functions: one for sending DHCP messages and another for receiving them. Signed-off-by: Nicolas BESNARD Signed-off-by: Paul Donald Link: https://github.com/openwrt/odhcp6c/pull/106 Signed-off-by: Álvaro Fernández Rojas --- diff --git a/src/dhcpv6.c b/src/dhcpv6.c index 3985927..42309c7 100644 --- a/src/dhcpv6.c +++ b/src/dhcpv6.c @@ -81,19 +81,19 @@ static int dhcpv6_commit_advert(void); // RFC 3315 - 5.5 Timeout and Delay values static struct dhcpv6_retx dhcpv6_retx[_DHCPV6_MSG_MAX] = { [DHCPV6_MSG_UNKNOWN] = {false, 1, 120, 0, "", - dhcpv6_handle_reconfigure, NULL}, + dhcpv6_handle_reconfigure, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1}, [DHCPV6_MSG_SOLICIT] = {true, 1, DHCPV6_SOL_MAX_RT, 0, "SOLICIT", - dhcpv6_handle_advert, dhcpv6_commit_advert}, + dhcpv6_handle_advert, dhcpv6_commit_advert, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1}, [DHCPV6_MSG_REQUEST] = {true, 1, DHCPV6_REQ_MAX_RT, 10, "REQUEST", - dhcpv6_handle_reply, NULL}, + dhcpv6_handle_reply, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1}, [DHCPV6_MSG_RENEW] = {false, 10, DHCPV6_REN_MAX_RT, 0, "RENEW", - dhcpv6_handle_reply, NULL}, + dhcpv6_handle_reply, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1}, [DHCPV6_MSG_REBIND] = {false, 10, DHCPV6_REB_MAX_RT, 0, "REBIND", - dhcpv6_handle_rebind_reply, NULL}, - [DHCPV6_MSG_RELEASE] = {false, 1, 0, 5, "RELEASE", NULL, NULL}, - [DHCPV6_MSG_DECLINE] = {false, 1, 0, 5, "DECLINE", NULL, NULL}, + dhcpv6_handle_rebind_reply, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1}, + [DHCPV6_MSG_RELEASE] = {false, 1, 0, 5, "RELEASE", NULL, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1}, + [DHCPV6_MSG_DECLINE] = {false, 1, 0, 5, "DECLINE", NULL, NULL, false, 0, 0, 0,{0, 0, 0}, 0, 0, 0, -1}, [DHCPV6_MSG_INFO_REQ] = {true, 1, DHCPV6_INF_MAX_RT, 0, "INFOREQ", - dhcpv6_handle_reply, NULL}, + dhcpv6_handle_reply, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1}, }; // Sockets @@ -1785,3 +1785,145 @@ int dhcpv6_promote_server_cand(void) return ret; } + +int dhcpv6_send_request(enum dhcpv6_msg type) +{ + struct dhcpv6_retx *retx = &dhcpv6_retx[type]; + + if (!retx->is_retransmit) { + retx->is_retransmit = true; + retx->rc = 0; + retx->timeout = UINT32_MAX; + + if (retx->delay) { + struct timespec ts = {0, 0}; + ts.tv_nsec = (dhcpv6_rand_delay((10000 * DHCPV6_REQ_DELAY) / 2) + (1000 * DHCPV6_REQ_DELAY) / 2) * 1000000; + while (nanosleep(&ts, &ts) < 0 && errno == EINTR); + } + + if (type == DHCPV6_MSG_UNKNOWN) + retx->timeout = t1; + else if (type == DHCPV6_MSG_RENEW) + retx->timeout = (t2 > t1) ? t2 - t1 : ((t1 == UINT32_MAX) ? UINT32_MAX : 0); + else if (type == DHCPV6_MSG_REBIND) + retx->timeout = (t3 > t2) ? t3 - t2 : ((t2 == UINT32_MAX) ? UINT32_MAX : 0); + + if (retx->timeout == 0) + return -1; + + syslog(LOG_NOTICE, "Starting %s transaction (timeout %"PRIu64"s, max rc %d)", + retx->name, retx->timeout, retx->max_rc); + + // Generate transaction ID + if (type != DHCPV6_MSG_UNKNOWN) { + odhcp6c_random(retx->tr_id, sizeof(retx->tr_id)); + } + + retx->start = odhcp6c_get_milli_time(); + retx->round_start = retx->start; + retx->rto = 0; + } + + if (retx->rto == 0) { + int64_t delay = dhcpv6_rand_delay(retx->init_timeo * 1000); + + // First RT MUST be strictly greater than IRT for solicit messages (RFC3313 17.1.2) + while (type == DHCPV6_MSG_SOLICIT && delay <= 0) + delay = dhcpv6_rand_delay(retx->init_timeo * 1000); + + retx->rto = (retx->init_timeo * 1000 + delay); + } else + retx->rto = (2 * retx->rto + dhcpv6_rand_delay(retx->rto)); + + if (retx->max_timeo && (retx->rto >= retx->max_timeo * 1000)) { + retx->rto = retx->max_timeo * 1000 + + dhcpv6_rand_delay(retx->max_timeo * 1000); + } + + // Calculate end for this round and elapsed time + retx->round_end = retx->round_start + retx->rto; + uint64_t elapsed = retx->round_start - retx->start; + + // Don't wait too long if timeout differs from infinite + if ((retx->timeout != UINT32_MAX) && (retx->round_end - retx->start > retx->timeout * 1000)) + retx->round_end = retx->timeout * 1000 + retx->start; + + // Built and send package + switch (type) { + case DHCPV6_MSG_UNKNOWN: + break; + default: + syslog(LOG_NOTICE, "Send %s message (elapsed %"PRIu64"ms, rc %d)", + retx->name, elapsed, retx->rc); + // Fall through + case DHCPV6_MSG_SOLICIT: + case DHCPV6_MSG_INFO_REQ: + dhcpv6_send(type, retx->tr_id, elapsed / 10); + retx->rc++; + } + return 0; +} + +int dhcpv6_receive_response(enum dhcpv6_msg type) +{ + ssize_t len = -1; + struct dhcpv6_retx *retx = &dhcpv6_retx[type]; + + uint8_t buf[1536]; + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cmsg_buf; + + struct iovec iov = {buf, sizeof(buf)}; + struct sockaddr_in6 addr; + struct msghdr msg = {.msg_name = &addr, .msg_namelen = sizeof(addr), + .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsg_buf.buf, + .msg_controllen = sizeof(cmsg_buf)}; + struct in6_pktinfo *pktinfo = NULL; + const struct dhcpv6_header *hdr = (const struct dhcpv6_header *)buf; + + // Receive cycle + len = recvmsg(sock, &msg, 0); + if (len < 0) { + syslog(LOG_ERR, "Error occured when reading the response of (%s) error(%s)", + retx->name, strerror(errno)); + return -1; + } + + for (struct cmsghdr *ch = CMSG_FIRSTHDR(&msg); ch != NULL; + ch = CMSG_NXTHDR(&msg, ch)) { + if (ch->cmsg_level == SOL_IPV6 && + ch->cmsg_type == IPV6_PKTINFO) { + pktinfo = (struct in6_pktinfo *)CMSG_DATA(ch); + break; + } + } + + if (pktinfo == NULL) { + return -1; + } + + if (!dhcpv6_response_is_valid(buf, len, retx->tr_id, type, + &pktinfo->ipi6_addr)) { + return -1; + } + + uint8_t *opt = &buf[4]; + uint8_t *opt_end = opt + len - 4; + retx->round_start = odhcp6c_get_milli_time(); + uint64_t elapsed = retx->round_start - retx->start; + + syslog(LOG_NOTICE, "Got a valid %s after %"PRIu64"ms", + dhcpv6_msg_to_str(hdr->msg_type), elapsed); + + if (retx->handler_reply) { + retx->reply_ret = retx->handler_reply(type, retx->rc, opt, opt_end, &addr); + len = retx->reply_ret; + } + + if (len > 0 && retx->round_end - retx->round_start > 1000) + retx->round_end = 1000 + retx->round_start; + + return retx->reply_ret; +} diff --git a/src/odhcp6c.h b/src/odhcp6c.h index 8bba932..6a2fc04 100644 --- a/src/odhcp6c.h +++ b/src/odhcp6c.h @@ -182,6 +182,15 @@ struct dhcpv6_retx { char name[8]; reply_handler *handler_reply; int(*handler_finish)(void); + bool is_retransmit; + uint64_t timeout; + uint8_t rc; + uint64_t start; + uint8_t tr_id[3]; + int64_t rto; + uint64_t round_start; + uint64_t round_end; + int reply_ret; }; // DHCPv6 Protocol Headers @@ -398,6 +407,8 @@ int dhcpv6_set_ia_mode(enum odhcp6c_ia_mode na, enum odhcp6c_ia_mode pd, bool st int dhcpv6_request(enum dhcpv6_msg type); int dhcpv6_poll_reconfigure(void); int dhcpv6_promote_server_cand(void); +int dhcpv6_send_request(enum dhcpv6_msg type); +int dhcpv6_receive_response(enum dhcpv6_msg type); int init_rtnetlink(void); int set_rtnetlink_addr(int ifindex, const struct in6_addr *addr,