Hi Roy,
Thanks for the help and info. After some more work we have solved most of our problems using the latest 9.2.0 build. We've gone ahead an attempted to make a patch that will help us in the expired state.
Since the discover tends to result in a different address being assigned, and we would prefer to keep the same address unless it is no longer valid, we tried changing to the rebind state instead. This worked except in the case where the range of addresses offered by the server changed, in that case we were never able to get an address just using rebind. Our work around is to periodically switch between discover and rebind while in the expired state.
The only thing that is missing is being able to drop the last lease for the discover stage and pick it back up for the rebind stage, I haven't been able to crack that problem. Is there a set of functions that could be used to either tell the OS to remove and re-add the address or to stop and start ARP responses for the address?
Thanks for any help you can offer.
If anyone else needs similar behaviour, or has critiques, here is the change:
diff --git a/src/dhcp.c b/src/dhcp.c
index 61cc256a..23f0a827 100644
--- a/src/dhcp.c
+++ b/src/dhcp.c
@@ -139,6 +139,8 @@ static void dhcp_handledhcp(struct interface *, struct bootp *, size_t,
const struct in_addr *);
static void dhcp_handleifudp(void *);
static int dhcp_initstate(struct interface *);
+static void dhcp_rebind(void *arg);
+static void dhcp_expire(void *arg);
void
dhcp_printoptions(const struct dhcpcd_ctx *ctx,
@@ -1869,7 +1871,18 @@ dhcp_discover(void *arg)
state->state = DHS_DISCOVER;
dhcp_new_xid(ifp);
eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
- if (!(state->added & STATE_EXPIRED)) {
+ if (state->added & STATE_EXPIRED) {
+ logwarnx("%s: expired lease discover state", ifp->name);
+ /*Set a short expire timer to switch back to the rebind state
+ When expired, with the last lease ip active, discover will
+ have a high probability to be assigned an address which is different
+ than the last lease. Ideally the last lease ip could be temporarily
+ dropped when in discover state.*/
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ 20, dhcp_expire, ifp);
+ }
+ else
+ {
if (ifo->fallback)
eloop_timeout_add_sec(ifp->ctx->eloop,
ifo->reboot, dhcp_fallback, ifp);
@@ -1879,6 +1892,7 @@ dhcp_discover(void *arg)
ifo->reboot, ipv4ll_start, ifp);
#endif
}
+
if (ifo->options & DHCPCD_REQUEST)
loginfox("%s: soliciting a DHCP lease (requesting %s)",
ifp->name, inet_ntoa(ifo->req_addr));
@@ -1907,13 +1921,28 @@ dhcp_expire(void *arg)
if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) {
logwarnx("%s: DHCP lease expired, extending lease", ifp->name);
state->added |= STATE_EXPIRED;
+ state->interval = 0;
+ /*STATE_EXPIRED will switch between a long period attempting to rebind
+ the last lease address, and a short period attempting to discover
+ a new address. The last lease IP will be active while in STATE_EXPIRED.
+ Discovering the active address will typically result in a different address
+ being assigned. Requesting to rebind will typically result in the active
+ address being assigned. Prefer to rebind, but periodically discover to handle
+ servers that don't respond to the rebind request.*/
+ if (state->state != DHS_REBIND) {
+ eloop_timeout_delete(ifp->ctx->eloop, send_discover, ifp);
+ dhcp_rebind(ifp);
+ }
+ else {
+ dhcp_discover(ifp);
+ }
} else {
logerrx("%s: DHCP lease expired", ifp->name);
dhcp_drop(ifp, "EXPIRE");
dhcp_unlink(ifp->ctx, state->leasefile);
+ state->interval = 0;
+ dhcp_discover(ifp);
}
- state->interval = 0;
- dhcp_discover(ifp);
}
#if defined(ARP) || defined(IN_IFF_DUPLICATED)
@@ -1966,9 +1995,23 @@ dhcp_rebind(void *arg)
struct dhcp_state *state = D_STATE(ifp);
struct dhcp_lease *lease = &state->lease;
- logwarnx("%s: failed to renew DHCP, rebinding", ifp->name);
- logdebugx("%s: expire in %"PRIu32" seconds",
- ifp->name, lease->leasetime - lease->rebindtime);
+ if (state->added & STATE_EXPIRED)
+ {
+ logwarnx("%s: rebinding last lease", ifp->name);
+ /*Set a long expire timer to switch back to the discover state
+ Some servers will not respond to the rebind request if the
+ requested address is outside the configured dynamic address range.
+ Need to periodically attempt discover to cover this possibility.*/
+ eloop_timeout_add_sec(ifp->ctx->eloop,
+ lease->leasetime/2, dhcp_expire, ifp);
+ }
+ else
+ {
+ logwarnx("%s: failed to renew DHCP, rebinding", ifp->name);
+ logdebugx("%s: expire in %"PRIu32" seconds",
+ ifp->name, lease->leasetime - lease->rebindtime);
+ }
+
state->state = DHS_REBIND;
eloop_timeout_delete(ifp->ctx->eloop, send_renew, ifp);
state->lease.server.s_addr = INADDR_ANY;
@@ -3209,6 +3252,11 @@ dhcp_handledhcp(struct interface *ifp, struct bootp *bootp, size_t bootp_len,
bootp_copied = false;
if ((type == 0 || type == DHCP_OFFER) && state->state == DHS_DISCOVER) {
+ if (state->added & STATE_EXPIRED) {
+ /* Make sure the previous, expired lease is dropped before processing the offer */
+ dhcp_drop(ifp, "EXPIRE");
+ dhcp_unlink(ifp->ctx, state->leasefile);
+ }
lease->frominfo = 0;
lease->addr.s_addr = bootp->yiaddr;
memcpy(&lease->cookie, bootp->vend, sizeof(lease->cookie));
-----Original Message-----
From: Roy Marples <roy@xxxxxxxxxxxx>
Sent: Wednesday, September 2, 2020 2:43 AM
To: Matthew Clarkson <mclarkson@xxxxxxxxxxxxxxxxxxxx>; dhcpcd-discuss@xxxxxxxxxxxx
Subject: Re: Extending a lease when dhcp server is offline (similar to lastleaseextend)
Hi Matthew
On 01/09/2020 23:47, Matthew Clarkson wrote:
Hi Roy,
I switched to testing with master, latest commit is https://roy.marples.name/cgit/dhcpcd.git/commit/?id=c628d4a2a7474f72b99dce03571732667fc0c7b1.
I am still seeing the server offer a different ip address once it comes back online. The dhcpcd output looks like this:
root@RCFA-0001p:~# dhcpcd --lastleaseextend --nobackground --timeout
10 br0
dhcpcd-9.1.4 starting
no such user dhcpcd
DUID 00:01:00:01:26:3d:a6:3e:00:02:d9:1f:ff:ff
br0: IAID d9:1f:ff:ff
br0: rebinding lease of 192.168.10.24
br0: leased 192.168.10.24 for 471 seconds
br0: adding route to 192.168.10.0/24
br0: adding default route via 192.168.10.1
br0: failed to renew DHCP, rebinding
br0: DHCP lease expired, extending lease
br0: soliciting a DHCP lease
br0: offered 192.168.10.25 from 192.168.10.2
br0: probing address 192.168.10.25/24
br0: leased 192.168.10.25 for 600 seconds
br0: changing route to 192.168.10.0/24
br0: changing default route via 192.168.10.1
Thanks.
I don't see this as a dhcpcd problem.
From the servers perspective your lease has expired - for example it might have handed 192.168.10.24 to another host.
I think ISC DHCP server keeps records of who had what ip address after expiry and only re-uses these once the pool of available addresses is exhausted and then oldest first. In this situation you would still get 192.168.10.24 if it's still available.
dnsmasq on the other hand i think purges the record when it expires so it will hand it out quite happily to another host.
What you are really asking for is to request an infinite lease time where your address is guaranteed by the server never to change.
You can do this by setting `leasetime 4294967295` in dhcpcd.conf which represents infinity in DHCP terms.
I've comitted a patch to allow `leasetime -1` which is easier to comprehend as infinity.
https://roy.marples.name/cgit/dhcpcd.git/commit/?id=2d3b623273de0dd2ce30e79af12953aa6f0f8a25
However, most servers will reject this request and give you real leasetime which will expire at some point.
Another option is to use INFORM to inform the DHCP server of your IP address rather than lease it.
Roy