summaryrefslogtreecommitdiff
path: root/libdhcp
diff options
context:
space:
mode:
Diffstat (limited to 'libdhcp')
-rw-r--r--libdhcp/dhcp_client.c94
1 files changed, 92 insertions, 2 deletions
diff --git a/libdhcp/dhcp_client.c b/libdhcp/dhcp_client.c
index 4ab456c..94806a6 100644
--- a/libdhcp/dhcp_client.c
+++ b/libdhcp/dhcp_client.c
@@ -117,6 +117,7 @@
#define debugf(fmt, ...) ((void)0)
#endif
+
enum requirement {
MUST,
MUST_NOT,
@@ -159,6 +160,9 @@ struct dhcp_client {
*/
uint32_t xid;
uint64_t time_ns_init;
+ uint64_t transmit_time_ns;
+ uint64_t transmit_delay_ns;
+ uint64_t transmit_deadline_ns;
struct net_ip4_addr lease_client_addr; /* .yiaddr */
struct net_ip4_addr lease_server_id; /* .options[DHCP_OPT_DHCP_SERVER_ID] */
uint64_t lease_time_ns_t1; /* .options[DHCP_OPT_RENEWAL_TIME] + time_ns_init */
@@ -699,6 +703,82 @@ static void dhcp_client_take_lease(struct dhcp_client *client, struct dhcp_recv_
});
}
+/* "in a 10Mb/sec Ethernet internetwork, the delay before the first
+ * retransmission SHOULD be 4 seconds" -- RFC 2131 */
+#define CONFIG_DHCP_RETRANSMIT_INIT_NS (4*NS_PER_S)
+/* "... randomized by the value of a uniform random number chosen from
+ * the range -1 to +1 [seconds]" -- RFC 2131 */
+#define CONFIG_DHCP_RETRANSMIT_RAND_NS (2*NS_PER_S)
+/* "... up to a maximum of 64 seconds." -- RFC 2131 */
+#define CONFIG_DHCP_RETRANSMIT_MAX_NS (64*NS_PER_S)
+
+#define CONFIG_DHCP_TIMEOUT_SELECTING_NS TODO
+#define CONFIG_DHCP_TIMEOUT_REQUESTING_NS TODO
+#define CONFIG_DHCP_TIMEOUT_PROBING_NS TODO
+
+/* Given `init`, the nth transmission is sent at
+ *
+ * n-1
+ * t = ∑ init*2^(i-1) = init*((2^n)-1)
+ * i=0
+ *
+ * This means that the at time t=x, the number of transmissions is
+ *
+ * n = floor(log_2(ceil(x/init)+1))
+ */
+#define _ceildiv(n, d) (( (n)+(d)-1 ) / (d))
+#define _floorlog2(x) (64-__builtin_clzll(x)-1)
+#define _transmissions_at(init, x) _floorlog2(_ceildiv(x, init)+1)
+#define MAX_TRANSMISSIONS(stage) _transmissions_at(CONFIG_DHCP_RETRANSMIT_INIT_NS, CONFIG_DHCP_TIMEOUT_##stage##_NS)
+
+/** "the retransmission algorithm in section 4.1" */
+static x retransmit_4_1(struct dhcp_client *client, uint64_t timeout_ns) {
+ /* "The client MUST adopt a retransmission strategy that
+ * incorporates a randomized exponential backoff algorithm to
+ * determine the delay between retransmissions."
+ *
+ * However, the exact algorithm is implementation-defined,
+ * section 4.1 only provides a suggested ("SHOULD") algorithm
+ * that meets these requirements. I'm not sure why I point
+ * this out; this implements the section 4.1 algorithm
+ * faithfully. */
+
+ if (!client->transmit_delay_ns)
+
+ uint64_t deadline = timeout_ns +
+ rand_uint63n(CONFIG_DHCP_RETRANSMIT_RAND_NS) - CONFIG_DHCP_RETRANSMIT_RAND_NS/2;
+
+ /* "Selecting a new 'xid' for each retransmission is an
+ * implementation decision. A client may choose to reuse the
+ * same 'xid' or select a new 'xid' for each retransmitted
+ * message." -- RFC 2131 */
+
+}
+
+static inline uint64_t dhcp_renew_rebind_deadline(struct dhcp_client *client) {
+ /* "In both RENEWING and REBINDING states, if the client
+ * receives no response to its DHCPREQUEST message, the
+ * client SHOULD wait one-half of the remaining time
+ * until T2 (in RENEWING state) and one-half of the
+ * remaining lease time (in REBINDING state), down to a
+ * minimum of 60 seconds, before retransmitting the
+ * DHCPREQUEST message." -- RFC 2131 */
+
+ uint64_t final_deadline = client->state == STATE_RENEWING
+ ? client->lease_time_ns_t2
+ : client->lease_time_ns_end;
+ if (!final_deadline)
+ return 0;
+
+ uint64_t now = VCALL(bootclock, get_time_ns);
+ if (final_deadline < now + 60*NS_PER_S)
+ return final_deadline;
+ uint64_t half_remaining = (final_deadline - now) / 2;
+ if (half_remaining < 60*NS_PER_S)
+ return 60*NS_PER_S;
+ return half_remaining;
+}
+
static void dhcp_client_setstate(struct dhcp_client *client,
typeof((struct dhcp_client){}.state) newstate,
uint8_t send_msgtyp, const char *errstr) {
@@ -722,6 +802,9 @@ static __attribute__((noreturn)) void dhcp_client_run(struct dhcp_client *client
dhcp_client_setstate(client, STATE_SELECTING, DHCP_MSGTYP_DISCOVER, NULL);
break;
case STATE_SELECTING:
+ /* TODO: "The client times out and retransmits
+ * the DHCPDISCOVER message if the client
+ * receives no DHCPOFFER messages." */
VCALL(client->sock, set_read_deadline, client->time_ns_init+CONFIG_DHCP_SELECTING_NS);
switch ((r = dhcp_client_recv(client, &msg))) {
case 0:
@@ -744,6 +827,9 @@ static __attribute__((noreturn)) void dhcp_client_run(struct dhcp_client *client
}
break;
case STATE_REQUESTING:
+ /* TODO: "The client retransmits the
+ * DHCPREQUEST according to the retransmission
+ * algorithm in section 4.1." */
VCALL(client->sock, set_read_deadline, 0);
switch ((r = dhcp_client_recv(client, &msg))) {
case 0:
@@ -786,7 +872,7 @@ static __attribute__((noreturn)) void dhcp_client_run(struct dhcp_client *client
client->xid = rand_uint63n(UINT32_MAX);
client->time_ns_init = VCALL(bootclock, get_time_ns);
- VCALL(client->sock, set_read_deadline, client->lease_time_ns_t2);
+ VCALL(client->sock, set_read_deadline, dhcp_renew_rebind_deadline(client));
switch ((r = dhcp_client_recv(client, &msg))) {
case 0:
switch (msg.options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) {
@@ -803,6 +889,8 @@ static __attribute__((noreturn)) void dhcp_client_run(struct dhcp_client *client
}
break;
case -NET_ETIMEDOUT:
+ if (VCALL(bootclock, get_time_ns) < client->lease_time_ns_t2)
+ break;
client->lease_server_id = net_ip4_addr_zero;
dhcp_client_setstate(client, STATE_REBINDING, DHCP_MSGTYP_REQUEST, NULL);
break;
@@ -812,7 +900,7 @@ static __attribute__((noreturn)) void dhcp_client_run(struct dhcp_client *client
}
break;
case STATE_REBINDING:
- VCALL(client->sock, set_read_deadline, client->lease_time_ns_end);
+ VCALL(client->sock, set_read_deadline, dhcp_renew_rebind_deadline(client));
switch ((r = dhcp_client_recv(client, &msg))) {
case 0:
switch (msg.options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) {
@@ -829,6 +917,8 @@ static __attribute__((noreturn)) void dhcp_client_run(struct dhcp_client *client
}
break;
case -NET_ETIMEDOUT:
+ if (VCALL(bootclock, get_time_ns) < client->lease_time_ns_end)
+ break;
VCALL(client->iface, ifdown);
dhcp_client_setstate(client, STATE_BOUND, 0, NULL);
break;