3 Failover protocol support code... */
6 * Copyright (C) 2004-2022 Internet Systems Consortium, Inc. ("ISC")
7 * Copyright (c) 1999-2003 by Internet Software Consortium
9 * This Source Code Form is subject to the terms of the Mozilla Public
10 * License, v. 2.0. If a copy of the MPL was not distributed with this
11 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
19 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 * Internet Systems Consortium, Inc.
23 * Newmarket, NH 03857 USA
25 * https://www.isc.org/
31 #include <omapip/omapip_p.h>
33 #if defined (FAILOVER_PROTOCOL)
34 dhcp_failover_state_t
*failover_states
;
35 static isc_result_t
do_a_failover_option (omapi_object_t
*,
36 dhcp_failover_link_t
*);
37 dhcp_failover_listener_t
*failover_listeners
;
39 static isc_result_t
failover_message_reference (failover_message_t
**,
41 const char *file
, int line
);
42 static isc_result_t
failover_message_dereference (failover_message_t
**,
43 const char *file
, int line
);
45 static void dhcp_failover_pool_balance(dhcp_failover_state_t
*state
);
46 static void dhcp_failover_pool_reqbalance(dhcp_failover_state_t
*state
);
47 static int dhcp_failover_pool_dobalance(dhcp_failover_state_t
*state
,
48 isc_boolean_t
*sendreq
);
49 static inline int secondary_not_hoarding(dhcp_failover_state_t
*state
,
51 static void scrub_lease(struct lease
* lease
, const char *file
, int line
);
53 int check_secs_byte_order
= 0; /* enables byte order check of secs field if 1 */
56 * \brief Performs a "pre-flight" sanity check of failover configuration
58 * Provides an opportunity to do post-parse pre-startup sanity checking
59 * of failover configuration. This allows checks to be done under test
60 * mode (-T), without requiring full startup for validation.
62 * Currently, it enforces all failover peers be used in at lease one
63 * pool. This logic was formerly located in dhcp_failover_startup.
65 * On failure, a fatal error is logged.
68 void dhcp_failover_sanity_check() {
69 dhcp_failover_state_t
*state
;
72 for (state
= failover_states
; state
; state
= state
->next
) {
73 if (state
->pool_count
== 0) {
74 log_error ("ERROR: Failover peer, %s, has no referring"
75 " pools. You must refer to each peer in at"
76 " least one pool declaration.",
81 if (state
->load_balance_max_secs
== 0) {
82 log_info ("WARNING: load balancing will be disabled "
83 "for failover peer, %s, "
84 "because its load balance max secs is 0",
90 log_fatal ("Failover configuration sanity check failed");
95 void dhcp_failover_startup ()
97 dhcp_failover_state_t
*state
;
101 for (state
= failover_states
; state
; state
= state
-> next
) {
102 dhcp_failover_state_transition (state
, "startup");
103 /* In case the peer is already running, immediately try
104 to establish a connection with it. */
105 status
= dhcp_failover_link_initiate ((omapi_object_t
*)state
);
106 if (status
!= ISC_R_SUCCESS
&& status
!= DHCP_R_INCOMPLETE
) {
107 #if defined (DEBUG_FAILOVER_TIMING)
108 log_info ("add_timeout +90 dhcp_failover_reconnect");
110 tv
. tv_sec
= cur_time
+ 90;
113 dhcp_failover_reconnect
, state
,
115 dhcp_failover_state_reference
,
117 dhcp_failover_state_dereference
);
118 log_error ("failover peer %s: %s", state
-> name
,
119 isc_result_totext (status
));
122 status
= (dhcp_failover_listen
123 ((omapi_object_t
*)state
));
124 if (status
!= ISC_R_SUCCESS
) {
125 #if defined (DEBUG_FAILOVER_TIMING)
126 log_info ("add_timeout +90 %s",
127 "dhcp_failover_listener_restart");
129 tv
. tv_sec
= cur_time
+ 90;
132 dhcp_failover_listener_restart
,
134 (tvref_t
)omapi_object_reference
,
135 (tvunref_t
)omapi_object_dereference
);
140 int dhcp_failover_write_all_states ()
142 dhcp_failover_state_t
*state
;
144 for (state
= failover_states
; state
; state
= state
-> next
) {
145 if (!write_failover_state (state
))
151 isc_result_t
enter_failover_peer (peer
)
152 dhcp_failover_state_t
*peer
;
154 dhcp_failover_state_t
*dup
= (dhcp_failover_state_t
*)0;
157 status
= find_failover_peer (&dup
, peer
-> name
, MDL
);
158 if (status
== ISC_R_NOTFOUND
) {
159 if (failover_states
) {
160 dhcp_failover_state_reference (&peer
-> next
,
161 failover_states
, MDL
);
162 dhcp_failover_state_dereference (&failover_states
,
165 dhcp_failover_state_reference (&failover_states
, peer
, MDL
);
166 return ISC_R_SUCCESS
;
168 dhcp_failover_state_dereference (&dup
, MDL
);
169 if (status
== ISC_R_SUCCESS
)
174 isc_result_t
find_failover_peer (peer
, name
, file
, line
)
175 dhcp_failover_state_t
**peer
;
180 dhcp_failover_state_t
*p
;
182 for (p
= failover_states
; p
; p
= p
-> next
)
183 if (!strcmp (name
, p
-> name
))
186 return dhcp_failover_state_reference (peer
, p
, file
, line
);
187 return ISC_R_NOTFOUND
;
190 /* The failover protocol has three objects associated with it. For
191 each failover partner declaration in the dhcpd.conf file, primary
192 or secondary, there is a failover_state object. For any primary or
193 secondary state object that has a connection to its peer, there is
194 also a failover_link object, which has its own input state separate
195 from the failover protocol state for managing the actual bytes
196 coming in off the wire. Finally, there will be one listener object
197 for every distinct port number associated with a secondary
198 failover_state object. Normally all secondary failover_state
199 objects are expected to listen on the same port number, so there
200 need be only one listener object, but if different port numbers are
201 specified for each failover object, there could be as many as one
202 listener object for each secondary failover_state object. */
204 /* This, then, is the implementation of the failover link object. */
206 isc_result_t
dhcp_failover_link_initiate (omapi_object_t
*h
)
209 dhcp_failover_link_t
*obj
;
210 dhcp_failover_state_t
*state
;
213 struct data_string ds
;
214 omapi_addr_list_t
*addrs
= (omapi_addr_list_t
*)0;
215 omapi_addr_t local_addr
;
217 /* Find the failover state in the object chain. */
218 for (o
= h
; o
-> outer
; o
= o
-> outer
)
220 for (; o
; o
= o
-> inner
) {
221 if (o
-> type
== dhcp_type_failover_state
)
225 return DHCP_R_INVALIDARG
;
226 state
= (dhcp_failover_state_t
*)o
;
228 obj
= (dhcp_failover_link_t
*)0;
229 status
= dhcp_failover_link_allocate (&obj
, MDL
);
230 if (status
!= ISC_R_SUCCESS
)
232 option_cache_reference (&obj
-> peer_address
,
233 state
-> partner
.address
, MDL
);
234 obj
-> peer_port
= state
-> partner
.port
;
235 dhcp_failover_state_reference (&obj
-> state_object
, state
, MDL
);
237 memset (&ds
, 0, sizeof ds
);
238 if (!evaluate_option_cache (&ds
, (struct packet
*)0, (struct lease
*)0,
239 (struct client_state
*)0,
240 (struct option_state
*)0,
241 (struct option_state
*)0,
242 &global_scope
, obj
-> peer_address
, MDL
)) {
243 dhcp_failover_link_dereference (&obj
, MDL
);
244 return ISC_R_UNEXPECTED
;
247 /* Make an omapi address list out of a buffer containing zero or more
249 status
= omapi_addr_list_new (&addrs
, ds
.len
/ 4, MDL
);
250 if (status
!= ISC_R_SUCCESS
) {
251 dhcp_failover_link_dereference (&obj
, MDL
);
255 for (i
= 0; i
< addrs
-> count
; i
++) {
256 addrs
-> addresses
[i
].addrtype
= AF_INET
;
257 addrs
-> addresses
[i
].addrlen
= sizeof (struct in_addr
);
258 memcpy (addrs
-> addresses
[i
].address
,
259 &ds
.data
[i
* 4], sizeof (struct in_addr
));
260 addrs
-> addresses
[i
].port
= obj
-> peer_port
;
262 data_string_forget (&ds
, MDL
);
264 /* Now figure out the local address that we're supposed to use. */
265 if (!state
-> me
.address
||
266 !evaluate_option_cache (&ds
, (struct packet
*)0,
268 (struct client_state
*)0,
269 (struct option_state
*)0,
270 (struct option_state
*)0,
271 &global_scope
, state
-> me
.address
,
273 memset (&local_addr
, 0, sizeof local_addr
);
274 local_addr
.addrtype
= AF_INET
;
275 local_addr
.addrlen
= sizeof (struct in_addr
);
276 if (!state
-> server_identifier
.len
) {
277 log_fatal ("failover peer %s: no local address.",
281 if (ds
.len
!= sizeof (struct in_addr
)) {
282 log_error("failover peer %s: 'address' parameter "
283 "fails to resolve to an IPv4 address",
285 data_string_forget (&ds
, MDL
);
286 dhcp_failover_link_dereference (&obj
, MDL
);
287 omapi_addr_list_dereference (&addrs
, MDL
);
288 return DHCP_R_INVALIDARG
;
290 local_addr
.addrtype
= AF_INET
;
291 local_addr
.addrlen
= ds
.len
;
292 memcpy (local_addr
.address
, ds
.data
, ds
.len
);
293 if (!state
-> server_identifier
.len
)
294 data_string_copy (&state
-> server_identifier
,
296 data_string_forget (&ds
, MDL
);
297 local_addr
.port
= 0; /* Let the O.S. choose. */
300 status
= omapi_connect_list ((omapi_object_t
*)obj
,
302 omapi_addr_list_dereference (&addrs
, MDL
);
304 dhcp_failover_link_dereference (&obj
, MDL
);
308 isc_result_t
dhcp_failover_link_signal (omapi_object_t
*h
,
309 const char *name
, va_list ap
)
312 dhcp_failover_link_t
*link
;
314 dhcp_failover_state_t
*s
, *state
= (dhcp_failover_state_t
*)0;
319 if (h
-> type
!= dhcp_type_failover_link
) {
320 /* XXX shouldn't happen. Put an assert here? */
321 return ISC_R_UNEXPECTED
;
323 link
= (dhcp_failover_link_t
*)h
;
325 if (!strcmp (name
, "connect")) {
326 if (link
-> state_object
-> i_am
== primary
) {
327 status
= dhcp_failover_send_connect (h
);
328 if (status
!= ISC_R_SUCCESS
) {
329 log_info ("dhcp_failover_send_connect: %s",
330 isc_result_totext (status
));
331 omapi_disconnect (h
-> outer
, 1);
334 status
= ISC_R_SUCCESS
;
335 /* Allow the peer fifteen seconds to send us a
337 #if defined (DEBUG_FAILOVER_TIMING)
338 log_info ("add_timeout +15 %s",
339 "dhcp_failover_link_startup_timeout");
341 tv
. tv_sec
= cur_time
+ 15;
344 dhcp_failover_link_startup_timeout
,
346 (tvref_t
)dhcp_failover_link_reference
,
347 (tvunref_t
)dhcp_failover_link_dereference
);
351 if (!strcmp (name
, "disconnect")) {
352 if (link
-> state_object
) {
353 dhcp_failover_state_reference (&state
,
354 link
-> state_object
, MDL
);
355 link
-> state
= dhcp_flink_disconnected
;
357 /* Make the transition. */
358 if (state
->link_to_peer
== link
)
359 dhcp_failover_state_transition(link
->state_object
, name
);
361 /* Schedule an attempt to reconnect. */
362 #if defined (DEBUG_FAILOVER_TIMING)
363 log_info("add_timeout +5 dhcp_failover_reconnect");
365 tv
.tv_sec
= cur_time
+ 5;
366 tv
.tv_usec
= cur_tv
.tv_usec
;
367 add_timeout(&tv
, dhcp_failover_reconnect
, state
,
368 (tvref_t
)dhcp_failover_state_reference
,
369 (tvunref_t
)dhcp_failover_state_dereference
);
371 dhcp_failover_state_dereference (&state
, MDL
);
373 return ISC_R_SUCCESS
;
376 if (!strcmp (name
, "status")) {
377 if (link
-> state_object
) {
380 status
= va_arg(ap
, isc_result_t
);
382 if ((status
== ISC_R_HOSTUNREACH
) || (status
== ISC_R_TIMEDOUT
)) {
383 dhcp_failover_state_reference (&state
,
384 link
-> state_object
, MDL
);
385 link
-> state
= dhcp_flink_disconnected
;
387 /* Make the transition. */
388 dhcp_failover_state_transition (link
-> state_object
,
391 /* Start trying to reconnect. */
392 #if defined (DEBUG_FAILOVER_TIMING)
393 log_info ("add_timeout +5 %s",
394 "dhcp_failover_reconnect");
396 tv
. tv_sec
= cur_time
+ 5;
398 add_timeout (&tv
, dhcp_failover_reconnect
,
400 (tvref_t
)dhcp_failover_state_reference
,
401 (tvunref_t
)dhcp_failover_state_dereference
);
403 dhcp_failover_state_dereference (&state
, MDL
);
405 return ISC_R_SUCCESS
;
408 /* Not a signal we recognize? */
409 if (strcmp (name
, "ready")) {
410 if (h
-> inner
&& h
-> inner
-> type
-> signal_handler
)
411 return (*(h
-> inner
-> type
-> signal_handler
))
412 (h
-> inner
, name
, ap
);
413 return ISC_R_NOTFOUND
;
416 if (!h
-> outer
|| h
-> outer
-> type
!= omapi_type_connection
)
417 return DHCP_R_INVALIDARG
;
420 /* We get here because we requested that we be woken up after
421 some number of bytes were read, and that number of bytes
422 has in fact been read. */
423 switch (link
-> state
) {
424 case dhcp_flink_start
:
425 link
-> state
= dhcp_flink_message_length_wait
;
426 if ((omapi_connection_require (c
, 2)) != ISC_R_SUCCESS
)
428 case dhcp_flink_message_length_wait
:
430 link
-> state
= dhcp_flink_message_wait
;
431 link
-> imsg
= dmalloc (sizeof (failover_message_t
), MDL
);
433 status
= ISC_R_NOMEMORY
;
436 failover_message_dereference (&link
->imsg
,
439 link
-> state
= dhcp_flink_disconnected
;
440 log_info ("message length wait: %s",
441 isc_result_totext (status
));
442 omapi_disconnect (c
, 1);
443 /* XXX just blow away the protocol state now?
444 XXX or will disconnect blow it away? */
445 return ISC_R_UNEXPECTED
;
447 memset (link
-> imsg
, 0, sizeof (failover_message_t
));
448 link
-> imsg
-> refcnt
= 1;
449 /* Get the length: */
450 omapi_connection_get_uint16 (c
, &link
-> imsg_len
);
451 link
-> imsg_count
= 0; /* Bytes read. */
453 /* Ensure the message is of valid length. */
454 if (link
->imsg_len
< DHCP_FAILOVER_MIN_MESSAGE_SIZE
||
455 link
->imsg_len
> DHCP_FAILOVER_MAX_MESSAGE_SIZE
) {
456 status
= ISC_R_UNEXPECTED
;
457 goto dhcp_flink_fail
;
460 if ((omapi_connection_require (c
, link
-> imsg_len
- 2U)) !=
463 case dhcp_flink_message_wait
:
464 /* Read in the message. At this point we have the
465 entire message in the input buffer. For each
466 incoming value ID, set a bit in the bitmask
467 indicating that we've gotten it. Maybe flag an
468 error message if the bit is already set. Once
469 we're done reading, we can check the bitmask to
470 make sure that the required fields for each message
471 have been included. */
473 link
-> imsg_count
+= 2; /* Count the length as read. */
475 /* Get message type. */
476 omapi_connection_copyout (&link
-> imsg
-> type
, c
, 1);
477 link
-> imsg_count
++;
479 /* Get message payload offset. */
480 omapi_connection_copyout (&link
-> imsg_payoff
, c
, 1);
481 link
-> imsg_count
++;
483 /* Get message time. */
484 omapi_connection_get_uint32 (c
, &link
-> imsg
-> time
);
485 link
-> imsg_count
+= 4;
487 /* Get transaction ID. */
488 omapi_connection_get_uint32 (c
, &link
-> imsg
-> xid
);
489 link
-> imsg_count
+= 4;
491 #if defined (DEBUG_FAILOVER_MESSAGES)
492 # if !defined(DEBUG_FAILOVER_CONTACT_MESSAGES)
493 if (link
->imsg
->type
== FTM_CONTACT
)
496 log_info ("link: message %s payoff %d time %ld xid %ld",
497 dhcp_failover_message_name (link
-> imsg
-> type
),
499 (unsigned long)link
-> imsg
-> time
,
500 (unsigned long)link
-> imsg
-> xid
);
501 # if !defined(DEBUG_FAILOVER_CONTACT_MESSAGES)
505 /* Skip over any portions of the message header that we
507 if (link
-> imsg_payoff
- link
-> imsg_count
) {
508 omapi_connection_copyout ((unsigned char *)0, c
,
509 (link
-> imsg_payoff
-
510 link
-> imsg_count
));
511 link
-> imsg_count
= link
-> imsg_payoff
;
514 /* Now start sucking options off the wire. */
515 while (link
-> imsg_count
< link
-> imsg_len
) {
516 status
= do_a_failover_option (c
, link
);
517 if (status
!= ISC_R_SUCCESS
)
518 goto dhcp_flink_fail
;
521 /* If it's a connect message, try to associate it with
523 /* XXX this should be authenticated! */
524 if (link
-> imsg
-> type
== FTM_CONNECT
) {
528 if (!(link
->imsg
->options_present
&
529 FTB_RELATIONSHIP_NAME
)) {
530 errmsg
= "missing relationship-name";
531 reason
= FTR_INVALID_PARTNER
;
535 /* See if we can find a failover_state object that
536 matches this connection. This message should only
537 be received by a secondary from a primary. */
538 for (s
= failover_states
; s
; s
= s
-> next
) {
539 if (dhcp_failover_state_match_by_name(s
,
540 &link
->imsg
->relationship_name
))
544 /* If we can't find a failover protocol state
545 for this remote host, drop the connection */
547 errmsg
= "unknown failover relationship name";
548 reason
= FTR_INVALID_PARTNER
;
551 /* XXX Send a refusal message first?
552 XXX Look in protocol spec for guidance. */
556 slen
= strlen(sname
);
557 } else if (link
->imsg
->options_present
&
558 FTB_RELATIONSHIP_NAME
) {
559 sname
= (char *)link
->imsg
->
560 relationship_name
.data
;
561 slen
= link
->imsg
->relationship_name
.count
;
564 slen
= strlen(sname
);
567 log_error("Failover CONNECT from %.*s: %s",
568 slen
, sname
, errmsg
);
569 dhcp_failover_send_connectack
570 ((omapi_object_t
*)link
, state
,
572 log_info ("failover: disconnect: %s", errmsg
);
573 omapi_disconnect (c
, 0);
574 link
-> state
= dhcp_flink_disconnected
;
575 return ISC_R_SUCCESS
;
578 if ((cur_time
> link
-> imsg
-> time
&&
579 cur_time
- link
-> imsg
-> time
> 60) ||
580 (cur_time
< link
-> imsg
-> time
&&
581 link
-> imsg
-> time
- cur_time
> 60)) {
582 errmsg
= "time offset too large";
583 reason
= FTR_TIMEMISMATCH
;
587 if (!(link
-> imsg
-> options_present
& FTB_HBA
) ||
588 link
-> imsg
-> hba
.count
!= 32) {
589 errmsg
= "invalid HBA";
590 reason
= FTR_HBA_CONFLICT
; /* XXX */
594 dfree (state
-> hba
, MDL
);
595 state
-> hba
= dmalloc (32, MDL
);
597 errmsg
= "no memory";
598 reason
= FTR_MISC_REJECT
;
601 memcpy (state
-> hba
, link
-> imsg
-> hba
.data
, 32);
603 if (!link
-> state_object
)
604 dhcp_failover_state_reference
605 (&link
-> state_object
, state
, MDL
);
606 if (!link
-> peer_address
)
607 option_cache_reference
608 (&link
-> peer_address
,
609 state
-> partner
.address
, MDL
);
612 /* If we don't have a state object at this point, it's
613 some kind of bogus situation, so just drop the
615 if (!link
-> state_object
) {
616 log_info ("failover: connect: no matching state.");
617 omapi_disconnect (c
, 1);
618 link
-> state
= dhcp_flink_disconnected
;
619 return DHCP_R_INVALIDARG
;
622 /* Once we have the entire message, and we've validated
623 it as best we can here, pass it to the parent. */
624 omapi_signal ((omapi_object_t
*)link
-> state_object
,
626 link
-> state
= dhcp_flink_message_length_wait
;
628 failover_message_dereference (&link
-> imsg
, MDL
);
629 /* XXX This is dangerous because we could get into a tight
630 XXX loop reading input without servicing any other stuff.
631 XXX There needs to be a way to relinquish control but
632 XXX get it back immediately if there's no other work to
634 if ((omapi_connection_require (c
, 2)) == ISC_R_SUCCESS
)
639 log_fatal("Impossible case at %s:%d.", MDL
);
642 return ISC_R_SUCCESS
;
645 static isc_result_t
do_a_failover_option (c
, link
)
647 dhcp_failover_link_t
*link
;
649 u_int16_t option_code
;
650 u_int16_t option_len
;
656 if (link
-> imsg_count
+ 2 > link
-> imsg_len
) {
657 log_error ("FAILOVER: message overflow at option code.");
658 return DHCP_R_PROTOCOLERROR
;
661 if (link
->imsg
->type
> FTM_MAX
) {
662 log_error ("FAILOVER: invalid message type: %d",
664 return DHCP_R_PROTOCOLERROR
;
667 /* Get option code. */
668 omapi_connection_get_uint16 (c
, &option_code
);
669 link
-> imsg_count
+= 2;
671 if (link
-> imsg_count
+ 2 > link
-> imsg_len
) {
672 log_error ("FAILOVER: message overflow at length.");
673 return DHCP_R_PROTOCOLERROR
;
676 /* Get option length. */
677 omapi_connection_get_uint16 (c
, &option_len
);
678 link
-> imsg_count
+= 2;
680 if (link
-> imsg_count
+ option_len
> link
-> imsg_len
) {
681 log_error ("FAILOVER: message overflow at data.");
682 return DHCP_R_PROTOCOLERROR
;
685 /* If it's an unknown code, skip over it. */
686 if ((option_code
> FTO_MAX
) ||
687 (ft_options
[option_code
].type
== FT_UNDEF
)) {
688 #if defined (DEBUG_FAILOVER_MESSAGES)
689 log_debug (" option code %d (%s) len %d (not recognized)",
691 dhcp_failover_option_name (option_code
),
694 omapi_connection_copyout ((unsigned char *)0, c
, option_len
);
695 link
-> imsg_count
+= option_len
;
696 return ISC_R_SUCCESS
;
699 /* If it's the digest, do it now. */
700 if (ft_options
[option_code
].type
== FT_DIGEST
) {
701 link
-> imsg_count
+= option_len
;
702 if (link
-> imsg_count
!= link
-> imsg_len
) {
703 log_error ("FAILOVER: digest not at end of message");
704 return DHCP_R_PROTOCOLERROR
;
706 #if defined (DEBUG_FAILOVER_MESSAGES)
707 log_debug (" option %s len %d",
708 ft_options
[option_code
].name
, option_len
);
710 /* For now, just dump it. */
711 omapi_connection_copyout ((unsigned char *)0, c
, option_len
);
712 return ISC_R_SUCCESS
;
715 /* Only accept an option once. */
716 if (link
-> imsg
-> options_present
& ft_options
[option_code
].bit
) {
717 log_error ("FAILOVER: duplicate option %s",
718 ft_options
[option_code
].name
);
719 return DHCP_R_PROTOCOLERROR
;
722 /* Make sure the option is appropriate for this type of message.
723 Really, any option is generally allowed for any message, and the
724 cases where this is not true are too complicated to represent in
725 this way - what this code is doing is to just avoid saving the
726 value of an option we don't have any way to use, which allows
727 us to make the failover_message structure smaller. */
728 if (ft_options
[option_code
].bit
&&
729 !(fto_allowed
[link
-> imsg
-> type
] &
730 ft_options
[option_code
].bit
)) {
731 omapi_connection_copyout ((unsigned char *)0, c
, option_len
);
732 link
-> imsg_count
+= option_len
;
733 return ISC_R_SUCCESS
;
736 /* Figure out how many elements, how big they are, and where
738 if (ft_options
[option_code
].num_present
) {
739 /* If this option takes a fixed number of elements,
740 we expect the space for them to be preallocated,
741 and we can just read the data in. */
743 op
= ((unsigned char *)link
-> imsg
) +
744 ft_options
[option_code
].offset
;
745 op_size
= ft_sizes
[ft_options
[option_code
].type
];
746 op_count
= ft_options
[option_code
].num_present
;
748 if (option_len
!= op_size
* op_count
) {
749 log_error ("FAILOVER: option size (%d:%d), option %s",
751 (ft_sizes
[ft_options
[option_code
].type
] *
752 ft_options
[option_code
].num_present
),
753 ft_options
[option_code
].name
);
754 return DHCP_R_PROTOCOLERROR
;
757 failover_option_t
*fo
;
759 /* FT_DDNS* are special - one or two bytes of status
760 followed by the client FQDN. */
762 /* Note: FT_DDNS* option support appears to be incomplete.
763 ISC-Bugs #36996 has been opened to address this. */
764 if (ft_options
[option_code
].type
== FT_DDNS
||
765 ft_options
[option_code
].type
== FT_DDNS1
) {
768 (((char *)link
-> imsg
) +
769 ft_options
[option_code
].offset
));
771 op_count
= (ft_options
[option_code
].type
== FT_DDNS1
774 omapi_connection_copyout (&ddns
-> codes
[0],
776 link
-> imsg_count
+= op_count
;
778 ddns
-> codes
[1] = 0;
780 op_count
= option_len
- op_count
;
782 ddns
-> length
= op_count
;
783 ddns
-> data
= dmalloc (op_count
, MDL
);
785 log_error ("FAILOVER: no memory getting%s(%d)",
786 " DNS data ", op_count
);
788 /* Actually, NO_MEMORY, but if we lose here
789 we have to drop the connection. */
790 return DHCP_R_PROTOCOLERROR
;
792 omapi_connection_copyout (ddns
-> data
, c
, op_count
);
796 /* A zero for num_present means that any number of
797 elements can appear, so we have to figure out how
798 many we got from the length of the option, and then
799 fill out a failover_option structure describing the
801 op_size
= ft_sizes
[ft_options
[option_code
].type
];
803 /* Make sure that option data length is a multiple of the
804 size of the data type being sent. */
805 if (op_size
> 1 && option_len
% op_size
) {
806 log_error ("FAILOVER: option_len %d not %s%d",
807 option_len
, "multiple of ", op_size
);
808 return DHCP_R_PROTOCOLERROR
;
811 op_count
= option_len
/ op_size
;
813 fo
= ((failover_option_t
*)
814 (((char *)link
-> imsg
) +
815 ft_options
[option_code
].offset
));
817 fo
-> count
= op_count
;
818 fo
-> data
= dmalloc (option_len
, MDL
);
820 log_error ("FAILOVER: no memory getting %s (%d)",
821 "option data", op_count
);
823 return DHCP_R_PROTOCOLERROR
;
828 /* For single-byte message values and multi-byte values that
829 don't need swapping, just read them in all at once. */
830 if (op_size
== 1 || ft_options
[option_code
].type
== FT_IPADDR
) {
831 omapi_connection_copyout ((unsigned char *)op
, c
, option_len
);
832 link
-> imsg_count
+= option_len
;
835 * As of 3.1.0, many option codes were changed to conform to
836 * draft revision 12 (which alphabetized, then renumbered all
837 * the option codes without preserving the version option code
838 * nor bumping its value). As it turns out, the message codes
839 * for CONNECT and CONNECTACK turn out the same, so it tries
840 * its darndest to connect, and falls short (when TLS_REQUEST
841 * comes up size 2 rather than size 1 as draft revision 12 also
844 * The VENDOR_CLASS code in 3.0.x was 11, which is now the HBA
845 * code. Both work out to be arbitrarily long text-or-byte
846 * strings, so they pass parsing.
848 * Note that it is possible (or intentional), if highly
849 * improbable, for the HBA bit array to exactly match
850 * isc-V3.0.x. Warning here is not an issue; if it really is
851 * 3.0.x, there will be a protocol error later on. If it isn't
852 * actually 3.0.x, then I guess the lucky user will have to
853 * live with a weird warning.
855 if ((option_code
== 11) && (option_len
> 9) &&
856 (strncmp((const char *)op
, "isc-V3.0.", 9) == 0)) {
857 log_error("WARNING: failover as of versions 3.1.0 and "
858 "on are not reverse compatible with "
865 /* For values that require swapping, read them in one at a time
866 using routines that swap bytes. */
867 for (i
= 0; i
< op_count
; i
++) {
868 switch (ft_options
[option_code
].type
) {
870 omapi_connection_get_uint32 (c
, (u_int32_t
*)op
);
872 link
-> imsg_count
+= 4;
876 omapi_connection_get_uint16 (c
, (u_int16_t
*)op
);
878 link
-> imsg_count
+= 2;
882 /* Everything else should have been handled
884 log_error ("FAILOVER: option %s: bad type %d",
885 ft_options
[option_code
].name
,
886 ft_options
[option_code
].type
);
887 return DHCP_R_PROTOCOLERROR
;
891 /* Remember that we got this option. */
892 link
-> imsg
-> options_present
|= ft_options
[option_code
].bit
;
893 return ISC_R_SUCCESS
;
896 isc_result_t
dhcp_failover_link_set_value (omapi_object_t
*h
,
898 omapi_data_string_t
*name
,
899 omapi_typed_data_t
*value
)
901 if (h
-> type
!= omapi_type_protocol
)
902 return DHCP_R_INVALIDARG
;
904 /* Never valid to set these. */
905 if (!omapi_ds_strcmp (name
, "link-port") ||
906 !omapi_ds_strcmp (name
, "link-name") ||
907 !omapi_ds_strcmp (name
, "link-state"))
910 if (h
-> inner
&& h
-> inner
-> type
-> set_value
)
911 return (*(h
-> inner
-> type
-> set_value
))
912 (h
-> inner
, id
, name
, value
);
913 return ISC_R_NOTFOUND
;
916 isc_result_t
dhcp_failover_link_get_value (omapi_object_t
*h
,
918 omapi_data_string_t
*name
,
919 omapi_value_t
**value
)
921 dhcp_failover_link_t
*link
;
923 if (h
-> type
!= omapi_type_protocol
)
924 return DHCP_R_INVALIDARG
;
925 link
= (dhcp_failover_link_t
*)h
;
927 if (!omapi_ds_strcmp (name
, "link-port")) {
928 return omapi_make_int_value (value
, name
,
929 (int)link
-> peer_port
, MDL
);
930 } else if (!omapi_ds_strcmp (name
, "link-state")) {
931 if (link
-> state
>= dhcp_flink_state_max
)
932 return omapi_make_string_value (value
, name
,
933 "invalid link state",
935 return omapi_make_string_value
937 dhcp_flink_state_names
[link
-> state
], MDL
);
940 if (h
-> inner
&& h
-> inner
-> type
-> get_value
)
941 return (*(h
-> inner
-> type
-> get_value
))
942 (h
-> inner
, id
, name
, value
);
943 return ISC_R_NOTFOUND
;
946 isc_result_t
dhcp_failover_link_destroy (omapi_object_t
*h
,
947 const char *file
, int line
)
949 dhcp_failover_link_t
*link
;
950 if (h
-> type
!= dhcp_type_failover_link
)
951 return DHCP_R_INVALIDARG
;
952 link
= (dhcp_failover_link_t
*)h
;
954 if (link
-> peer_address
)
955 option_cache_dereference (&link
-> peer_address
, file
, line
);
957 failover_message_dereference (&link
-> imsg
, file
, line
);
958 if (link
-> state_object
)
959 dhcp_failover_state_dereference (&link
-> state_object
,
961 return ISC_R_SUCCESS
;
964 /* Write all the published values associated with the object through the
965 specified connection. */
967 isc_result_t
dhcp_failover_link_stuff_values (omapi_object_t
*c
,
971 dhcp_failover_link_t
*link
;
974 if (l
-> type
!= dhcp_type_failover_link
)
975 return DHCP_R_INVALIDARG
;
976 link
= (dhcp_failover_link_t
*)l
;
978 status
= omapi_connection_put_name (c
, "link-port");
979 if (status
!= ISC_R_SUCCESS
)
981 status
= omapi_connection_put_uint32 (c
, sizeof (int));
982 if (status
!= ISC_R_SUCCESS
)
984 status
= omapi_connection_put_uint32 (c
, link
-> peer_port
);
985 if (status
!= ISC_R_SUCCESS
)
988 status
= omapi_connection_put_name (c
, "link-state");
989 if (status
!= ISC_R_SUCCESS
)
991 if (link
-> state
>= dhcp_flink_state_max
)
992 status
= omapi_connection_put_string (c
, "invalid link state");
994 status
= (omapi_connection_put_string
995 (c
, dhcp_flink_state_names
[link
-> state
]));
996 if (status
!= ISC_R_SUCCESS
)
999 if (link
-> inner
&& link
-> inner
-> type
-> stuff_values
)
1000 return (*(link
-> inner
-> type
-> stuff_values
)) (c
, id
,
1002 return ISC_R_SUCCESS
;
1005 /* Set up a listener for the omapi protocol. The handle stored points to
1006 a listener object, not a protocol object. */
1008 isc_result_t
dhcp_failover_listen (omapi_object_t
*h
)
1010 isc_result_t status
;
1011 dhcp_failover_listener_t
*obj
, *l
;
1012 omapi_value_t
*value
= (omapi_value_t
*)0;
1013 omapi_addr_t local_addr
;
1016 status
= omapi_get_value_str (h
, (omapi_object_t
*)0,
1017 "local-port", &value
);
1018 if (status
!= ISC_R_SUCCESS
)
1020 if (!value
-> value
) {
1021 omapi_value_dereference (&value
, MDL
);
1022 return DHCP_R_INVALIDARG
;
1025 status
= omapi_get_int_value (&port
, value
-> value
);
1026 omapi_value_dereference (&value
, MDL
);
1027 if (status
!= ISC_R_SUCCESS
)
1029 local_addr
.port
= port
;
1031 status
= omapi_get_value_str (h
, (omapi_object_t
*)0,
1032 "local-address", &value
);
1033 if (status
!= ISC_R_SUCCESS
)
1035 if (!value
-> value
) {
1037 omapi_value_dereference (&value
, MDL
);
1038 return DHCP_R_INVALIDARG
;
1041 if (value
-> value
-> type
!= omapi_datatype_data
||
1042 value
-> value
-> u
.buffer
.len
!= sizeof (struct in_addr
))
1045 memcpy (local_addr
.address
, value
-> value
-> u
.buffer
.value
,
1046 value
-> value
-> u
.buffer
.len
);
1047 local_addr
.addrlen
= value
-> value
-> u
.buffer
.len
;
1048 local_addr
.addrtype
= AF_INET
;
1050 omapi_value_dereference (&value
, MDL
);
1052 /* Are we already listening on this port and address? */
1053 for (l
= failover_listeners
; l
; l
= l
-> next
) {
1054 if (l
-> address
.port
== local_addr
.port
&&
1055 l
-> address
.addrtype
== local_addr
.addrtype
&&
1056 l
-> address
.addrlen
== local_addr
.addrlen
&&
1057 !memcmp (l
-> address
.address
, local_addr
.address
,
1058 local_addr
.addrlen
))
1061 /* Already listening. */
1063 return ISC_R_SUCCESS
;
1065 obj
= (dhcp_failover_listener_t
*)0;
1066 status
= dhcp_failover_listener_allocate (&obj
, MDL
);
1067 if (status
!= ISC_R_SUCCESS
)
1069 obj
-> address
= local_addr
;
1071 status
= omapi_listen_addr ((omapi_object_t
*)obj
, &obj
-> address
, 1);
1072 if (status
!= ISC_R_SUCCESS
)
1075 status
= omapi_object_reference (&h
-> outer
,
1076 (omapi_object_t
*)obj
, MDL
);
1077 if (status
!= ISC_R_SUCCESS
) {
1078 dhcp_failover_listener_dereference (&obj
, MDL
);
1081 status
= omapi_object_reference (&obj
-> inner
, h
, MDL
);
1082 if (status
!= ISC_R_SUCCESS
) {
1083 dhcp_failover_listener_dereference (&obj
, MDL
);
1087 /* Put this listener on the list. */
1088 if (failover_listeners
) {
1089 dhcp_failover_listener_reference (&obj
-> next
,
1090 failover_listeners
, MDL
);
1091 dhcp_failover_listener_dereference (&failover_listeners
, MDL
);
1093 dhcp_failover_listener_reference (&failover_listeners
, obj
, MDL
);
1095 return dhcp_failover_listener_dereference (&obj
, MDL
);
1098 /* Signal handler for protocol listener - if we get a connect signal,
1099 create a new protocol connection, otherwise pass the signal down. */
1101 isc_result_t
dhcp_failover_listener_signal (omapi_object_t
*o
,
1102 const char *name
, va_list ap
)
1104 isc_result_t status
;
1105 omapi_connection_object_t
*c
;
1106 dhcp_failover_link_t
*obj
;
1107 dhcp_failover_listener_t
*p
;
1108 dhcp_failover_state_t
*s
, *state
= (dhcp_failover_state_t
*)0;
1110 if (!o
|| o
-> type
!= dhcp_type_failover_listener
)
1111 return DHCP_R_INVALIDARG
;
1112 p
= (dhcp_failover_listener_t
*)o
;
1114 /* Not a signal we recognize? */
1115 if (strcmp (name
, "connect")) {
1116 if (p
-> inner
&& p
-> inner
-> type
-> signal_handler
)
1117 return (*(p
-> inner
-> type
-> signal_handler
))
1118 (p
-> inner
, name
, ap
);
1119 return ISC_R_NOTFOUND
;
1122 c
= va_arg (ap
, omapi_connection_object_t
*);
1123 if (!c
|| c
-> type
!= omapi_type_connection
)
1124 return DHCP_R_INVALIDARG
;
1126 /* See if we can find a failover_state object that
1127 matches this connection. */
1128 for (s
= failover_states
; s
; s
= s
-> next
) {
1129 if (dhcp_failover_state_match
1130 (s
, (u_int8_t
*)&c
-> remote_addr
.sin_addr
,
1131 sizeof c
-> remote_addr
.sin_addr
)) {
1137 log_info ("failover: listener: no matching state");
1138 omapi_disconnect ((omapi_object_t
*)c
, 1);
1139 return(ISC_R_NOTFOUND
);
1142 obj
= (dhcp_failover_link_t
*)0;
1143 status
= dhcp_failover_link_allocate (&obj
, MDL
);
1144 if (status
!= ISC_R_SUCCESS
)
1146 obj
-> peer_port
= ntohs (c
-> remote_addr
.sin_port
);
1148 status
= omapi_object_reference (&obj
-> outer
,
1149 (omapi_object_t
*)c
, MDL
);
1150 if (status
!= ISC_R_SUCCESS
) {
1152 dhcp_failover_link_dereference (&obj
, MDL
);
1153 log_info ("failover: listener: picayune failure.");
1154 omapi_disconnect ((omapi_object_t
*)c
, 1);
1158 status
= omapi_object_reference (&c
-> inner
,
1159 (omapi_object_t
*)obj
, MDL
);
1160 if (status
!= ISC_R_SUCCESS
)
1163 status
= dhcp_failover_state_reference (&obj
-> state_object
,
1165 if (status
!= ISC_R_SUCCESS
)
1168 omapi_signal_in ((omapi_object_t
*)obj
, "connect");
1170 return dhcp_failover_link_dereference (&obj
, MDL
);
1173 isc_result_t
dhcp_failover_listener_set_value (omapi_object_t
*h
,
1175 omapi_data_string_t
*name
,
1176 omapi_typed_data_t
*value
)
1178 if (h
-> type
!= dhcp_type_failover_listener
)
1179 return DHCP_R_INVALIDARG
;
1181 if (h
-> inner
&& h
-> inner
-> type
-> set_value
)
1182 return (*(h
-> inner
-> type
-> set_value
))
1183 (h
-> inner
, id
, name
, value
);
1184 return ISC_R_NOTFOUND
;
1187 isc_result_t
dhcp_failover_listener_get_value (omapi_object_t
*h
,
1189 omapi_data_string_t
*name
,
1190 omapi_value_t
**value
)
1192 if (h
-> type
!= dhcp_type_failover_listener
)
1193 return DHCP_R_INVALIDARG
;
1195 if (h
-> inner
&& h
-> inner
-> type
-> get_value
)
1196 return (*(h
-> inner
-> type
-> get_value
))
1197 (h
-> inner
, id
, name
, value
);
1198 return ISC_R_NOTFOUND
;
1201 isc_result_t
dhcp_failover_listener_destroy (omapi_object_t
*h
,
1202 const char *file
, int line
)
1204 dhcp_failover_listener_t
*l
;
1206 if (h
-> type
!= dhcp_type_failover_listener
)
1207 return DHCP_R_INVALIDARG
;
1208 l
= (dhcp_failover_listener_t
*)h
;
1210 dhcp_failover_listener_dereference (&l
-> next
, file
, line
);
1212 return ISC_R_SUCCESS
;
1215 /* Write all the published values associated with the object through the
1216 specified connection. */
1218 isc_result_t
dhcp_failover_listener_stuff (omapi_object_t
*c
,
1222 if (p
-> type
!= dhcp_type_failover_listener
)
1223 return DHCP_R_INVALIDARG
;
1225 if (p
-> inner
&& p
-> inner
-> type
-> stuff_values
)
1226 return (*(p
-> inner
-> type
-> stuff_values
)) (c
, id
,
1228 return ISC_R_SUCCESS
;
1231 /* Set up master state machine for the failover protocol. */
1233 isc_result_t
dhcp_failover_register (omapi_object_t
*h
)
1235 isc_result_t status
;
1236 dhcp_failover_state_t
*obj
;
1238 omapi_value_t
*value
= (omapi_value_t
*)0;
1240 status
= omapi_get_value_str (h
, (omapi_object_t
*)0,
1241 "local-port", &value
);
1242 if (status
!= ISC_R_SUCCESS
)
1244 if (!value
-> value
) {
1245 omapi_value_dereference (&value
, MDL
);
1246 return DHCP_R_INVALIDARG
;
1249 status
= omapi_get_int_value (&port
, value
-> value
);
1250 omapi_value_dereference (&value
, MDL
);
1251 if (status
!= ISC_R_SUCCESS
)
1254 obj
= (dhcp_failover_state_t
*)0;
1255 dhcp_failover_state_allocate (&obj
, MDL
);
1256 obj
-> me
.port
= port
;
1258 status
= omapi_listen ((omapi_object_t
*)obj
, port
, 1);
1259 if (status
!= ISC_R_SUCCESS
) {
1260 dhcp_failover_state_dereference (&obj
, MDL
);
1264 status
= omapi_object_reference (&h
-> outer
, (omapi_object_t
*)obj
,
1266 if (status
!= ISC_R_SUCCESS
) {
1267 dhcp_failover_state_dereference (&obj
, MDL
);
1270 status
= omapi_object_reference (&obj
-> inner
, h
, MDL
);
1271 dhcp_failover_state_dereference (&obj
, MDL
);
1275 /* Signal handler for protocol state machine. */
1277 isc_result_t
dhcp_failover_state_signal (omapi_object_t
*o
,
1278 const char *name
, va_list ap
)
1280 isc_result_t status
;
1281 dhcp_failover_state_t
*state
;
1282 dhcp_failover_link_t
*link
;
1285 if (!o
|| o
-> type
!= dhcp_type_failover_state
)
1286 return DHCP_R_INVALIDARG
;
1287 state
= (dhcp_failover_state_t
*)o
;
1289 /* Not a signal we recognize? */
1290 if (strcmp (name
, "disconnect") &&
1291 strcmp (name
, "message")) {
1292 if (state
-> inner
&& state
-> inner
-> type
-> signal_handler
)
1293 return (*(state
-> inner
-> type
-> signal_handler
))
1294 (state
-> inner
, name
, ap
);
1295 return ISC_R_NOTFOUND
;
1298 /* Handle connect signals by seeing what state we're in
1299 and potentially doing a state transition. */
1300 if (!strcmp (name
, "disconnect")) {
1301 link
= va_arg (ap
, dhcp_failover_link_t
*);
1303 dhcp_failover_link_dereference (&state
-> link_to_peer
, MDL
);
1304 dhcp_failover_state_transition (state
, "disconnect");
1305 if (state
-> i_am
== primary
) {
1306 #if defined (DEBUG_FAILOVER_TIMING)
1307 log_info ("add_timeout +90 %s",
1308 "dhcp_failover_reconnect");
1310 tv
. tv_sec
= cur_time
+ 90;
1312 add_timeout (&tv
, dhcp_failover_reconnect
,
1314 (tvref_t
)dhcp_failover_state_reference
,
1316 dhcp_failover_state_dereference
);
1318 } else if (!strcmp (name
, "message")) {
1319 link
= va_arg (ap
, dhcp_failover_link_t
*);
1321 if (link
-> imsg
-> type
== FTM_CONNECT
) {
1322 /* If we already have a link to the peer, it must be
1324 XXX Is this the right thing to do?
1325 XXX Probably not - what if both peers start at
1326 XXX the same time? */
1327 if (state
-> link_to_peer
) {
1328 dhcp_failover_send_connectack
1329 ((omapi_object_t
*)link
, state
,
1331 "already connected");
1332 omapi_disconnect (link
-> outer
, 1);
1333 return ISC_R_SUCCESS
;
1335 if (!(link
-> imsg
-> options_present
& FTB_MCLT
)) {
1336 dhcp_failover_send_connectack
1337 ((omapi_object_t
*)link
, state
,
1339 "no MCLT provided");
1340 omapi_disconnect (link
-> outer
, 1);
1341 return ISC_R_SUCCESS
;
1344 dhcp_failover_link_reference (&state
-> link_to_peer
,
1346 status
= (dhcp_failover_send_connectack
1347 ((omapi_object_t
*)link
, state
, 0, 0));
1348 if (status
!= ISC_R_SUCCESS
) {
1349 dhcp_failover_link_dereference
1350 (&state
-> link_to_peer
, MDL
);
1351 log_info ("dhcp_failover_send_connectack: %s",
1352 isc_result_totext (status
));
1353 omapi_disconnect (link
-> outer
, 1);
1354 return ISC_R_SUCCESS
;
1356 if (link
-> imsg
-> options_present
& FTB_MAX_UNACKED
)
1357 state
-> partner
.max_flying_updates
=
1358 link
-> imsg
-> max_unacked
;
1359 if (link
-> imsg
-> options_present
& FTB_RECEIVE_TIMER
)
1360 state
-> partner
.max_response_delay
=
1361 link
-> imsg
-> receive_timer
;
1362 state
-> mclt
= link
-> imsg
-> mclt
;
1363 dhcp_failover_send_state (state
);
1364 cancel_timeout (dhcp_failover_link_startup_timeout
,
1366 } else if (link
-> imsg
-> type
== FTM_CONNECTACK
) {
1371 cancel_timeout (dhcp_failover_link_startup_timeout
,
1374 if (!(link
->imsg
->options_present
&
1375 FTB_RELATIONSHIP_NAME
)) {
1376 errmsg
= "missing relationship-name";
1377 reason
= FTR_INVALID_PARTNER
;
1381 if (link
->imsg
->options_present
& FTB_REJECT_REASON
) {
1382 /* XXX: add message option to text output. */
1383 log_error ("Failover CONNECT to %s rejected: %s",
1384 state
? state
->name
: "unknown",
1385 (dhcp_failover_reject_reason_print
1386 (link
-> imsg
-> reject_reason
)));
1387 /* XXX print message from peer if peer sent message. */
1388 omapi_disconnect (link
-> outer
, 1);
1389 return ISC_R_SUCCESS
;
1392 if (!dhcp_failover_state_match_by_name(state
,
1393 &link
->imsg
->relationship_name
)) {
1394 /* XXX: Overflow results in log truncation, safe. */
1395 snprintf(errbuf
, sizeof(errbuf
), "remote failover "
1396 "relationship name %.*s does not match",
1397 (int)link
->imsg
->relationship_name
.count
,
1398 link
->imsg
->relationship_name
.data
);
1400 reason
= FTR_INVALID_PARTNER
;
1402 log_error("Failover CONNECTACK from %s: %s",
1403 state
->name
, errmsg
);
1404 dhcp_failover_send_disconnect ((omapi_object_t
*)link
,
1406 omapi_disconnect (link
-> outer
, 0);
1407 return ISC_R_SUCCESS
;
1410 if (state
-> link_to_peer
) {
1411 errmsg
= "already connected";
1412 reason
= FTR_DUP_CONNECTION
;
1416 if ((cur_time
> link
-> imsg
-> time
&&
1417 cur_time
- link
-> imsg
-> time
> 60) ||
1418 (cur_time
< link
-> imsg
-> time
&&
1419 link
-> imsg
-> time
- cur_time
> 60)) {
1420 errmsg
= "time offset too large";
1421 reason
= FTR_TIMEMISMATCH
;
1425 dhcp_failover_link_reference (&state
-> link_to_peer
,
1428 /* XXX This is probably the right thing to do, but
1429 XXX for release three, to make the smallest possible
1430 XXX change, we are doing this when the peer state
1431 XXX changes instead. */
1432 if (state
-> me
.state
== startup
)
1433 dhcp_failover_set_state (state
,
1434 state
-> saved_state
);
1437 dhcp_failover_send_state (state
);
1439 if (link
-> imsg
-> options_present
& FTB_MAX_UNACKED
)
1440 state
-> partner
.max_flying_updates
=
1441 link
-> imsg
-> max_unacked
;
1442 if (link
-> imsg
-> options_present
& FTB_RECEIVE_TIMER
)
1443 state
-> partner
.max_response_delay
=
1444 link
-> imsg
-> receive_timer
;
1445 #if defined (DEBUG_FAILOVER_CONTACT_TIMING)
1446 log_info ("add_timeout +%d %s",
1447 (int)state
-> partner
.max_response_delay
/ 3,
1448 "dhcp_failover_send_contact");
1450 tv
. tv_sec
= cur_time
+
1451 (int)state
-> partner
.max_response_delay
/ 3;
1454 dhcp_failover_send_contact
, state
,
1455 (tvref_t
)dhcp_failover_state_reference
,
1456 (tvunref_t
)dhcp_failover_state_dereference
);
1457 #if defined (DEBUG_FAILOVER_CONTACT_TIMING)
1458 log_info ("add_timeout +%d %s",
1459 (int)state
-> me
.max_response_delay
,
1460 "dhcp_failover_timeout");
1462 tv
. tv_sec
= cur_time
+
1463 (int)state
-> me
.max_response_delay
;
1466 dhcp_failover_timeout
, state
,
1467 (tvref_t
)dhcp_failover_state_reference
,
1468 (tvunref_t
)dhcp_failover_state_dereference
);
1469 } else if (link
-> imsg
-> type
== FTM_DISCONNECT
) {
1470 if (link
-> imsg
-> reject_reason
) {
1471 log_error ("Failover DISCONNECT from %s: %s",
1472 state
? state
->name
: "unknown",
1473 (dhcp_failover_reject_reason_print
1474 (link
-> imsg
-> reject_reason
)));
1476 omapi_disconnect (link
-> outer
, 1);
1477 } else if (link
-> imsg
-> type
== FTM_BNDUPD
) {
1478 dhcp_failover_process_bind_update (state
,
1480 } else if (link
-> imsg
-> type
== FTM_BNDACK
) {
1481 dhcp_failover_process_bind_ack (state
, link
-> imsg
);
1482 } else if (link
-> imsg
-> type
== FTM_UPDREQ
) {
1483 dhcp_failover_process_update_request (state
,
1485 } else if (link
-> imsg
-> type
== FTM_UPDREQALL
) {
1486 dhcp_failover_process_update_request_all
1487 (state
, link
-> imsg
);
1488 } else if (link
-> imsg
-> type
== FTM_UPDDONE
) {
1489 dhcp_failover_process_update_done (state
,
1491 } else if (link
-> imsg
-> type
== FTM_POOLREQ
) {
1492 dhcp_failover_pool_reqbalance(state
);
1493 } else if (link
-> imsg
-> type
== FTM_POOLRESP
) {
1494 log_info ("pool response: %ld leases",
1496 link
-> imsg
-> addresses_transferred
);
1497 } else if (link
-> imsg
-> type
== FTM_STATE
) {
1498 dhcp_failover_peer_state_changed (state
,
1502 /* Add a timeout so that if the partner doesn't send
1503 another message for the maximum transmit idle time
1504 plus a grace of one second, we close the
1506 if (state
-> link_to_peer
&&
1507 state
-> link_to_peer
== link
&&
1508 state
-> link_to_peer
-> state
!= dhcp_flink_disconnected
)
1510 #if defined (DEBUG_FAILOVER_CONTACT_TIMING)
1511 log_info ("add_timeout +%d %s",
1512 (int)state
-> me
.max_response_delay
,
1513 "dhcp_failover_timeout");
1515 tv
. tv_sec
= cur_time
+
1516 (int)state
-> me
.max_response_delay
;
1519 dhcp_failover_timeout
, state
,
1520 (tvref_t
)dhcp_failover_state_reference
,
1521 (tvunref_t
)dhcp_failover_state_dereference
);
1526 /* Handle all the events we care about... */
1527 return ISC_R_SUCCESS
;
1530 isc_result_t
dhcp_failover_state_transition (dhcp_failover_state_t
*state
,
1533 isc_result_t status
;
1535 /* XXX Check these state transitions against the spec! */
1536 if (!strcmp (name
, "disconnect")) {
1537 if (state
-> link_to_peer
) {
1538 log_info ("peer %s: disconnected", state
-> name
);
1539 if (state
-> link_to_peer
-> state_object
)
1540 dhcp_failover_state_dereference
1541 (&state
-> link_to_peer
-> state_object
, MDL
);
1542 dhcp_failover_link_dereference (&state
-> link_to_peer
,
1545 cancel_timeout (dhcp_failover_send_contact
, state
);
1546 cancel_timeout (dhcp_failover_timeout
, state
);
1547 cancel_timeout (dhcp_failover_startup_timeout
, state
);
1549 switch (state
-> me
.state
== startup
?
1550 state
-> saved_state
: state
-> me
.state
) {
1551 /* In these situations, we remain in the current
1552 * state, or if in startup enter those states.
1555 /* As the peer may not have received or may have
1556 * lost track of updates we sent previously we
1557 * rescind them, causing us to retransmit them
1558 * on an update request.
1560 dhcp_failover_rescind_updates(state
);
1563 case communications_interrupted
:
1569 case resolution_interrupted
:
1571 /* Already in the right state? */
1572 if (state
-> me
.state
== startup
)
1573 return (dhcp_failover_set_state
1574 (state
, state
-> saved_state
));
1575 return ISC_R_SUCCESS
;
1577 case potential_conflict
:
1578 return dhcp_failover_set_state
1579 (state
, resolution_interrupted
);
1582 return dhcp_failover_set_state
1583 (state
, communications_interrupted
);
1586 return dhcp_failover_set_state
1587 (state
, resolution_interrupted
);
1590 log_fatal("Impossible case at %s:%d.", MDL
);
1591 break; /* can't happen. */
1593 } else if (!strcmp (name
, "connect")) {
1594 switch (state
-> me
.state
) {
1595 case communications_interrupted
:
1596 status
= dhcp_failover_set_state (state
, normal
);
1597 dhcp_failover_send_updates (state
);
1600 case resolution_interrupted
:
1601 return dhcp_failover_set_state (state
,
1602 potential_conflict
);
1606 case potential_conflict
:
1615 return dhcp_failover_send_state (state
);
1618 log_fatal("Impossible case at %s:%d.", MDL
);
1621 } else if (!strcmp (name
, "startup")) {
1622 dhcp_failover_set_state (state
, startup
);
1623 return ISC_R_SUCCESS
;
1624 } else if (!strcmp (name
, "connect-timeout")) {
1625 switch (state
-> me
.state
) {
1626 case communications_interrupted
:
1628 case resolution_interrupted
:
1633 return ISC_R_SUCCESS
;
1640 return dhcp_failover_set_state
1641 (state
, communications_interrupted
);
1643 case potential_conflict
:
1644 return dhcp_failover_set_state
1645 (state
, resolution_interrupted
);
1648 log_fatal("Impossible case at %s:%d.", MDL
);
1652 return DHCP_R_INVALIDARG
;
1655 isc_result_t
dhcp_failover_set_service_state (dhcp_failover_state_t
*state
)
1657 switch (state
-> me
.state
) {
1659 state
-> service_state
= not_responding
;
1660 state
-> nrr
= " (my state unknown)";
1664 state
-> service_state
= service_partner_down
;
1669 state
-> service_state
= cooperating
;
1673 case communications_interrupted
:
1674 state
-> service_state
= not_cooperating
;
1678 case resolution_interrupted
:
1679 case potential_conflict
:
1681 state
-> service_state
= not_responding
;
1682 state
-> nrr
= " (resolving conflicts)";
1686 state
-> service_state
= not_responding
;
1687 state
-> nrr
= " (recovering)";
1691 state
-> service_state
= not_responding
;
1692 state
-> nrr
= " (shut down)";
1696 state
-> service_state
= not_responding
;
1697 state
-> nrr
= " (paused)";
1701 state
-> service_state
= not_responding
;
1702 state
-> nrr
= " (recover wait)";
1706 state
-> service_state
= not_responding
;
1707 state
-> nrr
= " (recover done)";
1711 state
-> service_state
= service_startup
;
1712 state
-> nrr
= " (startup)";
1716 log_fatal("Impossible case at %s:%d.\n", MDL
);
1720 /* Some peer states can require us not to respond, even if our
1722 /* XXX hm. I suspect this isn't true anymore. */
1723 if (state
-> service_state
!= not_responding
) {
1724 switch (state
-> partner
.state
) {
1726 state
-> service_state
= not_responding
;
1727 state
-> nrr
= " (peer demands: recovering)";
1730 case potential_conflict
:
1732 case resolution_interrupted
:
1733 state
-> service_state
= not_responding
;
1734 state
-> nrr
= " (peer demands: resolving conflicts)";
1737 /* Other peer states don't affect our behaviour. */
1743 return ISC_R_SUCCESS
;
1747 * \brief Return any leases on the ack queue back to the update queue
1749 * Re-schedule any pending updates by moving them from the ack queue
1750 * (update sent awaiting response) back to the update queue (need to
1751 * send an update for this lease). This will result in a retransmission
1754 * \param state is the state block for the failover connection we are
1758 void dhcp_failover_rescind_updates (dhcp_failover_state_t
*state
)
1762 if (state
->ack_queue_tail
== NULL
)
1765 /* Zap the flags. */
1766 for (lp
= state
->ack_queue_head
; lp
; lp
= lp
->next_pending
)
1767 lp
->flags
= ((lp
->flags
& ~ON_ACK_QUEUE
) | ON_UPDATE_QUEUE
);
1769 /* Now hook the ack queue to the beginning of the update queue. */
1770 if (state
->update_queue_head
) {
1771 lease_reference(&state
->ack_queue_tail
->next_pending
,
1772 state
->update_queue_head
, MDL
);
1773 lease_dereference(&state
->update_queue_head
, MDL
);
1775 lease_reference(&state
->update_queue_head
, state
->ack_queue_head
, MDL
);
1777 if (!state
->update_queue_tail
) {
1778 #if defined (POINTER_DEBUG)
1779 if (state
->ack_queue_tail
->next_pending
) {
1780 log_error("next pending on ack queue tail.");
1784 lease_reference(&state
->update_queue_tail
,
1785 state
->ack_queue_tail
, MDL
);
1787 lease_dereference(&state
->ack_queue_tail
, MDL
);
1788 lease_dereference(&state
->ack_queue_head
, MDL
);
1789 state
->cur_unacked_updates
= 0;
1792 isc_result_t
dhcp_failover_set_state (dhcp_failover_state_t
*state
,
1793 enum failover_state new_state
)
1795 enum failover_state saved_state
;
1798 struct shared_network
*s
;
1802 /* If we're in certain states where we're sending updates, and the peer
1803 * state changes, we need to re-schedule any pending updates just to
1804 * be on the safe side. This results in retransmission.
1806 switch (state
-> me
.state
) {
1808 case potential_conflict
:
1810 /* Move the ack queue to the update queue */
1811 dhcp_failover_rescind_updates(state
);
1813 /* We will re-queue a timeout later, if applicable. */
1814 cancel_timeout (dhcp_failover_keepalive
, state
);
1821 /* Tentatively make the transition. */
1822 saved_state
= state
-> me
.state
;
1823 saved_stos
= state
-> me
.stos
;
1825 /* Keep the old stos if we're going into recover_wait or if we're
1826 coming into or out of startup. */
1827 if (new_state
!= recover_wait
&& new_state
!= startup
&&
1828 saved_state
!= startup
)
1829 state
-> me
.stos
= cur_time
;
1831 /* If we're in shutdown, peer is in partner_down, and we're moving
1832 to recover, we can skip waiting for MCLT to expire. This happens
1833 when a server is moved administratively into shutdown prior to
1834 actually shutting down. Of course, if there are any updates
1835 pending we can't actually do this. */
1836 if (new_state
== recover
&& saved_state
== shut_down
&&
1837 state
-> partner
.state
== partner_down
&&
1838 !state
-> update_queue_head
&& !state
-> ack_queue_head
)
1839 state
-> me
.stos
= cur_time
- state
-> mclt
;
1841 state
-> me
.state
= new_state
;
1842 if (new_state
== startup
&& saved_state
!= startup
)
1843 state
-> saved_state
= saved_state
;
1845 /* If we can't record the new state, we can't make a state transition. */
1846 if (!write_failover_state (state
) || !commit_leases ()) {
1847 log_error ("Unable to record current failover state for %s",
1849 state
-> me
.state
= saved_state
;
1850 state
-> me
.stos
= saved_stos
;
1851 return ISC_R_IOERROR
;
1854 log_info ("failover peer %s: I move from %s to %s",
1855 state
-> name
, dhcp_failover_state_name_print (saved_state
),
1856 dhcp_failover_state_name_print (state
-> me
.state
));
1858 /* If both servers are now normal log it */
1859 if ((state
->me
.state
== normal
) && (state
->partner
.state
== normal
))
1860 log_info("failover peer %s: Both servers normal", state
->name
);
1862 /* If we were in startup and we just left it, cancel the timeout. */
1863 if (new_state
!= startup
&& saved_state
== startup
)
1864 cancel_timeout (dhcp_failover_startup_timeout
, state
);
1867 * If the state changes for any reason, cancel 'delayed auto state
1868 * changes' (currently there is just the one).
1870 cancel_timeout(dhcp_failover_auto_partner_down
, state
);
1872 /* Set our service state. */
1873 dhcp_failover_set_service_state (state
);
1875 /* Tell the peer about it. */
1876 if (state
-> link_to_peer
)
1877 dhcp_failover_send_state (state
);
1879 switch (new_state
) {
1880 case communications_interrupted
:
1882 * There is an optional feature to automatically enter partner
1883 * down after a timer expires, upon entering comms-interrupted.
1884 * This feature is generally not safe except in specific
1887 * A zero value (also the default) disables it.
1889 if (state
->auto_partner_down
== 0)
1892 #if defined (DEBUG_FAILOVER_TIMING)
1893 log_info("add_timeout +%lu dhcp_failover_auto_partner_down",
1894 (unsigned long)state
->auto_partner_down
);
1896 tv
.tv_sec
= cur_time
+ state
->auto_partner_down
;
1898 add_timeout(&tv
, dhcp_failover_auto_partner_down
, state
,
1899 (tvref_t
)omapi_object_reference
,
1900 (tvunref_t
)omapi_object_dereference
);
1904 /* Upon entering normal state, the server is expected to retransmit
1905 * all pending binding updates. This is a good opportunity to
1906 * rebalance the pool (potentially making new pending updates),
1907 * which also schedules the next pool rebalance.
1909 dhcp_failover_pool_balance(state
);
1910 dhcp_failover_generate_update_queue(state
, 0);
1912 if (state
->update_queue_tail
!= NULL
) {
1913 dhcp_failover_send_updates(state
);
1914 log_info("Sending updates to %s.", state
->name
);
1919 case potential_conflict
:
1920 if ((state
->i_am
== primary
) ||
1921 ((state
->i_am
== secondary
) &&
1922 (state
->partner
.state
== conflict_done
)))
1923 dhcp_failover_send_update_request (state
);
1927 #if defined (DEBUG_FAILOVER_TIMING)
1928 log_info ("add_timeout +15 %s",
1929 "dhcp_failover_startup_timeout");
1931 tv
. tv_sec
= cur_time
+ 15;
1934 dhcp_failover_startup_timeout
,
1936 (tvref_t
)omapi_object_reference
,
1938 omapi_object_dereference
);
1941 /* If we come back in recover_wait and there's still waiting
1942 to do, set a timeout. */
1944 if (state
-> me
.stos
+ state
-> mclt
> cur_time
) {
1945 #if defined (DEBUG_FAILOVER_TIMING)
1946 log_info ("add_timeout +%d %s",
1948 state
-> me
.stos
+ state
-> mclt
),
1949 "dhcp_failover_startup_timeout");
1951 tv
. tv_sec
= (int)(state
-> me
.stos
+ state
-> mclt
);
1954 dhcp_failover_recover_done
,
1956 (tvref_t
)omapi_object_reference
,
1958 omapi_object_dereference
);
1960 dhcp_failover_recover_done (state
);
1964 /* XXX: We're supposed to calculate if updreq or updreqall is
1965 * needed. In theory, we should only have to updreqall if we
1966 * are positive we lost our stable storage.
1968 if (state
-> link_to_peer
)
1969 dhcp_failover_send_update_request_all (state
);
1973 /* For every expired lease, set a timeout for it to become free. */
1974 for (s
= shared_networks
; s
; s
= s
->next
) {
1975 for (p
= s
->pools
; p
; p
= p
->next
) {
1976 #if defined (BINARY_LEASES)
1977 long int tiebreaker
= 0;
1979 if (p
->failover_peer
== state
) {
1980 for (l
= LEASE_GET_FIRST(p
->expired
);
1982 l
= LEASE_GET_NEXT(p
->expired
, l
)) {
1983 l
->tsfp
= state
->me
.stos
+ state
->mclt
;
1984 l
->sort_time
= (l
->tsfp
> l
->ends
) ?
1986 #if defined (BINARY_LEASES)
1987 /* If necessary fix up the tiebreaker so the leases
1988 * maintain proper sort order.
1990 l
->sort_tiebreaker
= tiebreaker
;
1991 if (tiebreaker
!= LONG_MAX
)
1997 l
= LEASE_GET_FIRST(p
->expired
);
1998 if (l
&& (l
->sort_time
< p
->next_event_time
)) {
2000 p
->next_event_time
= l
->sort_time
;
2001 #if defined (DEBUG_FAILOVER_TIMING)
2002 log_info ("add_timeout +%d %s",
2003 (int)(cur_time
- p
->next_event_time
),
2006 tv
.tv_sec
= p
->next_event_time
;
2008 add_timeout(&tv
, pool_timer
, p
,
2009 (tvref_t
)pool_reference
,
2010 (tvunref_t
)pool_dereference
);
2021 return ISC_R_SUCCESS
;
2024 isc_result_t
dhcp_failover_peer_state_changed (dhcp_failover_state_t
*state
,
2025 failover_message_t
*msg
)
2027 enum failover_state previous_state
= state
-> partner
.state
;
2028 enum failover_state new_state
;
2031 new_state
= msg
-> server_state
;
2032 startupp
= (msg
-> server_flags
& FTF_SERVER_STARTUP
) ? 1 : 0;
2034 if (state
-> partner
.state
== new_state
&& state
-> me
.state
) {
2035 switch (state
-> me
.state
) {
2038 * If we have a peer state we must be connected.
2039 * If so we should move to potential_conflict
2040 * instead of resolution_interrupted, otherwise
2041 * back to whereever we were before we stopped.
2043 if (state
->saved_state
== resolution_interrupted
)
2044 dhcp_failover_set_state(state
,
2045 potential_conflict
);
2047 dhcp_failover_set_state(state
,
2048 state
->saved_state
);
2049 return ISC_R_SUCCESS
;
2053 case potential_conflict
:
2058 return ISC_R_SUCCESS
;
2060 /* If we get a peer state change when we're
2061 disconnected, we always process it. */
2063 case communications_interrupted
:
2064 case resolution_interrupted
:
2070 log_fatal("Impossible case at %s:%d.", MDL
);
2075 state
-> partner
.state
= new_state
;
2076 state
-> partner
.stos
= cur_time
;
2078 log_info ("failover peer %s: peer moves from %s to %s",
2080 dhcp_failover_state_name_print (previous_state
),
2081 dhcp_failover_state_name_print (state
-> partner
.state
));
2083 /* If both servers are now normal log it */
2084 if ((state
->me
.state
== normal
) && (state
->partner
.state
== normal
))
2085 log_info("failover peer %s: Both servers normal", state
->name
);
2087 if (!write_failover_state (state
) || !commit_leases ()) {
2088 /* This is bad, but it's not fatal. Of course, if we
2089 can't write to the lease database, we're not going to
2090 get much done anyway. */
2091 log_error ("Unable to record current failover state for %s",
2095 /* Quickly validate the new state as being one of the 13 known
2098 switch (new_state
) {
2102 case communications_interrupted
:
2104 case potential_conflict
:
2109 case resolution_interrupted
:
2115 log_error("failover peer %s: Invalid state: %d", state
->name
,
2117 dhcp_failover_set_state(state
, shut_down
);
2118 return ISC_R_SUCCESS
;
2121 /* Do any state transitions that are required as a result of the
2122 peer's state transition. */
2124 switch (state
-> me
.state
== startup
?
2125 state
-> saved_state
: state
-> me
.state
) {
2127 switch (new_state
) {
2129 dhcp_failover_state_pool_check (state
);
2133 if (state
-> me
.state
== startup
)
2134 dhcp_failover_set_state (state
, recover
);
2136 dhcp_failover_set_state (state
,
2137 potential_conflict
);
2140 case potential_conflict
:
2141 case resolution_interrupted
:
2143 /* None of these transitions should ever occur. */
2144 log_error("Peer %s: Invalid state transition %s "
2145 "to %s.", state
->name
,
2146 dhcp_failover_state_name_print(previous_state
),
2147 dhcp_failover_state_name_print(new_state
));
2148 dhcp_failover_set_state (state
, shut_down
);
2153 dhcp_failover_set_state (state
, partner_down
);
2157 dhcp_failover_set_state (state
,
2158 communications_interrupted
);
2162 /* recover_wait, recover_done, unknown_state, startup,
2163 * communications_interrupted
2170 switch (new_state
) {
2172 log_info ("failover peer %s: requesting %s",
2173 state
-> name
, "full update from peer");
2174 /* Don't send updreqall if we're really in the
2175 startup state, because that will result in two
2177 if (state
-> me
.state
== recover
)
2178 dhcp_failover_send_update_request_all (state
);
2181 case potential_conflict
:
2182 case resolution_interrupted
:
2185 dhcp_failover_set_state (state
, potential_conflict
);
2189 case communications_interrupted
:
2190 /* We're supposed to send an update request at this
2192 /* XXX we don't currently have code here to do any
2193 XXX clever detection of when we should send an
2194 XXX UPDREQALL message rather than an UPDREQ
2195 XXX message. What to do, what to do? */
2196 /* Currently when we enter recover state, no matter
2197 * the reason, we send an UPDREQALL. So, it makes
2198 * the most sense to stick to that until something
2200 * Furthermore, we only want to send the update
2201 * request if we are not in startup state.
2203 if (state
-> me
.state
== recover
)
2204 dhcp_failover_send_update_request_all (state
);
2208 /* XXX We're not explicitly told what to do in this
2209 XXX case, but this transition is consistent with
2210 XXX what is elsewhere in the draft. */
2211 dhcp_failover_set_state (state
, partner_down
);
2214 /* We can't really do anything in this case. */
2216 /* paused, recover_done, recover_wait, unknown_state,
2223 case potential_conflict
:
2224 switch (new_state
) {
2226 /* This is an illegal transition. */
2227 log_error("Peer %s moves to normal during conflict "
2228 "resolution - panic, shutting down.",
2230 dhcp_failover_set_state(state
, shut_down
);
2234 if (previous_state
== potential_conflict
)
2235 dhcp_failover_send_update_request (state
);
2237 log_error("Peer %s: Unexpected move to "
2238 "conflict-done.", state
->name
);
2243 case potential_conflict
:
2245 case communications_interrupted
:
2246 case resolution_interrupted
:
2251 dhcp_failover_set_state (state
, recover
);
2255 dhcp_failover_set_state (state
, partner_down
);
2259 /* unknown_state, startup */
2265 switch (new_state
) {
2268 dhcp_failover_set_state(state
, new_state
);
2271 case potential_conflict
:
2272 case resolution_interrupted
:
2274 * This can happen when the connection is lost and
2275 * recovered after the primary has moved to
2276 * conflict-done but the secondary is still in
2277 * potential-conflict. In that case, we have to
2278 * remain in conflict-done.
2283 log_fatal("Peer %s: Invalid attempt to move from %s "
2284 "to %s while local state is conflict-done.",
2286 dhcp_failover_state_name_print(previous_state
),
2287 dhcp_failover_state_name_print(new_state
));
2292 /* Take no action if other server is starting up. */
2296 switch (new_state
) {
2297 /* This is where we should be. */
2303 dhcp_failover_set_state (state
, normal
);
2307 case potential_conflict
:
2309 case communications_interrupted
:
2310 case resolution_interrupted
:
2312 dhcp_failover_set_state (state
, potential_conflict
);
2316 /* shut_down, paused, unknown_state, startup */
2321 case communications_interrupted
:
2322 switch (new_state
) {
2324 /* Stick with the status quo. */
2327 /* If we're in communications-interrupted and an
2328 amnesic peer connects, go to the partner_down
2329 state immediately. */
2331 dhcp_failover_set_state (state
, partner_down
);
2335 case communications_interrupted
:
2338 /* XXX so we don't need to do this specially in
2339 XXX the CONNECT and CONNECTACK handlers. */
2340 dhcp_failover_send_updates (state
);
2341 dhcp_failover_set_state (state
, normal
);
2344 case potential_conflict
:
2346 case resolution_interrupted
:
2348 dhcp_failover_set_state (state
, potential_conflict
);
2352 dhcp_failover_set_state (state
, partner_down
);
2356 /* unknown_state, startup */
2361 case resolution_interrupted
:
2362 switch (new_state
) {
2365 case potential_conflict
:
2367 case communications_interrupted
:
2368 case resolution_interrupted
:
2372 dhcp_failover_set_state (state
, potential_conflict
);
2376 dhcp_failover_set_state (state
, partner_down
);
2380 /* paused, unknown_state, startup */
2385 /* Make no transitions while in recover_wait...just wait. */
2390 switch (new_state
) {
2392 log_error("Both servers have entered recover-done!");
2393 /* Fall through and tranistion to normal anyway */
2396 dhcp_failover_set_state (state
, normal
);
2400 dhcp_failover_set_state (state
, partner_down
);
2404 /* potential_conflict, partner_down,
2405 * communications_interrupted, resolution_interrupted,
2406 * paused, recover, recover_wait, unknown_state,
2413 /* We are essentially dead in the water when we're in
2414 either shut_down or paused states, and do not do any
2415 automatic state transitions. */
2420 /* XXX: Shouldn't this be a fatal condition? */
2425 log_fatal("Impossible condition at %s:%d.", MDL
);
2430 /* If we didn't make a transition out of startup as a result of
2431 the peer's state change, do it now as a result of the fact that
2432 we got a state change from the peer. */
2433 if (state
-> me
.state
== startup
&& state
-> saved_state
!= startup
)
2434 dhcp_failover_set_state (state
, state
-> saved_state
);
2436 /* For now, just set the service state based on the peer's state
2438 dhcp_failover_set_service_state (state
);
2440 return ISC_R_SUCCESS
;
2444 * Balance operation manual entry; startup, entrance to normal state. No
2445 * sense sending a POOLREQ at this stage; the peer is likely about to schedule
2446 * their own rebalance event upon entering normal themselves.
2449 dhcp_failover_pool_balance(dhcp_failover_state_t
*state
)
2451 /* Cancel pending event. */
2452 cancel_timeout(dhcp_failover_pool_rebalance
, state
);
2453 state
->sched_balance
= 0;
2455 dhcp_failover_pool_dobalance(state
, NULL
);
2459 * Balance operation entry from timer event. Once per timer interval is
2460 * the only time we want to emit POOLREQs (asserting an interrupt in our
2464 dhcp_failover_pool_rebalance(void *failover_state
)
2466 dhcp_failover_state_t
*state
;
2467 isc_boolean_t sendreq
= ISC_FALSE
;
2469 state
= (dhcp_failover_state_t
*)failover_state
;
2471 /* Clear scheduled event indicator. */
2472 state
->sched_balance
= 0;
2474 if (dhcp_failover_pool_dobalance(state
, &sendreq
))
2475 dhcp_failover_send_updates(state
);
2478 dhcp_failover_send_poolreq(state
);
2482 * Balance operation entry from POOLREQ protocol message. Do not permit a
2483 * POOLREQ to send back a POOLREQ. Ping pong.
2486 dhcp_failover_pool_reqbalance(dhcp_failover_state_t
*state
)
2490 /* Cancel pending event. */
2491 cancel_timeout(dhcp_failover_pool_rebalance
, state
);
2492 state
->sched_balance
= 0;
2494 queued
= dhcp_failover_pool_dobalance(state
, NULL
);
2496 dhcp_failover_send_poolresp(state
, queued
);
2499 dhcp_failover_send_updates(state
);
2501 log_info("peer %s: Got POOLREQ, answering negatively! "
2502 "Peer may be out of leases or database inconsistent.",
2507 * Do the meat of the work common to all forms of pool rebalance. If the
2508 * caller deems it appropriate to transmit POOLREQ messages, it can use the
2509 * sendreq pointer to pass in the address of a FALSE value which this function
2510 * will conditionally turn TRUE if a POOLREQ is determined to be necessary.
2511 * A NULL value may be passed, in which case no action is taken.
2514 dhcp_failover_pool_dobalance(dhcp_failover_state_t
*state
,
2515 isc_boolean_t
*sendreq
)
2517 int lts
, total
, thresh
, hold
, panic
, pass
;
2518 int leases_queued
= 0;
2519 struct lease
*lp
= NULL
;
2520 struct lease
*next
= NULL
;
2521 struct lease
*ltemp
= NULL
;
2522 struct shared_network
*s
;
2524 binding_state_t peer_lease_state
;
2525 /* binding_state_t my_lease_state; */
2526 /* XXX Why is this my_lease_state never used? */
2527 LEASE_STRUCT_PTR lq
;
2528 int (*log_func
)(const char *, ...);
2529 const char *result
, *reqlog
;
2531 if (state
-> me
.state
!= normal
)
2534 state
->last_balance
= cur_time
;
2536 for (s
= shared_networks
; s
; s
= s
->next
) {
2537 for (p
= s
->pools
; p
; p
= p
->next
) {
2538 if (p
->failover_peer
!= state
)
2541 /* Right now we're giving the peer half of the free leases.
2542 If we have more leases than the peer (i.e., more than
2543 half), then the number of leases we have, less the number
2544 of leases the peer has, will be how many more leases we
2545 have than the peer has. So if we send half that number
2546 to the peer, we should be even. */
2547 if (p
->failover_peer
->i_am
== primary
) {
2548 lts
= (p
->free_leases
- p
->backup_leases
) / 2;
2549 peer_lease_state
= FTS_BACKUP
;
2550 /* my_lease_state = FTS_FREE; */
2553 lts
= (p
->backup_leases
- p
->free_leases
) / 2;
2554 peer_lease_state
= FTS_FREE
;
2555 /* my_lease_state = FTS_BACKUP; */
2559 total
= p
->backup_leases
+ p
->free_leases
;
2561 thresh
= ((total
* state
->max_lease_misbalance
) + 50) / 100;
2562 hold
= ((total
* state
->max_lease_ownership
) + 50) / 100;
2565 * If we need leases (so lts is negative) more than negative
2566 * double the thresh%, panic and send poolreq to hopefully wake
2567 * up the peer (but more likely the db is inconsistent). But,
2568 * if this comes out zero, switch to -1 so that the POOLREQ is
2569 * sent on lts == -2 rather than right away at -1.
2571 * Note that we do not subtract -1 from panic all the time
2572 * because thresh% and hold% may come out to the same number,
2573 * and that is correct operation...where thresh% and hold% are
2574 * both -1, we want to send poolreq when lts reaches -3. So,
2575 * "-3 < -2", lts < panic.
2577 panic
= thresh
* -2;
2582 if ((sendreq
!= NULL
) && (lts
< panic
)) {
2583 reqlog
= " (requesting peer rebalance!)";
2584 *sendreq
= ISC_TRUE
;
2588 log_info("balancing pool %lx %s total %d free %d "
2589 "backup %d lts %d max-own (+/-)%d%s",
2591 (p
->shared_network
?
2592 p
->shared_network
->name
: ""), p
->lease_count
,
2593 p
->free_leases
, p
->backup_leases
, lts
, hold
,
2596 /* In the first pass, try to allocate leases to the
2597 * peer which it would normally be responsible for (if
2598 * the lease has a hardware address or client-identifier,
2599 * and the load-balance-algorithm chooses the peer to
2600 * answer that address), up to a hold% excess in the peer's
2601 * favor. In the second pass, just send the oldest (first
2602 * on the list) leases up to a hold% excess in our favor.
2604 * This could make for additional pool rebalance
2605 * events, but preserving MAC possession should be
2609 lease_reference(&lp
, LEASE_GET_FIRSTP(lq
), MDL
);
2613 lease_dereference(&next
, MDL
);
2614 ltemp
= LEASE_GET_NEXTP(lq
, lp
);
2616 lease_reference(&next
, ltemp
, MDL
);
2619 * Stop if the pool is 'balanced enough.'
2621 * The pool is balanced enough if:
2623 * 1) We're on the first run through and the peer has
2624 * its fair share of leases already (lts reaches
2626 * 2) We're on the second run through, we are shifting
2627 * never-used leases, and there is a perfectly even
2628 * balance (lts reaches zero).
2629 * 3) Second run through, we are shifting previously
2630 * used leases, and the local system has its fair
2631 * share but no more (lts reaches hold).
2633 * Note that this is implemented below in 3,2,1 order.
2643 } else if (lts
<= -hold
)
2646 if (pass
|| peer_wants_lease(lp
)) {
2649 lp
->next_binding_state
= peer_lease_state
;
2650 lp
->tstp
= cur_time
;
2651 lp
->starts
= cur_time
;
2653 scrub_lease(lp
, MDL
);
2654 if (!supersede_lease(lp
, NULL
, 0, 1, 0, 0) ||
2656 log_error("can't commit lease %s on "
2657 "giveaway", piaddr(lp
->ip_addr
));
2660 lease_dereference(&lp
, MDL
);
2662 lease_reference(&lp
, next
, MDL
);
2665 lease_reference(&lp
, LEASE_GET_FIRSTP(lq
), MDL
);
2670 lease_dereference(&next
, MDL
);
2672 lease_dereference(&lp
, MDL
);
2675 result
= "IMBALANCED";
2676 log_func
= log_error
;
2678 result
= "balanced";
2679 log_func
= log_info
;
2682 log_func("%s pool %lx %s total %d free %d backup %d "
2683 "lts %d max-misbal %d", result
, (unsigned long)p
,
2684 (p
->shared_network
?
2685 p
->shared_network
->name
: ""), p
->lease_count
,
2686 p
->free_leases
, p
->backup_leases
, lts
, thresh
);
2688 /* Recalculate next rebalance event timer. */
2689 dhcp_failover_pool_check(p
);
2696 return leases_queued
;
2699 /* dhcp_failover_pool_check: Called whenever FREE or BACKUP leases change
2700 * states, on both servers. Check the scheduled time to rebalance the pool
2701 * and lower it if applicable.
2704 dhcp_failover_pool_check(struct pool
*pool
)
2706 dhcp_failover_state_t
*peer
;
2709 struct lease
*ltemp
;
2711 peer
= pool
->failover_peer
;
2713 if(!peer
|| peer
->me
.state
!= normal
)
2716 /* Estimate the time left until lease exhaustion.
2717 * The first lease on the backup or free lists is also the oldest
2718 * lease. It is reasonable to guess that it will take at least
2719 * as much time for a pool to run out of leases, as the present
2720 * age of the oldest lease (seconds since it expired).
2722 * Note that this isn't so sane of an assumption if the oldest
2723 * lease is a virgin (ends = 0), we wind up sending this against
2724 * the max_balance bounds check.
2726 ltemp
= LEASE_GET_FIRST(pool
->free
);
2727 if(ltemp
&& ltemp
->ends
< cur_time
)
2728 est1
= cur_time
- ltemp
->ends
;
2732 ltemp
= LEASE_GET_FIRST(pool
->backup
);
2733 if(ltemp
&& ltemp
->ends
< cur_time
)
2734 est2
= cur_time
- ltemp
->ends
;
2738 /* We don't want to schedule rebalance for when we think we'll run
2739 * out of leases, we want to schedule the rebalance for when we think
2740 * the disparity will be 'large enough' to warrant action.
2742 est1
= ((est1
* peer
->max_lease_misbalance
) + 50) / 100;
2743 est2
= ((est2
* peer
->max_lease_misbalance
) + 50) / 100;
2745 /* Guess when the local system will begin issuing POOLREQ panic
2746 * attacks because "max_lease_misbalance*2" has been exceeded.
2748 if(peer
->i_am
== primary
)
2753 /* Select the smallest time. */
2757 /* Bounded by the maximum configured value. */
2758 if(est1
> peer
->max_balance
)
2759 est1
= peer
->max_balance
;
2761 /* Project this time into the future. */
2764 /* Do not move the time down under the minimum. */
2765 est2
= peer
->last_balance
+ peer
->min_balance
;
2766 if(peer
->last_balance
&& (est1
< est2
))
2769 /* Introduce a random delay. */
2770 est1
+= random() % 5;
2772 /* Do not move the time forward, or reset to the same time. */
2773 if(peer
->sched_balance
) {
2774 if (est1
>= peer
->sched_balance
)
2777 /* We are about to schedule the time down, cancel the
2780 cancel_timeout(dhcp_failover_pool_rebalance
, peer
);
2783 /* The time is different, and lower, use it. */
2784 peer
->sched_balance
= est1
;
2786 #if defined(DEBUG_FAILOVER_TIMING)
2787 log_info("add_timeout +%d dhcp_failover_pool_rebalance",
2788 (int)(est1
- cur_time
));
2792 add_timeout(&tv
, dhcp_failover_pool_rebalance
, peer
,
2793 (tvref_t
)dhcp_failover_state_reference
,
2794 (tvunref_t
)dhcp_failover_state_dereference
);
2797 int dhcp_failover_state_pool_check (dhcp_failover_state_t
*state
)
2799 struct shared_network
*s
;
2802 for (s
= shared_networks
; s
; s
= s
-> next
) {
2803 for (p
= s
-> pools
; p
; p
= p
-> next
) {
2804 if (p
-> failover_peer
!= state
)
2806 dhcp_failover_pool_check (p
);
2812 isc_result_t
dhcp_failover_send_updates (dhcp_failover_state_t
*state
)
2814 struct lease
*lp
= (struct lease
*)0;
2815 isc_result_t status
;
2817 /* Can't update peer if we're not talking to it! */
2818 if (!state
-> link_to_peer
)
2819 return ISC_R_SUCCESS
;
2821 /* If there are acks pending, transmit them prior to potentially
2822 * sending new updates for the same lease.
2824 if (state
->toack_queue_head
!= NULL
)
2825 dhcp_failover_send_acks(state
);
2827 while ((state
-> partner
.max_flying_updates
>
2828 state
-> cur_unacked_updates
) && state
-> update_queue_head
) {
2829 /* Grab the head of the update queue. */
2830 lease_reference (&lp
, state
-> update_queue_head
, MDL
);
2832 /* Send the update to the peer. */
2833 status
= dhcp_failover_send_bind_update (state
, lp
);
2834 if (status
!= ISC_R_SUCCESS
) {
2835 lease_dereference (&lp
, MDL
);
2838 lp
-> flags
&= ~ON_UPDATE_QUEUE
;
2840 /* Take it off the head of the update queue and put the next
2841 item in the update queue at the head. */
2842 lease_dereference (&state
-> update_queue_head
, MDL
);
2843 if (lp
-> next_pending
) {
2844 lease_reference (&state
-> update_queue_head
,
2845 lp
-> next_pending
, MDL
);
2846 lease_dereference (&lp
-> next_pending
, MDL
);
2848 lease_dereference (&state
-> update_queue_tail
, MDL
);
2851 if (state
-> ack_queue_head
) {
2853 (&state
-> ack_queue_tail
-> next_pending
,
2855 lease_dereference (&state
-> ack_queue_tail
, MDL
);
2857 lease_reference (&state
-> ack_queue_head
, lp
, MDL
);
2859 #if defined (POINTER_DEBUG)
2860 if (lp
-> next_pending
) {
2861 log_error ("ack_queue_tail: lp -> next_pending");
2865 lease_reference (&state
-> ack_queue_tail
, lp
, MDL
);
2866 lp
-> flags
|= ON_ACK_QUEUE
;
2867 lease_dereference (&lp
, MDL
);
2869 /* Count the object as an unacked update. */
2870 state
-> cur_unacked_updates
++;
2872 return ISC_R_SUCCESS
;
2875 /* Queue an update for a lease. Always returns 1 at this point - it's
2876 not an error for this to be called on a lease for which there's no
2879 int dhcp_failover_queue_update (struct lease
*lease
, int immediate
)
2881 dhcp_failover_state_t
*state
;
2883 if (!lease
-> pool
||
2884 !lease
-> pool
-> failover_peer
)
2887 /* If it's already on the update queue, leave it there. */
2888 if (lease
-> flags
& ON_UPDATE_QUEUE
)
2891 /* Get the failover state structure for this lease. */
2892 state
= lease
-> pool
-> failover_peer
;
2894 /* If it's on the ack queue, take it off. */
2895 if (lease
-> flags
& ON_ACK_QUEUE
)
2896 dhcp_failover_ack_queue_remove (state
, lease
);
2898 if (state
-> update_queue_head
) {
2899 lease_reference (&state
-> update_queue_tail
-> next_pending
,
2901 lease_dereference (&state
-> update_queue_tail
, MDL
);
2903 lease_reference (&state
-> update_queue_head
, lease
, MDL
);
2905 #if defined (POINTER_DEBUG)
2906 if (lease
-> next_pending
) {
2907 log_error ("next pending on update queue lease.");
2908 #if defined (DEBUG_RC_HISTORY)
2909 dump_rc_history (lease
);
2914 lease_reference (&state
-> update_queue_tail
, lease
, MDL
);
2915 lease
-> flags
|= ON_UPDATE_QUEUE
;
2917 dhcp_failover_send_updates (state
);
2921 int dhcp_failover_send_acks (dhcp_failover_state_t
*state
)
2923 failover_message_t
*msg
= (failover_message_t
*)0;
2925 /* Must commit all leases prior to acking them. */
2926 if (!commit_leases ())
2929 while (state
-> toack_queue_head
) {
2930 failover_message_reference
2931 (&msg
, state
-> toack_queue_head
, MDL
);
2932 failover_message_dereference
2933 (&state
-> toack_queue_head
, MDL
);
2935 failover_message_reference
2936 (&state
-> toack_queue_head
, msg
-> next
, MDL
);
2939 dhcp_failover_send_bind_ack (state
, msg
, 0, (const char *)0);
2941 failover_message_dereference (&msg
, MDL
);
2944 if (state
-> toack_queue_tail
)
2945 failover_message_dereference (&state
-> toack_queue_tail
, MDL
);
2946 state
-> pending_acks
= 0;
2951 void dhcp_failover_toack_queue_timeout (void *vs
)
2953 dhcp_failover_state_t
*state
= vs
;
2955 #if defined (DEBUG_FAILOVER_TIMING)
2956 log_info ("dhcp_failover_toack_queue_timeout");
2959 dhcp_failover_send_acks (state
);
2962 /* Queue an ack for a message. There is currently no way to queue a
2963 negative ack -- these need to be sent directly. */
2965 int dhcp_failover_queue_ack (dhcp_failover_state_t
*state
,
2966 failover_message_t
*msg
)
2970 if (state
-> toack_queue_head
) {
2971 failover_message_reference
2972 (&state
-> toack_queue_tail
-> next
, msg
, MDL
);
2973 failover_message_dereference (&state
-> toack_queue_tail
, MDL
);
2975 failover_message_reference (&state
-> toack_queue_head
,
2978 failover_message_reference (&state
-> toack_queue_tail
, msg
, MDL
);
2980 state
-> pending_acks
++;
2982 /* Flush the toack queue whenever we exceed half the number of
2983 allowed unacked updates. */
2984 if (state
-> pending_acks
>= state
-> partner
.max_flying_updates
/ 2) {
2985 dhcp_failover_send_acks (state
);
2988 /* Schedule a timeout to flush the ack queue. */
2989 if (state
-> pending_acks
> 0) {
2990 #if defined (DEBUG_FAILOVER_TIMING)
2991 log_info ("add_timeout +2 %s",
2992 "dhcp_failover_toack_queue_timeout");
2994 tv
. tv_sec
= cur_time
+ 2;
2997 dhcp_failover_toack_queue_timeout
, state
,
2998 (tvref_t
)dhcp_failover_state_reference
,
2999 (tvunref_t
)dhcp_failover_state_dereference
);
3005 void dhcp_failover_ack_queue_remove (dhcp_failover_state_t
*state
,
3006 struct lease
*lease
)
3010 if (!(lease
-> flags
& ON_ACK_QUEUE
))
3013 if (state
-> ack_queue_head
== lease
) {
3014 lease_dereference (&state
-> ack_queue_head
, MDL
);
3015 if (lease
-> next_pending
) {
3016 lease_reference (&state
-> ack_queue_head
,
3017 lease
-> next_pending
, MDL
);
3018 lease_dereference (&lease
-> next_pending
, MDL
);
3020 lease_dereference (&state
-> ack_queue_tail
, MDL
);
3023 for (lp
= state
-> ack_queue_head
;
3024 lp
&& lp
-> next_pending
!= lease
;
3025 lp
= lp
-> next_pending
)
3031 lease_dereference (&lp
-> next_pending
, MDL
);
3032 if (lease
-> next_pending
) {
3033 lease_reference (&lp
-> next_pending
,
3034 lease
-> next_pending
, MDL
);
3035 lease_dereference (&lease
-> next_pending
, MDL
);
3037 lease_dereference (&state
-> ack_queue_tail
, MDL
);
3038 if (lp
-> next_pending
) {
3039 log_error ("state -> ack_queue_tail");
3042 lease_reference (&state
-> ack_queue_tail
, lp
, MDL
);
3046 lease
-> flags
&= ~ON_ACK_QUEUE
;
3047 /* Multiple acks on one XID is an error and may cause badness. */
3048 lease
->last_xid
= 0;
3049 /* XXX: this violates draft-failover. We can't send another
3050 * update just because we forgot about an old one that hasn't
3053 state
-> cur_unacked_updates
--;
3056 * When updating leases as a result of an ack, we defer the commit
3057 * for performance reasons. When there are no more acks pending,
3060 if (state
-> cur_unacked_updates
== 0) {
3065 isc_result_t
dhcp_failover_state_set_value (omapi_object_t
*h
,
3067 omapi_data_string_t
*name
,
3068 omapi_typed_data_t
*value
)
3070 isc_result_t status
;
3072 if (h
-> type
!= dhcp_type_failover_state
)
3073 return DHCP_R_INVALIDARG
;
3075 /* This list of successful returns is completely wrong, but the
3076 fastest way to make dhcpctl do something vaguely sane when
3077 you try to change the local state. */
3079 if (!omapi_ds_strcmp (name
, "name")) {
3080 return ISC_R_SUCCESS
;
3081 } else if (!omapi_ds_strcmp (name
, "partner-address")) {
3082 return ISC_R_SUCCESS
;
3083 } else if (!omapi_ds_strcmp (name
, "local-address")) {
3084 return ISC_R_SUCCESS
;
3085 } else if (!omapi_ds_strcmp (name
, "partner-port")) {
3086 return ISC_R_SUCCESS
;
3087 } else if (!omapi_ds_strcmp (name
, "local-port")) {
3088 return ISC_R_SUCCESS
;
3089 } else if (!omapi_ds_strcmp (name
, "max-outstanding-updates")) {
3090 return ISC_R_SUCCESS
;
3091 } else if (!omapi_ds_strcmp (name
, "mclt")) {
3092 return ISC_R_SUCCESS
;
3093 } else if (!omapi_ds_strcmp (name
, "load-balance-max-secs")) {
3094 return ISC_R_SUCCESS
;
3095 } else if (!omapi_ds_strcmp (name
, "load-balance-hba")) {
3096 return ISC_R_SUCCESS
;
3097 } else if (!omapi_ds_strcmp (name
, "partner-state")) {
3098 return ISC_R_SUCCESS
;
3099 } else if (!omapi_ds_strcmp (name
, "local-state")) {
3101 status
= omapi_get_int_value (&l
, value
);
3102 if (status
!= ISC_R_SUCCESS
)
3104 return dhcp_failover_set_state ((dhcp_failover_state_t
*)h
, l
);
3105 } else if (!omapi_ds_strcmp (name
, "partner-stos")) {
3106 return ISC_R_SUCCESS
;
3107 } else if (!omapi_ds_strcmp (name
, "local-stos")) {
3108 return ISC_R_SUCCESS
;
3109 } else if (!omapi_ds_strcmp (name
, "hierarchy")) {
3110 return ISC_R_SUCCESS
;
3111 } else if (!omapi_ds_strcmp (name
, "last-packet-sent")) {
3112 return ISC_R_SUCCESS
;
3113 } else if (!omapi_ds_strcmp (name
, "last-timestamp-received")) {
3114 return ISC_R_SUCCESS
;
3115 } else if (!omapi_ds_strcmp (name
, "skew")) {
3116 return ISC_R_SUCCESS
;
3117 } else if (!omapi_ds_strcmp (name
, "max-response-delay")) {
3118 return ISC_R_SUCCESS
;
3119 } else if (!omapi_ds_strcmp (name
, "cur-unacked-updates")) {
3120 return ISC_R_SUCCESS
;
3123 if (h
-> inner
&& h
-> inner
-> type
-> set_value
)
3124 return (*(h
-> inner
-> type
-> set_value
))
3125 (h
-> inner
, id
, name
, value
);
3126 return ISC_R_NOTFOUND
;
3129 void dhcp_failover_keepalive (void *vs
)
3133 void dhcp_failover_reconnect (void *vs
)
3135 dhcp_failover_state_t
*state
= vs
;
3136 isc_result_t status
;
3139 #if defined (DEBUG_FAILOVER_TIMING)
3140 log_info ("dhcp_failover_reconnect");
3142 /* If we already connected the other way, let the connection
3143 recovery code initiate any retry that may be required. */
3144 if (state
-> link_to_peer
)
3147 status
= dhcp_failover_link_initiate ((omapi_object_t
*)state
);
3148 if (status
!= ISC_R_SUCCESS
&& status
!= DHCP_R_INCOMPLETE
) {
3149 log_info ("failover peer %s: %s", state
-> name
,
3150 isc_result_totext (status
));
3151 #if defined (DEBUG_FAILOVER_TIMING)
3152 log_info("add_timeout +90 dhcp_failover_reconnect");
3154 tv
. tv_sec
= cur_time
+ 90;
3156 add_timeout(&tv
, dhcp_failover_reconnect
, state
,
3157 (tvref_t
)dhcp_failover_state_reference
,
3158 (tvunref_t
)dhcp_failover_state_dereference
);
3162 void dhcp_failover_startup_timeout (void *vs
)
3164 dhcp_failover_state_t
*state
= vs
;
3166 #if defined (DEBUG_FAILOVER_TIMING)
3167 log_info ("dhcp_failover_startup_timeout");
3170 dhcp_failover_state_transition (state
, "disconnect");
3173 void dhcp_failover_link_startup_timeout (void *vl
)
3175 dhcp_failover_link_t
*link
= vl
;
3178 for (p
= (omapi_object_t
*)link
; p
-> inner
; p
= p
-> inner
)
3180 for (; p
; p
= p
-> outer
)
3181 if (p
-> type
== omapi_type_connection
)
3184 log_info ("failover: link startup timeout");
3185 omapi_disconnect (p
, 1);
3189 void dhcp_failover_listener_restart (void *vs
)
3191 dhcp_failover_state_t
*state
= vs
;
3192 isc_result_t status
;
3195 #if defined (DEBUG_FAILOVER_TIMING)
3196 log_info ("dhcp_failover_listener_restart");
3199 status
= dhcp_failover_listen ((omapi_object_t
*)state
);
3200 if (status
!= ISC_R_SUCCESS
) {
3201 log_info ("failover peer %s: %s", state
-> name
,
3202 isc_result_totext (status
));
3203 #if defined (DEBUG_FAILOVER_TIMING)
3204 log_info ("add_timeout +90 %s",
3205 "dhcp_failover_listener_restart");
3207 tv
. tv_sec
= cur_time
+ 90;
3210 dhcp_failover_listener_restart
, state
,
3211 (tvref_t
)dhcp_failover_state_reference
,
3212 (tvunref_t
)dhcp_failover_state_dereference
);
3217 dhcp_failover_auto_partner_down(void *vs
)
3219 dhcp_failover_state_t
*state
= vs
;
3221 #if defined (DEBUG_FAILOVER_TIMING)
3222 log_info("dhcp_failover_auto_partner_down");
3225 dhcp_failover_set_state(state
, partner_down
);
3228 isc_result_t
dhcp_failover_state_get_value (omapi_object_t
*h
,
3230 omapi_data_string_t
*name
,
3231 omapi_value_t
**value
)
3233 dhcp_failover_state_t
*s
;
3234 struct option_cache
*oc
;
3235 struct data_string ds
;
3236 isc_result_t status
;
3238 if (h
-> type
!= dhcp_type_failover_state
)
3239 return DHCP_R_INVALIDARG
;
3240 s
= (dhcp_failover_state_t
*)h
;
3242 if (!omapi_ds_strcmp (name
, "name")) {
3244 return omapi_make_string_value (value
,
3245 name
, s
-> name
, MDL
);
3246 return ISC_R_NOTFOUND
;
3247 } else if (!omapi_ds_strcmp (name
, "partner-address")) {
3248 oc
= s
-> partner
.address
;
3250 memset (&ds
, 0, sizeof ds
);
3251 if (!evaluate_option_cache (&ds
, (struct packet
*)0,
3253 (struct client_state
*)0,
3254 (struct option_state
*)0,
3255 (struct option_state
*)0,
3256 &global_scope
, oc
, MDL
)) {
3257 return ISC_R_NOTFOUND
;
3259 status
= omapi_make_const_value (value
,
3260 name
, ds
.data
, ds
.len
, MDL
);
3261 /* Disgusting kludge: */
3262 if (oc
== s
-> me
.address
&& !s
-> server_identifier
.len
)
3263 data_string_copy (&s
-> server_identifier
, &ds
, MDL
);
3264 data_string_forget (&ds
, MDL
);
3266 } else if (!omapi_ds_strcmp (name
, "local-address")) {
3267 oc
= s
-> me
.address
;
3269 } else if (!omapi_ds_strcmp (name
, "partner-port")) {
3270 return omapi_make_int_value (value
, name
,
3271 s
-> partner
.port
, MDL
);
3272 } else if (!omapi_ds_strcmp (name
, "local-port")) {
3273 return omapi_make_int_value (value
,
3274 name
, s
-> me
.port
, MDL
);
3275 } else if (!omapi_ds_strcmp (name
, "max-outstanding-updates")) {
3276 return omapi_make_uint_value (value
, name
,
3277 s
-> me
.max_flying_updates
,
3279 } else if (!omapi_ds_strcmp (name
, "mclt")) {
3280 return omapi_make_uint_value (value
, name
, s
-> mclt
, MDL
);
3281 } else if (!omapi_ds_strcmp (name
, "load-balance-max-secs")) {
3282 return omapi_make_int_value (value
, name
,
3283 s
-> load_balance_max_secs
, MDL
);
3284 } else if (!omapi_ds_strcmp (name
, "load-balance-hba")) {
3286 return omapi_make_const_value (value
, name
,
3288 return ISC_R_NOTFOUND
;
3289 } else if (!omapi_ds_strcmp (name
, "partner-state")) {
3290 return omapi_make_uint_value (value
, name
,
3291 s
-> partner
.state
, MDL
);
3292 } else if (!omapi_ds_strcmp (name
, "local-state")) {
3293 return omapi_make_uint_value (value
, name
,
3294 s
-> me
.state
, MDL
);
3295 } else if (!omapi_ds_strcmp (name
, "partner-stos")) {
3296 return omapi_make_int_value (value
, name
,
3297 s
-> partner
.stos
, MDL
);
3298 } else if (!omapi_ds_strcmp (name
, "local-stos")) {
3299 return omapi_make_int_value (value
, name
,
3301 } else if (!omapi_ds_strcmp (name
, "hierarchy")) {
3302 return omapi_make_uint_value (value
, name
, s
-> i_am
, MDL
);
3303 } else if (!omapi_ds_strcmp (name
, "last-packet-sent")) {
3304 return omapi_make_int_value (value
, name
,
3305 s
-> last_packet_sent
, MDL
);
3306 } else if (!omapi_ds_strcmp (name
, "last-timestamp-received")) {
3307 return omapi_make_int_value (value
, name
,
3308 s
-> last_timestamp_received
,
3310 } else if (!omapi_ds_strcmp (name
, "skew")) {
3311 return omapi_make_int_value (value
, name
, s
-> skew
, MDL
);
3312 } else if (!omapi_ds_strcmp (name
, "max-response-delay")) {
3313 return omapi_make_uint_value (value
, name
,
3314 s
-> me
.max_response_delay
,
3316 } else if (!omapi_ds_strcmp (name
, "cur-unacked-updates")) {
3317 return omapi_make_int_value (value
, name
,
3318 s
-> cur_unacked_updates
, MDL
);
3321 if (h
-> inner
&& h
-> inner
-> type
-> get_value
)
3322 return (*(h
-> inner
-> type
-> get_value
))
3323 (h
-> inner
, id
, name
, value
);
3324 return ISC_R_NOTFOUND
;
3327 isc_result_t
dhcp_failover_state_destroy (omapi_object_t
*h
,
3328 const char *file
, int line
)
3330 dhcp_failover_state_t
*s
;
3332 if (h
-> type
!= dhcp_type_failover_state
)
3333 return DHCP_R_INVALIDARG
;
3334 s
= (dhcp_failover_state_t
*)h
;
3336 if (s
-> link_to_peer
)
3337 dhcp_failover_link_dereference (&s
-> link_to_peer
, file
, line
);
3339 dfree (s
-> name
, MDL
);
3340 s
-> name
= (char *)0;
3342 if (s
-> partner
.address
)
3343 option_cache_dereference (&s
-> partner
.address
, file
, line
);
3344 if (s
-> me
.address
)
3345 option_cache_dereference (&s
-> me
.address
, file
, line
);
3347 dfree (s
-> hba
, file
, line
);
3348 s
-> hba
= (u_int8_t
*)0;
3350 if (s
-> update_queue_head
)
3351 lease_dereference (&s
-> update_queue_head
, file
, line
);
3352 if (s
-> update_queue_tail
)
3353 lease_dereference (&s
-> update_queue_tail
, file
, line
);
3354 if (s
-> ack_queue_head
)
3355 lease_dereference (&s
-> ack_queue_head
, file
, line
);
3356 if (s
-> ack_queue_tail
)
3357 lease_dereference (&s
-> ack_queue_tail
, file
, line
);
3358 if (s
-> send_update_done
)
3359 lease_dereference (&s
-> send_update_done
, file
, line
);
3360 if (s
-> toack_queue_head
)
3361 failover_message_dereference (&s
-> toack_queue_head
,
3363 if (s
-> toack_queue_tail
)
3364 failover_message_dereference (&s
-> toack_queue_tail
,
3366 return ISC_R_SUCCESS
;
3369 /* Write all the published values associated with the object through the
3370 specified connection. */
3372 isc_result_t
dhcp_failover_state_stuff (omapi_object_t
*c
,
3376 /* In this function c should be a (omapi_connection_object_t *) */
3378 dhcp_failover_state_t
*s
;
3379 isc_result_t status
;
3381 if (c
-> type
!= omapi_type_connection
)
3382 return DHCP_R_INVALIDARG
;
3384 if (h
-> type
!= dhcp_type_failover_state
)
3385 return DHCP_R_INVALIDARG
;
3386 s
= (dhcp_failover_state_t
*)h
;
3388 status
= omapi_connection_put_name (c
, "name");
3389 if (status
!= ISC_R_SUCCESS
)
3391 status
= omapi_connection_put_string (c
, s
-> name
);
3392 if (status
!= ISC_R_SUCCESS
)
3395 status
= omapi_connection_put_name (c
, "partner-address");
3396 if (status
!= ISC_R_SUCCESS
)
3398 status
= omapi_connection_put_uint32 (c
, sizeof s
-> partner
.address
);
3399 if (status
!= ISC_R_SUCCESS
)
3401 status
= omapi_connection_copyin (c
, (u_int8_t
*)&s
-> partner
.address
,
3402 sizeof s
-> partner
.address
);
3403 if (status
!= ISC_R_SUCCESS
)
3406 status
= omapi_connection_put_name (c
, "partner-port");
3407 if (status
!= ISC_R_SUCCESS
)
3409 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3410 if (status
!= ISC_R_SUCCESS
)
3412 status
= omapi_connection_put_uint32 (c
, (u_int32_t
)s
-> partner
.port
);
3413 if (status
!= ISC_R_SUCCESS
)
3416 status
= omapi_connection_put_name (c
, "local-address");
3417 if (status
!= ISC_R_SUCCESS
)
3419 status
= omapi_connection_put_uint32 (c
, sizeof s
-> me
.address
);
3420 if (status
!= ISC_R_SUCCESS
)
3422 status
= omapi_connection_copyin (c
, (u_int8_t
*)&s
-> me
.address
,
3423 sizeof s
-> me
.address
);
3424 if (status
!= ISC_R_SUCCESS
)
3427 status
= omapi_connection_put_name (c
, "local-port");
3428 if (status
!= ISC_R_SUCCESS
)
3430 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3431 if (status
!= ISC_R_SUCCESS
)
3433 status
= omapi_connection_put_uint32 (c
, (u_int32_t
)s
-> me
.port
);
3434 if (status
!= ISC_R_SUCCESS
)
3437 status
= omapi_connection_put_name (c
, "max-outstanding-updates");
3438 if (status
!= ISC_R_SUCCESS
)
3440 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3441 if (status
!= ISC_R_SUCCESS
)
3443 status
= omapi_connection_put_uint32 (c
,
3444 s
-> me
.max_flying_updates
);
3445 if (status
!= ISC_R_SUCCESS
)
3448 status
= omapi_connection_put_name (c
, "mclt");
3449 if (status
!= ISC_R_SUCCESS
)
3451 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3452 if (status
!= ISC_R_SUCCESS
)
3454 status
= omapi_connection_put_uint32 (c
, s
-> mclt
);
3455 if (status
!= ISC_R_SUCCESS
)
3458 status
= omapi_connection_put_name (c
, "load-balance-max-secs");
3459 if (status
!= ISC_R_SUCCESS
)
3461 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3462 if (status
!= ISC_R_SUCCESS
)
3464 status
= (omapi_connection_put_uint32
3465 (c
, (u_int32_t
)s
-> load_balance_max_secs
));
3466 if (status
!= ISC_R_SUCCESS
)
3471 status
= omapi_connection_put_name (c
, "load-balance-hba");
3472 if (status
!= ISC_R_SUCCESS
)
3474 status
= omapi_connection_put_uint32 (c
, 32);
3475 if (status
!= ISC_R_SUCCESS
)
3477 status
= omapi_connection_copyin (c
, s
-> hba
, 32);
3478 if (status
!= ISC_R_SUCCESS
)
3482 status
= omapi_connection_put_name (c
, "partner-state");
3483 if (status
!= ISC_R_SUCCESS
)
3485 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3486 if (status
!= ISC_R_SUCCESS
)
3488 status
= omapi_connection_put_uint32 (c
, s
-> partner
.state
);
3489 if (status
!= ISC_R_SUCCESS
)
3492 status
= omapi_connection_put_name (c
, "local-state");
3493 if (status
!= ISC_R_SUCCESS
)
3495 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3496 if (status
!= ISC_R_SUCCESS
)
3498 status
= omapi_connection_put_uint32 (c
, s
-> me
.state
);
3499 if (status
!= ISC_R_SUCCESS
)
3502 status
= omapi_connection_put_name (c
, "partner-stos");
3503 if (status
!= ISC_R_SUCCESS
)
3505 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3506 if (status
!= ISC_R_SUCCESS
)
3508 status
= omapi_connection_put_uint32 (c
,
3509 (u_int32_t
)s
-> partner
.stos
);
3510 if (status
!= ISC_R_SUCCESS
)
3513 status
= omapi_connection_put_name (c
, "local-stos");
3514 if (status
!= ISC_R_SUCCESS
)
3516 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3517 if (status
!= ISC_R_SUCCESS
)
3519 status
= omapi_connection_put_uint32 (c
, (u_int32_t
)s
-> me
.stos
);
3520 if (status
!= ISC_R_SUCCESS
)
3523 status
= omapi_connection_put_name (c
, "hierarchy");
3524 if (status
!= ISC_R_SUCCESS
)
3526 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3527 if (status
!= ISC_R_SUCCESS
)
3529 status
= omapi_connection_put_uint32 (c
, s
-> i_am
);
3530 if (status
!= ISC_R_SUCCESS
)
3533 status
= omapi_connection_put_name (c
, "last-packet-sent");
3534 if (status
!= ISC_R_SUCCESS
)
3536 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3537 if (status
!= ISC_R_SUCCESS
)
3539 status
= (omapi_connection_put_uint32
3540 (c
, (u_int32_t
)s
-> last_packet_sent
));
3541 if (status
!= ISC_R_SUCCESS
)
3544 status
= omapi_connection_put_name (c
, "last-timestamp-received");
3545 if (status
!= ISC_R_SUCCESS
)
3547 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3548 if (status
!= ISC_R_SUCCESS
)
3550 status
= (omapi_connection_put_uint32
3551 (c
, (u_int32_t
)s
-> last_timestamp_received
));
3552 if (status
!= ISC_R_SUCCESS
)
3555 status
= omapi_connection_put_name (c
, "skew");
3556 if (status
!= ISC_R_SUCCESS
)
3558 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3559 if (status
!= ISC_R_SUCCESS
)
3561 status
= omapi_connection_put_uint32 (c
, (u_int32_t
)s
-> skew
);
3562 if (status
!= ISC_R_SUCCESS
)
3565 status
= omapi_connection_put_name (c
, "max-response-delay");
3566 if (status
!= ISC_R_SUCCESS
)
3568 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3569 if (status
!= ISC_R_SUCCESS
)
3571 status
= (omapi_connection_put_uint32
3572 (c
, (u_int32_t
)s
-> me
.max_response_delay
));
3573 if (status
!= ISC_R_SUCCESS
)
3576 status
= omapi_connection_put_name (c
, "cur-unacked-updates");
3577 if (status
!= ISC_R_SUCCESS
)
3579 status
= omapi_connection_put_uint32 (c
, sizeof (u_int32_t
));
3580 if (status
!= ISC_R_SUCCESS
)
3582 status
= (omapi_connection_put_uint32
3583 (c
, (u_int32_t
)s
-> cur_unacked_updates
));
3584 if (status
!= ISC_R_SUCCESS
)
3587 if (h
-> inner
&& h
-> inner
-> type
-> stuff_values
)
3588 return (*(h
-> inner
-> type
-> stuff_values
)) (c
, id
,
3590 return ISC_R_SUCCESS
;
3593 isc_result_t
dhcp_failover_state_lookup (omapi_object_t
**sp
,
3595 omapi_object_t
*ref
)
3597 omapi_value_t
*tv
= (omapi_value_t
*)0;
3598 isc_result_t status
;
3599 dhcp_failover_state_t
*s
;
3602 return DHCP_R_NOKEYS
;
3604 /* First see if we were sent a handle. */
3605 status
= omapi_get_value_str (ref
, id
, "handle", &tv
);
3606 if (status
== ISC_R_SUCCESS
) {
3607 status
= omapi_handle_td_lookup (sp
, tv
-> value
);
3609 omapi_value_dereference (&tv
, MDL
);
3610 if (status
!= ISC_R_SUCCESS
)
3613 /* Don't return the object if the type is wrong. */
3614 if ((*sp
) -> type
!= dhcp_type_failover_state
) {
3615 omapi_object_dereference (sp
, MDL
);
3616 return DHCP_R_INVALIDARG
;
3620 /* Look the failover state up by peer name. */
3621 status
= omapi_get_value_str (ref
, id
, "name", &tv
);
3622 if (status
== ISC_R_SUCCESS
) {
3623 for (s
= failover_states
; s
; s
= s
-> next
) {
3624 unsigned l
= strlen (s
-> name
);
3625 if (l
== tv
-> value
-> u
.buffer
.len
&&
3627 tv
-> value
-> u
.buffer
.value
, l
))
3630 omapi_value_dereference (&tv
, MDL
);
3632 /* If we already have a lease, and it's not the same one,
3633 then the query was invalid. */
3634 if (*sp
&& *sp
!= (omapi_object_t
*)s
) {
3635 omapi_object_dereference (sp
, MDL
);
3636 return DHCP_R_KEYCONFLICT
;
3639 omapi_object_dereference (sp
, MDL
);
3640 return ISC_R_NOTFOUND
;
3642 /* XXX fix so that hash lookup itself creates
3643 XXX the reference. */
3644 omapi_object_reference (sp
, (omapi_object_t
*)s
, MDL
);
3647 /* If we get to here without finding a lease, no valid key was
3650 return DHCP_R_NOKEYS
;
3651 return ISC_R_SUCCESS
;
3654 isc_result_t
dhcp_failover_state_create (omapi_object_t
**sp
,
3657 return ISC_R_NOTIMPLEMENTED
;
3660 isc_result_t
dhcp_failover_state_remove (omapi_object_t
*sp
,
3663 return ISC_R_NOTIMPLEMENTED
;
3666 int dhcp_failover_state_match (dhcp_failover_state_t
*state
,
3667 u_int8_t
*addr
, unsigned addrlen
)
3669 struct data_string ds
;
3672 memset (&ds
, 0, sizeof ds
);
3673 if (evaluate_option_cache (&ds
, (struct packet
*)0,
3675 (struct client_state
*)0,
3676 (struct option_state
*)0,
3677 (struct option_state
*)0,
3679 state
-> partner
.address
, MDL
)) {
3680 for (i
= 0; i
+ addrlen
- 1 < ds
.len
; i
+= addrlen
) {
3681 if (!memcmp (&ds
.data
[i
],
3683 data_string_forget (&ds
, MDL
);
3687 data_string_forget (&ds
, MDL
);
3693 dhcp_failover_state_match_by_name(state
, name
)
3694 dhcp_failover_state_t
*state
;
3695 failover_option_t
*name
;
3697 if ((strlen(state
->name
) == name
->count
) &&
3698 (memcmp(state
->name
, name
->data
, name
->count
) == 0))
3704 const char *dhcp_failover_reject_reason_print (int reason
)
3706 static char resbuf
[sizeof("Undefined-255: This reason code is not defined "
3707 "in the protocol standard.")];
3709 if ((reason
> 0xff) || (reason
< 0))
3710 return "Reason code out of range.";
3713 case FTR_ILLEGAL_IP_ADDR
:
3714 return "Illegal IP address (not part of any address pool).";
3716 case FTR_FATAL_CONFLICT
:
3717 return "Fatal conflict exists: address in use by other client.";
3719 case FTR_MISSING_BINDINFO
:
3720 return "Missing binding information.";
3722 case FTR_TIMEMISMATCH
:
3723 return "Connection rejected, time mismatch too great.";
3725 case FTR_INVALID_MCLT
:
3726 return "Connection rejected, invalid MCLT.";
3728 case FTR_MISC_REJECT
:
3729 return "Connection rejected, unknown reason.";
3731 case FTR_DUP_CONNECTION
:
3732 return "Connection rejected, duplicate connection.";
3734 case FTR_INVALID_PARTNER
:
3735 return "Connection rejected, invalid failover partner.";
3737 case FTR_TLS_UNSUPPORTED
:
3738 return "TLS not supported.";
3740 case FTR_TLS_UNCONFIGURED
:
3741 return "TLS supported but not configured.";
3743 case FTR_TLS_REQUIRED
:
3744 return "TLS required but not supported by partner.";
3746 case FTR_DIGEST_UNSUPPORTED
:
3747 return "Message digest not supported.";
3749 case FTR_DIGEST_UNCONFIGURED
:
3750 return "Message digest not configured.";
3752 case FTR_VERSION_MISMATCH
:
3753 return "Protocol version mismatch.";
3755 case FTR_OUTDATED_BIND_INFO
:
3756 return "Outdated binding information.";
3758 case FTR_LESS_CRIT_BIND_INFO
:
3759 return "Less critical binding information.";
3761 case FTR_NO_TRAFFIC
:
3762 return "No traffic within sufficient time.";
3764 case FTR_HBA_CONFLICT
:
3765 return "Hash bucket assignment conflict.";
3767 case FTR_IP_NOT_RESERVED
:
3768 return "IP not reserved on this server.";
3770 case FTR_IP_DIGEST_FAILURE
:
3771 return "Message digest failed to compare.";
3773 case FTR_IP_MISSING_DIGEST
:
3774 return "Missing message digest.";
3777 return "Unknown Error.";
3780 sprintf(resbuf
, "Undefined-%d: This reason code is not defined in the "
3781 "protocol standard.", reason
);
3786 const char *dhcp_failover_state_name_print (enum failover_state state
)
3791 return "unknown-state";
3794 return "partner-down";
3800 return "conflict-done";
3802 case communications_interrupted
:
3803 return "communications-interrupted";
3805 case resolution_interrupted
:
3806 return "resolution-interrupted";
3808 case potential_conflict
:
3809 return "potential-conflict";
3815 return "recover-done";
3818 return "recover-wait";
3831 const char *dhcp_failover_message_name (unsigned type
)
3833 static char messbuf
[sizeof("unknown-message-255")];
3836 return "invalid-message";
3840 return "pool-request";
3843 return "pool-response";
3846 return "bind-update";
3854 case FTM_CONNECTACK
:
3855 return "connect-ack";
3858 return "update-request";
3861 return "update-done";
3864 return "update-request-all";
3872 case FTM_DISCONNECT
:
3873 return "disconnect";
3876 sprintf(messbuf
, "unknown-message-%u", type
);
3881 const char *dhcp_failover_option_name (unsigned type
)
3883 static char optbuf
[sizeof("unknown-option-65535")];
3886 return "invalid-option";
3889 case FTO_ADDRESSES_TRANSFERRED
:
3890 return "addresses-transferred";
3892 case FTO_ASSIGNED_IP_ADDRESS
:
3893 return "assigned-ip-address";
3895 case FTO_BINDING_STATUS
:
3896 return "binding-status";
3898 case FTO_CLIENT_IDENTIFIER
:
3899 return "client-identifier";
3910 case FTO_DELAYED_SERVICE
:
3911 return "delayed-service";
3919 case FTO_LEASE_EXPIRY
:
3920 return "lease-expiry";
3922 case FTO_MAX_UNACKED
:
3923 return "max-unacked";
3931 case FTO_MESSAGE_DIGEST
:
3932 return "message-digest";
3934 case FTO_POTENTIAL_EXPIRY
:
3935 return "potential-expiry";
3937 case FTO_PROTOCOL_VERSION
:
3938 return "protocol-version";
3940 case FTO_RECEIVE_TIMER
:
3941 return "receive-timer";
3943 case FTO_REJECT_REASON
:
3944 return "reject-reason";
3946 case FTO_RELATIONSHIP_NAME
:
3947 return "relationship-name";
3949 case FTO_REPLY_OPTIONS
:
3950 return "reply-options";
3952 case FTO_REQUEST_OPTIONS
:
3953 return "request-options";
3955 case FTO_SERVER_FLAGS
:
3956 return "server-flags";
3958 case FTO_SERVER_STATE
:
3959 return "server-state";
3967 case FTO_TLS_REQUEST
:
3968 return "tls-request";
3970 case FTO_VENDOR_CLASS
:
3971 return "vendor-class";
3973 case FTO_VENDOR_OPTIONS
:
3974 return "vendor-options";
3977 sprintf(optbuf
, "unknown-option-%u", type
);
3982 failover_option_t
*dhcp_failover_option_printf (unsigned code
,
3986 const char *fmt
, ...)
3991 /* %Audit% Truncation causes panic. %2004.06.17,Revisit%
3992 * It is unclear what the effects of truncation here are, or
3993 * how that condition should be handled. It seems that this
3994 * function is used for formatting messages in the failover
3995 * command channel. For now the safest thing is for
3996 * overflow-truncation to cause a fatal log.
3999 if (vsnprintf (tbuf
, sizeof tbuf
, fmt
, va
) >= sizeof tbuf
)
4000 log_fatal ("%s: vsnprintf would truncate",
4001 "dhcp_failover_make_option");
4004 return dhcp_failover_make_option (code
, obuf
, obufix
, obufmax
,
4005 strlen (tbuf
), tbuf
);
4008 failover_option_t
*dhcp_failover_make_option (unsigned code
,
4009 char *obuf
, unsigned *obufix
,
4010 unsigned obufmax
, ...)
4013 struct failover_option_info
*info
;
4015 unsigned size
, count
;
4021 #if defined (DEBUG_FAILOVER_MESSAGES)
4025 /* Note that the failover_option structure is used differently on
4026 input than on output - on input, count is an element count, and
4027 on output it's the number of bytes total in the option, including
4028 the option code and option length. */
4029 failover_option_t option
, *op
;
4032 /* Bogus option code? */
4033 if (code
< 1 || code
> FTO_MAX
|| ft_options
[code
].type
== FT_UNDEF
) {
4034 return &null_failover_option
;
4036 info
= &ft_options
[code
];
4038 va_start (va
, obufmax
);
4040 /* Get the number of elements and the size of the buffer we need
4042 if (info
-> type
== FT_DDNS
|| info
-> type
== FT_DDNS1
) {
4043 count
= info
-> type
== FT_DDNS
? 1 : 2;
4044 size
= va_arg (va
, int) + count
;
4046 /* Find out how many items in this list. */
4047 if (info
-> num_present
)
4048 count
= info
-> num_present
;
4050 count
= va_arg (va
, int);
4052 /* Figure out size. */
4053 switch (info
-> type
) {
4060 case FT_TEXT_OR_BYTES
:
4062 txt
= va_arg (va
, char *);
4067 ilen
= va_arg (va
, unsigned);
4068 size
= count
* ilen
;
4080 /* shouldn't get here. */
4081 log_fatal ("bogus type in failover_make_option: %d",
4083 return &null_failover_option
;
4089 /* Allocate a buffer for the option. */
4090 option
.count
= size
;
4091 option
.data
= dmalloc (option
.count
, MDL
);
4094 return &null_failover_option
;
4097 /* Put in the option code and option length. */
4098 putUShort (option
.data
, code
);
4099 putUShort (&option
.data
[2], size
- 4);
4101 #if defined (DEBUG_FAILOVER_MESSAGES)
4102 /* %Audit% Truncation causes panic. %2004.06.17,Revisit%
4103 * It is unclear what the effects of truncation here are, or
4104 * how that condition should be handled. It seems that this
4105 * message may be sent over the failover command channel.
4106 * For now the safest thing is for overflow-truncation to cause
4109 if (snprintf (tbuf
, sizeof tbuf
, " (%s<%d>", info
-> name
,
4110 option
.count
) >= sizeof tbuf
)
4111 log_fatal ("dhcp_failover_make_option: tbuf overflow");
4112 failover_print (obuf
, obufix
, obufmax
, tbuf
);
4115 /* Now put in the data. */
4116 switch (info
-> type
) {
4118 for (i
= 0; i
< count
; i
++) {
4119 val
= va_arg (va
, unsigned);
4120 #if defined (DEBUG_FAILOVER_MESSAGES)
4121 /* %Audit% Cannot exceed 24 bytes. %2004.06.17,Safe% */
4122 sprintf (tbuf
, " %d", val
);
4123 failover_print (obuf
, obufix
, obufmax
, tbuf
);
4125 option
.data
[i
+ 4] = val
;
4130 for (i
= 0; i
< count
; i
++) {
4131 iaddr
= va_arg (va
, u_int8_t
*);
4133 dfree (option
.data
, MDL
);
4134 log_error ("IP addrlen=%d, should be 4.",
4137 return &null_failover_option
;
4140 #if defined (DEBUG_FAILOVER_MESSAGES)
4141 /*%Audit% Cannot exceed 17 bytes. %2004.06.17,Safe%*/
4142 sprintf (tbuf
, " %u.%u.%u.%u",
4143 iaddr
[0], iaddr
[1], iaddr
[2], iaddr
[3]);
4144 failover_print (obuf
, obufix
, obufmax
, tbuf
);
4146 memcpy (&option
.data
[4 + i
* ilen
], iaddr
, ilen
);
4151 for (i
= 0; i
< count
; i
++) {
4152 val
= va_arg (va
, unsigned);
4153 #if defined (DEBUG_FAILOVER_MESSAGES)
4154 /*%Audit% Cannot exceed 24 bytes. %2004.06.17,Safe%*/
4155 sprintf (tbuf
, " %d", val
);
4156 failover_print (obuf
, obufix
, obufmax
, tbuf
);
4158 putULong (&option
.data
[4 + i
* 4], val
);
4164 bval
= va_arg (va
, u_int8_t
*);
4165 #if defined (DEBUG_FAILOVER_MESSAGES)
4166 for (i
= 0; i
< count
; i
++) {
4167 /* 23 bytes plus nul, safe. */
4168 sprintf (tbuf
, " %d", bval
[i
]);
4169 failover_print (obuf
, obufix
, obufmax
, tbuf
);
4172 memcpy (&option
.data
[4], bval
, count
);
4175 /* On output, TEXT_OR_BYTES is _always_ text, and always NUL
4176 terminated. Note that the caller should be careful not
4177 to provide a format and data that amount to more than 256
4178 bytes of data, since it will cause a fatal error. */
4179 case FT_TEXT_OR_BYTES
:
4181 #if defined (DEBUG_FAILOVER_MESSAGES)
4182 /* %Audit% Truncation causes panic. %2004.06.17,Revisit%
4183 * It is unclear what the effects of truncation here are, or
4184 * how that condition should be handled. It seems that this
4185 * function is used for formatting messages in the failover
4186 * command channel. For now the safest thing is for
4187 * overflow-truncation to cause a fatal log.
4189 if (snprintf (tbuf
, sizeof tbuf
, "\"%s\"", txt
) >= sizeof tbuf
)
4190 log_fatal ("dhcp_failover_make_option: tbuf overflow");
4191 failover_print (obuf
, obufix
, obufmax
, tbuf
);
4193 memcpy (&option
.data
[4], txt
, count
);
4198 option
.data
[4] = va_arg (va
, unsigned);
4200 option
.data
[5] = va_arg (va
, unsigned);
4201 bval
= va_arg (va
, u_int8_t
*);
4202 memcpy (&option
.data
[4 + count
], bval
, size
- count
- 4);
4203 #if defined (DEBUG_FAILOVER_MESSAGES)
4204 for (i
= 4; i
< size
; i
++) {
4205 /*%Audit% Cannot exceed 24 bytes. %2004.06.17,Safe%*/
4206 sprintf (tbuf
, " %d", option
.data
[i
]);
4207 failover_print (obuf
, obufix
, obufmax
, tbuf
);
4213 for (i
= 0; i
< count
; i
++) {
4214 val
= va_arg (va
, u_int32_t
);
4215 #if defined (DEBUG_FAILOVER_MESSAGES)
4216 /*%Audit% Cannot exceed 24 bytes. %2004.06.17,Safe%*/
4217 sprintf (tbuf
, " %d", val
);
4218 failover_print (obuf
, obufix
, obufmax
, tbuf
);
4220 putUShort (&option
.data
[4 + i
* 2], val
);
4229 #if defined DEBUG_FAILOVER_MESSAGES
4230 failover_print (obuf
, obufix
, obufmax
, ")");
4234 /* Now allocate a place to store what we just set up. */
4235 op
= dmalloc (sizeof (failover_option_t
), MDL
);
4237 dfree (option
.data
, MDL
);
4238 return &null_failover_option
;
4245 /* Send a failover message header. */
4247 isc_result_t
dhcp_failover_put_message (dhcp_failover_link_t
*link
,
4248 omapi_object_t
*connection
,
4249 int msg_type
, u_int32_t xid
, ...)
4255 failover_option_t
*option
;
4256 unsigned char *opbuf
;
4257 isc_result_t status
= ISC_R_SUCCESS
;
4261 /* Run through the argument list once to compute the length of
4262 the option portion of the message. */
4263 va_start (list
, xid
);
4264 while ((option
= va_arg (list
, failover_option_t
*))) {
4265 if (option
!= &skip_failover_option
)
4266 size
+= option
-> count
;
4267 if (option
== &null_failover_option
)
4272 /* Allocate an option buffer, unless we got an error. */
4273 if (!bad_option
&& size
) {
4274 opbuf
= dmalloc (size
, MDL
);
4276 status
= ISC_R_NOMEMORY
;
4278 opbuf
= (unsigned char *)0;
4280 va_start (list
, xid
);
4281 while ((option
= va_arg (list
, failover_option_t
*))) {
4282 if (option
== &skip_failover_option
)
4284 if (!bad_option
&& opbuf
)
4285 memcpy (&opbuf
[opix
],
4286 option
-> data
, option
-> count
);
4287 if (option
!= &null_failover_option
&&
4288 option
!= &skip_failover_option
) {
4289 opix
+= option
-> count
;
4290 dfree (option
-> data
, MDL
);
4291 dfree (option
, MDL
);
4297 return DHCP_R_INVALIDARG
;
4299 /* Now send the message header. */
4301 /* Message length. */
4302 status
= omapi_connection_put_uint16 (connection
, size
+ 12);
4303 if (status
!= ISC_R_SUCCESS
)
4308 status
= omapi_connection_copyin (connection
, &cbuf
, 1);
4309 if (status
!= ISC_R_SUCCESS
)
4312 /* Payload offset. */
4314 status
= omapi_connection_copyin (connection
, &cbuf
, 1);
4315 if (status
!= ISC_R_SUCCESS
)
4319 status
= omapi_connection_put_uint32 (connection
, (u_int32_t
)cur_time
);
4320 if (status
!= ISC_R_SUCCESS
)
4323 /* Transaction ID. */
4324 status
= omapi_connection_put_uint32(connection
, xid
);
4325 if (status
!= ISC_R_SUCCESS
)
4330 status
= omapi_connection_copyin (connection
, opbuf
, size
);
4331 if (status
!= ISC_R_SUCCESS
)
4335 if (link
-> state_object
&&
4336 link
-> state_object
-> link_to_peer
== link
) {
4337 #if defined (DEBUG_FAILOVER_CONTACT_TIMING)
4338 log_info ("add_timeout +%d %s",
4339 (int)(link
-> state_object
->
4340 partner
.max_response_delay
) / 3,
4341 "dhcp_failover_send_contact");
4343 tv
. tv_sec
= cur_time
+
4344 (int)(link
-> state_object
->
4345 partner
.max_response_delay
) / 3;
4348 dhcp_failover_send_contact
, link
-> state_object
,
4349 (tvref_t
)dhcp_failover_state_reference
,
4350 (tvunref_t
)dhcp_failover_state_dereference
);
4357 log_info ("dhcp_failover_put_message: something went wrong.");
4358 omapi_disconnect (connection
, 1);
4362 void dhcp_failover_timeout (void *vstate
)
4364 dhcp_failover_state_t
*state
= vstate
;
4365 dhcp_failover_link_t
*link
;
4367 #if defined (DEBUG_FAILOVER_TIMING)
4368 log_info ("dhcp_failover_timeout");
4371 if (!state
|| state
-> type
!= dhcp_type_failover_state
)
4373 link
= state
-> link_to_peer
;
4376 link
-> outer
-> type
!= omapi_type_connection
)
4379 log_error ("timeout waiting for failover peer %s", state
-> name
);
4381 /* If we haven't gotten a timely response, blow away the connection.
4382 This will cause the state to change automatically. */
4383 omapi_disconnect (link
-> outer
, 1);
4386 void dhcp_failover_send_contact (void *vstate
)
4388 dhcp_failover_state_t
*state
= vstate
;
4389 dhcp_failover_link_t
*link
;
4390 isc_result_t status
;
4392 #if defined(DEBUG_FAILOVER_MESSAGES) && \
4393 defined(DEBUG_FAILOVER_CONTACT_MESSAGES)
4395 unsigned obufix
= 0;
4397 failover_print(obuf
, &obufix
, sizeof(obuf
), "(contact");
4400 #if defined (DEBUG_FAILOVER_CONTACT_TIMING)
4401 log_info ("dhcp_failover_send_contact");
4404 if (!state
|| state
-> type
!= dhcp_type_failover_state
)
4406 link
= state
-> link_to_peer
;
4409 link
-> outer
-> type
!= omapi_type_connection
)
4412 status
= (dhcp_failover_put_message
4413 (link
, link
-> outer
,
4414 FTM_CONTACT
, link
->xid
++,
4415 (failover_option_t
*)0));
4417 #if defined(DEBUG_FAILOVER_MESSAGES) && \
4418 defined(DEBUG_FAILOVER_CONTACT_MESSAGES)
4419 if (status
!= ISC_R_SUCCESS
)
4420 failover_print(obuf
, &obufix
, sizeof(obuf
), " (failed)");
4421 failover_print(obuf
, &obufix
, sizeof(obuf
), ")");
4423 log_debug ("%s", obuf
);
4426 IGNORE_UNUSED(status
);
4431 isc_result_t
dhcp_failover_send_state (dhcp_failover_state_t
*state
)
4433 dhcp_failover_link_t
*link
;
4434 isc_result_t status
;
4436 #if defined (DEBUG_FAILOVER_MESSAGES)
4438 unsigned obufix
= 0;
4440 # define FMA obuf, &obufix, sizeof obuf
4441 failover_print (FMA
, "(state");
4443 # define FMA (char *)0, (unsigned *)0, 0
4446 if (!state
|| state
-> type
!= dhcp_type_failover_state
)
4447 return DHCP_R_INVALIDARG
;
4448 link
= state
-> link_to_peer
;
4451 link
-> outer
-> type
!= omapi_type_connection
)
4452 return DHCP_R_INVALIDARG
;
4454 status
= (dhcp_failover_put_message
4455 (link
, link
-> outer
,
4456 FTM_STATE
, link
->xid
++,
4457 dhcp_failover_make_option (FTO_SERVER_STATE
, FMA
,
4458 (state
-> me
.state
== startup
4459 ? state
-> saved_state
4460 : state
-> me
.state
)),
4461 dhcp_failover_make_option
4462 (FTO_SERVER_FLAGS
, FMA
,
4463 (state
-> service_state
== service_startup
4464 ? FTF_SERVER_STARTUP
: 0)),
4465 dhcp_failover_make_option (FTO_STOS
, FMA
, state
-> me
.stos
),
4466 (failover_option_t
*)0));
4468 #if defined (DEBUG_FAILOVER_MESSAGES)
4469 if (status
!= ISC_R_SUCCESS
)
4470 failover_print (FMA
, " (failed)");
4471 failover_print (FMA
, ")");
4473 log_debug ("%s", obuf
);
4476 IGNORE_UNUSED(status
);
4478 return ISC_R_SUCCESS
;
4481 /* Send a connect message. */
4483 isc_result_t
dhcp_failover_send_connect (omapi_object_t
*l
)
4485 dhcp_failover_link_t
*link
;
4486 dhcp_failover_state_t
*state
;
4487 isc_result_t status
;
4488 #if defined (DEBUG_FAILOVER_MESSAGES)
4490 unsigned obufix
= 0;
4492 # define FMA obuf, &obufix, sizeof obuf
4493 failover_print (FMA
, "(connect");
4495 # define FMA (char *)0, (unsigned *)0, 0
4498 if (!l
|| l
-> type
!= dhcp_type_failover_link
)
4499 return DHCP_R_INVALIDARG
;
4500 link
= (dhcp_failover_link_t
*)l
;
4501 state
= link
-> state_object
;
4502 if (!l
-> outer
|| l
-> outer
-> type
!= omapi_type_connection
)
4503 return DHCP_R_INVALIDARG
;
4506 (dhcp_failover_put_message
4508 FTM_CONNECT
, link
->xid
++,
4509 dhcp_failover_make_option(FTO_RELATIONSHIP_NAME
, FMA
,
4510 strlen(state
->name
), state
->name
),
4511 dhcp_failover_make_option (FTO_MAX_UNACKED
, FMA
,
4512 state
-> me
.max_flying_updates
),
4513 dhcp_failover_make_option (FTO_RECEIVE_TIMER
, FMA
,
4514 state
-> me
.max_response_delay
),
4515 dhcp_failover_option_printf(FTO_VENDOR_CLASS
, FMA
,
4516 "isc-%s", PACKAGE_VERSION
),
4517 dhcp_failover_make_option (FTO_PROTOCOL_VERSION
, FMA
,
4518 DHCP_FAILOVER_VERSION
),
4519 dhcp_failover_make_option (FTO_TLS_REQUEST
, FMA
,
4521 dhcp_failover_make_option (FTO_MCLT
, FMA
,
4524 ? dhcp_failover_make_option (FTO_HBA
, FMA
, 32, state
-> hba
)
4525 : &skip_failover_option
),
4526 (failover_option_t
*)0));
4528 #if defined (DEBUG_FAILOVER_MESSAGES)
4529 if (status
!= ISC_R_SUCCESS
)
4530 failover_print (FMA
, " (failed)");
4531 failover_print (FMA
, ")");
4533 log_debug ("%s", obuf
);
4539 isc_result_t
dhcp_failover_send_connectack (omapi_object_t
*l
,
4540 dhcp_failover_state_t
*state
,
4541 int reason
, const char *errmsg
)
4543 dhcp_failover_link_t
*link
;
4544 isc_result_t status
;
4545 #if defined (DEBUG_FAILOVER_MESSAGES)
4547 unsigned obufix
= 0;
4549 # define FMA obuf, &obufix, sizeof obuf
4550 failover_print (FMA
, "(connectack");
4552 # define FMA (char *)0, (unsigned *)0, 0
4555 if (!l
|| l
-> type
!= dhcp_type_failover_link
)
4556 return DHCP_R_INVALIDARG
;
4557 link
= (dhcp_failover_link_t
*)l
;
4558 if (!l
-> outer
|| l
-> outer
-> type
!= omapi_type_connection
)
4559 return DHCP_R_INVALIDARG
;
4562 (dhcp_failover_put_message
4564 FTM_CONNECTACK
, link
->imsg
->xid
,
4566 ? dhcp_failover_make_option(FTO_RELATIONSHIP_NAME
, FMA
,
4567 strlen(state
->name
), state
->name
)
4568 : (link
->imsg
->options_present
& FTB_RELATIONSHIP_NAME
)
4569 ? dhcp_failover_make_option(FTO_RELATIONSHIP_NAME
, FMA
,
4570 link
->imsg
->relationship_name
.count
,
4571 link
->imsg
->relationship_name
.data
)
4572 : &skip_failover_option
,
4574 ? dhcp_failover_make_option (FTO_MAX_UNACKED
, FMA
,
4575 state
-> me
.max_flying_updates
)
4576 : &skip_failover_option
,
4578 ? dhcp_failover_make_option (FTO_RECEIVE_TIMER
, FMA
,
4579 state
-> me
.max_response_delay
)
4580 : &skip_failover_option
,
4581 dhcp_failover_option_printf(FTO_VENDOR_CLASS
, FMA
,
4582 "isc-%s", PACKAGE_VERSION
),
4583 dhcp_failover_make_option (FTO_PROTOCOL_VERSION
, FMA
,
4584 DHCP_FAILOVER_VERSION
),
4585 (link
->imsg
->options_present
& FTB_TLS_REQUEST
)
4586 ? dhcp_failover_make_option(FTO_TLS_REPLY
, FMA
,
4588 : &skip_failover_option
,
4590 ? dhcp_failover_make_option (FTO_REJECT_REASON
,
4592 : &skip_failover_option
,
4594 ? dhcp_failover_make_option (FTO_MESSAGE
, FMA
,
4595 strlen (errmsg
), errmsg
)
4596 : &skip_failover_option
,
4597 (failover_option_t
*)0));
4599 #if defined (DEBUG_FAILOVER_MESSAGES)
4600 if (status
!= ISC_R_SUCCESS
)
4601 failover_print (FMA
, " (failed)");
4602 failover_print (FMA
, ")");
4604 log_debug ("%s", obuf
);
4610 isc_result_t
dhcp_failover_send_disconnect (omapi_object_t
*l
,
4612 const char *message
)
4614 dhcp_failover_link_t
*link
;
4615 isc_result_t status
;
4616 #if defined (DEBUG_FAILOVER_MESSAGES)
4618 unsigned obufix
= 0;
4620 # define FMA obuf, &obufix, sizeof obuf
4621 failover_print (FMA
, "(disconnect");
4623 # define FMA (char *)0, (unsigned *)0, 0
4626 if (!l
|| l
-> type
!= dhcp_type_failover_link
)
4627 return DHCP_R_INVALIDARG
;
4628 link
= (dhcp_failover_link_t
*)l
;
4629 if (!l
-> outer
|| l
-> outer
-> type
!= omapi_type_connection
)
4630 return DHCP_R_INVALIDARG
;
4632 if (!message
&& reason
)
4633 message
= dhcp_failover_reject_reason_print (reason
);
4635 status
= (dhcp_failover_put_message
4637 FTM_DISCONNECT
, link
->xid
++,
4638 dhcp_failover_make_option (FTO_REJECT_REASON
,
4641 ? dhcp_failover_make_option (FTO_MESSAGE
, FMA
,
4642 strlen (message
), message
)
4643 : &skip_failover_option
),
4644 (failover_option_t
*)0));
4646 #if defined (DEBUG_FAILOVER_MESSAGES)
4647 if (status
!= ISC_R_SUCCESS
)
4648 failover_print (FMA
, " (failed)");
4649 failover_print (FMA
, ")");
4651 log_debug ("%s", obuf
);
4657 /* Send a Bind Update message. */
4659 isc_result_t
dhcp_failover_send_bind_update (dhcp_failover_state_t
*state
,
4660 struct lease
*lease
)
4662 dhcp_failover_link_t
*link
;
4663 isc_result_t status
;
4665 binding_state_t transmit_state
;
4666 #if defined (DEBUG_FAILOVER_MESSAGES)
4668 unsigned obufix
= 0;
4670 # define FMA obuf, &obufix, sizeof obuf
4671 failover_print (FMA
, "(bndupd");
4673 # define FMA (char *)0, (unsigned *)0, 0
4676 if (!state
-> link_to_peer
||
4677 state
-> link_to_peer
-> type
!= dhcp_type_failover_link
)
4678 return DHCP_R_INVALIDARG
;
4679 link
= (dhcp_failover_link_t
*)state
-> link_to_peer
;
4681 if (!link
-> outer
|| link
-> outer
-> type
!= omapi_type_connection
)
4682 return DHCP_R_INVALIDARG
;
4684 transmit_state
= lease
->desired_binding_state
;
4685 if (lease
->flags
& RESERVED_LEASE
) {
4686 /* If we are listing an allocable (not yet ACTIVE etc) lease
4687 * as reserved, toggle to the peer's 'free state', per the
4688 * draft. This gives the peer permission to alloc it to the
4689 * chaddr/uid-named client.
4691 if ((state
->i_am
== primary
) && (transmit_state
== FTS_FREE
))
4692 transmit_state
= FTS_BACKUP
;
4693 else if ((state
->i_am
== secondary
) &&
4694 (transmit_state
== FTS_BACKUP
))
4695 transmit_state
= FTS_FREE
;
4697 flags
|= FTF_IP_FLAG_RESERVE
;
4699 if (lease
->flags
& BOOTP_LEASE
)
4700 flags
|= FTF_IP_FLAG_BOOTP
;
4702 /* last_xid == 0 is illegal, seek past zero if we hit it. */
4706 lease
->last_xid
= link
->xid
++;
4709 * Our very next action is to transmit a binding update relating to
4710 * this lease over the wire, and although there is a BNDACK, there is
4711 * no BNDACKACK or BNDACKACKACK...the basic issue as we send a BNDUPD,
4712 * we may not receive a BNDACK. This non-reception does not imply the
4713 * peer did not receive and process the BNDUPD. So at this point, we
4714 * must divest any state that would be dangerous to retain under the
4715 * impression the peer has been updated. Normally state changes like
4716 * this are processed in supersede_lease(), but in this case we need a
4717 * very late binding.
4719 * In failover rules, a server is permitted to work forward in certain
4720 * directions from a given lease's state; active leases may be
4721 * extended, so forth. There is an 'optimization' in the failover
4722 * draft that permits a server to 'rewind' any work they have not
4723 * informed the peer. Since we can't know if the peer received our
4724 * update but was unable to acknowledge it, we make this change on
4725 * transmit rather than upon receiving the acknowledgement.
4727 * XXX: Frequent lease commits are undesirable. This should hopefully
4728 * only trigger when a server is sending a lease /state change/, and
4729 * not merely an update such as with a renewal.
4731 if (lease
->rewind_binding_state
!= lease
->binding_state
) {
4732 lease
->rewind_binding_state
= lease
->binding_state
;
4738 /* Send the update. */
4739 status
= (dhcp_failover_put_message
4740 (link
, link
-> outer
,
4741 FTM_BNDUPD
, lease
->last_xid
,
4742 dhcp_failover_make_option (FTO_ASSIGNED_IP_ADDRESS
, FMA
,
4743 lease
-> ip_addr
.len
,
4744 lease
-> ip_addr
.iabuf
),
4745 dhcp_failover_make_option (FTO_BINDING_STATUS
, FMA
,
4746 lease
-> desired_binding_state
),
4748 ? dhcp_failover_make_option (FTO_CLIENT_IDENTIFIER
, FMA
,
4751 : &skip_failover_option
,
4752 lease
-> hardware_addr
.hlen
4753 ? dhcp_failover_make_option (FTO_CHADDR
, FMA
,
4754 lease
-> hardware_addr
.hlen
,
4755 lease
-> hardware_addr
.hbuf
)
4756 : &skip_failover_option
,
4757 dhcp_failover_make_option (FTO_LEASE_EXPIRY
, FMA
,
4759 dhcp_failover_make_option (FTO_POTENTIAL_EXPIRY
, FMA
,
4761 dhcp_failover_make_option (FTO_STOS
, FMA
,
4763 (lease
->cltt
!= 0) ?
4764 dhcp_failover_make_option(FTO_CLTT
, FMA
, lease
->cltt
) :
4765 &skip_failover_option
, /* No CLTT */
4766 flags
? dhcp_failover_make_option(FTO_IP_FLAGS
, FMA
,
4768 &skip_failover_option
, /* No IP_FLAGS */
4769 &skip_failover_option
, /* XXX DDNS */
4770 &skip_failover_option
, /* XXX request options */
4771 &skip_failover_option
, /* XXX reply options */
4772 (failover_option_t
*)0));
4774 #if defined (DEBUG_FAILOVER_MESSAGES)
4775 if (status
!= ISC_R_SUCCESS
)
4776 failover_print (FMA
, " (failed)");
4777 failover_print (FMA
, ")");
4779 log_debug ("%s", obuf
);
4785 /* Send a Bind ACK message. */
4787 isc_result_t
dhcp_failover_send_bind_ack (dhcp_failover_state_t
*state
,
4788 failover_message_t
*msg
,
4789 int reason
, const char *message
)
4791 dhcp_failover_link_t
*link
;
4792 isc_result_t status
;
4793 #if defined (DEBUG_FAILOVER_MESSAGES)
4795 unsigned obufix
= 0;
4797 # define FMA obuf, &obufix, sizeof obuf
4798 failover_print (FMA
, "(bndack");
4800 # define FMA (char *)0, (unsigned *)0, 0
4803 if (!state
-> link_to_peer
||
4804 state
-> link_to_peer
-> type
!= dhcp_type_failover_link
)
4805 return DHCP_R_INVALIDARG
;
4806 link
= (dhcp_failover_link_t
*)state
-> link_to_peer
;
4808 if (!link
-> outer
|| link
-> outer
-> type
!= omapi_type_connection
)
4809 return DHCP_R_INVALIDARG
;
4811 if (!message
&& reason
)
4812 message
= dhcp_failover_reject_reason_print (reason
);
4814 /* Send the update. */
4815 status
= (dhcp_failover_put_message
4816 (link
, link
-> outer
,
4817 FTM_BNDACK
, msg
->xid
,
4818 dhcp_failover_make_option (FTO_ASSIGNED_IP_ADDRESS
, FMA
,
4819 sizeof msg
-> assigned_addr
,
4820 &msg
-> assigned_addr
),
4821 #ifdef DO_BNDACK_SHOULD_NOT
4822 dhcp_failover_make_option (FTO_BINDING_STATUS
, FMA
,
4823 msg
-> binding_status
),
4824 (msg
-> options_present
& FTB_CLIENT_IDENTIFIER
)
4825 ? dhcp_failover_make_option (FTO_CLIENT_IDENTIFIER
, FMA
,
4826 msg
-> client_identifier
.count
,
4827 msg
-> client_identifier
.data
)
4828 : &skip_failover_option
,
4829 (msg
-> options_present
& FTB_CHADDR
)
4830 ? dhcp_failover_make_option (FTO_CHADDR
, FMA
,
4831 msg
-> chaddr
.count
,
4833 : &skip_failover_option
,
4834 dhcp_failover_make_option (FTO_LEASE_EXPIRY
, FMA
,
4836 dhcp_failover_make_option (FTO_POTENTIAL_EXPIRY
, FMA
,
4837 msg
-> potential_expiry
),
4838 dhcp_failover_make_option (FTO_STOS
, FMA
,
4840 (msg
->options_present
& FTB_CLTT
) ?
4841 dhcp_failover_make_option(FTO_CLTT
, FMA
, msg
->cltt
) :
4842 &skip_failover_option
, /* No CLTT in the msg to ack. */
4843 ((msg
->options_present
& FTB_IP_FLAGS
) && msg
->ip_flags
) ?
4844 dhcp_failover_make_option(FTO_IP_FLAGS
, FMA
,
4846 : &skip_failover_option
,
4847 #endif /* DO_BNDACK_SHOULD_NOT */
4849 ? dhcp_failover_make_option(FTO_REJECT_REASON
, FMA
, reason
)
4850 : &skip_failover_option
,
4852 ? dhcp_failover_make_option (FTO_MESSAGE
, FMA
,
4853 strlen (message
), message
)
4854 : &skip_failover_option
,
4855 #ifdef DO_BNDACK_SHOULD_NOT
4856 &skip_failover_option
, /* XXX DDNS */
4857 &skip_failover_option
, /* XXX request options */
4858 &skip_failover_option
, /* XXX reply options */
4859 #endif /* DO_BNDACK_SHOULD_NOT */
4860 (failover_option_t
*)0));
4862 #if defined (DEBUG_FAILOVER_MESSAGES)
4863 if (status
!= ISC_R_SUCCESS
)
4864 failover_print (FMA
, " (failed)");
4865 failover_print (FMA
, ")");
4867 log_debug ("%s", obuf
);
4873 isc_result_t
dhcp_failover_send_poolreq (dhcp_failover_state_t
*state
)
4875 dhcp_failover_link_t
*link
;
4876 isc_result_t status
;
4877 #if defined (DEBUG_FAILOVER_MESSAGES)
4879 unsigned obufix
= 0;
4881 # define FMA obuf, &obufix, sizeof obuf
4882 failover_print (FMA
, "(poolreq");
4884 # define FMA (char *)0, (unsigned *)0, 0
4887 if (!state
-> link_to_peer
||
4888 state
-> link_to_peer
-> type
!= dhcp_type_failover_link
)
4889 return DHCP_R_INVALIDARG
;
4890 link
= (dhcp_failover_link_t
*)state
-> link_to_peer
;
4892 if (!link
-> outer
|| link
-> outer
-> type
!= omapi_type_connection
)
4893 return DHCP_R_INVALIDARG
;
4895 status
= (dhcp_failover_put_message
4896 (link
, link
-> outer
,
4897 FTM_POOLREQ
, link
->xid
++,
4898 (failover_option_t
*)0));
4900 #if defined (DEBUG_FAILOVER_MESSAGES)
4901 if (status
!= ISC_R_SUCCESS
)
4902 failover_print (FMA
, " (failed)");
4903 failover_print (FMA
, ")");
4905 log_debug ("%s", obuf
);
4911 isc_result_t
dhcp_failover_send_poolresp (dhcp_failover_state_t
*state
,
4914 dhcp_failover_link_t
*link
;
4915 isc_result_t status
;
4916 #if defined (DEBUG_FAILOVER_MESSAGES)
4918 unsigned obufix
= 0;
4920 # define FMA obuf, &obufix, sizeof obuf
4921 failover_print (FMA
, "(poolresp");
4923 # define FMA (char *)0, (unsigned *)0, 0
4926 if (!state
-> link_to_peer
||
4927 state
-> link_to_peer
-> type
!= dhcp_type_failover_link
)
4928 return DHCP_R_INVALIDARG
;
4929 link
= (dhcp_failover_link_t
*)state
-> link_to_peer
;
4931 if (!link
-> outer
|| link
-> outer
-> type
!= omapi_type_connection
)
4932 return DHCP_R_INVALIDARG
;
4934 status
= (dhcp_failover_put_message
4935 (link
, link
-> outer
,
4936 FTM_POOLRESP
, link
->imsg
->xid
,
4937 dhcp_failover_make_option (FTO_ADDRESSES_TRANSFERRED
, FMA
,
4939 (failover_option_t
*)0));
4941 #if defined (DEBUG_FAILOVER_MESSAGES)
4942 if (status
!= ISC_R_SUCCESS
)
4943 failover_print (FMA
, " (failed)");
4944 failover_print (FMA
, ")");
4946 log_debug ("%s", obuf
);
4952 isc_result_t
dhcp_failover_send_update_request (dhcp_failover_state_t
*state
)
4954 dhcp_failover_link_t
*link
;
4955 isc_result_t status
;
4956 #if defined (DEBUG_FAILOVER_MESSAGES)
4958 unsigned obufix
= 0;
4960 # define FMA obuf, &obufix, sizeof obuf
4961 failover_print (FMA
, "(updreq");
4963 # define FMA (char *)0, (unsigned *)0, 0
4966 if (!state
->link_to_peer
||
4967 state
->link_to_peer
->type
!= dhcp_type_failover_link
)
4968 return (DHCP_R_INVALIDARG
);
4969 link
= (dhcp_failover_link_t
*)state
->link_to_peer
;
4971 if (!link
->outer
|| link
->outer
->type
!= omapi_type_connection
)
4972 return (DHCP_R_INVALIDARG
);
4974 /* We allow an update to be restarted in case we requested an update
4975 * and were interrupted by something. If we had an ALL going we need
4976 * to restart that. Otherwise we simply continue with the request */
4977 if (state
->curUPD
== FTM_UPDREQALL
) {
4978 return (dhcp_failover_send_update_request_all(state
));
4981 status
= (dhcp_failover_put_message(link
, link
->outer
, FTM_UPDREQ
,
4982 link
->xid
++, NULL
));
4984 state
->curUPD
= FTM_UPDREQ
;
4986 #if defined (DEBUG_FAILOVER_MESSAGES)
4987 if (status
!= ISC_R_SUCCESS
)
4988 failover_print(FMA
, " (failed)");
4989 failover_print(FMA
, ")");
4991 log_debug("%s", obuf
);
4995 if (status
== ISC_R_SUCCESS
) {
4996 log_info("Sent update request message to %s", state
->name
);
4998 log_error("Failed to send update request all message to %s: %s",
4999 state
->name
, isc_result_totext(status
));
5004 isc_result_t
dhcp_failover_send_update_request_all (dhcp_failover_state_t
5007 dhcp_failover_link_t
*link
;
5008 isc_result_t status
;
5009 #if defined (DEBUG_FAILOVER_MESSAGES)
5011 unsigned obufix
= 0;
5013 # define FMA obuf, &obufix, sizeof obuf
5014 failover_print (FMA
, "(updreqall");
5016 # define FMA (char *)0, (unsigned *)0, 0
5019 if (!state
->link_to_peer
||
5020 state
->link_to_peer
->type
!= dhcp_type_failover_link
)
5021 return (DHCP_R_INVALIDARG
);
5022 link
= (dhcp_failover_link_t
*)state
->link_to_peer
;
5024 if (!link
->outer
|| link
->outer
->type
!= omapi_type_connection
)
5025 return (DHCP_R_INVALIDARG
);
5027 /* We allow an update to be restarted in case we requested an update
5028 * and were interrupted by something.
5031 status
= (dhcp_failover_put_message(link
, link
->outer
, FTM_UPDREQALL
,
5032 link
->xid
++, NULL
));
5034 state
->curUPD
= FTM_UPDREQALL
;
5036 #if defined (DEBUG_FAILOVER_MESSAGES)
5037 if (status
!= ISC_R_SUCCESS
)
5038 failover_print(FMA
, " (failed)");
5039 failover_print(FMA
, ")");
5041 log_debug("%s", obuf
);
5045 if (status
== ISC_R_SUCCESS
) {
5046 log_info("Sent update request all message to %s", state
->name
);
5048 log_error("Failed to send update request all message to %s: %s",
5049 state
->name
, isc_result_totext(status
));
5054 isc_result_t
dhcp_failover_send_update_done (dhcp_failover_state_t
*state
)
5056 dhcp_failover_link_t
*link
;
5057 isc_result_t status
;
5058 #if defined (DEBUG_FAILOVER_MESSAGES)
5060 unsigned obufix
= 0;
5062 # define FMA obuf, &obufix, sizeof obuf
5063 failover_print (FMA
, "(upddone");
5065 # define FMA (char *)0, (unsigned *)0, 0
5068 if (!state
-> link_to_peer
||
5069 state
-> link_to_peer
-> type
!= dhcp_type_failover_link
)
5070 return DHCP_R_INVALIDARG
;
5071 link
= (dhcp_failover_link_t
*)state
-> link_to_peer
;
5073 if (!link
-> outer
|| link
-> outer
-> type
!= omapi_type_connection
)
5074 return DHCP_R_INVALIDARG
;
5076 status
= (dhcp_failover_put_message
5077 (link
, link
-> outer
,
5078 FTM_UPDDONE
, state
->updxid
,
5079 (failover_option_t
*)0));
5081 #if defined (DEBUG_FAILOVER_MESSAGES)
5082 if (status
!= ISC_R_SUCCESS
)
5083 failover_print (FMA
, " (failed)");
5084 failover_print (FMA
, ")");
5086 log_debug ("%s", obuf
);
5090 log_info ("Sent update done message to %s", state
-> name
);
5092 state
->updxid
--; /* Paranoia, just so it mismatches. */
5094 /* There may be uncommitted leases at this point (since
5095 dhcp_failover_process_bind_ack() doesn't commit leases);
5096 commit the lease file. */
5103 * failover_lease_is_better() compares the binding update in 'msg' with
5104 * the current lease in 'lease'. If the determination is that the binding
5105 * update shouldn't be allowed to update/crush more critical binding info
5106 * on the lease, the lease is preferred. A value of true is returned if the
5107 * local lease is preferred, or false if the remote binding update is
5110 * For now this function is hopefully simplistic and trivial. It may be that
5111 * a more detailed system of preferences is required, so this is something we
5112 * should monitor as we gain experience with these dueling events.
5114 static isc_boolean_t
5115 failover_lease_is_better(dhcp_failover_state_t
*state
, struct lease
*lease
,
5116 failover_message_t
*msg
)
5118 binding_state_t local_state
;
5121 if (lease
->binding_state
!= lease
->desired_binding_state
)
5122 local_state
= lease
->desired_binding_state
;
5124 local_state
= lease
->binding_state
;
5126 if ((msg
->options_present
& FTB_CLTT
) != 0)
5127 msg_cltt
= msg
->cltt
;
5131 switch(local_state
) {
5133 if (msg
->binding_status
== FTS_ACTIVE
) {
5134 if (msg_cltt
< lease
->cltt
)
5136 else if (msg_cltt
> lease
->cltt
)
5138 else if (state
->i_am
== primary
)
5142 } else if (msg
->binding_status
== FTS_EXPIRED
) {
5153 if (msg
->binding_status
== FTS_ACTIVE
)
5155 else if (state
->i_am
== primary
)
5159 /* FALL THROUGH to impossible condition */
5162 log_fatal("Impossible condition at %s:%d.", MDL
);
5165 log_fatal("Impossible condition at %s:%d.", MDL
);
5166 /* Silence compiler warning. */
5170 isc_result_t
dhcp_failover_process_bind_update (dhcp_failover_state_t
*state
,
5171 failover_message_t
*msg
)
5173 struct lease
*lt
= NULL
, *lease
= NULL
;
5175 int reason
= FTR_MISC_REJECT
;
5176 const char *message
;
5177 int new_binding_state
;
5178 int send_to_backup
= 0;
5179 int required_options
;
5180 isc_boolean_t chaddr_changed
= ISC_FALSE
;
5181 isc_boolean_t ident_changed
= ISC_FALSE
;
5183 /* Validate the binding update. */
5184 required_options
= FTB_ASSIGNED_IP_ADDRESS
| FTB_BINDING_STATUS
;
5185 if ((msg
->options_present
& required_options
) != required_options
) {
5186 message
= "binding update lacks required options";
5187 reason
= FTR_MISSING_BINDINFO
;
5191 ia
.len
= sizeof msg
-> assigned_addr
;
5192 memcpy (ia
.iabuf
, &msg
-> assigned_addr
, ia
.len
);
5194 if (!find_lease_by_ip_addr (&lease
, ia
, MDL
)) {
5195 message
= "unknown IP address";
5196 reason
= FTR_ILLEGAL_IP_ADDR
;
5201 * If this lease is covered by a different failover peering
5202 * relationship, assert an error.
5204 if ((lease
->pool
== NULL
) || (lease
->pool
->failover_peer
== NULL
) ||
5205 (lease
->pool
->failover_peer
!= state
)) {
5206 message
= "IP address is covered by a different failover "
5207 "relationship state";
5208 reason
= FTR_ILLEGAL_IP_ADDR
;
5213 * Dueling updates: This happens when both servers send a BNDUPD
5214 * at the same time. We want the best update to win, which means
5215 * we reject if we think ours is better, or cancel if we think the
5216 * peer's is better. We only assert a problem if the lease is on
5217 * the ACK queue, not on the UPDATE queue. This means that after
5218 * accepting this server's BNDUPD, we will send our own BNDUPD
5219 * /after/ sending the BNDACK (this order was recently enforced in
5220 * queue processing).
5222 if ((lease
->flags
& ON_ACK_QUEUE
) != 0) {
5223 if (failover_lease_is_better(state
, lease
, msg
)) {
5224 message
= "incoming update is less critical than "
5226 reason
= FTR_LESS_CRIT_BIND_INFO
;
5229 /* This makes it so we ignore any spurious ACKs. */
5230 dhcp_failover_ack_queue_remove(state
, lease
);
5234 /* Install the new info. Start by taking a copy to markup. */
5235 if (!lease_copy (<
, lease
, MDL
)) {
5236 message
= "no memory";
5240 if (msg
-> options_present
& FTB_CHADDR
) {
5241 if (msg
->binding_status
== FTS_ABANDONED
) {
5242 message
= "BNDUPD to ABANDONED with a CHADDR";
5245 if (msg
-> chaddr
.count
> sizeof lt
-> hardware_addr
.hbuf
) {
5246 message
= "chaddr too long";
5250 if ((lt
->hardware_addr
.hlen
!= msg
->chaddr
.count
) ||
5251 (memcmp(lt
->hardware_addr
.hbuf
, msg
->chaddr
.data
,
5252 msg
->chaddr
.count
) != 0))
5253 chaddr_changed
= ISC_TRUE
;
5255 lt
-> hardware_addr
.hlen
= msg
-> chaddr
.count
;
5256 memcpy (lt
-> hardware_addr
.hbuf
, msg
-> chaddr
.data
,
5257 msg
-> chaddr
.count
);
5258 } else if (msg
->binding_status
== FTS_ACTIVE
||
5259 msg
->binding_status
== FTS_EXPIRED
||
5260 msg
->binding_status
== FTS_RELEASED
) {
5261 message
= "BNDUPD without CHADDR";
5262 reason
= FTR_MISSING_BINDINFO
;
5264 } else if (msg
->binding_status
== FTS_ABANDONED
) {
5265 chaddr_changed
= ISC_TRUE
;
5266 lt
->hardware_addr
.hlen
= 0;
5268 binding_scope_dereference(<
->scope
, MDL
);
5271 /* There is no explicit message content to indicate that the client
5272 * supplied no client-identifier. So if we don't hear of a value,
5273 * we discard the last one.
5275 if (msg
->options_present
& FTB_CLIENT_IDENTIFIER
) {
5276 if (msg
->binding_status
== FTS_ABANDONED
) {
5277 message
= "BNDUPD to ABANDONED with client-id";
5281 if ((lt
->uid_len
!= msg
->client_identifier
.count
) ||
5282 (lt
->uid
== NULL
) || /* Sanity; should never happen. */
5283 (memcmp(lt
->uid
, msg
->client_identifier
.data
,
5285 ident_changed
= ISC_TRUE
;
5287 lt
->uid_len
= msg
->client_identifier
.count
;
5289 /* Allocate the lt->uid buffer if we haven't already, or
5290 * re-allocate the lt-uid buffer if we have one that is not
5291 * large enough. Otherwise, just use the extant buffer.
5293 if (!lt
->uid
|| lt
->uid
== lt
->uid_buf
||
5294 lt
->uid_len
> lt
->uid_max
) {
5295 if (lt
->uid
&& lt
->uid
!= lt
->uid_buf
)
5296 dfree(lt
->uid
, MDL
);
5298 if (lt
->uid_len
> sizeof(lt
->uid_buf
)) {
5299 lt
->uid_max
= lt
->uid_len
;
5300 lt
->uid
= dmalloc(lt
->uid_len
, MDL
);
5302 message
= "no memory";
5306 lt
->uid_max
= sizeof(lt
->uid_buf
);
5307 lt
->uid
= lt
->uid_buf
;
5311 msg
-> client_identifier
.data
, lt
-> uid_len
);
5312 } else if (lt
->uid
&& msg
->binding_status
!= FTS_RESET
&&
5313 msg
->binding_status
!= FTS_FREE
&&
5314 msg
->binding_status
!= FTS_BACKUP
) {
5315 ident_changed
= ISC_TRUE
;
5316 if (lt
->uid
!= lt
->uid_buf
)
5317 dfree (lt
->uid
, MDL
);
5319 lt
->uid_max
= lt
->uid_len
= 0;
5323 * A server's configuration can assign a 'binding scope';
5325 * set var = "value";
5327 * The problem with these binding scopes is that they are refreshed
5328 * when the server processes a client's DHCP packet. A local binding
5329 * scope is trash, then, when the lease has been assigned by the
5330 * partner server. There is no real way to detect this, a peer may
5331 * be updating us (as through potential conflict) with a binding we
5332 * sent them, but we can trivially detect the /problematic/ case;
5335 * primary allocates lease to client A, assigns ddns name A.
5337 * secondary enters partner down.
5338 * lease expires, and is set free.
5339 * lease is allocated to client B and given ddns name B.
5342 * The binding update in this case will be active->active, but the
5343 * client identification on the lease will have changed. The ddns
5344 * update on client A will have leaked if we just remove the binding
5347 if (msg
->binding_status
== FTS_ACTIVE
&&
5348 (chaddr_changed
|| ident_changed
)) {
5349 #if defined (NSUPDATE)
5350 (void) ddns_removals(lease
, NULL
, NULL
, ISC_FALSE
);
5351 #endif /* NSUPDATE */
5353 if (lease
->scope
!= NULL
)
5354 binding_scope_dereference(&lease
->scope
, MDL
);
5357 /* XXX Times may need to be adjusted based on clock skew! */
5358 if (msg
-> options_present
& FTB_STOS
) {
5359 lt
-> starts
= msg
-> stos
;
5361 if (msg
-> options_present
& FTB_LEASE_EXPIRY
) {
5362 lt
-> ends
= msg
-> expiry
;
5364 if (msg
->options_present
& FTB_POTENTIAL_EXPIRY
) {
5365 lt
->atsfp
= lt
->tsfp
= msg
->potential_expiry
;
5367 if (msg
->options_present
& FTB_IP_FLAGS
) {
5368 if (msg
->ip_flags
& FTF_IP_FLAG_RESERVE
) {
5369 if ((((state
->i_am
== primary
) &&
5370 (lease
->binding_state
== FTS_FREE
)) ||
5371 ((state
->i_am
== secondary
) &&
5372 (lease
->binding_state
== FTS_BACKUP
))) &&
5373 !(lease
->flags
& RESERVED_LEASE
)) {
5374 message
= "Address is not reserved.";
5375 reason
= FTR_IP_NOT_RESERVED
;
5379 lt
->flags
|= RESERVED_LEASE
;
5381 lt
->flags
&= ~RESERVED_LEASE
;
5383 if (msg
->ip_flags
& FTF_IP_FLAG_BOOTP
) {
5384 if ((((state
->i_am
== primary
) &&
5385 (lease
->binding_state
== FTS_FREE
)) ||
5386 ((state
->i_am
== secondary
) &&
5387 (lease
->binding_state
== FTS_BACKUP
))) &&
5388 !(lease
->flags
& BOOTP_LEASE
)) {
5389 message
= "Address is not allocated to BOOTP.";
5392 lt
->flags
|= BOOTP_LEASE
;
5394 lt
->flags
&= ~BOOTP_LEASE
;
5396 if (msg
->ip_flags
& ~(FTF_IP_FLAG_RESERVE
| FTF_IP_FLAG_BOOTP
))
5397 log_info("Unknown IP-flags set in BNDUPD (0x%x).",
5399 } else /* Flags may only not appear if the values are zero. */
5400 lt
->flags
&= ~(RESERVED_LEASE
| BOOTP_LEASE
);
5402 #if defined (DEBUG_LEASE_STATE_TRANSITIONS)
5403 log_info ("processing state transition for %s: %s to %s",
5404 piaddr (lease
-> ip_addr
),
5405 binding_state_print (lease
-> binding_state
),
5406 binding_state_print (msg
-> binding_status
));
5409 /* If we're in normal state, make sure the state transition
5411 if (state
-> me
.state
== normal
) {
5413 (normal_binding_state_transition_check
5414 (lease
, state
, msg
-> binding_status
,
5415 msg
-> potential_expiry
));
5416 /* XXX if the transition the peer asked for isn't
5417 XXX allowed, maybe we should make the transition
5418 XXX into potential-conflict at this point. */
5421 (conflict_binding_state_transition_check
5422 (lease
, state
, msg
-> binding_status
,
5423 msg
-> potential_expiry
));
5425 if (new_binding_state
!= msg
-> binding_status
) {
5428 if (snprintf (outbuf
, sizeof outbuf
,
5429 "%s: invalid state transition: %s to %s",
5430 piaddr (lease
-> ip_addr
),
5431 binding_state_print (lease
-> binding_state
),
5432 binding_state_print (msg
-> binding_status
))
5434 log_fatal ("%s: impossible outbuf overflow",
5435 "dhcp_failover_process_bind_update");
5437 dhcp_failover_send_bind_ack (state
, msg
,
5442 if (new_binding_state
== FTS_EXPIRED
||
5443 new_binding_state
== FTS_RELEASED
||
5444 new_binding_state
== FTS_RESET
) {
5445 lt
-> next_binding_state
= FTS_FREE
;
5447 /* Mac address affinity. Assign the lease to
5448 * BACKUP state if we are the primary and the
5449 * peer is more likely to reallocate this lease
5450 * to a returning client.
5452 if ((state
->i_am
== primary
) &&
5453 !(lt
->flags
& (RESERVED_LEASE
| BOOTP_LEASE
)))
5454 send_to_backup
= peer_wants_lease(lt
);
5456 lt
-> next_binding_state
= new_binding_state
;
5458 msg
-> binding_status
= lt
-> next_binding_state
;
5461 * If we accept a peer's binding update, then we can't rewind a
5462 * lease behind the peer's state.
5464 lease
->rewind_binding_state
= lt
->next_binding_state
;
5466 /* Try to install the new information. */
5467 if (!supersede_lease (lease
, lt
, 0, 0, 0, 0) ||
5468 !write_lease (lease
)) {
5469 message
= "database update failed";
5471 dhcp_failover_send_bind_ack (state
, msg
, reason
, message
);
5474 dhcp_failover_queue_ack (state
, msg
);
5477 /* If it is probably wise, assign lease to backup state if the peer
5478 * is not already hoarding leases.
5480 if (send_to_backup
&& secondary_not_hoarding(state
, lease
->pool
)) {
5481 lease
->next_binding_state
= FTS_BACKUP
;
5482 lease
->tstp
= cur_time
;
5483 lease
->starts
= cur_time
;
5485 if (!supersede_lease(lease
, NULL
, 0, 1, 0, 0) ||
5486 !write_lease(lease
))
5487 log_error("can't commit lease %s for mac addr "
5488 "affinity", piaddr(lease
->ip_addr
));
5490 dhcp_failover_send_updates(state
);
5495 lease_dereference (<
, MDL
);
5497 lease_dereference (&lease
, MDL
);
5499 return ISC_R_SUCCESS
;
5502 /* This was hairy enough I didn't want to do it all in an if statement.
5504 * Returns: Truth is the secondary is allowed to get more leases based upon
5505 * MAC address affinity. False otherwise.
5508 secondary_not_hoarding(dhcp_failover_state_t
*state
, struct pool
*p
) {
5513 total
= p
->free_leases
+ p
->backup_leases
;
5515 /* How many leases is one side or the other allowed to "hold"? */
5516 hold
= ((total
* state
->max_lease_ownership
) + 50) / 100;
5518 /* If we were to send leases (or if the secondary were to send us
5519 * leases in the negative direction), how many would that be?
5521 lts
= (p
->free_leases
- p
->backup_leases
) / 2;
5523 /* The peer is not hoarding leases if we would send them more leases
5524 * (or they would take fewer leases) than the maximum they are allowed
5525 * to hold (the negative hold).
5527 return(lts
> -hold
);
5530 isc_result_t
dhcp_failover_process_bind_ack (dhcp_failover_state_t
*state
,
5531 failover_message_t
*msg
)
5533 struct lease
*lease
= NULL
;
5535 const char *message
= "no memory";
5536 u_int32_t pot_expire
;
5537 int send_to_backup
= ISC_FALSE
;
5540 ia
.len
= sizeof msg
-> assigned_addr
;
5541 memcpy (ia
.iabuf
, &msg
-> assigned_addr
, ia
.len
);
5543 if (!find_lease_by_ip_addr (&lease
, ia
, MDL
)) {
5544 message
= "no such lease";
5548 /* XXX check for conflicts. */
5549 if (msg
-> options_present
& FTB_REJECT_REASON
) {
5550 log_error ("bind update on %s from %s rejected: %.*s",
5551 piaddr (ia
), state
-> name
,
5552 (int)((msg
-> options_present
& FTB_MESSAGE
)
5553 ? msg
-> message
.count
5554 : strlen (dhcp_failover_reject_reason_print
5555 (msg
-> reject_reason
))),
5556 (msg
-> options_present
& FTB_MESSAGE
)
5557 ? (const char *)(msg
-> message
.data
)
5558 : (dhcp_failover_reject_reason_print
5559 (msg
-> reject_reason
)));
5563 /* Silently discard acks for leases we did not update (or multiple
5566 if (!lease
->last_xid
)
5569 if (lease
->last_xid
!= msg
->xid
) {
5570 message
= "xid mismatch";
5574 /* XXX Times may need to be adjusted based on clock skew! */
5575 if (msg
->options_present
& FTO_POTENTIAL_EXPIRY
)
5576 pot_expire
= msg
->potential_expiry
;
5578 pot_expire
= lease
->tstp
;
5580 /* If the lease was desired to enter a binding state, we set
5581 * such a value upon transmitting a bndupd. We do not clear it
5582 * if we receive a bndupd in the meantime (or change the state
5583 * of the lease again ourselves), but we do set binding_state
5584 * if we get a bndupd.
5586 * So desired_binding_state tells us what we sent a bndupd for,
5587 * and binding_state tells us what we have since determined in
5590 if (lease
->desired_binding_state
== FTS_EXPIRED
||
5591 lease
->desired_binding_state
== FTS_RESET
||
5592 lease
->desired_binding_state
== FTS_RELEASED
)
5594 /* It is not a problem to do this directly as we call
5595 * supersede_lease immediately after: the lease is requeued
5596 * even if its sort order (tsfp) has changed.
5598 lease
->atsfp
= lease
->tsfp
= pot_expire
;
5599 if ((state
->i_am
== secondary
) &&
5600 (lease
->flags
& RESERVED_LEASE
))
5601 lease
->next_binding_state
= FTS_BACKUP
;
5603 lease
->next_binding_state
= FTS_FREE
;
5605 /* Clear this condition for the next go-round. */
5606 lease
->desired_binding_state
= lease
->next_binding_state
;
5608 /* The peer will have made this state change, so set rewind. */
5609 lease
->rewind_binding_state
= lease
->next_binding_state
;
5611 supersede_lease(lease
, NULL
, 0, 0, 0, 0);
5614 /* Lease has returned to FREE state from the
5615 * transitional states. If the lease 'belongs'
5616 * to a client that would be served by the
5617 * peer, process a binding update now to send
5618 * the lease to backup state. But not if we
5619 * think we already have.
5621 if (state
->i_am
== primary
&&
5622 !(lease
->flags
& (RESERVED_LEASE
| BOOTP_LEASE
)) &&
5623 peer_wants_lease(lease
))
5624 send_to_backup
= ISC_TRUE
;
5626 if (!send_to_backup
&& state
->me
.state
== normal
)
5629 /* XXX It could be a problem to do this directly if the lease
5630 * XXX is sorted by tsfp.
5632 lease
->atsfp
= lease
->tsfp
= pot_expire
;
5633 if (lease
->desired_binding_state
!= lease
->binding_state
) {
5634 lease
->next_binding_state
=
5635 lease
->desired_binding_state
;
5636 supersede_lease(lease
, NULL
, 0, 0, 0, 0);
5639 /* Commit the lease only after a two-second timeout,
5640 so that if we get a bunch of acks in quick
5641 succession (e.g., when stealing leases from the
5642 secondary), we do not do an immediate commit for
5644 tv
.tv_sec
= cur_time
+ 2;
5646 add_timeout(&tv
, commit_leases_timeout
, (void *)0, 0, 0);
5650 dhcp_failover_ack_queue_remove (state
, lease
);
5652 /* If we are supposed to send an update done after we send
5653 this lease, go ahead and send it. */
5654 if (state
-> send_update_done
== lease
) {
5655 lease_dereference (&state
-> send_update_done
, MDL
);
5656 dhcp_failover_send_update_done (state
);
5659 /* Now that the lease is off the ack queue, consider putting it
5660 * back on the update queue for mac address affinity.
5662 if (send_to_backup
&& secondary_not_hoarding(state
, lease
->pool
)) {
5663 lease
->next_binding_state
= FTS_BACKUP
;
5664 lease
->tstp
= lease
->starts
= cur_time
;
5666 if (!supersede_lease(lease
, NULL
, 0, 1, 0, 0) ||
5667 !write_lease(lease
))
5668 log_error("can't commit lease %s for "
5669 "client affinity", piaddr(lease
->ip_addr
));
5671 if (state
->me
.state
== normal
)
5675 /* If there are updates pending, we've created space to send at
5677 dhcp_failover_send_updates (state
);
5680 lease_dereference (&lease
, MDL
);
5681 return ISC_R_SUCCESS
;
5684 log_info ("bind update on %s got ack from %s: %s.",
5685 piaddr (ia
), state
-> name
, message
);
5689 isc_result_t
dhcp_failover_generate_update_queue (dhcp_failover_state_t
*state
,
5692 struct shared_network
*s
;
5696 #define FREE_LEASES 0
5697 #define ACTIVE_LEASES 1
5698 #define EXPIRED_LEASES 2
5699 #define ABANDONED_LEASES 3
5700 #define BACKUP_LEASES 4
5701 #define RESERVED_LEASES 5
5702 LEASE_STRUCT_PTR lptr
[RESERVED_LEASES
+1];
5704 /* Loop through each pool in each shared network and call the
5705 expiry routine on the pool. */
5706 for (s
= shared_networks
; s
; s
= s
-> next
) {
5707 for (p
= s
-> pools
; p
; p
= p
-> next
) {
5708 if (p
->failover_peer
!= state
)
5711 lptr
[FREE_LEASES
] = &p
->free
;
5712 lptr
[ACTIVE_LEASES
] = &p
->active
;
5713 lptr
[EXPIRED_LEASES
] = &p
->expired
;
5714 lptr
[ABANDONED_LEASES
] = &p
->abandoned
;
5715 lptr
[BACKUP_LEASES
] = &p
->backup
;
5716 lptr
[RESERVED_LEASES
] = &p
->reserved
;
5718 for (i
= FREE_LEASES
; i
<= RESERVED_LEASES
; i
++) {
5719 for (l
= LEASE_GET_FIRSTP(lptr
[i
]);
5721 l
= LEASE_GET_NEXTP(lptr
[i
], l
)) {
5722 if ((l
->flags
& ON_QUEUE
) == 0 &&
5724 (l
->tstp
> l
->atsfp
) ||
5725 (i
== EXPIRED_LEASES
))) {
5726 l
-> desired_binding_state
= l
-> binding_state
;
5727 dhcp_failover_queue_update (l
, 0);
5733 return ISC_R_SUCCESS
;
5737 dhcp_failover_process_update_request (dhcp_failover_state_t
*state
,
5738 failover_message_t
*msg
)
5740 if (state
->send_update_done
) {
5741 log_info("Received update request while old update still "
5742 "flying! Silently discarding old request.");
5743 lease_dereference(&state
->send_update_done
, MDL
);
5746 /* Generate a fresh update queue. */
5747 dhcp_failover_generate_update_queue (state
, 0);
5749 state
->updxid
= msg
->xid
;
5751 /* If there's anything on the update queue (there shouldn't be
5752 anything on the ack queue), trigger an update done message
5753 when we get an ack for that lease. */
5754 if (state
-> update_queue_tail
) {
5755 lease_reference (&state
-> send_update_done
,
5756 state
-> update_queue_tail
, MDL
);
5757 dhcp_failover_send_updates (state
);
5758 log_info ("Update request from %s: sending update",
5761 /* Otherwise, there are no updates to send, so we can
5762 just send an UPDDONE message immediately. */
5763 dhcp_failover_send_update_done (state
);
5764 log_info ("Update request from %s: nothing pending",
5768 return ISC_R_SUCCESS
;
5772 dhcp_failover_process_update_request_all (dhcp_failover_state_t
*state
,
5773 failover_message_t
*msg
)
5775 if (state
->send_update_done
) {
5776 log_info("Received update request while old update still "
5777 "flying! Silently discarding old request.");
5778 lease_dereference(&state
->send_update_done
, MDL
);
5781 /* Generate a fresh update queue that includes every lease. */
5782 dhcp_failover_generate_update_queue (state
, 1);
5784 state
->updxid
= msg
->xid
;
5786 if (state
-> update_queue_tail
) {
5787 lease_reference (&state
-> send_update_done
,
5788 state
-> update_queue_tail
, MDL
);
5789 dhcp_failover_send_updates (state
);
5790 log_info ("Update request all from %s: sending update",
5793 /* This should really never happen, but it could happen
5794 on a server that currently has no leases configured. */
5795 dhcp_failover_send_update_done (state
);
5796 log_info ("Update request all from %s: nothing pending",
5800 return ISC_R_SUCCESS
;
5804 dhcp_failover_process_update_done (dhcp_failover_state_t
*state
,
5805 failover_message_t
*msg
)
5809 log_info ("failover peer %s: peer update completed.",
5812 state
-> curUPD
= 0;
5814 switch (state
-> me
.state
) {
5818 case communications_interrupted
:
5819 case resolution_interrupted
:
5825 break; /* shouldn't happen. */
5827 /* We got the UPDDONE, so we can go into normal state! */
5828 case potential_conflict
:
5829 if (state
->partner
.state
== conflict_done
) {
5830 if (state
->i_am
== secondary
) {
5831 dhcp_failover_set_state (state
, normal
);
5833 log_error("Secondary is in conflict_done "
5834 "state after conflict resolution, "
5835 "this is illegal.");
5836 dhcp_failover_set_state (state
, shut_down
);
5839 if (state
->i_am
== primary
)
5840 dhcp_failover_set_state (state
, conflict_done
);
5842 log_error("Spurious update-done message.");
5848 log_error("Spurious update-done message.");
5852 /* Wait for MCLT to expire before moving to recover_done,
5853 except that if both peers come up in recover, there is
5854 no point in waiting for MCLT to expire - this probably
5855 indicates the initial startup of a newly-configured
5857 if (state
-> me
.stos
+ state
-> mclt
> cur_time
&&
5858 state
-> partner
.state
!= recover
&&
5859 state
-> partner
.state
!= recover_done
) {
5860 dhcp_failover_set_state (state
, recover_wait
);
5861 #if defined (DEBUG_FAILOVER_TIMING)
5862 log_info ("add_timeout +%d %s",
5864 state
-> me
.stos
+ state
-> mclt
),
5865 "dhcp_failover_recover_done");
5867 tv
. tv_sec
= (int)(state
-> me
.stos
+ state
-> mclt
);
5870 dhcp_failover_recover_done
,
5872 (tvref_t
)omapi_object_reference
,
5874 omapi_object_dereference
);
5876 dhcp_failover_recover_done (state
);
5879 return ISC_R_SUCCESS
;
5882 void dhcp_failover_recover_done (void *sp
)
5884 dhcp_failover_state_t
*state
= sp
;
5886 #if defined (DEBUG_FAILOVER_TIMING)
5887 log_info ("dhcp_failover_recover_done");
5890 dhcp_failover_set_state (state
, recover_done
);
5893 #if defined (DEBUG_FAILOVER_MESSAGES)
5894 /* Print hunks of failover messages, doing line breaks as appropriate.
5895 Note that this assumes syslog is being used, rather than, e.g., the
5896 Windows NT logging facility, where just dumping the whole message in
5897 one hunk would be more appropriate. */
5899 void failover_print (char *obuf
,
5900 unsigned *obufix
, unsigned obufmax
, const char *s
)
5902 int len
= strlen (s
);
5904 while (len
+ *obufix
+ 1 >= obufmax
) {
5905 log_debug ("%s", obuf
);
5907 log_debug ("%s", s
);
5913 strcpy (&obuf
[*obufix
], s
);
5916 #endif /* defined (DEBUG_FAILOVER_MESSAGES) */
5918 /* Taken from draft-ietf-dhc-loadb-01.txt: */
5919 /* A "mixing table" of 256 distinct values, in pseudo-random order. */
5920 unsigned char loadb_mx_tbl
[256] = {
5921 251, 175, 119, 215, 81, 14, 79, 191, 103, 49,
5922 181, 143, 186, 157, 0, 232, 31, 32, 55, 60,
5923 152, 58, 17, 237, 174, 70, 160, 144, 220, 90,
5924 57, 223, 59, 3, 18, 140, 111, 166, 203, 196,
5925 134, 243, 124, 95, 222, 179, 197, 65, 180, 48,
5926 36, 15, 107, 46, 233, 130, 165, 30, 123, 161,
5927 209, 23, 97, 16, 40, 91, 219, 61, 100, 10,
5928 210, 109, 250, 127, 22, 138, 29, 108, 244, 67,
5929 207, 9, 178, 204, 74, 98, 126, 249, 167, 116,
5930 34, 77, 193, 200, 121, 5, 20, 113, 71, 35,
5931 128, 13, 182, 94, 25, 226, 227, 199, 75, 27,
5932 41, 245, 230, 224, 43, 225, 177, 26, 155, 150,
5933 212, 142, 218, 115, 241, 73, 88, 105, 39, 114,
5934 62, 255, 192, 201, 145, 214, 168, 158, 221, 148,
5935 154, 122, 12, 84, 82, 163, 44, 139, 228, 236,
5936 205, 242, 217, 11, 187, 146, 159, 64, 86, 239,
5937 195, 42, 106, 198, 118, 112, 184, 172, 87, 2,
5938 173, 117, 176, 229, 247, 253, 137, 185, 99, 164,
5939 102, 147, 45, 66, 231, 52, 141, 211, 194, 206,
5940 246, 238, 56, 110, 78, 248, 63, 240, 189, 93,
5941 92, 51, 53, 183, 19, 171, 72, 50, 33, 104,
5942 101, 69, 8, 252, 83, 120, 76, 135, 85, 54,
5943 202, 125, 188, 213, 96, 235, 136, 208, 162, 129,
5944 190, 132, 156, 38, 47, 1, 7, 254, 24, 4,
5945 216, 131, 89, 21, 28, 133, 37, 153, 149, 80,
5946 170, 68, 6, 169, 234, 151 };
5948 static unsigned char loadb_p_hash (const unsigned char *, unsigned);
5949 static unsigned char loadb_p_hash (const unsigned char *key
, unsigned len
)
5951 unsigned char hash
= len
;
5953 for(i
= len
; i
> 0; )
5954 hash
= loadb_mx_tbl
[hash
^ (key
[--i
])];
5958 int load_balance_mine (struct packet
*packet
, dhcp_failover_state_t
*state
)
5960 struct option_cache
*oc
;
5961 struct data_string ds
;
5962 unsigned char hbaix
;
5966 ec
= ntohs(packet
->raw
->secs
);
5969 * If desired check to see if the secs field may have been byte
5970 * swapped. We assume it has if the high order byte isn't cleared
5971 * while the low order byte is cleared. In this case we swap the
5972 * bytes and continue processing.
5974 if ((check_secs_byte_order
== 1) &&
5975 ((ec
> 255) && ((ec
& 0xff) == 0))) {
5976 ec
= (ec
>> 8) | (ec
<< 8);
5979 if ((state
->load_balance_max_secs
== 0) ||
5980 (state
->load_balance_max_secs
< ec
)) {
5984 /* If we don't have a hash bucket array, we can't tell if this
5985 one's ours, so we assume it's not. */
5989 oc
= lookup_option(&dhcp_universe
, packet
->options
,
5990 DHO_DHCP_CLIENT_IDENTIFIER
);
5991 memset(&ds
, 0, sizeof ds
);
5993 evaluate_option_cache(&ds
, packet
, NULL
, NULL
,
5994 packet
->options
, NULL
,
5995 &global_scope
, oc
, MDL
)) {
5996 hbaix
= loadb_p_hash(ds
.data
, ds
.len
);
5998 data_string_forget(&ds
, MDL
);
6000 hbaix
= loadb_p_hash(packet
->raw
->chaddr
,
6004 hm
= state
->hba
[(hbaix
>> 3) & 0x1F] & (1 << (hbaix
& 0x07));
6006 if (state
->i_am
== primary
)
6012 /* The inverse of load_balance_mine ("load balance theirs"). We can't
6013 * use the regular load_balance_mine() and invert it because of the case
6014 * where there might not be an HBA, and we want to indicate false here
6015 * in this case only.
6018 peer_wants_lease(struct lease
*lp
)
6020 dhcp_failover_state_t
*state
;
6021 unsigned char hbaix
;
6027 state
= lp
->pool
->failover_peer
;
6029 if (!state
|| !state
->hba
)
6033 hbaix
= loadb_p_hash(lp
->uid
, lp
->uid_len
);
6034 else if (lp
->hardware_addr
.hlen
> 1)
6035 /* Skip the first byte, which is the hardware type, and is
6036 * not included during actual load balancing checks above
6037 * since it is separate from the packet header chaddr field.
6038 * The remainder of the hardware address should be identical
6039 * to the chaddr contents.
6041 hbaix
= loadb_p_hash(lp
->hardware_addr
.hbuf
+ 1,
6042 lp
->hardware_addr
.hlen
- 1);
6043 else /* impossible to categorize into LBA */
6046 hm
= state
->hba
[(hbaix
>> 3) & 0x1F] & (1 << (hbaix
& 0x07));
6048 if (state
->i_am
== primary
)
6054 /* This deals with what to do with bind updates when
6055 we're in the normal state
6057 Note that tsfp had better be set from the latest bind update
6058 _before_ this function is called! */
6061 normal_binding_state_transition_check (struct lease
*lease
,
6062 dhcp_failover_state_t
*state
,
6063 binding_state_t binding_state
,
6066 binding_state_t new_state
;
6068 /* If there is no transition, it's no problem. */
6069 if (binding_state
== lease
-> binding_state
)
6070 return binding_state
;
6072 switch (lease
-> binding_state
) {
6075 switch (binding_state
) {
6082 /* If the lease was free, and our peer is primary,
6083 then it can make it active, or abandoned, or
6084 backup. Abandoned is treated like free in
6086 if (state
-> i_am
== secondary
)
6087 return binding_state
;
6089 /* Otherwise, it can't legitimately do any sort of
6090 state transition. Because the lease was free,
6091 and the error has already been made, we allow the
6092 peer to change its state anyway, but log a warning
6093 message in hopes that the error will be fixed. */
6094 case FTS_FREE
: /* for compiler */
6095 new_state
= binding_state
;
6099 log_fatal ("Impossible case at %s:%d.", MDL
);
6103 /* The secondary can't change the state of an active
6105 if (state
-> i_am
== primary
) {
6106 /* Except that the client may send the DHCPRELEASE
6107 to the secondary. We also allow for when the
6108 secondary gets a DECLINE and the primary does not.*/
6109 if ((binding_state
== FTS_RELEASED
) ||
6110 (binding_state
== FTS_ABANDONED
))
6111 return binding_state
;
6113 new_state
= lease
-> binding_state
;
6117 /* So this is only for transitions made by the primary: */
6118 switch (binding_state
) {
6121 /* Can't set a lease to free or backup until the
6122 peer agrees that it's expired. */
6123 if (tsfp
> cur_time
) {
6124 new_state
= lease
-> binding_state
;
6127 return binding_state
;
6130 /* XXX 65 should be the clock skew between the peers
6131 XXX plus a fudge factor. This code will result
6132 XXX in problems if MCLT is really short or the
6133 XXX max-lease-time is really short (less than the
6134 XXX fudge factor. */
6135 if (lease
-> ends
- 65 > cur_time
) {
6136 new_state
= lease
-> binding_state
;
6144 return binding_state
;
6147 log_fatal ("Impossible case at %s:%d.", MDL
);
6152 switch (binding_state
) {
6155 /* Can't set a lease to free or backup until the
6156 peer agrees that it's expired. */
6157 if (tsfp
> cur_time
) {
6158 new_state
= lease
-> binding_state
;
6161 return binding_state
;
6168 return binding_state
;
6171 log_fatal ("Impossible case at %s:%d.", MDL
);
6175 switch (binding_state
) {
6179 /* These are invalid state transitions - should we
6186 return binding_state
;
6189 log_fatal ("Impossible case at %s:%d.", MDL
);
6193 switch (binding_state
) {
6196 /* Can't set a lease to free or backup until the
6197 peer agrees that it's expired. */
6198 if (tsfp
> cur_time
) {
6199 new_state
= lease
-> binding_state
;
6202 return binding_state
;
6209 return binding_state
;
6212 log_fatal ("Impossible case at %s:%d.", MDL
);
6216 switch (binding_state
) {
6222 /* If the lease was in backup, and our peer
6223 is secondary, then it can make it active
6225 if (state
-> i_am
== primary
)
6226 return binding_state
;
6228 /* Either the primary or the secondary can
6229 reasonably move a lease from the backup
6230 state to the free state. */
6232 return binding_state
;
6235 new_state
= lease
-> binding_state
;
6239 log_fatal ("Impossible case at %s:%d.", MDL
);
6244 log_fatal ("Impossible case at %s:%d.", MDL
);
6251 /* Determine whether the state transition is okay when we're potentially
6252 in conflict with the peer. */
6254 conflict_binding_state_transition_check (struct lease
*lease
,
6255 dhcp_failover_state_t
*state
,
6256 binding_state_t binding_state
,
6259 binding_state_t new_state
;
6261 /* If there is no transition, it's no problem. */
6262 if (binding_state
== lease
-> binding_state
)
6263 new_state
= binding_state
;
6265 switch (lease
-> binding_state
) {
6266 /* If we think the lease is not in use, then the
6267 state into which the partner put it is just fine,
6275 new_state
= binding_state
;
6278 /* If we think the lease *is* in use, then we're not
6279 going to take the partner's change if the partner
6280 thinks it's free. */
6282 switch (binding_state
) {
6285 new_state
= lease
-> binding_state
;
6289 /* If we don't agree about expiry, it's
6290 * invalid. 65 should allow for max
6291 * clock skew (60) plus some fudge.
6292 * XXX: should we refetch cur_time?
6294 if ((lease
->ends
- 65) > cur_time
)
6295 new_state
= lease
->binding_state
;
6297 new_state
= binding_state
;
6300 /* RELEASED, RESET, and ABANDONED indicate
6301 * that our partner has information about
6302 * this lease that we did not witness. Our
6308 new_state
= binding_state
;
6312 log_fatal ("Impossible case at %s:%d.", MDL
);
6318 log_fatal ("Impossible case at %s:%d.", MDL
);
6325 /* We can reallocate a lease under the following circumstances:
6327 (1) It belongs to us - it's FTS_FREE, and we're primary, or it's
6328 FTS_BACKUP, and we're secondary.
6329 (2) We're in partner_down, and the lease is not active, and we
6330 can be sure that the other server didn't make it active.
6331 We can only be sure that the server didn't make it active
6332 when we are in the partner_down state and one of the following
6333 two conditions holds:
6334 (a) in the case that the time sent from the peer is earlier than
6335 the time we entered the partner_down state, at least MCLT has
6336 gone by since we entered partner_down, or
6337 (b) in the case that the time sent from the peer is later than
6338 the time when we entered partner_down, the current time is
6339 later than the time sent from the peer by at least MCLT. */
6341 int lease_mine_to_reallocate (struct lease
*lease
)
6343 dhcp_failover_state_t
*peer
;
6345 if (lease
&& lease
->pool
&&
6346 (peer
= lease
->pool
->failover_peer
)) {
6348 * In addition to the normal rules governing wether a server
6349 * is allowed to operate changes on a lease, the server is
6350 * allowed to operate on a lease from the standpoint of the
6351 * most conservative guess of the peer's state for this lease.
6353 switch (lease
->binding_state
) {
6355 /* ACTIVE leases may not be reallocated. */
6360 /* FREE leases may only be allocated by the primary,
6361 * unless the secondary is acting in partner_down
6362 * state and stos+mclt or tsfp+mclt has expired,
6363 * whichever is greater.
6365 * ABANDONED are treated the same as FREE for all
6366 * purposes here. Note that servers will only try
6367 * for ABANDONED leases as a last resort anyway.
6369 if (peer
-> i_am
== primary
)
6372 return(peer
->service_state
== service_partner_down
&&
6373 ((lease
->tsfp
< peer
->me
.stos
) ?
6374 (peer
->me
.stos
+ peer
->mclt
< cur_time
) :
6375 (lease
->tsfp
+ peer
->mclt
< cur_time
)));
6380 * These leases are generally untouchable until the
6381 * peer acknowledges their state change. However, as
6382 * this is impossible if the peer is offline, the
6383 * failover protocol permits an 'optimization' to
6384 * rewind the lease to a previous state that the server
6385 * is allowed to operate on, if that was the state that
6386 * was last acknowledged by the peer.
6388 * So if a lease was free, was allocated by this
6389 * server, and expired without ever being transmitted
6390 * to the peer, it can be returned to free and given
6391 * to any new client legally.
6393 if ((peer
->i_am
== primary
) &&
6394 (lease
->rewind_binding_state
== FTS_FREE
))
6396 if ((peer
->i_am
== secondary
) &&
6397 (lease
->rewind_binding_state
== FTS_BACKUP
))
6400 /* FALL THROUGH (released, expired, reset) */
6403 * Released, expired, and reset leases go onto the
6404 * 'expired' queue all together. Upon entry into
6405 * partner-down state, this queue of leases has their
6406 * tsfp values modified to equal stos+mclt, the point
6407 * at which the server is allowed to remove them from
6408 * these transitional states.
6410 * Note that although tsfp has been possibly extended
6411 * past the actual tsfp we received from the peer, we
6412 * don't have to take any special action. Since tsfp
6413 * will be equal to the current time when the lease
6414 * transitions to free, tsfp will not be used to grant
6415 * lease-times longer than the MCLT to clients, which
6416 * is the only danger for this sort of modification.
6418 return((peer
->service_state
== service_partner_down
) &&
6419 (lease
->tsfp
< cur_time
));
6422 /* Only the secondary may allocate BACKUP leases,
6423 * unless in partner_down state in which case at
6424 * least TSFP+MCLT or STOS+MCLT must have expired,
6425 * whichever is greater.
6427 if (peer
->i_am
== secondary
)
6430 return((peer
->service_state
== service_partner_down
) &&
6431 ((lease
->tsfp
< peer
->me
.stos
) ?
6432 (peer
->me
.stos
+ peer
->mclt
< cur_time
) :
6433 (lease
->tsfp
+ peer
->mclt
< cur_time
)));
6436 /* All lease states appear above. */
6437 log_fatal("Impossible case at %s:%d.", MDL
);
6443 return(lease
->binding_state
== FTS_FREE
||
6444 lease
->binding_state
== FTS_BACKUP
);
6449 static isc_result_t
failover_message_reference (failover_message_t
**mp
,
6450 failover_message_t
*m
,
6451 const char *file
, int line
)
6455 return ISC_R_SUCCESS
;
6458 static isc_result_t
failover_message_dereference (failover_message_t
**mp
,
6459 const char *file
, int line
)
6461 failover_message_t
*m
;
6464 if (m
-> refcnt
== 0) {
6466 failover_message_dereference (&m
-> next
,
6468 if (m
-> chaddr
.data
)
6469 dfree (m
-> chaddr
.data
, file
, line
);
6470 if (m
-> client_identifier
.data
)
6471 dfree (m
-> client_identifier
.data
, file
, line
);
6473 dfree (m
-> hba
.data
, file
, line
);
6474 if (m
-> message
.data
)
6475 dfree (m
-> message
.data
, file
, line
);
6476 if (m
-> relationship_name
.data
)
6477 dfree (m
-> relationship_name
.data
, file
, line
);
6478 if (m
-> reply_options
.data
)
6479 dfree (m
-> reply_options
.data
, file
, line
);
6480 if (m
-> request_options
.data
)
6481 dfree (m
-> request_options
.data
, file
, line
);
6482 if (m
-> vendor_class
.data
)
6483 dfree (m
-> vendor_class
.data
, file
, line
);
6484 if (m
-> vendor_options
.data
)
6485 dfree (m
-> vendor_options
.data
, file
, line
);
6487 dfree (m
-> ddns
.data
, file
, line
);
6488 dfree (*mp
, file
, line
);
6491 return ISC_R_SUCCESS
;
6494 OMAPI_OBJECT_ALLOC (dhcp_failover_state
, dhcp_failover_state_t
,
6495 dhcp_type_failover_state
)
6496 OMAPI_OBJECT_ALLOC (dhcp_failover_listener
, dhcp_failover_listener_t
,
6497 dhcp_type_failover_listener
)
6498 OMAPI_OBJECT_ALLOC (dhcp_failover_link
, dhcp_failover_link_t
,
6499 dhcp_type_failover_link
)
6500 #endif /* defined (FAILOVER_PROTOCOL) */
6502 const char *binding_state_print (enum failover_state state
)
6541 * \brief Given a char pointer, return always return a printable value
6543 * This function is intended to be used in within log statements, such that
6544 * its invocation only occurs if the logging level is enabled.
6546 * \param value pointer the character to print
6548 * \return If value is null, returns the string "<none>", if it contains
6549 * non-printable bytes, returns the string "<unsuitable for printing>",
6550 * otherwise it returns a const pointer to value
6552 const char *printable(const char* value
) {
6553 const char *print_value
= "<none>";
6555 if ((strlen (value
) <= 64) &&
6556 db_printable((unsigned char*)value
)) {
6557 print_value
= value
;
6560 print_value
= "<unsuitable for printing>";
6564 return (print_value
);
6568 * \brief Remove information from a prior use of a lease
6570 * Remove information from a lease that is not germain to lease affinity
6572 * \param lease the lease to scrub
6574 void scrub_lease(struct lease
* lease
, const char *file
, int line
) {
6575 #if defined (DEBUG_FAILOVER_MESSAGES)
6576 /* While technically not associated with FO messaging this log statement
6577 * draws more questions then it helps, so we'll ifdef it out */
6578 log_debug ("%s(%d):scrubbing lease for %s, hostname: %s", file
, line
,
6579 piaddr(lease
->ip_addr
), printable(lease
->client_hostname
));
6582 if (lease
->client_hostname
) {
6583 dfree (lease
->client_hostname
, MDL
);
6584 lease
->client_hostname
= (char *)0;