dhcpv6: refactor dhcp_request Function
authorNicolas BESNARD <[email protected]>
Mon, 28 Oct 2024 14:41:51 +0000 (14:41 +0000)
committerÁlvaro Fernández Rojas <[email protected]>
Mon, 3 Nov 2025 15:14:47 +0000 (16:14 +0100)
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 <[email protected]>
Signed-off-by: Paul Donald <[email protected]>
Link: https://github.com/openwrt/odhcp6c/pull/106
Signed-off-by: Álvaro Fernández Rojas <[email protected]>
src/dhcpv6.c
src/odhcp6c.h

index 398592700ef71494d3716fa57cbdb5def009f4ff..42309c7396b28566f858995046987107a1df3ee8 100644 (file)
@@ -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, "<POLL>",
-                       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;
+}
index 8bba932522a2ba95027d379986e08103e4cf8fd1..6a2fc0492fd25502120902f18587b44993cc1fad 100644 (file)
@@ -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,