diff options
author | David Hankins <dhankins@isc.org> | 2010-02-17 19:59:09 +0000 |
---|---|---|
committer | David Hankins <dhankins@isc.org> | 2010-02-17 19:59:09 +0000 |
commit | 96b8a095e2fb4c3a902fd8a6c6019e777f1833f9 (patch) | |
tree | fb4d94d4efe3795110a4b216585bba5a8f76b9b4 | |
parent | 03211a6f82dd2881f8fe0db7efe1fda3ac686240 (diff) | |
download | isc-dhcp-96b8a095e2fb4c3a902fd8a6c6019e777f1833f9.tar.gz |
Pull missing changes from HEAD down to v4_2.
- When using 'ignore client-updates;', the FQDN returned to the client
is no longer truncated to one octet.
- Cleaned up an unused hardware address variable in nak_lease().
- Manpage entries for the ia-pd and ia-prefix options were updated to
reflect support for prefix delegation.
- An optimization described in the failover protocol draft is now included,
which permits a DHCP server operating in communications-interrupted state
to 'rewind' a lease to the state most recently transmitted to its peer,
greatly increasing a server's endurance in communications-interrupted.
This is supported using a new 'rewind state' record on the dhcpd.leases
entry for each lease.
-rw-r--r-- | RELNOTES | 28 | ||||
-rw-r--r-- | common/conflex.c | 109 | ||||
-rw-r--r-- | common/dhcp-options.5 | 14 | ||||
-rw-r--r-- | includes/dhcpd.h | 19 | ||||
-rw-r--r-- | includes/dhctoken.h | 3 | ||||
-rw-r--r-- | server/confpars.c | 36 | ||||
-rw-r--r-- | server/db.c | 14 | ||||
-rw-r--r-- | server/ddns.c | 2 | ||||
-rw-r--r-- | server/dhcp.c | 154 | ||||
-rw-r--r-- | server/failover.c | 92 | ||||
-rw-r--r-- | server/mdb.c | 122 |
11 files changed, 426 insertions, 167 deletions
@@ -39,6 +39,28 @@ The system has only been tested on Linux, FreeBSD, and Solaris, and may not work on other platforms. Please report any problems and suggested fixes to <dhcp-users@isc.org>. + + Changes since 4.2.0a1 + +- When using 'ignore client-updates;', the FQDN returned to the client + is no longer truncated to one octet. + +- Cleaned up an unused hardware address variable in nak_lease(). + +- Manpage entries for the ia-pd and ia-prefix options were updated to + reflect support for prefix delegation. + +- Cleaned up some compiler warnings + +- An optimization described in the failover protocol draft is now included, + which permits a DHCP server operating in communications-interrupted state + to 'rewind' a lease to the state most recently transmitted to its peer, + greatly increasing a server's endurance in communications-interrupted. + This is supported using a new 'rewind state' record on the dhcpd.leases + entry for each lease. + +- Fix the trace code which was broken by the changes to the DDNS code. + Changes since 4.1.0 (new features) - Failover port configuration can now be left to defaults (port 647) as @@ -82,12 +104,6 @@ work on other platforms. Please report any problems and suggested fixes to extended to attempt to match DHCPv6 clients by the last octets of a DUID-LL or DUID-LLT provided by the client. - Changes since 4.2.0a1 (bug fixes) - -- Cleaned up some compiler warnings - -- Fix the trace code which was broken by the changes to the DDNS code. - Changes since 4.1.0 (bug fixes) - Remove infinite loop in token_print_indent_concat(). diff --git a/common/conflex.c b/common/conflex.c index f4775ff4..77a40be3 100644 --- a/common/conflex.c +++ b/common/conflex.c @@ -1229,63 +1229,72 @@ intern(char *atom, enum dhcp_token dfv) { return PAUSED; break; case 'r': - if (!strcasecmp (atom + 1, "esolution-interrupted")) - return RESOLUTION_INTERRUPTED; - if (!strcasecmp (atom + 1, "ange")) + if (!strcasecmp(atom + 1, "ange")) return RANGE; - if (!strcasecmp(atom + 1, "ange6")) { + if (!strcasecmp(atom + 1, "ange6")) return RANGE6; + if (isascii(atom[1]) && (tolower(atom[1]) == 'e')) { + if (!strcasecmp(atom + 2, "bind")) + return REBIND; + if (!strcasecmp(atom + 2, "boot")) + return REBOOT; + if (!strcasecmp(atom + 2, "contact-interval")) + return RECONTACT_INTERVAL; + if (!strncasecmp(atom + 2, "cover", 5)) { + if (atom[7] == '\0') + return RECOVER; + if (!strcasecmp(atom + 7, "-done")) + return RECOVER_DONE; + if (!strcasecmp(atom + 7, "-wait")) + return RECOVER_WAIT; + break; + } + if (!strcasecmp(atom + 2, "fresh")) + return REFRESH; + if (!strcasecmp(atom + 2, "fused")) + return NS_REFUSED; + if (!strcasecmp(atom + 2, "ject")) + return REJECT; + if (!strcasecmp(atom + 2, "lease")) + return RELEASE; + if (!strcasecmp(atom + 2, "leased")) + return TOKEN_RELEASED; + if (!strcasecmp(atom + 2, "move")) + return REMOVE; + if (!strcasecmp(atom + 2, "new")) + return RENEW; + if (!strcasecmp(atom + 2, "quest")) + return REQUEST; + if (!strcasecmp(atom + 2, "quire")) + return REQUIRE; + if (isascii(atom[2]) && (tolower(atom[2]) == 's')) { + if (!strcasecmp(atom + 3, "erved")) + return TOKEN_RESERVED; + if (!strcasecmp(atom + 3, "et")) + return TOKEN_RESET; + if (!strcasecmp(atom + 3, + "olution-interrupted")) + return RESOLUTION_INTERRUPTED; + break; + } + if (!strcasecmp(atom + 2, "try")) + return RETRY; + if (!strcasecmp(atom + 2, "turn")) + return RETURN; + if (!strcasecmp(atom + 2, "verse")) + return REVERSE; + if (!strcasecmp(atom + 2, "wind")) + return REWIND; + break; } - if (!strcasecmp (atom + 1, "ecover")) - return RECOVER; - if (!strcasecmp (atom + 1, "ecover-done")) - return RECOVER_DONE; - if (!strcasecmp (atom + 1, "ecover-wait")) - return RECOVER_WAIT; - if (!strcasecmp (atom + 1, "econtact-interval")) - return RECONTACT_INTERVAL; - if (!strcasecmp (atom + 1, "equest")) - return REQUEST; - if (!strcasecmp (atom + 1, "equire")) - return REQUIRE; - if (!strcasecmp (atom + 1, "equire")) - return REQUIRE; - if (!strcasecmp (atom + 1, "etry")) - return RETRY; - if (!strcasecmp (atom + 1, "eturn")) - return RETURN; - if (!strcasecmp (atom + 1, "enew")) - return RENEW; - if (!strcasecmp (atom + 1, "ebind")) - return REBIND; - if (!strcasecmp (atom + 1, "eboot")) - return REBOOT; - if (!strcasecmp (atom + 1, "eject")) - return REJECT; - if (!strcasecmp (atom + 1, "everse")) - return REVERSE; - if (!strcasecmp (atom + 1, "elease")) - return RELEASE; - if (!strcasecmp (atom + 1, "efused")) - return NS_REFUSED; - if (!strcasecmp (atom + 1, "eleased")) - return TOKEN_RELEASED; - if (!strcasecmp (atom + 1, "eset")) - return TOKEN_RESET; - if (!strcasecmp (atom + 1, "eserved")) - return TOKEN_RESERVED; - if (!strcasecmp (atom + 1, "emove")) - return REMOVE; - if (!strcasecmp (atom + 1, "efresh")) - return REFRESH; break; case 's': - if (!strcasecmp(atom + 1, "cript")) - return SCRIPT; + if (!strcasecmp(atom + 1, "cript")) + return SCRIPT; if (isascii(atom[1]) && tolower((unsigned char)atom[1]) == 'e') { - if (!strcasecmp(atom + 2, "arch")) - return SEARCH; + if (!strcasecmp(atom + 2, "arch")) + return SEARCH; if (isascii(atom[2]) && tolower((unsigned char)atom[2]) == 'c') { if (!strncasecmp(atom + 3, "ond", 3)) { diff --git a/common/dhcp-options.5 b/common/dhcp-options.5 index bb660713..0525d5dd 100644 --- a/common/dhcp-options.5 +++ b/common/dhcp-options.5 @@ -1,4 +1,4 @@ -.\" $Id: dhcp-options.5,v 1.45.18.1 2009/11/20 01:48:59 sar Exp $ +.\" $Id: dhcp-options.5,v 1.45.18.2 2010/02/17 19:59:09 dhankins Exp $ .\" .\" Copyright (c) 2004-2009 by Internet Systems Consortium, Inc. ("ISC") .\" Copyright (c) 1996-2003 by Internet Software Consortium @@ -1546,18 +1546,18 @@ the "search" line in /etc/resolv.conf. .RS 0.25i .PP The \fBia-pd\fR option is manufactured by clients and servers to create a -Prefix Delegation binding - to delegate an IPv6 prefix to the client. There -is not yet any support for prefix delegation in this software, and this -option is provided informationally only. +Prefix Delegation binding - to delegate an IPv6 prefix to the client. It is +not directly edited in dhcpd.conf(5) or dhclient.conf(5), but rather is +manufactured and consumed by the software. .RE .PP .B option \fBdhcp6.ia-prefix\fR \fIstring\fR\fB;\fR .RS 0.25i .PP The \fBia-prefix\fR option is placed inside \fBia-pd\fR options in order -to identify the prefix(es) allocated to the client. There is not yet -any suport for prefix delegation in this software, and this option is -provided informationally only. +to identify the prefix(es) allocated to the client. It is not directly +edited in dhcpd.conf(5) or dhclient.conf(5), but rather is +manufactured and consumed by the software. .RE .PP .B option diff --git a/includes/dhcpd.h b/includes/dhcpd.h index 0b643205..0c7037d7 100644 --- a/includes/dhcpd.h +++ b/includes/dhcpd.h @@ -495,13 +495,28 @@ struct lease { RESERVED_LEASE | \ BOOTP_LEASE) + /* + * The lease's binding state is its current state. The next binding + * state is the next state this lease will move into by expiration, + * or timers in general. The desired binding state is used on lease + * updates; the caller is attempting to move the lease to the desired + * binding state (and this may either succeed or fail, so the binding + * state must be preserved). + * + * The 'rewind' binding state is used in failover processing. It + * is used for an optimization when out of communications; it allows + * the server to "rewind" a lease to the previous state acknowledged + * by the peer, and progress forward from that point. + */ binding_state_t binding_state; binding_state_t next_binding_state; binding_state_t desired_binding_state; - + binding_state_t rewind_binding_state; + struct lease_state *state; - /* 'tsfp' is more of an 'effective' tsfp. It may be calculated from + /* + * 'tsfp' is more of an 'effective' tsfp. It may be calculated from * stos+mclt for example if it's an expired lease and the server is * in partner-down state. 'atsfp' is zeroed whenever a lease is * updated - and only set when the peer acknowledges it. This diff --git a/includes/dhctoken.h b/includes/dhctoken.h index 54793449..6cfa04b0 100644 --- a/includes/dhctoken.h +++ b/includes/dhctoken.h @@ -356,7 +356,8 @@ enum dhcp_token { ANYCAST_MAC = 659, CONFLICT_DONE = 660, AUTO_PARTNER_DOWN = 661, - GETHOSTNAME = 662 + GETHOSTNAME = 662, + REWIND = 663 }; #define is_identifier(x) ((x) >= FIRST_TOKEN && \ diff --git a/server/confpars.c b/server/confpars.c index d6380f22..27a692be 100644 --- a/server/confpars.c +++ b/server/confpars.c @@ -3095,6 +3095,16 @@ int parse_lease_declaration (struct lease **lp, struct parse *cfile) } goto do_binding_state; + case REWIND: + seenbit = 512; + token = next_token(&val, NULL, cfile); + if (token != BINDING) { + parse_warn(cfile, "expecting 'binding'"); + skip_to_semi(cfile); + break; + } + goto do_binding_state; + case BINDING: seenbit = 256; @@ -3152,13 +3162,26 @@ int parse_lease_declaration (struct lease **lp, struct parse *cfile) if (seenbit == 256) { lease -> binding_state = new_state; - /* If no next binding state is specified, it's - the same as the current state. */ + /* + * Apply default/conservative next/rewind + * binding states if they haven't been set + * yet. These defaults will be over-ridden if + * they are set later in parsing. + */ if (!(seenmask & 128)) - lease -> next_binding_state = new_state; - } else + lease->next_binding_state = new_state; + + /* The most conservative rewind state. */ + if (!(seenmask & 512)) + lease->rewind_binding_state = new_state; + } else if (seenbit == 128) lease -> next_binding_state = new_state; - + else if (seenbit == 512) + lease->rewind_binding_state = new_state; + else + log_fatal("Impossible condition at %s:%d.", + MDL); + parse_semi (cfile); break; @@ -3406,6 +3429,9 @@ int parse_lease_declaration (struct lease **lp, struct parse *cfile) lease -> next_binding_state = FTS_FREE; } else lease -> next_binding_state = lease -> binding_state; + + /* The most conservative rewind state implies no rewind. */ + lease->rewind_binding_state = lease->binding_state; } if (!(seenmask & 65536)) diff --git a/server/db.c b/server/db.c index b5dc7e5f..9a903e04 100644 --- a/server/db.c +++ b/server/db.c @@ -168,6 +168,20 @@ int write_lease (lease) : "abandoned")) < 0) ++errors; + /* + * In this case, if the rewind state is not present in the lease file, + * the reader will use the current binding state as the most + * conservative (safest) state. So if the in-memory rewind state is + * for some reason invalid, the best thing to do is not to write a + * state and let the reader take on a safe state. + */ + if ((lease->binding_state != lease->rewind_binding_state) && + (lease->rewind_binding_state > 0) && + (lease->rewind_binding_state <= FTS_LAST) && + (fprintf(db_file, "\n rewind binding state %s;", + binding_state_names[lease->rewind_binding_state-1])) < 0) + ++errors; + if (lease->flags & RESERVED_LEASE) if (fprintf(db_file, "\n reserved;") < 0) ++errors; diff --git a/server/ddns.c b/server/ddns.c index 75f1c633..b196147e 100644 --- a/server/ddns.c +++ b/server/ddns.c @@ -589,7 +589,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, */ memcpy(&bp->data[5], d1.data, d1.len); if (!save_option_buffer(&fqdn_universe, options, - bp, &bp->data[5], 1, + bp, &bp->data[5], d1.len, FQDN_FQDN, 0)) goto badfqdn; diff --git a/server/dhcp.c b/server/dhcp.c index c0f1efbe..0a970725 100644 --- a/server/dhcp.c +++ b/server/dhcp.c @@ -312,12 +312,19 @@ void dhcpdiscover (packet, ms_nulltp) if (lease && lease -> pool && lease -> pool -> failover_peer) { peer = lease -> pool -> failover_peer; - /* If the lease is ours to allocate, then allocate it. + /* + * If the lease is ours to (re)allocate, then allocate it. + * * If the lease is active, it belongs to the client. This * is the right lease, if we are to offer one. We decide * whether or not to offer later on. + * + * If the lease was last active, and we've reached this + * point, then it was last active with the same client. We + * can safely re-activate the lease with this client. */ if (lease->binding_state == FTS_ACTIVE || + lease->rewind_binding_state == FTS_ACTIVE || lease_mine_to_reallocate(lease)) { ; /* This space intentionally left blank. */ @@ -521,13 +528,18 @@ void dhcprequest (packet, ms_nulltp, ip_lease) goto out; } - /* If the lease is in a transitional state, we can't - renew it. */ - if ((lease -> binding_state == FTS_RELEASED || - lease -> binding_state == FTS_EXPIRED) && - !lease_mine_to_reallocate (lease)) { - log_debug ("%s: lease in transition state %s", msgbuf, - lease -> binding_state == FTS_RELEASED + /* + * If the lease is in a transitional state, we can't + * renew it unless we can rewind it to a non-transitional + * state (active, free, or backup). lease_mine_to_reallocate() + * checks for free/backup, so we only need to check for active. + */ + if ((lease->binding_state == FTS_RELEASED || + lease->binding_state == FTS_EXPIRED) && + lease->rewind_binding_state != FTS_ACTIVE && + !lease_mine_to_reallocate(lease)) { + log_debug("%s: lease in transition state %s", msgbuf, + (lease->binding_state == FTS_RELEASED) ? "released" : "expired"); goto out; } @@ -1303,7 +1315,6 @@ void nak_lease (packet, cip) struct dhcp_packet raw; unsigned char nak = DHCPNAK; struct packet outgoing; - struct hardware hto; unsigned i; struct option_state *options = (struct option_state *)0; struct option_cache *oc = (struct option_cache *)0; @@ -1405,8 +1416,6 @@ void nak_lease (packet, cip) ? inet_ntoa (packet -> raw -> giaddr) : packet -> interface -> name); - - #ifdef DEBUG_PACKET dump_packet (packet); dump_raw ((unsigned char *)packet -> raw, packet -> packet_length); @@ -1414,13 +1423,6 @@ void nak_lease (packet, cip) dump_raw ((unsigned char *)&raw, outgoing.packet_length); #endif -#if 0 - hto.hbuf [0] = packet -> raw -> htype; - hto.hlen = packet -> raw -> hlen; - memcpy (&hto.hbuf [1], packet -> raw -> chaddr, hto.hlen); - hto.hlen++; -#endif - /* Set up the common stuff... */ to.sin_family = AF_INET; #ifdef HAVE_SA_LEN @@ -1442,10 +1444,9 @@ void nak_lease (packet, cip) to.sin_port = remote_port; /* for testing. */ if (fallback_interface) { - result = send_packet (fallback_interface, - packet, &raw, - outgoing.packet_length, - from, &to, &hto); + result = send_packet(fallback_interface, packet, &raw, + outgoing.packet_length, from, &to, + NULL); return; } } else { @@ -1454,9 +1455,8 @@ void nak_lease (packet, cip) } errno = 0; - result = send_packet (packet -> interface, - packet, &raw, outgoing.packet_length, - from, &to, (struct hardware *)0); + result = send_packet(packet->interface, packet, &raw, + outgoing.packet_length, from, &to, NULL); } void ack_lease (packet, lease, offer, when, msg, ms_nulltp, hp) @@ -3327,7 +3327,8 @@ int find_lease (struct lease **lp, goto out; } - /* If we found leases matching the client identifier, loop through + /* + * If we found leases matching the client identifier, loop through * the n_uid pointer looking for one that's actually valid. We * can't do this until we get here because we depend on * packet -> known, which may be set by either the uid host @@ -3343,14 +3344,20 @@ int find_lease (struct lease **lp, #endif #if defined (FAILOVER_PROTOCOL) - /* When failover is active, it's possible that there could - be two "free" leases for the same uid, but only one of - them that's available for this failover peer to allocate. */ - if (uid_lease -> binding_state != FTS_ACTIVE && - !lease_mine_to_reallocate (uid_lease)) { + /* + * When we lookup a lease by uid, we know the client identifier + * matches the lease's record. If it is active, or was last + * active with the same client, we can trivially extend it. + * If is not or was not active, we can allocate it to this + * client if it matches the usual free/backup criteria (which + * is contained in lease_mine_to_reallocate()). + */ + if (uid_lease->binding_state != FTS_ACTIVE && + uid_lease->rewind_binding_state != FTS_ACTIVE && + !lease_mine_to_reallocate(uid_lease)) { #if defined (DEBUG_FIND_LEASE) - log_info ("not mine to allocate: %s", - piaddr (uid_lease -> ip_addr)); + log_info("not active or not mine to allocate: %s", + piaddr(uid_lease->ip_addr)); #endif goto n_uid; } @@ -3410,19 +3417,32 @@ int find_lease (struct lease **lp, piaddr (hw_lease -> ip_addr)); #endif #if defined (FAILOVER_PROTOCOL) - /* When failover is active, it's possible that there could - be two "free" leases for the same uid, but only one of - them that's available for this failover peer to allocate. */ - if (hw_lease -> binding_state != FTS_ACTIVE && - !lease_mine_to_reallocate (hw_lease)) { + /* + * When we lookup a lease by chaddr, we know the MAC address + * matches the lease record (we will check if the lease has a + * client-id the client does not next). If the lease is + * currently active or was last active with this client, we can + * trivially extend it. Otherwise, there are a set of rules + * that govern if we can reallocate this lease to any client + * ("lease_mine_to_reallocate()") including this one. + */ + if (hw_lease->binding_state != FTS_ACTIVE && + hw_lease->rewind_binding_state != FTS_ACTIVE && + !lease_mine_to_reallocate(hw_lease)) { #if defined (DEBUG_FIND_LEASE) - log_info ("not mine to allocate: %s", - piaddr (hw_lease -> ip_addr)); + log_info("not active or not mine to allocate: %s", + piaddr(hw_lease->ip_addr)); #endif goto n_hw; } #endif + /* + * This conditional skips "potentially active" leases (leases + * we think are expired may be extended by the peer, etc) that + * may be assigned to a differently /client-identified/ client + * with the same MAC address. + */ if (hw_lease -> binding_state != FTS_FREE && hw_lease -> binding_state != FTS_BACKUP && hw_lease -> uid && @@ -3507,8 +3527,15 @@ int find_lease (struct lease **lp, lease_dereference (&ip_lease, MDL); } - /* Toss ip_lease if it hasn't yet expired and doesn't belong to the - client. */ + /* + * If the requested address is in use (or potentially in use) by + * a different client, it can't be granted. + * + * This first conditional only detects if the lease is currently + * identified to a different client (client-id and/or chaddr + * mismatch). In this case we may not want to give the client the + * lease, if doing so may potentially be an addressing conflict. + */ if (ip_lease && (ip_lease -> uid ? (!have_client_identifier || @@ -3520,11 +3547,14 @@ int find_lease (struct lease **lp, memcmp (&ip_lease -> hardware_addr.hbuf [1], packet -> raw -> chaddr, (unsigned)(ip_lease -> hardware_addr.hlen - 1))))) { - /* If we're not doing failover, the only state in which - we can allocate this lease to the client is FTS_FREE. - If we are doing failover, things are more complicated. - If the lease is free or backup, we let the caller decide - whether or not to give it out. */ + /* + * A lease is unavailable for allocation to a new client if + * it is not in the FREE or BACKUP state. There may be + * leases that are in the expired state with a rewinding + * state that is free or backup, but these will be processed + * into the free or backup states by expiration processes, so + * checking for them here is superfluous. + */ if (ip_lease -> binding_state != FTS_FREE && ip_lease -> binding_state != FTS_BACKUP) { #if defined (DEBUG_FIND_LEASE) @@ -3538,18 +3568,21 @@ int find_lease (struct lease **lp, } } - /* If we got an ip_lease and a uid_lease or hw_lease, and ip_lease - is not active, and is not ours to reallocate, forget about it. */ + /* + * If we got an ip_lease and a uid_lease or hw_lease, and ip_lease + * is/was not active, and is not ours to reallocate, forget about it. + */ if (ip_lease && (uid_lease || hw_lease) && - ip_lease -> binding_state != FTS_ACTIVE && + ip_lease->binding_state != FTS_ACTIVE && + ip_lease->rewind_binding_state != FTS_ACTIVE && #if defined(FAILOVER_PROTOCOL) - !lease_mine_to_reallocate (ip_lease) && + !lease_mine_to_reallocate(ip_lease) && #endif - packet -> packet_type == DHCPDISCOVER) { + packet->packet_type == DHCPDISCOVER) { #if defined (DEBUG_FIND_LEASE) - log_info ("ip lease not ours to offer."); + log_info("ip lease not active or not ours to offer."); #endif - lease_dereference (&ip_lease, MDL); + lease_dereference(&ip_lease, MDL); } /* If for some reason the client has more than one lease @@ -3985,6 +4018,7 @@ int allocate_lease (struct lease **lp, struct packet *packet, } } + /* Try abandoned leases as a last resort. */ if ((candl == NULL) && (pool->abandoned != NULL) && lease_mine_to_reallocate(pool->abandoned)) @@ -4015,6 +4049,18 @@ int allocate_lease (struct lease **lp, struct packet *packet, continue; } + /* + * There are tiers of lease state preference, listed here in + * reverse order (least to most preferential): + * + * ABANDONED + * FREE/BACKUP + * + * If the selected lease and candidate are both of the same + * state, select the oldest (longest ago) expiration time + * between the two. If the candidate lease is of a higher + * preferred grade over the selected lease, use it. + */ if ((lease -> binding_state == FTS_ABANDONED) && ((candl -> binding_state != FTS_ABANDONED) || (candl -> ends < lease -> ends))) { diff --git a/server/failover.c b/server/failover.c index 44c8f750..53957bb1 100644 --- a/server/failover.c +++ b/server/failover.c @@ -4584,6 +4584,36 @@ isc_result_t dhcp_failover_send_bind_update (dhcp_failover_state_t *state, lease->last_xid = link->xid++; + /* + * Our very next action is to transmit a binding update relating to + * this lease over the wire, and although there is a BNDACK, there is + * no BNDACKACK or BNDACKACKACK...the basic issue as we send a BNDUPD, + * we may not receive a BNDACK. This non-reception does not imply the + * peer did not receive and process the BNDUPD. So at this point, we + * must divest any state that would be dangerous to retain under the + * impression the peer has been updated. Normally state changes like + * this are processed in supersede_lease(), but in this case we need a + * very late binding. + * + * In failover rules, a server is permitted to work forward in certain + * directions from a given lease's state; active leases may be + * extended, so forth. There is an 'optimization' in the failover + * draft that permits a server to 'rewind' any work they have not + * informed the peer. Since we can't know if the peer received our + * update but was unable to acknowledge it, we make this change on + * transmit rather than upon receiving the acknowledgement. + * + * XXX: Frequent lease commits are undesirable. This should hopefully + * only trigger when a server is sending a lease /state change/, and + * not merely an update such as with a renewal. + */ + if (lease->rewind_binding_state != lease->binding_state) { + lease->rewind_binding_state = lease->binding_state; + + write_lease(lease); + commit_leases(); + } + /* Send the update. */ status = (dhcp_failover_put_message (link, link -> outer, @@ -5296,6 +5326,12 @@ isc_result_t dhcp_failover_process_bind_update (dhcp_failover_state_t *state, } msg -> binding_status = lt -> next_binding_state; + /* + * If we accept a peer's binding update, then we can't rewind a + * lease behind the peer's state. + */ + lease->rewind_binding_state = lt->next_binding_state; + /* Try to install the new information. */ if (!supersede_lease (lease, lt, 0, 0, 0) || !write_lease (lease)) { @@ -5435,8 +5471,13 @@ isc_result_t dhcp_failover_process_bind_ack (dhcp_failover_state_t *state, lease->next_binding_state = FTS_BACKUP; else lease->next_binding_state = FTS_FREE; + /* Clear this condition for the next go-round. */ lease->desired_binding_state = lease->next_binding_state; + + /* The peer will have made this state change, so set rewind. */ + lease->rewind_binding_state = lease->next_binding_state; + supersede_lease(lease, (struct lease *)0, 0, 0, 0); write_lease(lease); @@ -6158,6 +6199,12 @@ int lease_mine_to_reallocate (struct lease *lease) if (lease && lease->pool && (peer = lease->pool->failover_peer)) { + /* + * In addition to the normal rules governing wether a server + * is allowed to operate changes on a lease, the server is + * allowed to operate on a lease from the standpoint of the + * most conservative guess of the peer's state for this lease. + */ switch (lease->binding_state) { case FTS_ACTIVE: /* ACTIVE leases may not be reallocated. */ @@ -6182,23 +6229,46 @@ int lease_mine_to_reallocate (struct lease *lease) (peer->me.stos + peer->mclt < cur_time) : (lease->tsfp + peer->mclt < cur_time))); - case FTS_RESET: case FTS_RELEASED: case FTS_EXPIRED: - /* These three lease states go onto the 'expired' - * queue. Upon entry into partner-down state, this - * queue of leases has their tsfp values modified - * to equal stos+mclt, the point at which the server - * is allowed to remove them from these transitional - * states without an acknowledgement. + /* + * These leases are generally untouchable until the + * peer acknowledges their state change. However, as + * this is impossible if the peer is offline, the + * failover protocol permits an 'optimization' to + * rewind the lease to a previous state that the server + * is allowed to operate on, if that was the state that + * was last acknowledged by the peer. + * + * So if a lease was free, was allocated by this + * server, and expired without ever being transmitted + * to the peer, it can be returned to free and given + * to any new client legally. + */ + if ((peer->i_am == primary) && + (lease->rewind_binding_state == FTS_FREE)) + return 1; + if ((peer->i_am == secondary) && + (lease->rewind_binding_state == FTS_BACKUP)) + return 1; + + /* FALL THROUGH (released, expired, reset) */ + case FTS_RESET: + /* + * Released, expired, and reset leases go onto the + * 'expired' queue all together. Upon entry into + * partner-down state, this queue of leases has their + * tsfp values modified to equal stos+mclt, the point + * at which the server is allowed to remove them from + * these transitional states. * * Note that although tsfp has been possibly extended * past the actual tsfp we received from the peer, we * don't have to take any special action. Since tsfp - * is now in the past (or now), we can guarantee that - * this server will only allocate a lease time equal - * to MCLT, rather than a TSFP-optimal lease, which is - * the only danger for a lease in one of these states. + * will be equal to the current time when the lease + * transitions to free, tsfp will not be used to grant + * lease-times longer than the MCLT to clients, which + * is the only danger for this sort of modification. */ return((peer->service_state == service_partner_down) && (lease->tsfp < cur_time)); diff --git a/server/mdb.c b/server/mdb.c index 4668f67f..5815659c 100644 --- a/server/mdb.c +++ b/server/mdb.c @@ -801,15 +801,15 @@ void new_address_range (cfile, low, high, subnet, pool, lpchain) i + min)), isc_result_totext (status)); #endif - lp -> ip_addr = ip_addr (subnet -> net, - subnet -> netmask, i + min); - lp -> starts = MIN_TIME; - lp -> ends = MIN_TIME; - subnet_reference (&lp -> subnet, subnet, MDL); - pool_reference (&lp -> pool, pool, MDL); - lp -> binding_state = FTS_FREE; - lp -> next_binding_state = FTS_FREE; - lp -> flags = 0; + lp->ip_addr = ip_addr(subnet->net, subnet->netmask, i + min); + lp->starts = MIN_TIME; + lp->ends = MIN_TIME; + subnet_reference(&lp->subnet, subnet, MDL); + pool_reference(&lp->pool, pool, MDL); + lp->binding_state = FTS_FREE; + lp->next_binding_state = FTS_FREE; + lp->rewind_binding_state = FTS_FREE; + lp->flags = 0; /* Remember the lease in the IP address hash. */ if (find_lease_by_ip_addr (<, lp -> ip_addr, MDL)) { @@ -1224,7 +1224,8 @@ int supersede_lease (comp, lease, commit, propogate, pimmediate) just_move_it: #if defined (FAILOVER_PROTOCOL) - /* Atsfp should be cleared upon any state change that implies + /* + * Atsfp should be cleared upon any state change that implies * propagation whether supersede_lease was given a copy lease * structure or not (often from the pool_timer()). */ @@ -1356,6 +1357,24 @@ int supersede_lease (comp, lease, commit, propogate, pimmediate) } if (commit) { +#if defined(FAILOVER_PROTOCOL) + /* + * If commit and propogate are set, then we can save a + * possible fsync later in BNDUPD socket transmission by + * stepping the rewind state forward to the new state, in + * case it has changed. This is only worth doing if the + * failover connection is currently connected, as in this + * case it is likely we will be transmitting to the peer very + * shortly. + */ + if (propogate && (comp->pool->failover_peer != NULL) && + ((comp->pool->failover_peer->service_state == + cooperating) || + (comp->pool->failover_peer->service_state == + not_responding))) + comp->rewind_binding_state = comp->binding_state; +#endif + if (!write_lease (comp)) return 0; if ((server_starting & SS_NOSYNC) == 0) { @@ -1404,11 +1423,10 @@ void make_binding_state_transition (struct lease *lease) (( #if defined (FAILOVER_PROTOCOL) peer && - (lease -> binding_state == FTS_EXPIRED || - (peer -> i_am == secondary && - lease -> binding_state == FTS_ACTIVE)) && - (lease -> next_binding_state == FTS_FREE || - lease -> next_binding_state == FTS_BACKUP)) || + (lease->binding_state == FTS_EXPIRED || + lease->binding_state == FTS_ACTIVE) && + (lease->next_binding_state == FTS_FREE || + lease->next_binding_state == FTS_BACKUP)) || (!peer && #endif lease -> binding_state == FTS_ACTIVE && @@ -1564,7 +1582,6 @@ void make_binding_state_transition (struct lease *lease) piaddr (lease -> ip_addr), binding_state_print (lease -> next_binding_state)); #endif - } /* Copy the contents of one lease into another, correctly maintaining @@ -1636,6 +1653,7 @@ int lease_copy (struct lease **lp, lt->cltt = lease -> cltt; lt->binding_state = lease->binding_state; lt->next_binding_state = lease->next_binding_state; + lt->rewind_binding_state = lease->rewind_binding_state; status = lease_reference(lp, lt, file, line); lease_dereference(<, MDL); return status == ISC_R_SUCCESS; @@ -1690,7 +1708,20 @@ void release_lease (lease, packet) lease->tstp = cur_time; #if defined (FAILOVER_PROTOCOL) if (lease -> pool && lease -> pool -> failover_peer) { - lease -> next_binding_state = FTS_RELEASED; + dhcp_failover_state_t *peer = NULL; + + if (lease->pool != NULL) + peer = lease->pool->failover_peer; + + if ((peer->service_state == not_cooperating) && + (((peer->i_am == primary) && + (lease->rewind_binding_state == FTS_FREE)) || + ((peer->i_am == secondary) && + (lease->rewind_binding_state == FTS_BACKUP)))) { + lease->next_binding_state = + lease->rewind_binding_state; + } else + lease -> next_binding_state = FTS_RELEASED; } else { lease -> next_binding_state = FTS_FREE; } @@ -1801,13 +1832,26 @@ void pool_timer (vpool) continue; #if defined (FAILOVER_PROTOCOL) - if (pool -> failover_peer && - pool -> failover_peer -> me.state != partner_down) { - /* The secondary can't remove a lease from the - active state except in partner_down. */ - if (i == ACTIVE_LEASES && - pool -> failover_peer -> i_am == secondary) + if (pool->failover_peer && + pool->failover_peer->me.state != partner_down) { + /* + * Normally the secondary doesn't initiate expiration + * events (unless in partner-down), but rather relies + * on the primary to expire the lease. However, when + * disconnected from its peer, the server is allowed to + * rewind a lease to the previous state that the peer + * would have recorded it. This means there may be + * opportunities for active->free or active->backup + * expirations while out of contact. + * + * Q: Should we limit this expiration to + * comms-interrupt rather than not-normal? + */ + if ((i == ACTIVE_LEASES) && + (pool->failover_peer->i_am == secondary) && + (pool->failover_peer->me.state == normal)) continue; + /* Leases in an expired state don't move to free because of a timeout unless we're in partner_down. */ @@ -1837,10 +1881,29 @@ void pool_timer (vpool) state change should happen, just call supersede_lease on it to make the change happen. */ - if (lease -> next_binding_state != - lease -> binding_state) - supersede_lease (lease, - (struct lease *)0, 1, 1, 1); + if (lease->next_binding_state != lease->binding_state) + { +#if defined(FAILOVER_PROTOCOL) + dhcp_failover_state_t *peer = NULL; + + if (lease->pool != NULL) + peer = lease->pool->failover_peer; + + /* Can we rewind the lease to a free state? */ + if (peer != NULL && + peer->service_state == not_cooperating && + lease->next_binding_state == FTS_EXPIRED && + ((peer->i_am == primary && + lease->rewind_binding_state == FTS_FREE) + || + (peer->i_am == secondary && + lease->rewind_binding_state == + FTS_BACKUP))) + lease->next_binding_state = + lease->rewind_binding_state; +#endif + supersede_lease(lease, NULL, 1, 1, 1); + } lease_dereference (&lease, MDL); if (next) @@ -2287,9 +2350,8 @@ int write_leases () for (i = FREE_LEASES; i <= RESERVED_LEASES; i++) { for (l = *(lptr [i]); l; l = l -> next) { #if !defined (DEBUG_DUMP_ALL_LEASES) - if (l -> hardware_addr.hlen || - l -> uid_len || - (l -> binding_state != FTS_FREE)) + if (l->hardware_addr.hlen != 0 || l->uid_len != 0 || + l->tsfp != 0 || l->binding_state != FTS_FREE) #endif { if (!write_lease (l)) |