diff options
Diffstat (limited to 'libdhcp')
-rw-r--r-- | libdhcp/dhcp_client.c | 94 |
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; |