1 1.2 christos /* $NetBSD: failover.c,v 1.4 2022/04/03 01:10:59 christos Exp $ */ 2 1.1 christos 3 1.1 christos /* failover.c 4 1.1 christos 5 1.1 christos Failover protocol support code... */ 6 1.1 christos 7 1.1 christos /* 8 1.4 christos * Copyright (C) 2004-2022 Internet Systems Consortium, Inc. ("ISC") 9 1.1 christos * Copyright (c) 1999-2003 by Internet Software Consortium 10 1.1 christos * 11 1.1 christos * This Source Code Form is subject to the terms of the Mozilla Public 12 1.1 christos * License, v. 2.0. If a copy of the MPL was not distributed with this 13 1.1 christos * file, You can obtain one at http://mozilla.org/MPL/2.0/. 14 1.1 christos * 15 1.1 christos * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES 16 1.1 christos * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 17 1.1 christos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR 18 1.1 christos * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 19 1.1 christos * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 20 1.1 christos * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 21 1.1 christos * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 22 1.1 christos * 23 1.1 christos * Internet Systems Consortium, Inc. 24 1.4 christos * PO Box 360 25 1.4 christos * Newmarket, NH 03857 USA 26 1.1 christos * <info (at) isc.org> 27 1.1 christos * https://www.isc.org/ 28 1.1 christos * 29 1.1 christos */ 30 1.1 christos 31 1.1 christos #include <sys/cdefs.h> 32 1.2 christos __RCSID("$NetBSD: failover.c,v 1.4 2022/04/03 01:10:59 christos Exp $"); 33 1.1 christos 34 1.1 christos #include "cdefs.h" 35 1.1 christos #include "dhcpd.h" 36 1.1 christos #include <omapip/omapip_p.h> 37 1.1 christos 38 1.1 christos #if defined (FAILOVER_PROTOCOL) 39 1.1 christos dhcp_failover_state_t *failover_states; 40 1.1 christos static isc_result_t do_a_failover_option (omapi_object_t *, 41 1.1 christos dhcp_failover_link_t *); 42 1.1 christos dhcp_failover_listener_t *failover_listeners; 43 1.1 christos 44 1.1 christos static isc_result_t failover_message_reference (failover_message_t **, 45 1.1 christos failover_message_t *, 46 1.1 christos const char *file, int line); 47 1.1 christos static isc_result_t failover_message_dereference (failover_message_t **, 48 1.1 christos const char *file, int line); 49 1.1 christos 50 1.1 christos static void dhcp_failover_pool_balance(dhcp_failover_state_t *state); 51 1.1 christos static void dhcp_failover_pool_reqbalance(dhcp_failover_state_t *state); 52 1.1 christos static int dhcp_failover_pool_dobalance(dhcp_failover_state_t *state, 53 1.1 christos isc_boolean_t *sendreq); 54 1.1 christos static inline int secondary_not_hoarding(dhcp_failover_state_t *state, 55 1.1 christos struct pool *p); 56 1.1 christos static void scrub_lease(struct lease* lease, const char *file, int line); 57 1.1 christos 58 1.1 christos int check_secs_byte_order = 0; /* enables byte order check of secs field if 1 */ 59 1.1 christos 60 1.1 christos /*! 61 1.1 christos * \brief Performs a "pre-flight" sanity check of failover configuration 62 1.1 christos * 63 1.1 christos * Provides an opportunity to do post-parse pre-startup sanity checking 64 1.1 christos * of failover configuration. This allows checks to be done under test 65 1.1 christos * mode (-T), without requiring full startup for validation. 66 1.1 christos * 67 1.1 christos * Currently, it enforces all failover peers be used in at lease one 68 1.1 christos * pool. This logic was formerly located in dhcp_failover_startup. 69 1.1 christos * 70 1.1 christos * On failure, a fatal error is logged. 71 1.1 christos * 72 1.1 christos */ 73 1.1 christos void dhcp_failover_sanity_check() { 74 1.1 christos dhcp_failover_state_t *state; 75 1.1 christos int fail_count = 0; 76 1.1 christos 77 1.1 christos for (state = failover_states; state; state = state->next) { 78 1.1 christos if (state->pool_count == 0) { 79 1.1 christos log_error ("ERROR: Failover peer, %s, has no referring" 80 1.1 christos " pools. You must refer to each peer in at" 81 1.1 christos " least one pool declaration.", 82 1.1 christos state->name); 83 1.1 christos fail_count++; 84 1.1 christos } 85 1.1 christos 86 1.1 christos if (state->load_balance_max_secs == 0) { 87 1.1 christos log_info ("WARNING: load balancing will be disabled " 88 1.1 christos "for failover peer, %s, " 89 1.1 christos "because its load balance max secs is 0", 90 1.1 christos state->name); 91 1.1 christos } 92 1.1 christos } 93 1.1 christos 94 1.1 christos if (fail_count) { 95 1.1 christos log_fatal ("Failover configuration sanity check failed"); 96 1.1 christos } 97 1.1 christos 98 1.1 christos } 99 1.1 christos 100 1.1 christos void dhcp_failover_startup () 101 1.1 christos { 102 1.1 christos dhcp_failover_state_t *state; 103 1.1 christos isc_result_t status; 104 1.1 christos struct timeval tv; 105 1.1 christos 106 1.1 christos for (state = failover_states; state; state = state -> next) { 107 1.1 christos dhcp_failover_state_transition (state, "startup"); 108 1.1 christos /* In case the peer is already running, immediately try 109 1.1 christos to establish a connection with it. */ 110 1.1 christos status = dhcp_failover_link_initiate ((omapi_object_t *)state); 111 1.1 christos if (status != ISC_R_SUCCESS && status != DHCP_R_INCOMPLETE) { 112 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 113 1.1 christos log_info ("add_timeout +90 dhcp_failover_reconnect"); 114 1.1 christos #endif 115 1.1 christos tv . tv_sec = cur_time + 90; 116 1.1 christos tv . tv_usec = 0; 117 1.1 christos add_timeout (&tv, 118 1.1 christos dhcp_failover_reconnect, state, 119 1.1 christos (tvref_t) 120 1.1 christos dhcp_failover_state_reference, 121 1.1 christos (tvunref_t) 122 1.1 christos dhcp_failover_state_dereference); 123 1.1 christos log_error ("failover peer %s: %s", state -> name, 124 1.1 christos isc_result_totext (status)); 125 1.1 christos } 126 1.1 christos 127 1.1 christos status = (dhcp_failover_listen 128 1.1 christos ((omapi_object_t *)state)); 129 1.1 christos if (status != ISC_R_SUCCESS) { 130 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 131 1.1 christos log_info ("add_timeout +90 %s", 132 1.1 christos "dhcp_failover_listener_restart"); 133 1.1 christos #endif 134 1.1 christos tv . tv_sec = cur_time + 90; 135 1.1 christos tv . tv_usec = 0; 136 1.1 christos add_timeout (&tv, 137 1.1 christos dhcp_failover_listener_restart, 138 1.1 christos state, 139 1.1 christos (tvref_t)omapi_object_reference, 140 1.1 christos (tvunref_t)omapi_object_dereference); 141 1.1 christos } 142 1.1 christos } 143 1.1 christos } 144 1.1 christos 145 1.1 christos int dhcp_failover_write_all_states () 146 1.1 christos { 147 1.1 christos dhcp_failover_state_t *state; 148 1.1 christos 149 1.1 christos for (state = failover_states; state; state = state -> next) { 150 1.1 christos if (!write_failover_state (state)) 151 1.1 christos return 0; 152 1.1 christos } 153 1.1 christos return 1; 154 1.1 christos } 155 1.1 christos 156 1.1 christos isc_result_t enter_failover_peer (peer) 157 1.1 christos dhcp_failover_state_t *peer; 158 1.1 christos { 159 1.1 christos dhcp_failover_state_t *dup = (dhcp_failover_state_t *)0; 160 1.1 christos isc_result_t status; 161 1.1 christos 162 1.1 christos status = find_failover_peer (&dup, peer -> name, MDL); 163 1.1 christos if (status == ISC_R_NOTFOUND) { 164 1.1 christos if (failover_states) { 165 1.1 christos dhcp_failover_state_reference (&peer -> next, 166 1.1 christos failover_states, MDL); 167 1.1 christos dhcp_failover_state_dereference (&failover_states, 168 1.1 christos MDL); 169 1.1 christos } 170 1.1 christos dhcp_failover_state_reference (&failover_states, peer, MDL); 171 1.1 christos return ISC_R_SUCCESS; 172 1.1 christos } 173 1.1 christos dhcp_failover_state_dereference (&dup, MDL); 174 1.1 christos if (status == ISC_R_SUCCESS) 175 1.1 christos return ISC_R_EXISTS; 176 1.1 christos return status; 177 1.1 christos } 178 1.1 christos 179 1.1 christos isc_result_t find_failover_peer (peer, name, file, line) 180 1.1 christos dhcp_failover_state_t **peer; 181 1.1 christos const char *name; 182 1.1 christos const char *file; 183 1.1 christos int line; 184 1.1 christos { 185 1.1 christos dhcp_failover_state_t *p; 186 1.1 christos 187 1.1 christos for (p = failover_states; p; p = p -> next) 188 1.1 christos if (!strcmp (name, p -> name)) 189 1.1 christos break; 190 1.1 christos if (p) 191 1.1 christos return dhcp_failover_state_reference (peer, p, file, line); 192 1.1 christos return ISC_R_NOTFOUND; 193 1.1 christos } 194 1.1 christos 195 1.1 christos /* The failover protocol has three objects associated with it. For 196 1.1 christos each failover partner declaration in the dhcpd.conf file, primary 197 1.1 christos or secondary, there is a failover_state object. For any primary or 198 1.1 christos secondary state object that has a connection to its peer, there is 199 1.1 christos also a failover_link object, which has its own input state separate 200 1.1 christos from the failover protocol state for managing the actual bytes 201 1.1 christos coming in off the wire. Finally, there will be one listener object 202 1.1 christos for every distinct port number associated with a secondary 203 1.1 christos failover_state object. Normally all secondary failover_state 204 1.1 christos objects are expected to listen on the same port number, so there 205 1.1 christos need be only one listener object, but if different port numbers are 206 1.1 christos specified for each failover object, there could be as many as one 207 1.1 christos listener object for each secondary failover_state object. */ 208 1.1 christos 209 1.1 christos /* This, then, is the implementation of the failover link object. */ 210 1.1 christos 211 1.1 christos isc_result_t dhcp_failover_link_initiate (omapi_object_t *h) 212 1.1 christos { 213 1.1 christos isc_result_t status; 214 1.1 christos dhcp_failover_link_t *obj; 215 1.1 christos dhcp_failover_state_t *state; 216 1.1 christos omapi_object_t *o; 217 1.1 christos int i; 218 1.1 christos struct data_string ds; 219 1.1 christos omapi_addr_list_t *addrs = (omapi_addr_list_t *)0; 220 1.1 christos omapi_addr_t local_addr; 221 1.1 christos 222 1.1 christos /* Find the failover state in the object chain. */ 223 1.1 christos for (o = h; o -> outer; o = o -> outer) 224 1.1 christos ; 225 1.1 christos for (; o; o = o -> inner) { 226 1.1 christos if (o -> type == dhcp_type_failover_state) 227 1.1 christos break; 228 1.1 christos } 229 1.1 christos if (!o) 230 1.1 christos return DHCP_R_INVALIDARG; 231 1.1 christos state = (dhcp_failover_state_t *)o; 232 1.1 christos 233 1.1 christos obj = (dhcp_failover_link_t *)0; 234 1.1 christos status = dhcp_failover_link_allocate (&obj, MDL); 235 1.1 christos if (status != ISC_R_SUCCESS) 236 1.1 christos return status; 237 1.1 christos option_cache_reference (&obj -> peer_address, 238 1.1 christos state -> partner.address, MDL); 239 1.1 christos obj -> peer_port = state -> partner.port; 240 1.1 christos dhcp_failover_state_reference (&obj -> state_object, state, MDL); 241 1.1 christos 242 1.1 christos memset (&ds, 0, sizeof ds); 243 1.1 christos if (!evaluate_option_cache (&ds, (struct packet *)0, (struct lease *)0, 244 1.1 christos (struct client_state *)0, 245 1.1 christos (struct option_state *)0, 246 1.1 christos (struct option_state *)0, 247 1.1 christos &global_scope, obj -> peer_address, MDL)) { 248 1.1 christos dhcp_failover_link_dereference (&obj, MDL); 249 1.1 christos return ISC_R_UNEXPECTED; 250 1.1 christos } 251 1.1 christos 252 1.1 christos /* Make an omapi address list out of a buffer containing zero or more 253 1.1 christos IPv4 addresses. */ 254 1.1 christos status = omapi_addr_list_new (&addrs, ds.len / 4, MDL); 255 1.1 christos if (status != ISC_R_SUCCESS) { 256 1.1 christos dhcp_failover_link_dereference (&obj, MDL); 257 1.1 christos return status; 258 1.1 christos } 259 1.1 christos 260 1.1 christos for (i = 0; i < addrs -> count; i++) { 261 1.1 christos addrs -> addresses [i].addrtype = AF_INET; 262 1.1 christos addrs -> addresses [i].addrlen = sizeof (struct in_addr); 263 1.1 christos memcpy (addrs -> addresses [i].address, 264 1.1 christos &ds.data [i * 4], sizeof (struct in_addr)); 265 1.1 christos addrs -> addresses [i].port = obj -> peer_port; 266 1.1 christos } 267 1.1 christos data_string_forget (&ds, MDL); 268 1.1 christos 269 1.1 christos /* Now figure out the local address that we're supposed to use. */ 270 1.1 christos if (!state -> me.address || 271 1.1 christos !evaluate_option_cache (&ds, (struct packet *)0, 272 1.1 christos (struct lease *)0, 273 1.1 christos (struct client_state *)0, 274 1.1 christos (struct option_state *)0, 275 1.1 christos (struct option_state *)0, 276 1.1 christos &global_scope, state -> me.address, 277 1.1 christos MDL)) { 278 1.1 christos memset (&local_addr, 0, sizeof local_addr); 279 1.1 christos local_addr.addrtype = AF_INET; 280 1.1 christos local_addr.addrlen = sizeof (struct in_addr); 281 1.1 christos if (!state -> server_identifier.len) { 282 1.1 christos log_fatal ("failover peer %s: no local address.", 283 1.1 christos state -> name); 284 1.1 christos } 285 1.1 christos } else { 286 1.1 christos if (ds.len != sizeof (struct in_addr)) { 287 1.1 christos log_error("failover peer %s: 'address' parameter " 288 1.1 christos "fails to resolve to an IPv4 address", 289 1.1 christos state->name); 290 1.1 christos data_string_forget (&ds, MDL); 291 1.1 christos dhcp_failover_link_dereference (&obj, MDL); 292 1.1 christos omapi_addr_list_dereference (&addrs, MDL); 293 1.1 christos return DHCP_R_INVALIDARG; 294 1.1 christos } 295 1.1 christos local_addr.addrtype = AF_INET; 296 1.1 christos local_addr.addrlen = ds.len; 297 1.1 christos memcpy (local_addr.address, ds.data, ds.len); 298 1.1 christos if (!state -> server_identifier.len) 299 1.1 christos data_string_copy (&state -> server_identifier, 300 1.1 christos &ds, MDL); 301 1.1 christos data_string_forget (&ds, MDL); 302 1.1 christos local_addr.port = 0; /* Let the O.S. choose. */ 303 1.1 christos } 304 1.1 christos 305 1.1 christos status = omapi_connect_list ((omapi_object_t *)obj, 306 1.1 christos addrs, &local_addr); 307 1.1 christos omapi_addr_list_dereference (&addrs, MDL); 308 1.1 christos 309 1.1 christos dhcp_failover_link_dereference (&obj, MDL); 310 1.1 christos return status; 311 1.1 christos } 312 1.1 christos 313 1.1 christos isc_result_t dhcp_failover_link_signal (omapi_object_t *h, 314 1.1 christos const char *name, va_list ap) 315 1.1 christos { 316 1.1 christos isc_result_t status; 317 1.1 christos dhcp_failover_link_t *link; 318 1.1 christos omapi_object_t *c; 319 1.1 christos dhcp_failover_state_t *s, *state = (dhcp_failover_state_t *)0; 320 1.1 christos char *sname; 321 1.1 christos int slen; 322 1.1 christos struct timeval tv; 323 1.1 christos 324 1.1 christos if (h -> type != dhcp_type_failover_link) { 325 1.1 christos /* XXX shouldn't happen. Put an assert here? */ 326 1.1 christos return ISC_R_UNEXPECTED; 327 1.1 christos } 328 1.1 christos link = (dhcp_failover_link_t *)h; 329 1.1 christos 330 1.1 christos if (!strcmp (name, "connect")) { 331 1.1 christos if (link -> state_object -> i_am == primary) { 332 1.1 christos status = dhcp_failover_send_connect (h); 333 1.1 christos if (status != ISC_R_SUCCESS) { 334 1.1 christos log_info ("dhcp_failover_send_connect: %s", 335 1.1 christos isc_result_totext (status)); 336 1.1 christos omapi_disconnect (h -> outer, 1); 337 1.1 christos } 338 1.1 christos } else 339 1.1 christos status = ISC_R_SUCCESS; 340 1.1 christos /* Allow the peer fifteen seconds to send us a 341 1.1 christos startup message. */ 342 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 343 1.1 christos log_info ("add_timeout +15 %s", 344 1.1 christos "dhcp_failover_link_startup_timeout"); 345 1.1 christos #endif 346 1.1 christos tv . tv_sec = cur_time + 15; 347 1.1 christos tv . tv_usec = 0; 348 1.1 christos add_timeout (&tv, 349 1.1 christos dhcp_failover_link_startup_timeout, 350 1.1 christos link, 351 1.1 christos (tvref_t)dhcp_failover_link_reference, 352 1.1 christos (tvunref_t)dhcp_failover_link_dereference); 353 1.1 christos return status; 354 1.1 christos } 355 1.1 christos 356 1.1 christos if (!strcmp (name, "disconnect")) { 357 1.1 christos if (link -> state_object) { 358 1.1 christos dhcp_failover_state_reference (&state, 359 1.1 christos link -> state_object, MDL); 360 1.1 christos link -> state = dhcp_flink_disconnected; 361 1.1 christos 362 1.1 christos /* Make the transition. */ 363 1.1 christos if (state->link_to_peer == link) 364 1.1 christos dhcp_failover_state_transition(link->state_object, name); 365 1.1 christos 366 1.1 christos /* Schedule an attempt to reconnect. */ 367 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 368 1.1 christos log_info("add_timeout +5 dhcp_failover_reconnect"); 369 1.1 christos #endif 370 1.1 christos tv.tv_sec = cur_time + 5; 371 1.1 christos tv.tv_usec = cur_tv.tv_usec; 372 1.1 christos add_timeout(&tv, dhcp_failover_reconnect, state, 373 1.1 christos (tvref_t)dhcp_failover_state_reference, 374 1.1 christos (tvunref_t)dhcp_failover_state_dereference); 375 1.1 christos 376 1.1 christos dhcp_failover_state_dereference (&state, MDL); 377 1.1 christos } 378 1.1 christos return ISC_R_SUCCESS; 379 1.1 christos } 380 1.1 christos 381 1.1 christos if (!strcmp (name, "status")) { 382 1.1 christos if (link -> state_object) { 383 1.1 christos isc_result_t status; 384 1.1 christos 385 1.1 christos status = va_arg(ap, isc_result_t); 386 1.1 christos 387 1.1 christos if ((status == ISC_R_HOSTUNREACH) || (status == ISC_R_TIMEDOUT)) { 388 1.1 christos dhcp_failover_state_reference (&state, 389 1.1 christos link -> state_object, MDL); 390 1.1 christos link -> state = dhcp_flink_disconnected; 391 1.1 christos 392 1.1 christos /* Make the transition. */ 393 1.1 christos dhcp_failover_state_transition (link -> state_object, 394 1.1 christos "disconnect"); 395 1.1 christos 396 1.1 christos /* Start trying to reconnect. */ 397 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 398 1.1 christos log_info ("add_timeout +5 %s", 399 1.1 christos "dhcp_failover_reconnect"); 400 1.1 christos #endif 401 1.1 christos tv . tv_sec = cur_time + 5; 402 1.1 christos tv . tv_usec = 0; 403 1.1 christos add_timeout (&tv, dhcp_failover_reconnect, 404 1.1 christos state, 405 1.1 christos (tvref_t)dhcp_failover_state_reference, 406 1.1 christos (tvunref_t)dhcp_failover_state_dereference); 407 1.1 christos } 408 1.1 christos dhcp_failover_state_dereference (&state, MDL); 409 1.1 christos } 410 1.1 christos return ISC_R_SUCCESS; 411 1.1 christos } 412 1.1 christos 413 1.1 christos /* Not a signal we recognize? */ 414 1.1 christos if (strcmp (name, "ready")) { 415 1.1 christos if (h -> inner && h -> inner -> type -> signal_handler) 416 1.1 christos return (*(h -> inner -> type -> signal_handler)) 417 1.1 christos (h -> inner, name, ap); 418 1.1 christos return ISC_R_NOTFOUND; 419 1.1 christos } 420 1.1 christos 421 1.1 christos if (!h -> outer || h -> outer -> type != omapi_type_connection) 422 1.1 christos return DHCP_R_INVALIDARG; 423 1.1 christos c = h -> outer; 424 1.1 christos 425 1.1 christos /* We get here because we requested that we be woken up after 426 1.1 christos some number of bytes were read, and that number of bytes 427 1.1 christos has in fact been read. */ 428 1.1 christos switch (link -> state) { 429 1.1 christos case dhcp_flink_start: 430 1.1 christos link -> state = dhcp_flink_message_length_wait; 431 1.1 christos if ((omapi_connection_require (c, 2)) != ISC_R_SUCCESS) 432 1.1 christos break; 433 1.1 christos case dhcp_flink_message_length_wait: 434 1.1 christos next_message: 435 1.1 christos link -> state = dhcp_flink_message_wait; 436 1.1 christos link -> imsg = dmalloc (sizeof (failover_message_t), MDL); 437 1.1 christos if (!link -> imsg) { 438 1.1 christos status = ISC_R_NOMEMORY; 439 1.1 christos dhcp_flink_fail: 440 1.1 christos if (link -> imsg) { 441 1.1 christos failover_message_dereference (&link->imsg, 442 1.1 christos MDL); 443 1.1 christos } 444 1.1 christos link -> state = dhcp_flink_disconnected; 445 1.1 christos log_info ("message length wait: %s", 446 1.1 christos isc_result_totext (status)); 447 1.1 christos omapi_disconnect (c, 1); 448 1.1 christos /* XXX just blow away the protocol state now? 449 1.1 christos XXX or will disconnect blow it away? */ 450 1.1 christos return ISC_R_UNEXPECTED; 451 1.1 christos } 452 1.1 christos memset (link -> imsg, 0, sizeof (failover_message_t)); 453 1.1 christos link -> imsg -> refcnt = 1; 454 1.1 christos /* Get the length: */ 455 1.1 christos omapi_connection_get_uint16 (c, &link -> imsg_len); 456 1.1 christos link -> imsg_count = 0; /* Bytes read. */ 457 1.1 christos 458 1.1 christos /* Ensure the message is of valid length. */ 459 1.1 christos if (link->imsg_len < DHCP_FAILOVER_MIN_MESSAGE_SIZE || 460 1.1 christos link->imsg_len > DHCP_FAILOVER_MAX_MESSAGE_SIZE) { 461 1.1 christos status = ISC_R_UNEXPECTED; 462 1.1 christos goto dhcp_flink_fail; 463 1.1 christos } 464 1.1 christos 465 1.1 christos if ((omapi_connection_require (c, link -> imsg_len - 2U)) != 466 1.1 christos ISC_R_SUCCESS) 467 1.1 christos break; 468 1.1 christos case dhcp_flink_message_wait: 469 1.1 christos /* Read in the message. At this point we have the 470 1.1 christos entire message in the input buffer. For each 471 1.1 christos incoming value ID, set a bit in the bitmask 472 1.1 christos indicating that we've gotten it. Maybe flag an 473 1.1 christos error message if the bit is already set. Once 474 1.1 christos we're done reading, we can check the bitmask to 475 1.1 christos make sure that the required fields for each message 476 1.1 christos have been included. */ 477 1.1 christos 478 1.1 christos link -> imsg_count += 2; /* Count the length as read. */ 479 1.1 christos 480 1.1 christos /* Get message type. */ 481 1.1 christos omapi_connection_copyout (&link -> imsg -> type, c, 1); 482 1.1 christos link -> imsg_count++; 483 1.1 christos 484 1.1 christos /* Get message payload offset. */ 485 1.1 christos omapi_connection_copyout (&link -> imsg_payoff, c, 1); 486 1.1 christos link -> imsg_count++; 487 1.1 christos 488 1.1 christos /* Get message time. */ 489 1.1 christos omapi_connection_get_uint32 (c, &link -> imsg -> time); 490 1.1 christos link -> imsg_count += 4; 491 1.1 christos 492 1.1 christos /* Get transaction ID. */ 493 1.1 christos omapi_connection_get_uint32 (c, &link -> imsg -> xid); 494 1.1 christos link -> imsg_count += 4; 495 1.1 christos 496 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 497 1.1 christos # if !defined(DEBUG_FAILOVER_CONTACT_MESSAGES) 498 1.1 christos if (link->imsg->type == FTM_CONTACT) 499 1.1 christos goto skip_contact; 500 1.1 christos # endif 501 1.1 christos log_info ("link: message %s payoff %d time %ld xid %ld", 502 1.1 christos dhcp_failover_message_name (link -> imsg -> type), 503 1.1 christos link -> imsg_payoff, 504 1.1 christos (unsigned long)link -> imsg -> time, 505 1.1 christos (unsigned long)link -> imsg -> xid); 506 1.1 christos # if !defined(DEBUG_FAILOVER_CONTACT_MESSAGES) 507 1.1 christos skip_contact: 508 1.1 christos # endif 509 1.1 christos #endif 510 1.1 christos /* Skip over any portions of the message header that we 511 1.1 christos don't understand. */ 512 1.1 christos if (link -> imsg_payoff - link -> imsg_count) { 513 1.1 christos omapi_connection_copyout ((unsigned char *)0, c, 514 1.1 christos (link -> imsg_payoff - 515 1.1 christos link -> imsg_count)); 516 1.1 christos link -> imsg_count = link -> imsg_payoff; 517 1.1 christos } 518 1.1 christos 519 1.1 christos /* Now start sucking options off the wire. */ 520 1.1 christos while (link -> imsg_count < link -> imsg_len) { 521 1.1 christos status = do_a_failover_option (c, link); 522 1.1 christos if (status != ISC_R_SUCCESS) 523 1.1 christos goto dhcp_flink_fail; 524 1.1 christos } 525 1.1 christos 526 1.1 christos /* If it's a connect message, try to associate it with 527 1.1 christos a state object. */ 528 1.1 christos /* XXX this should be authenticated! */ 529 1.1 christos if (link -> imsg -> type == FTM_CONNECT) { 530 1.1 christos const char *errmsg; 531 1.1 christos int reason; 532 1.1 christos 533 1.1 christos if (!(link->imsg->options_present & 534 1.1 christos FTB_RELATIONSHIP_NAME)) { 535 1.1 christos errmsg = "missing relationship-name"; 536 1.1 christos reason = FTR_INVALID_PARTNER; 537 1.1 christos goto badconnect; 538 1.1 christos } 539 1.1 christos 540 1.1 christos /* See if we can find a failover_state object that 541 1.1 christos matches this connection. This message should only 542 1.1 christos be received by a secondary from a primary. */ 543 1.1 christos for (s = failover_states; s; s = s -> next) { 544 1.1 christos if (dhcp_failover_state_match_by_name(s, 545 1.1 christos &link->imsg->relationship_name)) 546 1.1 christos state = s; 547 1.1 christos } 548 1.1 christos 549 1.1 christos /* If we can't find a failover protocol state 550 1.1 christos for this remote host, drop the connection */ 551 1.1 christos if (!state) { 552 1.1 christos errmsg = "unknown failover relationship name"; 553 1.1 christos reason = FTR_INVALID_PARTNER; 554 1.1 christos 555 1.1 christos badconnect: 556 1.1 christos /* XXX Send a refusal message first? 557 1.1 christos XXX Look in protocol spec for guidance. */ 558 1.1 christos 559 1.1 christos if (state != NULL) { 560 1.1 christos sname = state->name; 561 1.1 christos slen = strlen(sname); 562 1.1 christos } else if (link->imsg->options_present & 563 1.1 christos FTB_RELATIONSHIP_NAME) { 564 1.1 christos sname = (char *)link->imsg-> 565 1.1 christos relationship_name.data; 566 1.1 christos slen = link->imsg->relationship_name.count; 567 1.1 christos } else { 568 1.1 christos sname = "unknown"; 569 1.1 christos slen = strlen(sname); 570 1.1 christos } 571 1.1 christos 572 1.1 christos log_error("Failover CONNECT from %.*s: %s", 573 1.1 christos slen, sname, errmsg); 574 1.1 christos dhcp_failover_send_connectack 575 1.1 christos ((omapi_object_t *)link, state, 576 1.1 christos reason, errmsg); 577 1.1 christos log_info ("failover: disconnect: %s", errmsg); 578 1.1 christos omapi_disconnect (c, 0); 579 1.1 christos link -> state = dhcp_flink_disconnected; 580 1.1 christos return ISC_R_SUCCESS; 581 1.1 christos } 582 1.1 christos 583 1.1 christos if ((cur_time > link -> imsg -> time && 584 1.1 christos cur_time - link -> imsg -> time > 60) || 585 1.1 christos (cur_time < link -> imsg -> time && 586 1.1 christos link -> imsg -> time - cur_time > 60)) { 587 1.1 christos errmsg = "time offset too large"; 588 1.1 christos reason = FTR_TIMEMISMATCH; 589 1.1 christos goto badconnect; 590 1.1 christos } 591 1.1 christos 592 1.1 christos if (!(link -> imsg -> options_present & FTB_HBA) || 593 1.1 christos link -> imsg -> hba.count != 32) { 594 1.1 christos errmsg = "invalid HBA"; 595 1.1 christos reason = FTR_HBA_CONFLICT; /* XXX */ 596 1.1 christos goto badconnect; 597 1.1 christos } 598 1.1 christos if (state -> hba) 599 1.1 christos dfree (state -> hba, MDL); 600 1.1 christos state -> hba = dmalloc (32, MDL); 601 1.1 christos if (!state -> hba) { 602 1.1 christos errmsg = "no memory"; 603 1.1 christos reason = FTR_MISC_REJECT; 604 1.1 christos goto badconnect; 605 1.1 christos } 606 1.1 christos memcpy (state -> hba, link -> imsg -> hba.data, 32); 607 1.1 christos 608 1.1 christos if (!link -> state_object) 609 1.1 christos dhcp_failover_state_reference 610 1.1 christos (&link -> state_object, state, MDL); 611 1.1 christos if (!link -> peer_address) 612 1.1 christos option_cache_reference 613 1.1 christos (&link -> peer_address, 614 1.1 christos state -> partner.address, MDL); 615 1.1 christos } 616 1.1 christos 617 1.1 christos /* If we don't have a state object at this point, it's 618 1.1 christos some kind of bogus situation, so just drop the 619 1.1 christos connection. */ 620 1.1 christos if (!link -> state_object) { 621 1.1 christos log_info ("failover: connect: no matching state."); 622 1.1 christos omapi_disconnect (c, 1); 623 1.1 christos link -> state = dhcp_flink_disconnected; 624 1.1 christos return DHCP_R_INVALIDARG; 625 1.1 christos } 626 1.1 christos 627 1.1 christos /* Once we have the entire message, and we've validated 628 1.1 christos it as best we can here, pass it to the parent. */ 629 1.1 christos omapi_signal ((omapi_object_t *)link -> state_object, 630 1.1 christos "message", link); 631 1.1 christos link -> state = dhcp_flink_message_length_wait; 632 1.1 christos if (link -> imsg) 633 1.1 christos failover_message_dereference (&link -> imsg, MDL); 634 1.1 christos /* XXX This is dangerous because we could get into a tight 635 1.1 christos XXX loop reading input without servicing any other stuff. 636 1.1 christos XXX There needs to be a way to relinquish control but 637 1.1 christos XXX get it back immediately if there's no other work to 638 1.1 christos XXX do. */ 639 1.1 christos if ((omapi_connection_require (c, 2)) == ISC_R_SUCCESS) 640 1.1 christos goto next_message; 641 1.1 christos break; 642 1.1 christos 643 1.1 christos default: 644 1.1 christos log_fatal("Impossible case at %s:%d.", MDL); 645 1.1 christos break; 646 1.1 christos } 647 1.1 christos return ISC_R_SUCCESS; 648 1.1 christos } 649 1.1 christos 650 1.1 christos static isc_result_t do_a_failover_option (c, link) 651 1.1 christos omapi_object_t *c; 652 1.1 christos dhcp_failover_link_t *link; 653 1.1 christos { 654 1.1 christos u_int16_t option_code; 655 1.1 christos u_int16_t option_len; 656 1.1 christos unsigned char *op; 657 1.1 christos unsigned op_size; 658 1.1 christos unsigned op_count; 659 1.1 christos int i; 660 1.1 christos 661 1.1 christos if (link -> imsg_count + 2 > link -> imsg_len) { 662 1.1 christos log_error ("FAILOVER: message overflow at option code."); 663 1.1 christos return DHCP_R_PROTOCOLERROR; 664 1.1 christos } 665 1.1 christos 666 1.1 christos if (link->imsg->type > FTM_MAX) { 667 1.1 christos log_error ("FAILOVER: invalid message type: %d", 668 1.1 christos link->imsg->type); 669 1.1 christos return DHCP_R_PROTOCOLERROR; 670 1.1 christos } 671 1.1 christos 672 1.1 christos /* Get option code. */ 673 1.1 christos omapi_connection_get_uint16 (c, &option_code); 674 1.1 christos link -> imsg_count += 2; 675 1.1 christos 676 1.1 christos if (link -> imsg_count + 2 > link -> imsg_len) { 677 1.1 christos log_error ("FAILOVER: message overflow at length."); 678 1.1 christos return DHCP_R_PROTOCOLERROR; 679 1.1 christos } 680 1.1 christos 681 1.1 christos /* Get option length. */ 682 1.1 christos omapi_connection_get_uint16 (c, &option_len); 683 1.1 christos link -> imsg_count += 2; 684 1.1 christos 685 1.1 christos if (link -> imsg_count + option_len > link -> imsg_len) { 686 1.1 christos log_error ("FAILOVER: message overflow at data."); 687 1.1 christos return DHCP_R_PROTOCOLERROR; 688 1.1 christos } 689 1.1 christos 690 1.1 christos /* If it's an unknown code, skip over it. */ 691 1.1 christos if ((option_code > FTO_MAX) || 692 1.1 christos (ft_options[option_code].type == FT_UNDEF)) { 693 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 694 1.1 christos log_debug (" option code %d (%s) len %d (not recognized)", 695 1.1 christos option_code, 696 1.1 christos dhcp_failover_option_name (option_code), 697 1.1 christos option_len); 698 1.1 christos #endif 699 1.1 christos omapi_connection_copyout ((unsigned char *)0, c, option_len); 700 1.1 christos link -> imsg_count += option_len; 701 1.1 christos return ISC_R_SUCCESS; 702 1.1 christos } 703 1.1 christos 704 1.1 christos /* If it's the digest, do it now. */ 705 1.1 christos if (ft_options [option_code].type == FT_DIGEST) { 706 1.1 christos link -> imsg_count += option_len; 707 1.1 christos if (link -> imsg_count != link -> imsg_len) { 708 1.1 christos log_error ("FAILOVER: digest not at end of message"); 709 1.1 christos return DHCP_R_PROTOCOLERROR; 710 1.1 christos } 711 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 712 1.1 christos log_debug (" option %s len %d", 713 1.1 christos ft_options [option_code].name, option_len); 714 1.1 christos #endif 715 1.1 christos /* For now, just dump it. */ 716 1.1 christos omapi_connection_copyout ((unsigned char *)0, c, option_len); 717 1.1 christos return ISC_R_SUCCESS; 718 1.1 christos } 719 1.1 christos 720 1.1 christos /* Only accept an option once. */ 721 1.1 christos if (link -> imsg -> options_present & ft_options [option_code].bit) { 722 1.1 christos log_error ("FAILOVER: duplicate option %s", 723 1.1 christos ft_options [option_code].name); 724 1.1 christos return DHCP_R_PROTOCOLERROR; 725 1.1 christos } 726 1.1 christos 727 1.1 christos /* Make sure the option is appropriate for this type of message. 728 1.1 christos Really, any option is generally allowed for any message, and the 729 1.1 christos cases where this is not true are too complicated to represent in 730 1.1 christos this way - what this code is doing is to just avoid saving the 731 1.1 christos value of an option we don't have any way to use, which allows 732 1.1 christos us to make the failover_message structure smaller. */ 733 1.1 christos if (ft_options [option_code].bit && 734 1.1 christos !(fto_allowed [link -> imsg -> type] & 735 1.1 christos ft_options [option_code].bit)) { 736 1.1 christos omapi_connection_copyout ((unsigned char *)0, c, option_len); 737 1.1 christos link -> imsg_count += option_len; 738 1.1 christos return ISC_R_SUCCESS; 739 1.1 christos } 740 1.1 christos 741 1.1 christos /* Figure out how many elements, how big they are, and where 742 1.1 christos to store them. */ 743 1.1 christos if (ft_options [option_code].num_present) { 744 1.1 christos /* If this option takes a fixed number of elements, 745 1.1 christos we expect the space for them to be preallocated, 746 1.1 christos and we can just read the data in. */ 747 1.1 christos 748 1.1 christos op = ((unsigned char *)link -> imsg) + 749 1.1 christos ft_options [option_code].offset; 750 1.1 christos op_size = ft_sizes [ft_options [option_code].type]; 751 1.1 christos op_count = ft_options [option_code].num_present; 752 1.1 christos 753 1.1 christos if (option_len != op_size * op_count) { 754 1.1 christos log_error ("FAILOVER: option size (%d:%d), option %s", 755 1.1 christos option_len, 756 1.1 christos (ft_sizes [ft_options [option_code].type] * 757 1.1 christos ft_options [option_code].num_present), 758 1.1 christos ft_options [option_code].name); 759 1.1 christos return DHCP_R_PROTOCOLERROR; 760 1.1 christos } 761 1.1 christos } else { 762 1.1 christos failover_option_t *fo; 763 1.1 christos 764 1.1 christos /* FT_DDNS* are special - one or two bytes of status 765 1.1 christos followed by the client FQDN. */ 766 1.1 christos 767 1.1 christos /* Note: FT_DDNS* option support appears to be incomplete. 768 1.1 christos ISC-Bugs #36996 has been opened to address this. */ 769 1.1 christos if (ft_options [option_code].type == FT_DDNS || 770 1.1 christos ft_options [option_code].type == FT_DDNS1) { 771 1.1 christos ddns_fqdn_t *ddns = 772 1.1 christos ((ddns_fqdn_t *) 773 1.1 christos (((char *)link -> imsg) + 774 1.1 christos ft_options [option_code].offset)); 775 1.1 christos 776 1.1 christos op_count = (ft_options [option_code].type == FT_DDNS1 777 1.1 christos ? 1 : 2); 778 1.1 christos 779 1.1 christos omapi_connection_copyout (&ddns -> codes [0], 780 1.1 christos c, op_count); 781 1.1 christos link -> imsg_count += op_count; 782 1.1 christos if (op_count == 1) 783 1.1 christos ddns -> codes [1] = 0; 784 1.1 christos op_size = 1; 785 1.1 christos op_count = option_len - op_count; 786 1.1 christos 787 1.1 christos ddns -> length = op_count; 788 1.1 christos ddns -> data = dmalloc (op_count, MDL); 789 1.1 christos if (!ddns -> data) { 790 1.1 christos log_error ("FAILOVER: no memory getting%s(%d)", 791 1.1 christos " DNS data ", op_count); 792 1.1 christos 793 1.1 christos /* Actually, NO_MEMORY, but if we lose here 794 1.1 christos we have to drop the connection. */ 795 1.1 christos return DHCP_R_PROTOCOLERROR; 796 1.1 christos } 797 1.1 christos omapi_connection_copyout (ddns -> data, c, op_count); 798 1.1 christos goto out; 799 1.1 christos } 800 1.1 christos 801 1.1 christos /* A zero for num_present means that any number of 802 1.1 christos elements can appear, so we have to figure out how 803 1.1 christos many we got from the length of the option, and then 804 1.1 christos fill out a failover_option structure describing the 805 1.1 christos data. */ 806 1.1 christos op_size = ft_sizes [ft_options [option_code].type]; 807 1.1 christos 808 1.1 christos /* Make sure that option data length is a multiple of the 809 1.1 christos size of the data type being sent. */ 810 1.1 christos if (op_size > 1 && option_len % op_size) { 811 1.1 christos log_error ("FAILOVER: option_len %d not %s%d", 812 1.1 christos option_len, "multiple of ", op_size); 813 1.1 christos return DHCP_R_PROTOCOLERROR; 814 1.1 christos } 815 1.1 christos 816 1.1 christos op_count = option_len / op_size; 817 1.1 christos 818 1.1 christos fo = ((failover_option_t *) 819 1.1 christos (((char *)link -> imsg) + 820 1.1 christos ft_options [option_code].offset)); 821 1.1 christos 822 1.1 christos fo -> count = op_count; 823 1.1 christos fo -> data = dmalloc (option_len, MDL); 824 1.1 christos if (!fo -> data) { 825 1.1 christos log_error ("FAILOVER: no memory getting %s (%d)", 826 1.1 christos "option data", op_count); 827 1.1 christos 828 1.1 christos return DHCP_R_PROTOCOLERROR; 829 1.1 christos } 830 1.1 christos op = fo -> data; 831 1.1 christos } 832 1.1 christos 833 1.1 christos /* For single-byte message values and multi-byte values that 834 1.1 christos don't need swapping, just read them in all at once. */ 835 1.1 christos if (op_size == 1 || ft_options [option_code].type == FT_IPADDR) { 836 1.1 christos omapi_connection_copyout ((unsigned char *)op, c, option_len); 837 1.1 christos link -> imsg_count += option_len; 838 1.1 christos 839 1.1 christos /* 840 1.1 christos * As of 3.1.0, many option codes were changed to conform to 841 1.1 christos * draft revision 12 (which alphabetized, then renumbered all 842 1.1 christos * the option codes without preserving the version option code 843 1.1 christos * nor bumping its value). As it turns out, the message codes 844 1.1 christos * for CONNECT and CONNECTACK turn out the same, so it tries 845 1.1 christos * its darndest to connect, and falls short (when TLS_REQUEST 846 1.1 christos * comes up size 2 rather than size 1 as draft revision 12 also 847 1.1 christos * mandates). 848 1.1 christos * 849 1.1 christos * The VENDOR_CLASS code in 3.0.x was 11, which is now the HBA 850 1.1 christos * code. Both work out to be arbitrarily long text-or-byte 851 1.1 christos * strings, so they pass parsing. 852 1.1 christos * 853 1.1 christos * Note that it is possible (or intentional), if highly 854 1.1 christos * improbable, for the HBA bit array to exactly match 855 1.1 christos * isc-V3.0.x. Warning here is not an issue; if it really is 856 1.1 christos * 3.0.x, there will be a protocol error later on. If it isn't 857 1.1 christos * actually 3.0.x, then I guess the lucky user will have to 858 1.1 christos * live with a weird warning. 859 1.1 christos */ 860 1.1 christos if ((option_code == 11) && (option_len > 9) && 861 1.1 christos (strncmp((const char *)op, "isc-V3.0.", 9) == 0)) { 862 1.1 christos log_error("WARNING: failover as of versions 3.1.0 and " 863 1.1 christos "on are not reverse compatible with " 864 1.1 christos "versions 3.0.x."); 865 1.1 christos } 866 1.1 christos 867 1.1 christos goto out; 868 1.1 christos } 869 1.1 christos 870 1.1 christos /* For values that require swapping, read them in one at a time 871 1.1 christos using routines that swap bytes. */ 872 1.1 christos for (i = 0; i < op_count; i++) { 873 1.1 christos switch (ft_options [option_code].type) { 874 1.1 christos case FT_UINT32: 875 1.1 christos omapi_connection_get_uint32 (c, (u_int32_t *)op); 876 1.1 christos op += 4; 877 1.1 christos link -> imsg_count += 4; 878 1.1 christos break; 879 1.1 christos 880 1.1 christos case FT_UINT16: 881 1.1 christos omapi_connection_get_uint16 (c, (u_int16_t *)op); 882 1.1 christos op += 2; 883 1.1 christos link -> imsg_count += 2; 884 1.1 christos break; 885 1.1 christos 886 1.1 christos default: 887 1.1 christos /* Everything else should have been handled 888 1.1 christos already. */ 889 1.1 christos log_error ("FAILOVER: option %s: bad type %d", 890 1.1 christos ft_options [option_code].name, 891 1.1 christos ft_options [option_code].type); 892 1.1 christos return DHCP_R_PROTOCOLERROR; 893 1.1 christos } 894 1.1 christos } 895 1.1 christos out: 896 1.1 christos /* Remember that we got this option. */ 897 1.1 christos link -> imsg -> options_present |= ft_options [option_code].bit; 898 1.1 christos return ISC_R_SUCCESS; 899 1.1 christos } 900 1.1 christos 901 1.1 christos isc_result_t dhcp_failover_link_set_value (omapi_object_t *h, 902 1.1 christos omapi_object_t *id, 903 1.1 christos omapi_data_string_t *name, 904 1.1 christos omapi_typed_data_t *value) 905 1.1 christos { 906 1.1 christos if (h -> type != omapi_type_protocol) 907 1.1 christos return DHCP_R_INVALIDARG; 908 1.1 christos 909 1.1 christos /* Never valid to set these. */ 910 1.1 christos if (!omapi_ds_strcmp (name, "link-port") || 911 1.1 christos !omapi_ds_strcmp (name, "link-name") || 912 1.1 christos !omapi_ds_strcmp (name, "link-state")) 913 1.1 christos return ISC_R_NOPERM; 914 1.1 christos 915 1.1 christos if (h -> inner && h -> inner -> type -> set_value) 916 1.1 christos return (*(h -> inner -> type -> set_value)) 917 1.1 christos (h -> inner, id, name, value); 918 1.1 christos return ISC_R_NOTFOUND; 919 1.1 christos } 920 1.1 christos 921 1.1 christos isc_result_t dhcp_failover_link_get_value (omapi_object_t *h, 922 1.1 christos omapi_object_t *id, 923 1.1 christos omapi_data_string_t *name, 924 1.1 christos omapi_value_t **value) 925 1.1 christos { 926 1.1 christos dhcp_failover_link_t *link; 927 1.1 christos 928 1.1 christos if (h -> type != omapi_type_protocol) 929 1.1 christos return DHCP_R_INVALIDARG; 930 1.1 christos link = (dhcp_failover_link_t *)h; 931 1.1 christos 932 1.1 christos if (!omapi_ds_strcmp (name, "link-port")) { 933 1.1 christos return omapi_make_int_value (value, name, 934 1.1 christos (int)link -> peer_port, MDL); 935 1.1 christos } else if (!omapi_ds_strcmp (name, "link-state")) { 936 1.1 christos if (link -> state >= dhcp_flink_state_max) 937 1.1 christos return omapi_make_string_value (value, name, 938 1.1 christos "invalid link state", 939 1.1 christos MDL); 940 1.1 christos return omapi_make_string_value 941 1.1 christos (value, name, 942 1.1 christos dhcp_flink_state_names [link -> state], MDL); 943 1.1 christos } 944 1.1 christos 945 1.1 christos if (h -> inner && h -> inner -> type -> get_value) 946 1.1 christos return (*(h -> inner -> type -> get_value)) 947 1.1 christos (h -> inner, id, name, value); 948 1.1 christos return ISC_R_NOTFOUND; 949 1.1 christos } 950 1.1 christos 951 1.1 christos isc_result_t dhcp_failover_link_destroy (omapi_object_t *h, 952 1.1 christos const char *file, int line) 953 1.1 christos { 954 1.1 christos dhcp_failover_link_t *link; 955 1.1 christos if (h -> type != dhcp_type_failover_link) 956 1.1 christos return DHCP_R_INVALIDARG; 957 1.1 christos link = (dhcp_failover_link_t *)h; 958 1.1 christos 959 1.1 christos if (link -> peer_address) 960 1.1 christos option_cache_dereference (&link -> peer_address, file, line); 961 1.1 christos if (link -> imsg) 962 1.1 christos failover_message_dereference (&link -> imsg, file, line); 963 1.1 christos if (link -> state_object) 964 1.1 christos dhcp_failover_state_dereference (&link -> state_object, 965 1.1 christos file, line); 966 1.1 christos return ISC_R_SUCCESS; 967 1.1 christos } 968 1.1 christos 969 1.1 christos /* Write all the published values associated with the object through the 970 1.1 christos specified connection. */ 971 1.1 christos 972 1.1 christos isc_result_t dhcp_failover_link_stuff_values (omapi_object_t *c, 973 1.1 christos omapi_object_t *id, 974 1.1 christos omapi_object_t *l) 975 1.1 christos { 976 1.1 christos dhcp_failover_link_t *link; 977 1.1 christos isc_result_t status; 978 1.1 christos 979 1.1 christos if (l -> type != dhcp_type_failover_link) 980 1.1 christos return DHCP_R_INVALIDARG; 981 1.1 christos link = (dhcp_failover_link_t *)l; 982 1.1 christos 983 1.1 christos status = omapi_connection_put_name (c, "link-port"); 984 1.1 christos if (status != ISC_R_SUCCESS) 985 1.1 christos return status; 986 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (int)); 987 1.1 christos if (status != ISC_R_SUCCESS) 988 1.1 christos return status; 989 1.1 christos status = omapi_connection_put_uint32 (c, link -> peer_port); 990 1.1 christos if (status != ISC_R_SUCCESS) 991 1.1 christos return status; 992 1.1 christos 993 1.1 christos status = omapi_connection_put_name (c, "link-state"); 994 1.1 christos if (status != ISC_R_SUCCESS) 995 1.1 christos return status; 996 1.1 christos if (link -> state >= dhcp_flink_state_max) 997 1.1 christos status = omapi_connection_put_string (c, "invalid link state"); 998 1.1 christos else 999 1.1 christos status = (omapi_connection_put_string 1000 1.1 christos (c, dhcp_flink_state_names [link -> state])); 1001 1.1 christos if (status != ISC_R_SUCCESS) 1002 1.1 christos return status; 1003 1.1 christos 1004 1.1 christos if (link -> inner && link -> inner -> type -> stuff_values) 1005 1.1 christos return (*(link -> inner -> type -> stuff_values)) (c, id, 1006 1.1 christos link -> inner); 1007 1.1 christos return ISC_R_SUCCESS; 1008 1.1 christos } 1009 1.1 christos 1010 1.1 christos /* Set up a listener for the omapi protocol. The handle stored points to 1011 1.1 christos a listener object, not a protocol object. */ 1012 1.1 christos 1013 1.1 christos isc_result_t dhcp_failover_listen (omapi_object_t *h) 1014 1.1 christos { 1015 1.1 christos isc_result_t status; 1016 1.1 christos dhcp_failover_listener_t *obj, *l; 1017 1.1 christos omapi_value_t *value = (omapi_value_t *)0; 1018 1.1 christos omapi_addr_t local_addr; 1019 1.1 christos unsigned long port; 1020 1.1 christos 1021 1.1 christos status = omapi_get_value_str (h, (omapi_object_t *)0, 1022 1.1 christos "local-port", &value); 1023 1.1 christos if (status != ISC_R_SUCCESS) 1024 1.1 christos return status; 1025 1.1 christos if (!value -> value) { 1026 1.1 christos omapi_value_dereference (&value, MDL); 1027 1.1 christos return DHCP_R_INVALIDARG; 1028 1.1 christos } 1029 1.1 christos 1030 1.1 christos status = omapi_get_int_value (&port, value -> value); 1031 1.1 christos omapi_value_dereference (&value, MDL); 1032 1.1 christos if (status != ISC_R_SUCCESS) 1033 1.1 christos return status; 1034 1.1 christos local_addr.port = port; 1035 1.1 christos 1036 1.1 christos status = omapi_get_value_str (h, (omapi_object_t *)0, 1037 1.1 christos "local-address", &value); 1038 1.1 christos if (status != ISC_R_SUCCESS) 1039 1.1 christos return status; 1040 1.1 christos if (!value -> value) { 1041 1.1 christos nogood: 1042 1.1 christos omapi_value_dereference (&value, MDL); 1043 1.1 christos return DHCP_R_INVALIDARG; 1044 1.1 christos } 1045 1.1 christos 1046 1.1 christos if (value -> value -> type != omapi_datatype_data || 1047 1.1 christos value -> value -> u.buffer.len != sizeof (struct in_addr)) 1048 1.1 christos goto nogood; 1049 1.1 christos 1050 1.1 christos memcpy (local_addr.address, value -> value -> u.buffer.value, 1051 1.1 christos value -> value -> u.buffer.len); 1052 1.1 christos local_addr.addrlen = value -> value -> u.buffer.len; 1053 1.1 christos local_addr.addrtype = AF_INET; 1054 1.1 christos 1055 1.1 christos omapi_value_dereference (&value, MDL); 1056 1.1 christos 1057 1.1 christos /* Are we already listening on this port and address? */ 1058 1.1 christos for (l = failover_listeners; l; l = l -> next) { 1059 1.1 christos if (l -> address.port == local_addr.port && 1060 1.1 christos l -> address.addrtype == local_addr.addrtype && 1061 1.1 christos l -> address.addrlen == local_addr.addrlen && 1062 1.1 christos !memcmp (l -> address.address, local_addr.address, 1063 1.1 christos local_addr.addrlen)) 1064 1.1 christos break; 1065 1.1 christos } 1066 1.1 christos /* Already listening. */ 1067 1.1 christos if (l) 1068 1.1 christos return ISC_R_SUCCESS; 1069 1.1 christos 1070 1.1 christos obj = (dhcp_failover_listener_t *)0; 1071 1.1 christos status = dhcp_failover_listener_allocate (&obj, MDL); 1072 1.1 christos if (status != ISC_R_SUCCESS) 1073 1.1 christos return status; 1074 1.1 christos obj -> address = local_addr; 1075 1.1 christos 1076 1.1 christos status = omapi_listen_addr ((omapi_object_t *)obj, &obj -> address, 1); 1077 1.1 christos if (status != ISC_R_SUCCESS) 1078 1.1 christos return status; 1079 1.1 christos 1080 1.1 christos status = omapi_object_reference (&h -> outer, 1081 1.1 christos (omapi_object_t *)obj, MDL); 1082 1.1 christos if (status != ISC_R_SUCCESS) { 1083 1.1 christos dhcp_failover_listener_dereference (&obj, MDL); 1084 1.1 christos return status; 1085 1.1 christos } 1086 1.1 christos status = omapi_object_reference (&obj -> inner, h, MDL); 1087 1.1 christos if (status != ISC_R_SUCCESS) { 1088 1.1 christos dhcp_failover_listener_dereference (&obj, MDL); 1089 1.1 christos return status; 1090 1.1 christos } 1091 1.1 christos 1092 1.1 christos /* Put this listener on the list. */ 1093 1.1 christos if (failover_listeners) { 1094 1.1 christos dhcp_failover_listener_reference (&obj -> next, 1095 1.1 christos failover_listeners, MDL); 1096 1.1 christos dhcp_failover_listener_dereference (&failover_listeners, MDL); 1097 1.1 christos } 1098 1.1 christos dhcp_failover_listener_reference (&failover_listeners, obj, MDL); 1099 1.1 christos 1100 1.1 christos return dhcp_failover_listener_dereference (&obj, MDL); 1101 1.1 christos } 1102 1.1 christos 1103 1.1 christos /* Signal handler for protocol listener - if we get a connect signal, 1104 1.1 christos create a new protocol connection, otherwise pass the signal down. */ 1105 1.1 christos 1106 1.1 christos isc_result_t dhcp_failover_listener_signal (omapi_object_t *o, 1107 1.1 christos const char *name, va_list ap) 1108 1.1 christos { 1109 1.1 christos isc_result_t status; 1110 1.1 christos omapi_connection_object_t *c; 1111 1.1 christos dhcp_failover_link_t *obj; 1112 1.1 christos dhcp_failover_listener_t *p; 1113 1.1 christos dhcp_failover_state_t *s, *state = (dhcp_failover_state_t *)0; 1114 1.1 christos 1115 1.1 christos if (!o || o -> type != dhcp_type_failover_listener) 1116 1.1 christos return DHCP_R_INVALIDARG; 1117 1.1 christos p = (dhcp_failover_listener_t *)o; 1118 1.1 christos 1119 1.1 christos /* Not a signal we recognize? */ 1120 1.1 christos if (strcmp (name, "connect")) { 1121 1.1 christos if (p -> inner && p -> inner -> type -> signal_handler) 1122 1.1 christos return (*(p -> inner -> type -> signal_handler)) 1123 1.1 christos (p -> inner, name, ap); 1124 1.1 christos return ISC_R_NOTFOUND; 1125 1.1 christos } 1126 1.1 christos 1127 1.1 christos c = va_arg (ap, omapi_connection_object_t *); 1128 1.1 christos if (!c || c -> type != omapi_type_connection) 1129 1.1 christos return DHCP_R_INVALIDARG; 1130 1.1 christos 1131 1.1 christos /* See if we can find a failover_state object that 1132 1.1 christos matches this connection. */ 1133 1.1 christos for (s = failover_states; s; s = s -> next) { 1134 1.1 christos if (dhcp_failover_state_match 1135 1.1 christos (s, (u_int8_t *)&c -> remote_addr.sin_addr, 1136 1.1 christos sizeof c -> remote_addr.sin_addr)) { 1137 1.1 christos state = s; 1138 1.1 christos break; 1139 1.1 christos } 1140 1.1 christos } 1141 1.1 christos if (!state) { 1142 1.1 christos log_info ("failover: listener: no matching state"); 1143 1.1 christos omapi_disconnect ((omapi_object_t *)c, 1); 1144 1.1 christos return(ISC_R_NOTFOUND); 1145 1.1 christos } 1146 1.1 christos 1147 1.1 christos obj = (dhcp_failover_link_t *)0; 1148 1.1 christos status = dhcp_failover_link_allocate (&obj, MDL); 1149 1.1 christos if (status != ISC_R_SUCCESS) 1150 1.1 christos return status; 1151 1.1 christos obj -> peer_port = ntohs (c -> remote_addr.sin_port); 1152 1.1 christos 1153 1.1 christos status = omapi_object_reference (&obj -> outer, 1154 1.1 christos (omapi_object_t *)c, MDL); 1155 1.1 christos if (status != ISC_R_SUCCESS) { 1156 1.1 christos lose: 1157 1.1 christos dhcp_failover_link_dereference (&obj, MDL); 1158 1.1 christos log_info ("failover: listener: picayune failure."); 1159 1.1 christos omapi_disconnect ((omapi_object_t *)c, 1); 1160 1.1 christos return status; 1161 1.1 christos } 1162 1.1 christos 1163 1.1 christos status = omapi_object_reference (&c -> inner, 1164 1.1 christos (omapi_object_t *)obj, MDL); 1165 1.1 christos if (status != ISC_R_SUCCESS) 1166 1.1 christos goto lose; 1167 1.1 christos 1168 1.1 christos status = dhcp_failover_state_reference (&obj -> state_object, 1169 1.1 christos state, MDL); 1170 1.1 christos if (status != ISC_R_SUCCESS) 1171 1.1 christos goto lose; 1172 1.1 christos 1173 1.1 christos omapi_signal_in ((omapi_object_t *)obj, "connect"); 1174 1.1 christos 1175 1.1 christos return dhcp_failover_link_dereference (&obj, MDL); 1176 1.1 christos } 1177 1.1 christos 1178 1.1 christos isc_result_t dhcp_failover_listener_set_value (omapi_object_t *h, 1179 1.1 christos omapi_object_t *id, 1180 1.1 christos omapi_data_string_t *name, 1181 1.1 christos omapi_typed_data_t *value) 1182 1.1 christos { 1183 1.1 christos if (h -> type != dhcp_type_failover_listener) 1184 1.1 christos return DHCP_R_INVALIDARG; 1185 1.1 christos 1186 1.1 christos if (h -> inner && h -> inner -> type -> set_value) 1187 1.1 christos return (*(h -> inner -> type -> set_value)) 1188 1.1 christos (h -> inner, id, name, value); 1189 1.1 christos return ISC_R_NOTFOUND; 1190 1.1 christos } 1191 1.1 christos 1192 1.1 christos isc_result_t dhcp_failover_listener_get_value (omapi_object_t *h, 1193 1.1 christos omapi_object_t *id, 1194 1.1 christos omapi_data_string_t *name, 1195 1.1 christos omapi_value_t **value) 1196 1.1 christos { 1197 1.1 christos if (h -> type != dhcp_type_failover_listener) 1198 1.1 christos return DHCP_R_INVALIDARG; 1199 1.1 christos 1200 1.1 christos if (h -> inner && h -> inner -> type -> get_value) 1201 1.1 christos return (*(h -> inner -> type -> get_value)) 1202 1.1 christos (h -> inner, id, name, value); 1203 1.1 christos return ISC_R_NOTFOUND; 1204 1.1 christos } 1205 1.1 christos 1206 1.1 christos isc_result_t dhcp_failover_listener_destroy (omapi_object_t *h, 1207 1.1 christos const char *file, int line) 1208 1.1 christos { 1209 1.1 christos dhcp_failover_listener_t *l; 1210 1.1 christos 1211 1.1 christos if (h -> type != dhcp_type_failover_listener) 1212 1.1 christos return DHCP_R_INVALIDARG; 1213 1.1 christos l = (dhcp_failover_listener_t *)h; 1214 1.1 christos if (l -> next) 1215 1.1 christos dhcp_failover_listener_dereference (&l -> next, file, line); 1216 1.1 christos 1217 1.1 christos return ISC_R_SUCCESS; 1218 1.1 christos } 1219 1.1 christos 1220 1.1 christos /* Write all the published values associated with the object through the 1221 1.1 christos specified connection. */ 1222 1.1 christos 1223 1.1 christos isc_result_t dhcp_failover_listener_stuff (omapi_object_t *c, 1224 1.1 christos omapi_object_t *id, 1225 1.1 christos omapi_object_t *p) 1226 1.1 christos { 1227 1.1 christos if (p -> type != dhcp_type_failover_listener) 1228 1.1 christos return DHCP_R_INVALIDARG; 1229 1.1 christos 1230 1.1 christos if (p -> inner && p -> inner -> type -> stuff_values) 1231 1.1 christos return (*(p -> inner -> type -> stuff_values)) (c, id, 1232 1.1 christos p -> inner); 1233 1.1 christos return ISC_R_SUCCESS; 1234 1.1 christos } 1235 1.1 christos 1236 1.1 christos /* Set up master state machine for the failover protocol. */ 1237 1.1 christos 1238 1.1 christos isc_result_t dhcp_failover_register (omapi_object_t *h) 1239 1.1 christos { 1240 1.1 christos isc_result_t status; 1241 1.1 christos dhcp_failover_state_t *obj; 1242 1.1 christos unsigned long port; 1243 1.1 christos omapi_value_t *value = (omapi_value_t *)0; 1244 1.1 christos 1245 1.1 christos status = omapi_get_value_str (h, (omapi_object_t *)0, 1246 1.1 christos "local-port", &value); 1247 1.1 christos if (status != ISC_R_SUCCESS) 1248 1.1 christos return status; 1249 1.1 christos if (!value -> value) { 1250 1.1 christos omapi_value_dereference (&value, MDL); 1251 1.1 christos return DHCP_R_INVALIDARG; 1252 1.1 christos } 1253 1.1 christos 1254 1.1 christos status = omapi_get_int_value (&port, value -> value); 1255 1.1 christos omapi_value_dereference (&value, MDL); 1256 1.1 christos if (status != ISC_R_SUCCESS) 1257 1.1 christos return status; 1258 1.1 christos 1259 1.1 christos obj = (dhcp_failover_state_t *)0; 1260 1.1 christos dhcp_failover_state_allocate (&obj, MDL); 1261 1.1 christos obj -> me.port = port; 1262 1.1 christos 1263 1.1 christos status = omapi_listen ((omapi_object_t *)obj, port, 1); 1264 1.1 christos if (status != ISC_R_SUCCESS) { 1265 1.1 christos dhcp_failover_state_dereference (&obj, MDL); 1266 1.1 christos return status; 1267 1.1 christos } 1268 1.1 christos 1269 1.1 christos status = omapi_object_reference (&h -> outer, (omapi_object_t *)obj, 1270 1.1 christos MDL); 1271 1.1 christos if (status != ISC_R_SUCCESS) { 1272 1.1 christos dhcp_failover_state_dereference (&obj, MDL); 1273 1.1 christos return status; 1274 1.1 christos } 1275 1.1 christos status = omapi_object_reference (&obj -> inner, h, MDL); 1276 1.1 christos dhcp_failover_state_dereference (&obj, MDL); 1277 1.1 christos return status; 1278 1.1 christos } 1279 1.1 christos 1280 1.1 christos /* Signal handler for protocol state machine. */ 1281 1.1 christos 1282 1.1 christos isc_result_t dhcp_failover_state_signal (omapi_object_t *o, 1283 1.1 christos const char *name, va_list ap) 1284 1.1 christos { 1285 1.1 christos isc_result_t status; 1286 1.1 christos dhcp_failover_state_t *state; 1287 1.1 christos dhcp_failover_link_t *link; 1288 1.1 christos struct timeval tv; 1289 1.1 christos 1290 1.1 christos if (!o || o -> type != dhcp_type_failover_state) 1291 1.1 christos return DHCP_R_INVALIDARG; 1292 1.1 christos state = (dhcp_failover_state_t *)o; 1293 1.1 christos 1294 1.1 christos /* Not a signal we recognize? */ 1295 1.1 christos if (strcmp (name, "disconnect") && 1296 1.1 christos strcmp (name, "message")) { 1297 1.1 christos if (state -> inner && state -> inner -> type -> signal_handler) 1298 1.1 christos return (*(state -> inner -> type -> signal_handler)) 1299 1.1 christos (state -> inner, name, ap); 1300 1.1 christos return ISC_R_NOTFOUND; 1301 1.1 christos } 1302 1.1 christos 1303 1.1 christos /* Handle connect signals by seeing what state we're in 1304 1.1 christos and potentially doing a state transition. */ 1305 1.1 christos if (!strcmp (name, "disconnect")) { 1306 1.1 christos link = va_arg (ap, dhcp_failover_link_t *); 1307 1.1 christos 1308 1.1 christos dhcp_failover_link_dereference (&state -> link_to_peer, MDL); 1309 1.1 christos dhcp_failover_state_transition (state, "disconnect"); 1310 1.1 christos if (state -> i_am == primary) { 1311 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 1312 1.1 christos log_info ("add_timeout +90 %s", 1313 1.1 christos "dhcp_failover_reconnect"); 1314 1.1 christos #endif 1315 1.1 christos tv . tv_sec = cur_time + 90; 1316 1.1 christos tv . tv_usec = 0; 1317 1.1 christos add_timeout (&tv, dhcp_failover_reconnect, 1318 1.1 christos state, 1319 1.1 christos (tvref_t)dhcp_failover_state_reference, 1320 1.1 christos (tvunref_t) 1321 1.1 christos dhcp_failover_state_dereference); 1322 1.1 christos } 1323 1.1 christos } else if (!strcmp (name, "message")) { 1324 1.1 christos link = va_arg (ap, dhcp_failover_link_t *); 1325 1.1 christos 1326 1.1 christos if (link -> imsg -> type == FTM_CONNECT) { 1327 1.1 christos /* If we already have a link to the peer, it must be 1328 1.1 christos dead, so drop it. 1329 1.1 christos XXX Is this the right thing to do? 1330 1.1 christos XXX Probably not - what if both peers start at 1331 1.1 christos XXX the same time? */ 1332 1.1 christos if (state -> link_to_peer) { 1333 1.1 christos dhcp_failover_send_connectack 1334 1.1 christos ((omapi_object_t *)link, state, 1335 1.1 christos FTR_DUP_CONNECTION, 1336 1.1 christos "already connected"); 1337 1.1 christos omapi_disconnect (link -> outer, 1); 1338 1.1 christos return ISC_R_SUCCESS; 1339 1.1 christos } 1340 1.1 christos if (!(link -> imsg -> options_present & FTB_MCLT)) { 1341 1.1 christos dhcp_failover_send_connectack 1342 1.1 christos ((omapi_object_t *)link, state, 1343 1.1 christos FTR_INVALID_MCLT, 1344 1.1 christos "no MCLT provided"); 1345 1.1 christos omapi_disconnect (link -> outer, 1); 1346 1.1 christos return ISC_R_SUCCESS; 1347 1.1 christos } 1348 1.1 christos 1349 1.1 christos dhcp_failover_link_reference (&state -> link_to_peer, 1350 1.1 christos link, MDL); 1351 1.1 christos status = (dhcp_failover_send_connectack 1352 1.1 christos ((omapi_object_t *)link, state, 0, 0)); 1353 1.1 christos if (status != ISC_R_SUCCESS) { 1354 1.1 christos dhcp_failover_link_dereference 1355 1.1 christos (&state -> link_to_peer, MDL); 1356 1.1 christos log_info ("dhcp_failover_send_connectack: %s", 1357 1.1 christos isc_result_totext (status)); 1358 1.1 christos omapi_disconnect (link -> outer, 1); 1359 1.1 christos return ISC_R_SUCCESS; 1360 1.1 christos } 1361 1.1 christos if (link -> imsg -> options_present & FTB_MAX_UNACKED) 1362 1.1 christos state -> partner.max_flying_updates = 1363 1.1 christos link -> imsg -> max_unacked; 1364 1.1 christos if (link -> imsg -> options_present & FTB_RECEIVE_TIMER) 1365 1.1 christos state -> partner.max_response_delay = 1366 1.1 christos link -> imsg -> receive_timer; 1367 1.1 christos state -> mclt = link -> imsg -> mclt; 1368 1.1 christos dhcp_failover_send_state (state); 1369 1.1 christos cancel_timeout (dhcp_failover_link_startup_timeout, 1370 1.1 christos link); 1371 1.1 christos } else if (link -> imsg -> type == FTM_CONNECTACK) { 1372 1.1 christos const char *errmsg; 1373 1.1 christos char errbuf[1024]; 1374 1.1 christos int reason; 1375 1.1 christos 1376 1.1 christos cancel_timeout (dhcp_failover_link_startup_timeout, 1377 1.1 christos link); 1378 1.1 christos 1379 1.1 christos if (!(link->imsg->options_present & 1380 1.1 christos FTB_RELATIONSHIP_NAME)) { 1381 1.1 christos errmsg = "missing relationship-name"; 1382 1.1 christos reason = FTR_INVALID_PARTNER; 1383 1.1 christos goto badconnectack; 1384 1.1 christos } 1385 1.1 christos 1386 1.1 christos if (link->imsg->options_present & FTB_REJECT_REASON) { 1387 1.1 christos /* XXX: add message option to text output. */ 1388 1.1 christos log_error ("Failover CONNECT to %s rejected: %s", 1389 1.1 christos state ? state->name : "unknown", 1390 1.1 christos (dhcp_failover_reject_reason_print 1391 1.1 christos (link -> imsg -> reject_reason))); 1392 1.1 christos /* XXX print message from peer if peer sent message. */ 1393 1.1 christos omapi_disconnect (link -> outer, 1); 1394 1.1 christos return ISC_R_SUCCESS; 1395 1.1 christos } 1396 1.1 christos 1397 1.1 christos if (!dhcp_failover_state_match_by_name(state, 1398 1.1 christos &link->imsg->relationship_name)) { 1399 1.1 christos /* XXX: Overflow results in log truncation, safe. */ 1400 1.1 christos snprintf(errbuf, sizeof(errbuf), "remote failover " 1401 1.1 christos "relationship name %.*s does not match", 1402 1.1 christos (int)link->imsg->relationship_name.count, 1403 1.1 christos link->imsg->relationship_name.data); 1404 1.1 christos errmsg = errbuf; 1405 1.1 christos reason = FTR_INVALID_PARTNER; 1406 1.1 christos badconnectack: 1407 1.1 christos log_error("Failover CONNECTACK from %s: %s", 1408 1.1 christos state->name, errmsg); 1409 1.1 christos dhcp_failover_send_disconnect ((omapi_object_t *)link, 1410 1.1 christos reason, errmsg); 1411 1.1 christos omapi_disconnect (link -> outer, 0); 1412 1.1 christos return ISC_R_SUCCESS; 1413 1.1 christos } 1414 1.1 christos 1415 1.1 christos if (state -> link_to_peer) { 1416 1.1 christos errmsg = "already connected"; 1417 1.1 christos reason = FTR_DUP_CONNECTION; 1418 1.1 christos goto badconnectack; 1419 1.1 christos } 1420 1.1 christos 1421 1.1 christos if ((cur_time > link -> imsg -> time && 1422 1.1 christos cur_time - link -> imsg -> time > 60) || 1423 1.1 christos (cur_time < link -> imsg -> time && 1424 1.1 christos link -> imsg -> time - cur_time > 60)) { 1425 1.1 christos errmsg = "time offset too large"; 1426 1.1 christos reason = FTR_TIMEMISMATCH; 1427 1.1 christos goto badconnectack; 1428 1.1 christos } 1429 1.1 christos 1430 1.1 christos dhcp_failover_link_reference (&state -> link_to_peer, 1431 1.1 christos link, MDL); 1432 1.1 christos #if 0 1433 1.1 christos /* XXX This is probably the right thing to do, but 1434 1.1 christos XXX for release three, to make the smallest possible 1435 1.1 christos XXX change, we are doing this when the peer state 1436 1.1 christos XXX changes instead. */ 1437 1.1 christos if (state -> me.state == startup) 1438 1.1 christos dhcp_failover_set_state (state, 1439 1.1 christos state -> saved_state); 1440 1.1 christos else 1441 1.1 christos #endif 1442 1.1 christos dhcp_failover_send_state (state); 1443 1.1 christos 1444 1.1 christos if (link -> imsg -> options_present & FTB_MAX_UNACKED) 1445 1.1 christos state -> partner.max_flying_updates = 1446 1.1 christos link -> imsg -> max_unacked; 1447 1.1 christos if (link -> imsg -> options_present & FTB_RECEIVE_TIMER) 1448 1.1 christos state -> partner.max_response_delay = 1449 1.1 christos link -> imsg -> receive_timer; 1450 1.1 christos #if defined (DEBUG_FAILOVER_CONTACT_TIMING) 1451 1.1 christos log_info ("add_timeout +%d %s", 1452 1.1 christos (int)state -> partner.max_response_delay / 3, 1453 1.1 christos "dhcp_failover_send_contact"); 1454 1.1 christos #endif 1455 1.1 christos tv . tv_sec = cur_time + 1456 1.1 christos (int)state -> partner.max_response_delay / 3; 1457 1.1 christos tv . tv_usec = 0; 1458 1.1 christos add_timeout (&tv, 1459 1.1 christos dhcp_failover_send_contact, state, 1460 1.1 christos (tvref_t)dhcp_failover_state_reference, 1461 1.1 christos (tvunref_t)dhcp_failover_state_dereference); 1462 1.1 christos #if defined (DEBUG_FAILOVER_CONTACT_TIMING) 1463 1.1 christos log_info ("add_timeout +%d %s", 1464 1.1 christos (int)state -> me.max_response_delay, 1465 1.1 christos "dhcp_failover_timeout"); 1466 1.1 christos #endif 1467 1.1 christos tv . tv_sec = cur_time + 1468 1.1 christos (int)state -> me.max_response_delay; 1469 1.1 christos tv . tv_usec = 0; 1470 1.1 christos add_timeout (&tv, 1471 1.1 christos dhcp_failover_timeout, state, 1472 1.1 christos (tvref_t)dhcp_failover_state_reference, 1473 1.1 christos (tvunref_t)dhcp_failover_state_dereference); 1474 1.1 christos } else if (link -> imsg -> type == FTM_DISCONNECT) { 1475 1.1 christos if (link -> imsg -> reject_reason) { 1476 1.1 christos log_error ("Failover DISCONNECT from %s: %s", 1477 1.1 christos state ? state->name : "unknown", 1478 1.1 christos (dhcp_failover_reject_reason_print 1479 1.1 christos (link -> imsg -> reject_reason))); 1480 1.1 christos } 1481 1.1 christos omapi_disconnect (link -> outer, 1); 1482 1.1 christos } else if (link -> imsg -> type == FTM_BNDUPD) { 1483 1.1 christos dhcp_failover_process_bind_update (state, 1484 1.1 christos link -> imsg); 1485 1.1 christos } else if (link -> imsg -> type == FTM_BNDACK) { 1486 1.1 christos dhcp_failover_process_bind_ack (state, link -> imsg); 1487 1.1 christos } else if (link -> imsg -> type == FTM_UPDREQ) { 1488 1.1 christos dhcp_failover_process_update_request (state, 1489 1.1 christos link -> imsg); 1490 1.1 christos } else if (link -> imsg -> type == FTM_UPDREQALL) { 1491 1.1 christos dhcp_failover_process_update_request_all 1492 1.1 christos (state, link -> imsg); 1493 1.1 christos } else if (link -> imsg -> type == FTM_UPDDONE) { 1494 1.1 christos dhcp_failover_process_update_done (state, 1495 1.1 christos link -> imsg); 1496 1.1 christos } else if (link -> imsg -> type == FTM_POOLREQ) { 1497 1.1 christos dhcp_failover_pool_reqbalance(state); 1498 1.1 christos } else if (link -> imsg -> type == FTM_POOLRESP) { 1499 1.1 christos log_info ("pool response: %ld leases", 1500 1.1 christos (unsigned long) 1501 1.1 christos link -> imsg -> addresses_transferred); 1502 1.1 christos } else if (link -> imsg -> type == FTM_STATE) { 1503 1.1 christos dhcp_failover_peer_state_changed (state, 1504 1.1 christos link -> imsg); 1505 1.1 christos } 1506 1.1 christos 1507 1.1 christos /* Add a timeout so that if the partner doesn't send 1508 1.1 christos another message for the maximum transmit idle time 1509 1.1 christos plus a grace of one second, we close the 1510 1.1 christos connection. */ 1511 1.1 christos if (state -> link_to_peer && 1512 1.1 christos state -> link_to_peer == link && 1513 1.1 christos state -> link_to_peer -> state != dhcp_flink_disconnected) 1514 1.1 christos { 1515 1.1 christos #if defined (DEBUG_FAILOVER_CONTACT_TIMING) 1516 1.1 christos log_info ("add_timeout +%d %s", 1517 1.1 christos (int)state -> me.max_response_delay, 1518 1.1 christos "dhcp_failover_timeout"); 1519 1.1 christos #endif 1520 1.1 christos tv . tv_sec = cur_time + 1521 1.1 christos (int)state -> me.max_response_delay; 1522 1.1 christos tv . tv_usec = 0; 1523 1.1 christos add_timeout (&tv, 1524 1.1 christos dhcp_failover_timeout, state, 1525 1.1 christos (tvref_t)dhcp_failover_state_reference, 1526 1.1 christos (tvunref_t)dhcp_failover_state_dereference); 1527 1.1 christos 1528 1.1 christos } 1529 1.1 christos } 1530 1.1 christos 1531 1.1 christos /* Handle all the events we care about... */ 1532 1.1 christos return ISC_R_SUCCESS; 1533 1.1 christos } 1534 1.1 christos 1535 1.1 christos isc_result_t dhcp_failover_state_transition (dhcp_failover_state_t *state, 1536 1.1 christos const char *name) 1537 1.1 christos { 1538 1.1 christos isc_result_t status; 1539 1.1 christos 1540 1.1 christos /* XXX Check these state transitions against the spec! */ 1541 1.1 christos if (!strcmp (name, "disconnect")) { 1542 1.1 christos if (state -> link_to_peer) { 1543 1.1 christos log_info ("peer %s: disconnected", state -> name); 1544 1.1 christos if (state -> link_to_peer -> state_object) 1545 1.1 christos dhcp_failover_state_dereference 1546 1.1 christos (&state -> link_to_peer -> state_object, MDL); 1547 1.1 christos dhcp_failover_link_dereference (&state -> link_to_peer, 1548 1.1 christos MDL); 1549 1.1 christos } 1550 1.1 christos cancel_timeout (dhcp_failover_send_contact, state); 1551 1.1 christos cancel_timeout (dhcp_failover_timeout, state); 1552 1.1 christos cancel_timeout (dhcp_failover_startup_timeout, state); 1553 1.1 christos 1554 1.1 christos switch (state -> me.state == startup ? 1555 1.1 christos state -> saved_state : state -> me.state) { 1556 1.1 christos /* In these situations, we remain in the current 1557 1.1 christos * state, or if in startup enter those states. 1558 1.1 christos */ 1559 1.1 christos case conflict_done: 1560 1.1 christos /* As the peer may not have received or may have 1561 1.1 christos * lost track of updates we sent previously we 1562 1.1 christos * rescind them, causing us to retransmit them 1563 1.1 christos * on an update request. 1564 1.1 christos */ 1565 1.1 christos dhcp_failover_rescind_updates(state); 1566 1.1 christos /* fall through */ 1567 1.1 christos 1568 1.1 christos case communications_interrupted: 1569 1.1 christos case partner_down: 1570 1.1 christos case paused: 1571 1.1 christos case recover: 1572 1.1 christos case recover_done: 1573 1.1 christos case recover_wait: 1574 1.1 christos case resolution_interrupted: 1575 1.1 christos case shut_down: 1576 1.1 christos /* Already in the right state? */ 1577 1.1 christos if (state -> me.state == startup) 1578 1.1 christos return (dhcp_failover_set_state 1579 1.1 christos (state, state -> saved_state)); 1580 1.1 christos return ISC_R_SUCCESS; 1581 1.1 christos 1582 1.1 christos case potential_conflict: 1583 1.1 christos return dhcp_failover_set_state 1584 1.1 christos (state, resolution_interrupted); 1585 1.1 christos 1586 1.1 christos case normal: 1587 1.1 christos return dhcp_failover_set_state 1588 1.1 christos (state, communications_interrupted); 1589 1.1 christos 1590 1.1 christos case unknown_state: 1591 1.1 christos return dhcp_failover_set_state 1592 1.1 christos (state, resolution_interrupted); 1593 1.1 christos 1594 1.1 christos default: 1595 1.1 christos log_fatal("Impossible case at %s:%d.", MDL); 1596 1.1 christos break; /* can't happen. */ 1597 1.1 christos } 1598 1.1 christos } else if (!strcmp (name, "connect")) { 1599 1.1 christos switch (state -> me.state) { 1600 1.1 christos case communications_interrupted: 1601 1.1 christos status = dhcp_failover_set_state (state, normal); 1602 1.1 christos dhcp_failover_send_updates (state); 1603 1.1 christos return status; 1604 1.1 christos 1605 1.1 christos case resolution_interrupted: 1606 1.1 christos return dhcp_failover_set_state (state, 1607 1.1 christos potential_conflict); 1608 1.1 christos 1609 1.1 christos case conflict_done: 1610 1.1 christos case partner_down: 1611 1.1 christos case potential_conflict: 1612 1.1 christos case normal: 1613 1.1 christos case recover: 1614 1.1 christos case shut_down: 1615 1.1 christos case paused: 1616 1.1 christos case unknown_state: 1617 1.1 christos case recover_done: 1618 1.1 christos case startup: 1619 1.1 christos case recover_wait: 1620 1.1 christos return dhcp_failover_send_state (state); 1621 1.1 christos 1622 1.1 christos default: 1623 1.1 christos log_fatal("Impossible case at %s:%d.", MDL); 1624 1.1 christos break; 1625 1.1 christos } 1626 1.1 christos } else if (!strcmp (name, "startup")) { 1627 1.1 christos dhcp_failover_set_state (state, startup); 1628 1.1 christos return ISC_R_SUCCESS; 1629 1.1 christos } else if (!strcmp (name, "connect-timeout")) { 1630 1.1 christos switch (state -> me.state) { 1631 1.1 christos case communications_interrupted: 1632 1.1 christos case partner_down: 1633 1.1 christos case resolution_interrupted: 1634 1.1 christos case paused: 1635 1.1 christos case startup: 1636 1.1 christos case shut_down: 1637 1.1 christos case conflict_done: 1638 1.1 christos return ISC_R_SUCCESS; 1639 1.1 christos 1640 1.1 christos case normal: 1641 1.1 christos case recover: 1642 1.1 christos case recover_wait: 1643 1.1 christos case recover_done: 1644 1.1 christos case unknown_state: 1645 1.1 christos return dhcp_failover_set_state 1646 1.1 christos (state, communications_interrupted); 1647 1.1 christos 1648 1.1 christos case potential_conflict: 1649 1.1 christos return dhcp_failover_set_state 1650 1.1 christos (state, resolution_interrupted); 1651 1.1 christos 1652 1.1 christos default: 1653 1.1 christos log_fatal("Impossible case at %s:%d.", MDL); 1654 1.1 christos break; 1655 1.1 christos } 1656 1.1 christos } 1657 1.1 christos return DHCP_R_INVALIDARG; 1658 1.1 christos } 1659 1.1 christos 1660 1.1 christos isc_result_t dhcp_failover_set_service_state (dhcp_failover_state_t *state) 1661 1.1 christos { 1662 1.1 christos switch (state -> me.state) { 1663 1.1 christos case unknown_state: 1664 1.1 christos state -> service_state = not_responding; 1665 1.1 christos state -> nrr = " (my state unknown)"; 1666 1.1 christos break; 1667 1.1 christos 1668 1.1 christos case partner_down: 1669 1.1 christos state -> service_state = service_partner_down; 1670 1.1 christos state -> nrr = ""; 1671 1.1 christos break; 1672 1.1 christos 1673 1.1 christos case normal: 1674 1.1 christos state -> service_state = cooperating; 1675 1.1 christos state -> nrr = ""; 1676 1.1 christos break; 1677 1.1 christos 1678 1.1 christos case communications_interrupted: 1679 1.1 christos state -> service_state = not_cooperating; 1680 1.1 christos state -> nrr = ""; 1681 1.1 christos break; 1682 1.1 christos 1683 1.1 christos case resolution_interrupted: 1684 1.1 christos case potential_conflict: 1685 1.1 christos case conflict_done: 1686 1.1 christos state -> service_state = not_responding; 1687 1.1 christos state -> nrr = " (resolving conflicts)"; 1688 1.1 christos break; 1689 1.1 christos 1690 1.1 christos case recover: 1691 1.1 christos state -> service_state = not_responding; 1692 1.1 christos state -> nrr = " (recovering)"; 1693 1.1 christos break; 1694 1.1 christos 1695 1.1 christos case shut_down: 1696 1.1 christos state -> service_state = not_responding; 1697 1.1 christos state -> nrr = " (shut down)"; 1698 1.1 christos break; 1699 1.1 christos 1700 1.1 christos case paused: 1701 1.1 christos state -> service_state = not_responding; 1702 1.1 christos state -> nrr = " (paused)"; 1703 1.1 christos break; 1704 1.1 christos 1705 1.1 christos case recover_wait: 1706 1.1 christos state -> service_state = not_responding; 1707 1.1 christos state -> nrr = " (recover wait)"; 1708 1.1 christos break; 1709 1.1 christos 1710 1.1 christos case recover_done: 1711 1.1 christos state -> service_state = not_responding; 1712 1.1 christos state -> nrr = " (recover done)"; 1713 1.1 christos break; 1714 1.1 christos 1715 1.1 christos case startup: 1716 1.1 christos state -> service_state = service_startup; 1717 1.1 christos state -> nrr = " (startup)"; 1718 1.1 christos break; 1719 1.1 christos 1720 1.1 christos default: 1721 1.1 christos log_fatal("Impossible case at %s:%d.\n", MDL); 1722 1.1 christos break; 1723 1.1 christos } 1724 1.1 christos 1725 1.1 christos /* Some peer states can require us not to respond, even if our 1726 1.1 christos state doesn't. */ 1727 1.1 christos /* XXX hm. I suspect this isn't true anymore. */ 1728 1.1 christos if (state -> service_state != not_responding) { 1729 1.1 christos switch (state -> partner.state) { 1730 1.1 christos case partner_down: 1731 1.1 christos state -> service_state = not_responding; 1732 1.1 christos state -> nrr = " (peer demands: recovering)"; 1733 1.1 christos break; 1734 1.1 christos 1735 1.1 christos case potential_conflict: 1736 1.1 christos case conflict_done: 1737 1.1 christos case resolution_interrupted: 1738 1.1 christos state -> service_state = not_responding; 1739 1.1 christos state -> nrr = " (peer demands: resolving conflicts)"; 1740 1.1 christos break; 1741 1.1 christos 1742 1.1 christos /* Other peer states don't affect our behaviour. */ 1743 1.1 christos default: 1744 1.1 christos break; 1745 1.1 christos } 1746 1.1 christos } 1747 1.1 christos 1748 1.1 christos return ISC_R_SUCCESS; 1749 1.1 christos } 1750 1.1 christos 1751 1.1 christos /*! 1752 1.1 christos * \brief Return any leases on the ack queue back to the update queue 1753 1.1 christos * 1754 1.1 christos * Re-schedule any pending updates by moving them from the ack queue 1755 1.1 christos * (update sent awaiting response) back to the update queue (need to 1756 1.1 christos * send an update for this lease). This will result in a retransmission 1757 1.1 christos * of the update. 1758 1.1 christos * 1759 1.1 christos * \param state is the state block for the failover connection we are 1760 1.1 christos * updating. 1761 1.1 christos */ 1762 1.1 christos 1763 1.1 christos void dhcp_failover_rescind_updates (dhcp_failover_state_t *state) 1764 1.1 christos { 1765 1.1 christos struct lease *lp; 1766 1.1 christos 1767 1.1 christos if (state->ack_queue_tail == NULL) 1768 1.1 christos return; 1769 1.1 christos 1770 1.1 christos /* Zap the flags. */ 1771 1.1 christos for (lp = state->ack_queue_head; lp; lp = lp->next_pending) 1772 1.1 christos lp->flags = ((lp->flags & ~ON_ACK_QUEUE) | ON_UPDATE_QUEUE); 1773 1.1 christos 1774 1.1 christos /* Now hook the ack queue to the beginning of the update queue. */ 1775 1.1 christos if (state->update_queue_head) { 1776 1.1 christos lease_reference(&state->ack_queue_tail->next_pending, 1777 1.1 christos state->update_queue_head, MDL); 1778 1.1 christos lease_dereference(&state->update_queue_head, MDL); 1779 1.1 christos } 1780 1.1 christos lease_reference(&state->update_queue_head, state->ack_queue_head, MDL); 1781 1.1 christos 1782 1.1 christos if (!state->update_queue_tail) { 1783 1.1 christos #if defined (POINTER_DEBUG) 1784 1.1 christos if (state->ack_queue_tail->next_pending) { 1785 1.1 christos log_error("next pending on ack queue tail."); 1786 1.1 christos abort(); 1787 1.1 christos } 1788 1.1 christos #endif 1789 1.1 christos lease_reference(&state->update_queue_tail, 1790 1.1 christos state->ack_queue_tail, MDL); 1791 1.1 christos } 1792 1.1 christos lease_dereference(&state->ack_queue_tail, MDL); 1793 1.1 christos lease_dereference(&state->ack_queue_head, MDL); 1794 1.1 christos state->cur_unacked_updates = 0; 1795 1.1 christos } 1796 1.1 christos 1797 1.1 christos isc_result_t dhcp_failover_set_state (dhcp_failover_state_t *state, 1798 1.1 christos enum failover_state new_state) 1799 1.1 christos { 1800 1.1 christos enum failover_state saved_state; 1801 1.1 christos TIME saved_stos; 1802 1.1 christos struct pool *p; 1803 1.1 christos struct shared_network *s; 1804 1.1 christos struct lease *l; 1805 1.1 christos struct timeval tv; 1806 1.1 christos 1807 1.1 christos /* If we're in certain states where we're sending updates, and the peer 1808 1.1 christos * state changes, we need to re-schedule any pending updates just to 1809 1.1 christos * be on the safe side. This results in retransmission. 1810 1.1 christos */ 1811 1.1 christos switch (state -> me.state) { 1812 1.1 christos case normal: 1813 1.1 christos case potential_conflict: 1814 1.1 christos case partner_down: 1815 1.1 christos /* Move the ack queue to the update queue */ 1816 1.1 christos dhcp_failover_rescind_updates(state); 1817 1.1 christos 1818 1.1 christos /* We will re-queue a timeout later, if applicable. */ 1819 1.1 christos cancel_timeout (dhcp_failover_keepalive, state); 1820 1.1 christos break; 1821 1.1 christos 1822 1.1 christos default: 1823 1.1 christos break; 1824 1.1 christos } 1825 1.1 christos 1826 1.1 christos /* Tentatively make the transition. */ 1827 1.1 christos saved_state = state -> me.state; 1828 1.1 christos saved_stos = state -> me.stos; 1829 1.1 christos 1830 1.1 christos /* Keep the old stos if we're going into recover_wait or if we're 1831 1.1 christos coming into or out of startup. */ 1832 1.1 christos if (new_state != recover_wait && new_state != startup && 1833 1.1 christos saved_state != startup) 1834 1.1 christos state -> me.stos = cur_time; 1835 1.1 christos 1836 1.1 christos /* If we're in shutdown, peer is in partner_down, and we're moving 1837 1.1 christos to recover, we can skip waiting for MCLT to expire. This happens 1838 1.1 christos when a server is moved administratively into shutdown prior to 1839 1.1 christos actually shutting down. Of course, if there are any updates 1840 1.1 christos pending we can't actually do this. */ 1841 1.1 christos if (new_state == recover && saved_state == shut_down && 1842 1.1 christos state -> partner.state == partner_down && 1843 1.1 christos !state -> update_queue_head && !state -> ack_queue_head) 1844 1.1 christos state -> me.stos = cur_time - state -> mclt; 1845 1.1 christos 1846 1.1 christos state -> me.state = new_state; 1847 1.1 christos if (new_state == startup && saved_state != startup) 1848 1.1 christos state -> saved_state = saved_state; 1849 1.1 christos 1850 1.1 christos /* If we can't record the new state, we can't make a state transition. */ 1851 1.1 christos if (!write_failover_state (state) || !commit_leases ()) { 1852 1.1 christos log_error ("Unable to record current failover state for %s", 1853 1.1 christos state -> name); 1854 1.1 christos state -> me.state = saved_state; 1855 1.1 christos state -> me.stos = saved_stos; 1856 1.1 christos return ISC_R_IOERROR; 1857 1.1 christos } 1858 1.1 christos 1859 1.1 christos log_info ("failover peer %s: I move from %s to %s", 1860 1.1 christos state -> name, dhcp_failover_state_name_print (saved_state), 1861 1.1 christos dhcp_failover_state_name_print (state -> me.state)); 1862 1.1 christos 1863 1.1 christos /* If both servers are now normal log it */ 1864 1.1 christos if ((state->me.state == normal) && (state->partner.state == normal)) 1865 1.1 christos log_info("failover peer %s: Both servers normal", state->name); 1866 1.1 christos 1867 1.1 christos /* If we were in startup and we just left it, cancel the timeout. */ 1868 1.1 christos if (new_state != startup && saved_state == startup) 1869 1.1 christos cancel_timeout (dhcp_failover_startup_timeout, state); 1870 1.1 christos 1871 1.1 christos /* 1872 1.1 christos * If the state changes for any reason, cancel 'delayed auto state 1873 1.1 christos * changes' (currently there is just the one). 1874 1.1 christos */ 1875 1.1 christos cancel_timeout(dhcp_failover_auto_partner_down, state); 1876 1.1 christos 1877 1.1 christos /* Set our service state. */ 1878 1.1 christos dhcp_failover_set_service_state (state); 1879 1.1 christos 1880 1.1 christos /* Tell the peer about it. */ 1881 1.1 christos if (state -> link_to_peer) 1882 1.1 christos dhcp_failover_send_state (state); 1883 1.1 christos 1884 1.1 christos switch (new_state) { 1885 1.1 christos case communications_interrupted: 1886 1.1 christos /* 1887 1.1 christos * There is an optional feature to automatically enter partner 1888 1.1 christos * down after a timer expires, upon entering comms-interrupted. 1889 1.1 christos * This feature is generally not safe except in specific 1890 1.1 christos * circumstances. 1891 1.1 christos * 1892 1.1 christos * A zero value (also the default) disables it. 1893 1.1 christos */ 1894 1.1 christos if (state->auto_partner_down == 0) 1895 1.1 christos break; 1896 1.1 christos 1897 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 1898 1.1 christos log_info("add_timeout +%lu dhcp_failover_auto_partner_down", 1899 1.1 christos (unsigned long)state->auto_partner_down); 1900 1.1 christos #endif 1901 1.1 christos tv.tv_sec = cur_time + state->auto_partner_down; 1902 1.1 christos tv.tv_usec = 0; 1903 1.1 christos add_timeout(&tv, dhcp_failover_auto_partner_down, state, 1904 1.1 christos (tvref_t)omapi_object_reference, 1905 1.1 christos (tvunref_t)omapi_object_dereference); 1906 1.1 christos break; 1907 1.1 christos 1908 1.1 christos case normal: 1909 1.1 christos /* Upon entering normal state, the server is expected to retransmit 1910 1.1 christos * all pending binding updates. This is a good opportunity to 1911 1.1 christos * rebalance the pool (potentially making new pending updates), 1912 1.1 christos * which also schedules the next pool rebalance. 1913 1.1 christos */ 1914 1.1 christos dhcp_failover_pool_balance(state); 1915 1.1 christos dhcp_failover_generate_update_queue(state, 0); 1916 1.1 christos 1917 1.1 christos if (state->update_queue_tail != NULL) { 1918 1.1 christos dhcp_failover_send_updates(state); 1919 1.1 christos log_info("Sending updates to %s.", state->name); 1920 1.1 christos } 1921 1.1 christos 1922 1.1 christos break; 1923 1.1 christos 1924 1.1 christos case potential_conflict: 1925 1.1 christos if ((state->i_am == primary) || 1926 1.1 christos ((state->i_am == secondary) && 1927 1.1 christos (state->partner.state == conflict_done))) 1928 1.1 christos dhcp_failover_send_update_request (state); 1929 1.1 christos break; 1930 1.1 christos 1931 1.1 christos case startup: 1932 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 1933 1.1 christos log_info ("add_timeout +15 %s", 1934 1.1 christos "dhcp_failover_startup_timeout"); 1935 1.1 christos #endif 1936 1.1 christos tv . tv_sec = cur_time + 15; 1937 1.1 christos tv . tv_usec = 0; 1938 1.1 christos add_timeout (&tv, 1939 1.1 christos dhcp_failover_startup_timeout, 1940 1.1 christos state, 1941 1.1 christos (tvref_t)omapi_object_reference, 1942 1.1 christos (tvunref_t) 1943 1.1 christos omapi_object_dereference); 1944 1.1 christos break; 1945 1.1 christos 1946 1.1 christos /* If we come back in recover_wait and there's still waiting 1947 1.1 christos to do, set a timeout. */ 1948 1.1 christos case recover_wait: 1949 1.1 christos if (state -> me.stos + state -> mclt > cur_time) { 1950 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 1951 1.1 christos log_info ("add_timeout +%d %s", 1952 1.1 christos (int)(cur_time - 1953 1.1 christos state -> me.stos + state -> mclt), 1954 1.1 christos "dhcp_failover_startup_timeout"); 1955 1.1 christos #endif 1956 1.1 christos tv . tv_sec = (int)(state -> me.stos + state -> mclt); 1957 1.1 christos tv . tv_usec = 0; 1958 1.1 christos add_timeout (&tv, 1959 1.1 christos dhcp_failover_recover_done, 1960 1.1 christos state, 1961 1.1 christos (tvref_t)omapi_object_reference, 1962 1.1 christos (tvunref_t) 1963 1.1 christos omapi_object_dereference); 1964 1.1 christos } else 1965 1.1 christos dhcp_failover_recover_done (state); 1966 1.1 christos break; 1967 1.1 christos 1968 1.1 christos case recover: 1969 1.1 christos /* XXX: We're supposed to calculate if updreq or updreqall is 1970 1.1 christos * needed. In theory, we should only have to updreqall if we 1971 1.1 christos * are positive we lost our stable storage. 1972 1.1 christos */ 1973 1.1 christos if (state -> link_to_peer) 1974 1.1 christos dhcp_failover_send_update_request_all (state); 1975 1.1 christos break; 1976 1.1 christos 1977 1.1 christos case partner_down: 1978 1.1 christos /* For every expired lease, set a timeout for it to become free. */ 1979 1.1 christos for (s = shared_networks; s; s = s->next) { 1980 1.1 christos for (p = s->pools; p; p = p->next) { 1981 1.1 christos #if defined (BINARY_LEASES) 1982 1.1 christos long int tiebreaker = 0; 1983 1.1 christos #endif 1984 1.1 christos if (p->failover_peer == state) { 1985 1.1 christos for (l = LEASE_GET_FIRST(p->expired); 1986 1.1 christos l != NULL; 1987 1.1 christos l = LEASE_GET_NEXT(p->expired, l)) { 1988 1.1 christos l->tsfp = state->me.stos + state->mclt; 1989 1.1 christos l->sort_time = (l->tsfp > l->ends) ? 1990 1.1 christos l->tsfp : l->ends; 1991 1.1 christos #if defined (BINARY_LEASES) 1992 1.1 christos /* If necessary fix up the tiebreaker so the leases 1993 1.1 christos * maintain proper sort order. 1994 1.1 christos */ 1995 1.1 christos l->sort_tiebreaker = tiebreaker; 1996 1.1 christos if (tiebreaker != LONG_MAX) 1997 1.1 christos tiebreaker++; 1998 1.1 christos #endif 1999 1.1 christos 2000 1.1 christos } 2001 1.1 christos 2002 1.1 christos l = LEASE_GET_FIRST(p->expired); 2003 1.1 christos if (l && (l->sort_time < p->next_event_time)) { 2004 1.1 christos 2005 1.1 christos p->next_event_time = l->sort_time; 2006 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 2007 1.1 christos log_info ("add_timeout +%d %s", 2008 1.1 christos (int)(cur_time - p->next_event_time), 2009 1.1 christos "pool_timer"); 2010 1.1 christos #endif 2011 1.1 christos tv.tv_sec = p->next_event_time; 2012 1.1 christos tv.tv_usec = 0; 2013 1.1 christos add_timeout(&tv, pool_timer, p, 2014 1.1 christos (tvref_t)pool_reference, 2015 1.1 christos (tvunref_t)pool_dereference); 2016 1.1 christos } 2017 1.1 christos } 2018 1.1 christos } 2019 1.1 christos } 2020 1.1 christos break; 2021 1.1 christos 2022 1.1 christos default: 2023 1.1 christos break; 2024 1.1 christos } 2025 1.1 christos 2026 1.1 christos return ISC_R_SUCCESS; 2027 1.1 christos } 2028 1.1 christos 2029 1.1 christos isc_result_t dhcp_failover_peer_state_changed (dhcp_failover_state_t *state, 2030 1.1 christos failover_message_t *msg) 2031 1.1 christos { 2032 1.1 christos enum failover_state previous_state = state -> partner.state; 2033 1.1 christos enum failover_state new_state; 2034 1.1 christos int startupp; 2035 1.1 christos 2036 1.1 christos new_state = msg -> server_state; 2037 1.1 christos startupp = (msg -> server_flags & FTF_SERVER_STARTUP) ? 1 : 0; 2038 1.1 christos 2039 1.1 christos if (state -> partner.state == new_state && state -> me.state) { 2040 1.1 christos switch (state -> me.state) { 2041 1.1 christos case startup: 2042 1.1 christos /* 2043 1.1 christos * If we have a peer state we must be connected. 2044 1.1 christos * If so we should move to potential_conflict 2045 1.1 christos * instead of resolution_interrupted, otherwise 2046 1.1 christos * back to whereever we were before we stopped. 2047 1.1 christos */ 2048 1.1 christos if (state->saved_state == resolution_interrupted) 2049 1.1 christos dhcp_failover_set_state(state, 2050 1.1 christos potential_conflict); 2051 1.1 christos else 2052 1.1 christos dhcp_failover_set_state(state, 2053 1.1 christos state->saved_state); 2054 1.1 christos return ISC_R_SUCCESS; 2055 1.1 christos 2056 1.1 christos case unknown_state: 2057 1.1 christos case normal: 2058 1.1 christos case potential_conflict: 2059 1.1 christos case recover_done: 2060 1.1 christos case shut_down: 2061 1.1 christos case paused: 2062 1.1 christos case recover_wait: 2063 1.1 christos return ISC_R_SUCCESS; 2064 1.1 christos 2065 1.1 christos /* If we get a peer state change when we're 2066 1.1 christos disconnected, we always process it. */ 2067 1.1 christos case partner_down: 2068 1.1 christos case communications_interrupted: 2069 1.1 christos case resolution_interrupted: 2070 1.1 christos case recover: 2071 1.1 christos case conflict_done: 2072 1.1 christos break; 2073 1.1 christos 2074 1.1 christos default: 2075 1.1 christos log_fatal("Impossible case at %s:%d.", MDL); 2076 1.1 christos break; 2077 1.1 christos } 2078 1.1 christos } 2079 1.1 christos 2080 1.1 christos state -> partner.state = new_state; 2081 1.1 christos state -> partner.stos = cur_time; 2082 1.1 christos 2083 1.1 christos log_info ("failover peer %s: peer moves from %s to %s", 2084 1.1 christos state -> name, 2085 1.1 christos dhcp_failover_state_name_print (previous_state), 2086 1.1 christos dhcp_failover_state_name_print (state -> partner.state)); 2087 1.1 christos 2088 1.1 christos /* If both servers are now normal log it */ 2089 1.1 christos if ((state->me.state == normal) && (state->partner.state == normal)) 2090 1.1 christos log_info("failover peer %s: Both servers normal", state->name); 2091 1.1 christos 2092 1.1 christos if (!write_failover_state (state) || !commit_leases ()) { 2093 1.1 christos /* This is bad, but it's not fatal. Of course, if we 2094 1.1 christos can't write to the lease database, we're not going to 2095 1.1 christos get much done anyway. */ 2096 1.1 christos log_error ("Unable to record current failover state for %s", 2097 1.1 christos state -> name); 2098 1.1 christos } 2099 1.1 christos 2100 1.1 christos /* Quickly validate the new state as being one of the 13 known 2101 1.1 christos * states. 2102 1.1 christos */ 2103 1.1 christos switch (new_state) { 2104 1.1 christos case unknown_state: 2105 1.1 christos case startup: 2106 1.1 christos case normal: 2107 1.1 christos case communications_interrupted: 2108 1.1 christos case partner_down: 2109 1.1 christos case potential_conflict: 2110 1.1 christos case recover: 2111 1.1 christos case paused: 2112 1.1 christos case shut_down: 2113 1.1 christos case recover_done: 2114 1.1 christos case resolution_interrupted: 2115 1.1 christos case conflict_done: 2116 1.1 christos case recover_wait: 2117 1.1 christos break; 2118 1.1 christos 2119 1.1 christos default: 2120 1.1 christos log_error("failover peer %s: Invalid state: %d", state->name, 2121 1.1 christos new_state); 2122 1.1 christos dhcp_failover_set_state(state, shut_down); 2123 1.1 christos return ISC_R_SUCCESS; 2124 1.1 christos } 2125 1.1 christos 2126 1.1 christos /* Do any state transitions that are required as a result of the 2127 1.1 christos peer's state transition. */ 2128 1.1 christos 2129 1.1 christos switch (state -> me.state == startup ? 2130 1.1 christos state -> saved_state : state -> me.state) { 2131 1.1 christos case normal: 2132 1.1 christos switch (new_state) { 2133 1.1 christos case normal: 2134 1.1 christos dhcp_failover_state_pool_check (state); 2135 1.1 christos break; 2136 1.1 christos 2137 1.1 christos case partner_down: 2138 1.1 christos if (state -> me.state == startup) 2139 1.1 christos dhcp_failover_set_state (state, recover); 2140 1.1 christos else 2141 1.1 christos dhcp_failover_set_state (state, 2142 1.1 christos potential_conflict); 2143 1.1 christos break; 2144 1.1 christos 2145 1.1 christos case potential_conflict: 2146 1.1 christos case resolution_interrupted: 2147 1.1 christos case conflict_done: 2148 1.1 christos /* None of these transitions should ever occur. */ 2149 1.1 christos log_error("Peer %s: Invalid state transition %s " 2150 1.1 christos "to %s.", state->name, 2151 1.1 christos dhcp_failover_state_name_print(previous_state), 2152 1.1 christos dhcp_failover_state_name_print(new_state)); 2153 1.1 christos dhcp_failover_set_state (state, shut_down); 2154 1.1 christos break; 2155 1.1 christos 2156 1.1 christos case recover: 2157 1.1 christos case shut_down: 2158 1.1 christos dhcp_failover_set_state (state, partner_down); 2159 1.1 christos break; 2160 1.1 christos 2161 1.1 christos case paused: 2162 1.1 christos dhcp_failover_set_state (state, 2163 1.1 christos communications_interrupted); 2164 1.1 christos break; 2165 1.1 christos 2166 1.1 christos default: 2167 1.1 christos /* recover_wait, recover_done, unknown_state, startup, 2168 1.1 christos * communications_interrupted 2169 1.1 christos */ 2170 1.1 christos break; 2171 1.1 christos } 2172 1.1 christos break; 2173 1.1 christos 2174 1.1 christos case recover: 2175 1.1 christos switch (new_state) { 2176 1.1 christos case recover: 2177 1.1 christos log_info ("failover peer %s: requesting %s", 2178 1.1 christos state -> name, "full update from peer"); 2179 1.1 christos /* Don't send updreqall if we're really in the 2180 1.1 christos startup state, because that will result in two 2181 1.1 christos being sent. */ 2182 1.1 christos if (state -> me.state == recover) 2183 1.1 christos dhcp_failover_send_update_request_all (state); 2184 1.1 christos break; 2185 1.1 christos 2186 1.1 christos case potential_conflict: 2187 1.1 christos case resolution_interrupted: 2188 1.1 christos case conflict_done: 2189 1.1 christos case normal: 2190 1.1 christos dhcp_failover_set_state (state, potential_conflict); 2191 1.1 christos break; 2192 1.1 christos 2193 1.1 christos case partner_down: 2194 1.1 christos case communications_interrupted: 2195 1.1 christos /* We're supposed to send an update request at this 2196 1.1 christos point. */ 2197 1.1 christos /* XXX we don't currently have code here to do any 2198 1.1 christos XXX clever detection of when we should send an 2199 1.1 christos XXX UPDREQALL message rather than an UPDREQ 2200 1.1 christos XXX message. What to do, what to do? */ 2201 1.1 christos /* Currently when we enter recover state, no matter 2202 1.1 christos * the reason, we send an UPDREQALL. So, it makes 2203 1.1 christos * the most sense to stick to that until something 2204 1.1 christos * better is done. 2205 1.1 christos * Furthermore, we only want to send the update 2206 1.1 christos * request if we are not in startup state. 2207 1.1 christos */ 2208 1.1 christos if (state -> me.state == recover) 2209 1.1 christos dhcp_failover_send_update_request_all (state); 2210 1.1 christos break; 2211 1.1 christos 2212 1.1 christos case shut_down: 2213 1.1 christos /* XXX We're not explicitly told what to do in this 2214 1.1 christos XXX case, but this transition is consistent with 2215 1.1 christos XXX what is elsewhere in the draft. */ 2216 1.1 christos dhcp_failover_set_state (state, partner_down); 2217 1.1 christos break; 2218 1.1 christos 2219 1.1 christos /* We can't really do anything in this case. */ 2220 1.1 christos default: 2221 1.1 christos /* paused, recover_done, recover_wait, unknown_state, 2222 1.1 christos * startup. 2223 1.1 christos */ 2224 1.1 christos break; 2225 1.1 christos } 2226 1.1 christos break; 2227 1.1 christos 2228 1.1 christos case potential_conflict: 2229 1.1 christos switch (new_state) { 2230 1.1 christos case normal: 2231 1.1 christos /* This is an illegal transition. */ 2232 1.1 christos log_error("Peer %s moves to normal during conflict " 2233 1.1 christos "resolution - panic, shutting down.", 2234 1.1 christos state->name); 2235 1.1 christos dhcp_failover_set_state(state, shut_down); 2236 1.1 christos break; 2237 1.1 christos 2238 1.1 christos case conflict_done: 2239 1.1 christos if (previous_state == potential_conflict) 2240 1.1 christos dhcp_failover_send_update_request (state); 2241 1.1 christos else 2242 1.1 christos log_error("Peer %s: Unexpected move to " 2243 1.1 christos "conflict-done.", state->name); 2244 1.1 christos break; 2245 1.1 christos 2246 1.1 christos case recover_done: 2247 1.1 christos case recover_wait: 2248 1.1 christos case potential_conflict: 2249 1.1 christos case partner_down: 2250 1.1 christos case communications_interrupted: 2251 1.1 christos case resolution_interrupted: 2252 1.1 christos case paused: 2253 1.1 christos break; 2254 1.1 christos 2255 1.1 christos case recover: 2256 1.1 christos dhcp_failover_set_state (state, recover); 2257 1.1 christos break; 2258 1.1 christos 2259 1.1 christos case shut_down: 2260 1.1 christos dhcp_failover_set_state (state, partner_down); 2261 1.1 christos break; 2262 1.1 christos 2263 1.1 christos default: 2264 1.1 christos /* unknown_state, startup */ 2265 1.1 christos break; 2266 1.1 christos } 2267 1.1 christos break; 2268 1.1 christos 2269 1.1 christos case conflict_done: 2270 1.1 christos switch (new_state) { 2271 1.1 christos case normal: 2272 1.1 christos case shut_down: 2273 1.1 christos dhcp_failover_set_state(state, new_state); 2274 1.1 christos break; 2275 1.1 christos 2276 1.1 christos case potential_conflict: 2277 1.1 christos case resolution_interrupted: 2278 1.1 christos /* 2279 1.1 christos * This can happen when the connection is lost and 2280 1.1 christos * recovered after the primary has moved to 2281 1.1 christos * conflict-done but the secondary is still in 2282 1.1 christos * potential-conflict. In that case, we have to 2283 1.1 christos * remain in conflict-done. 2284 1.1 christos */ 2285 1.1 christos break; 2286 1.1 christos 2287 1.1 christos default: 2288 1.1 christos log_fatal("Peer %s: Invalid attempt to move from %s " 2289 1.1 christos "to %s while local state is conflict-done.", 2290 1.1 christos state->name, 2291 1.1 christos dhcp_failover_state_name_print(previous_state), 2292 1.1 christos dhcp_failover_state_name_print(new_state)); 2293 1.1 christos } 2294 1.1 christos break; 2295 1.1 christos 2296 1.1 christos case partner_down: 2297 1.1 christos /* Take no action if other server is starting up. */ 2298 1.1 christos if (startupp) 2299 1.1 christos break; 2300 1.1 christos 2301 1.1 christos switch (new_state) { 2302 1.1 christos /* This is where we should be. */ 2303 1.1 christos case recover: 2304 1.1 christos case recover_wait: 2305 1.1 christos break; 2306 1.1 christos 2307 1.1 christos case recover_done: 2308 1.1 christos dhcp_failover_set_state (state, normal); 2309 1.1 christos break; 2310 1.1 christos 2311 1.1 christos case normal: 2312 1.1 christos case potential_conflict: 2313 1.1 christos case partner_down: 2314 1.1 christos case communications_interrupted: 2315 1.1 christos case resolution_interrupted: 2316 1.1 christos case conflict_done: 2317 1.1 christos dhcp_failover_set_state (state, potential_conflict); 2318 1.1 christos break; 2319 1.1 christos 2320 1.1 christos default: 2321 1.1 christos /* shut_down, paused, unknown_state, startup */ 2322 1.1 christos break; 2323 1.1 christos } 2324 1.1 christos break; 2325 1.1 christos 2326 1.1 christos case communications_interrupted: 2327 1.1 christos switch (new_state) { 2328 1.1 christos case paused: 2329 1.1 christos /* Stick with the status quo. */ 2330 1.1 christos break; 2331 1.1 christos 2332 1.1 christos /* If we're in communications-interrupted and an 2333 1.1 christos amnesic peer connects, go to the partner_down 2334 1.1 christos state immediately. */ 2335 1.1 christos case recover: 2336 1.1 christos dhcp_failover_set_state (state, partner_down); 2337 1.1 christos break; 2338 1.1 christos 2339 1.1 christos case normal: 2340 1.1 christos case communications_interrupted: 2341 1.1 christos case recover_done: 2342 1.1 christos case recover_wait: 2343 1.1 christos /* XXX so we don't need to do this specially in 2344 1.1 christos XXX the CONNECT and CONNECTACK handlers. */ 2345 1.1 christos dhcp_failover_send_updates (state); 2346 1.1 christos dhcp_failover_set_state (state, normal); 2347 1.1 christos break; 2348 1.1 christos 2349 1.1 christos case potential_conflict: 2350 1.1 christos case partner_down: 2351 1.1 christos case resolution_interrupted: 2352 1.1 christos case conflict_done: 2353 1.1 christos dhcp_failover_set_state (state, potential_conflict); 2354 1.1 christos break; 2355 1.1 christos 2356 1.1 christos case shut_down: 2357 1.1 christos dhcp_failover_set_state (state, partner_down); 2358 1.1 christos break; 2359 1.1 christos 2360 1.1 christos default: 2361 1.1 christos /* unknown_state, startup */ 2362 1.1 christos break; 2363 1.1 christos } 2364 1.1 christos break; 2365 1.1 christos 2366 1.1 christos case resolution_interrupted: 2367 1.1 christos switch (new_state) { 2368 1.1 christos case normal: 2369 1.1 christos case recover: 2370 1.1 christos case potential_conflict: 2371 1.1 christos case partner_down: 2372 1.1 christos case communications_interrupted: 2373 1.1 christos case resolution_interrupted: 2374 1.1 christos case conflict_done: 2375 1.1 christos case recover_done: 2376 1.1 christos case recover_wait: 2377 1.1 christos dhcp_failover_set_state (state, potential_conflict); 2378 1.1 christos break; 2379 1.1 christos 2380 1.1 christos case shut_down: 2381 1.1 christos dhcp_failover_set_state (state, partner_down); 2382 1.1 christos break; 2383 1.1 christos 2384 1.1 christos default: 2385 1.1 christos /* paused, unknown_state, startup */ 2386 1.1 christos break; 2387 1.1 christos } 2388 1.1 christos break; 2389 1.1 christos 2390 1.1 christos /* Make no transitions while in recover_wait...just wait. */ 2391 1.1 christos case recover_wait: 2392 1.1 christos break; 2393 1.1 christos 2394 1.1 christos case recover_done: 2395 1.1 christos switch (new_state) { 2396 1.1 christos case recover_done: 2397 1.1 christos log_error("Both servers have entered recover-done!"); 2398 1.1 christos /* Fall through and tranistion to normal anyway */ 2399 1.1 christos 2400 1.1 christos case normal: 2401 1.1 christos dhcp_failover_set_state (state, normal); 2402 1.1 christos break; 2403 1.1 christos 2404 1.1 christos case shut_down: 2405 1.1 christos dhcp_failover_set_state (state, partner_down); 2406 1.1 christos break; 2407 1.1 christos 2408 1.1 christos default: 2409 1.1 christos /* potential_conflict, partner_down, 2410 1.1 christos * communications_interrupted, resolution_interrupted, 2411 1.1 christos * paused, recover, recover_wait, unknown_state, 2412 1.1 christos * startup. 2413 1.1 christos */ 2414 1.1 christos break; 2415 1.1 christos } 2416 1.1 christos break; 2417 1.1 christos 2418 1.1 christos /* We are essentially dead in the water when we're in 2419 1.1 christos either shut_down or paused states, and do not do any 2420 1.1 christos automatic state transitions. */ 2421 1.1 christos case shut_down: 2422 1.1 christos case paused: 2423 1.1 christos break; 2424 1.1 christos 2425 1.1 christos /* XXX: Shouldn't this be a fatal condition? */ 2426 1.1 christos case unknown_state: 2427 1.1 christos break; 2428 1.1 christos 2429 1.1 christos default: 2430 1.1 christos log_fatal("Impossible condition at %s:%d.", MDL); 2431 1.1 christos break; 2432 1.1 christos 2433 1.1 christos } 2434 1.1 christos 2435 1.1 christos /* If we didn't make a transition out of startup as a result of 2436 1.1 christos the peer's state change, do it now as a result of the fact that 2437 1.1 christos we got a state change from the peer. */ 2438 1.1 christos if (state -> me.state == startup && state -> saved_state != startup) 2439 1.1 christos dhcp_failover_set_state (state, state -> saved_state); 2440 1.1 christos 2441 1.1 christos /* For now, just set the service state based on the peer's state 2442 1.1 christos if necessary. */ 2443 1.1 christos dhcp_failover_set_service_state (state); 2444 1.1 christos 2445 1.1 christos return ISC_R_SUCCESS; 2446 1.1 christos } 2447 1.1 christos 2448 1.1 christos /* 2449 1.1 christos * Balance operation manual entry; startup, entrance to normal state. No 2450 1.1 christos * sense sending a POOLREQ at this stage; the peer is likely about to schedule 2451 1.1 christos * their own rebalance event upon entering normal themselves. 2452 1.1 christos */ 2453 1.1 christos static void 2454 1.1 christos dhcp_failover_pool_balance(dhcp_failover_state_t *state) 2455 1.1 christos { 2456 1.1 christos /* Cancel pending event. */ 2457 1.1 christos cancel_timeout(dhcp_failover_pool_rebalance, state); 2458 1.1 christos state->sched_balance = 0; 2459 1.1 christos 2460 1.1 christos dhcp_failover_pool_dobalance(state, NULL); 2461 1.1 christos } 2462 1.1 christos 2463 1.1 christos /* 2464 1.1 christos * Balance operation entry from timer event. Once per timer interval is 2465 1.1 christos * the only time we want to emit POOLREQs (asserting an interrupt in our 2466 1.1 christos * peer). 2467 1.1 christos */ 2468 1.1 christos void 2469 1.1 christos dhcp_failover_pool_rebalance(void *failover_state) 2470 1.1 christos { 2471 1.1 christos dhcp_failover_state_t *state; 2472 1.1 christos isc_boolean_t sendreq = ISC_FALSE; 2473 1.1 christos 2474 1.1 christos state = (dhcp_failover_state_t *)failover_state; 2475 1.1 christos 2476 1.1 christos /* Clear scheduled event indicator. */ 2477 1.1 christos state->sched_balance = 0; 2478 1.1 christos 2479 1.1 christos if (dhcp_failover_pool_dobalance(state, &sendreq)) 2480 1.1 christos dhcp_failover_send_updates(state); 2481 1.1 christos 2482 1.1 christos if (sendreq) 2483 1.1 christos dhcp_failover_send_poolreq(state); 2484 1.1 christos } 2485 1.1 christos 2486 1.1 christos /* 2487 1.1 christos * Balance operation entry from POOLREQ protocol message. Do not permit a 2488 1.1 christos * POOLREQ to send back a POOLREQ. Ping pong. 2489 1.1 christos */ 2490 1.1 christos static void 2491 1.1 christos dhcp_failover_pool_reqbalance(dhcp_failover_state_t *state) 2492 1.1 christos { 2493 1.1 christos int queued; 2494 1.1 christos 2495 1.1 christos /* Cancel pending event. */ 2496 1.1 christos cancel_timeout(dhcp_failover_pool_rebalance, state); 2497 1.1 christos state->sched_balance = 0; 2498 1.1 christos 2499 1.1 christos queued = dhcp_failover_pool_dobalance(state, NULL); 2500 1.1 christos 2501 1.1 christos dhcp_failover_send_poolresp(state, queued); 2502 1.1 christos 2503 1.1 christos if (queued) 2504 1.1 christos dhcp_failover_send_updates(state); 2505 1.1 christos else 2506 1.1 christos log_info("peer %s: Got POOLREQ, answering negatively! " 2507 1.1 christos "Peer may be out of leases or database inconsistent.", 2508 1.1 christos state->name); 2509 1.1 christos } 2510 1.1 christos 2511 1.1 christos /* 2512 1.1 christos * Do the meat of the work common to all forms of pool rebalance. If the 2513 1.1 christos * caller deems it appropriate to transmit POOLREQ messages, it can use the 2514 1.1 christos * sendreq pointer to pass in the address of a FALSE value which this function 2515 1.1 christos * will conditionally turn TRUE if a POOLREQ is determined to be necessary. 2516 1.1 christos * A NULL value may be passed, in which case no action is taken. 2517 1.1 christos */ 2518 1.1 christos static int 2519 1.1 christos dhcp_failover_pool_dobalance(dhcp_failover_state_t *state, 2520 1.1 christos isc_boolean_t *sendreq) 2521 1.1 christos { 2522 1.1 christos int lts, total, thresh, hold, panic, pass; 2523 1.1 christos int leases_queued = 0; 2524 1.1 christos struct lease *lp = NULL; 2525 1.1 christos struct lease *next = NULL; 2526 1.1 christos struct lease *ltemp = NULL; 2527 1.1 christos struct shared_network *s; 2528 1.1 christos struct pool *p; 2529 1.1 christos binding_state_t peer_lease_state; 2530 1.1 christos /* binding_state_t my_lease_state; */ 2531 1.1 christos /* XXX Why is this my_lease_state never used? */ 2532 1.1 christos LEASE_STRUCT_PTR lq; 2533 1.1 christos int (*log_func)(const char *, ...); 2534 1.1 christos const char *result, *reqlog; 2535 1.1 christos 2536 1.1 christos if (state -> me.state != normal) 2537 1.1 christos return 0; 2538 1.1 christos 2539 1.1 christos state->last_balance = cur_time; 2540 1.1 christos 2541 1.1 christos for (s = shared_networks ; s ; s = s->next) { 2542 1.1 christos for (p = s->pools ; p ; p = p->next) { 2543 1.1 christos if (p->failover_peer != state) 2544 1.1 christos continue; 2545 1.1 christos 2546 1.1 christos /* Right now we're giving the peer half of the free leases. 2547 1.1 christos If we have more leases than the peer (i.e., more than 2548 1.1 christos half), then the number of leases we have, less the number 2549 1.1 christos of leases the peer has, will be how many more leases we 2550 1.1 christos have than the peer has. So if we send half that number 2551 1.1 christos to the peer, we should be even. */ 2552 1.1 christos if (p->failover_peer->i_am == primary) { 2553 1.1 christos lts = (p->free_leases - p->backup_leases) / 2; 2554 1.1 christos peer_lease_state = FTS_BACKUP; 2555 1.1 christos /* my_lease_state = FTS_FREE; */ 2556 1.1 christos lq = &p->free; 2557 1.1 christos } else { 2558 1.1 christos lts = (p->backup_leases - p->free_leases) / 2; 2559 1.1 christos peer_lease_state = FTS_FREE; 2560 1.1 christos /* my_lease_state = FTS_BACKUP; */ 2561 1.1 christos lq = &p->backup; 2562 1.1 christos } 2563 1.1 christos 2564 1.1 christos total = p->backup_leases + p->free_leases; 2565 1.1 christos 2566 1.1 christos thresh = ((total * state->max_lease_misbalance) + 50) / 100; 2567 1.1 christos hold = ((total * state->max_lease_ownership) + 50) / 100; 2568 1.1 christos 2569 1.1 christos /* 2570 1.1 christos * If we need leases (so lts is negative) more than negative 2571 1.1 christos * double the thresh%, panic and send poolreq to hopefully wake 2572 1.1 christos * up the peer (but more likely the db is inconsistent). But, 2573 1.1 christos * if this comes out zero, switch to -1 so that the POOLREQ is 2574 1.1 christos * sent on lts == -2 rather than right away at -1. 2575 1.1 christos * 2576 1.1 christos * Note that we do not subtract -1 from panic all the time 2577 1.1 christos * because thresh% and hold% may come out to the same number, 2578 1.1 christos * and that is correct operation...where thresh% and hold% are 2579 1.1 christos * both -1, we want to send poolreq when lts reaches -3. So, 2580 1.1 christos * "-3 < -2", lts < panic. 2581 1.1 christos */ 2582 1.1 christos panic = thresh * -2; 2583 1.1 christos 2584 1.1 christos if (panic == 0) 2585 1.1 christos panic = -1; 2586 1.1 christos 2587 1.1 christos if ((sendreq != NULL) && (lts < panic)) { 2588 1.1 christos reqlog = " (requesting peer rebalance!)"; 2589 1.1 christos *sendreq = ISC_TRUE; 2590 1.1 christos } else 2591 1.1 christos reqlog = ""; 2592 1.1 christos 2593 1.1 christos log_info("balancing pool %lx %s total %d free %d " 2594 1.1 christos "backup %d lts %d max-own (+/-)%d%s", 2595 1.1 christos (unsigned long)p, 2596 1.1 christos (p->shared_network ? 2597 1.1 christos p->shared_network->name : ""), p->lease_count, 2598 1.1 christos p->free_leases, p->backup_leases, lts, hold, 2599 1.1 christos reqlog); 2600 1.1 christos 2601 1.1 christos /* In the first pass, try to allocate leases to the 2602 1.1 christos * peer which it would normally be responsible for (if 2603 1.1 christos * the lease has a hardware address or client-identifier, 2604 1.1 christos * and the load-balance-algorithm chooses the peer to 2605 1.1 christos * answer that address), up to a hold% excess in the peer's 2606 1.1 christos * favor. In the second pass, just send the oldest (first 2607 1.1 christos * on the list) leases up to a hold% excess in our favor. 2608 1.1 christos * 2609 1.1 christos * This could make for additional pool rebalance 2610 1.1 christos * events, but preserving MAC possession should be 2611 1.1 christos * worth it. 2612 1.1 christos */ 2613 1.1 christos pass = 0; 2614 1.1 christos lease_reference(&lp, LEASE_GET_FIRSTP(lq), MDL); 2615 1.1 christos 2616 1.1 christos while (lp) { 2617 1.1 christos if (next) 2618 1.1 christos lease_dereference(&next, MDL); 2619 1.1 christos ltemp = LEASE_GET_NEXTP(lq, lp); 2620 1.1 christos if (ltemp != NULL) 2621 1.1 christos lease_reference(&next, ltemp, MDL); 2622 1.1 christos 2623 1.1 christos /* 2624 1.1 christos * Stop if the pool is 'balanced enough.' 2625 1.1 christos * 2626 1.1 christos * The pool is balanced enough if: 2627 1.1 christos * 2628 1.1 christos * 1) We're on the first run through and the peer has 2629 1.1 christos * its fair share of leases already (lts reaches 2630 1.1 christos * -hold). 2631 1.1 christos * 2) We're on the second run through, we are shifting 2632 1.1 christos * never-used leases, and there is a perfectly even 2633 1.1 christos * balance (lts reaches zero). 2634 1.1 christos * 3) Second run through, we are shifting previously 2635 1.1 christos * used leases, and the local system has its fair 2636 1.1 christos * share but no more (lts reaches hold). 2637 1.1 christos * 2638 1.1 christos * Note that this is implemented below in 3,2,1 order. 2639 1.1 christos */ 2640 1.1 christos if (pass) { 2641 1.1 christos if (lp->ends) { 2642 1.1 christos if (lts <= hold) 2643 1.1 christos break; 2644 1.1 christos } else { 2645 1.1 christos if (lts <= 0) 2646 1.1 christos break; 2647 1.1 christos } 2648 1.1 christos } else if (lts <= -hold) 2649 1.1 christos break; 2650 1.1 christos 2651 1.1 christos if (pass || peer_wants_lease(lp)) { 2652 1.1 christos --lts; 2653 1.1 christos ++leases_queued; 2654 1.1 christos lp->next_binding_state = peer_lease_state; 2655 1.1 christos lp->tstp = cur_time; 2656 1.1 christos lp->starts = cur_time; 2657 1.1 christos 2658 1.1 christos scrub_lease(lp, MDL); 2659 1.1 christos if (!supersede_lease(lp, NULL, 0, 1, 0, 0) || 2660 1.1 christos !write_lease(lp)) 2661 1.1 christos log_error("can't commit lease %s on " 2662 1.1 christos "giveaway", piaddr(lp->ip_addr)); 2663 1.1 christos } 2664 1.1 christos 2665 1.1 christos lease_dereference(&lp, MDL); 2666 1.1 christos if (next) 2667 1.1 christos lease_reference(&lp, next, MDL); 2668 1.1 christos else if (!pass) { 2669 1.1 christos pass = 1; 2670 1.1 christos lease_reference(&lp, LEASE_GET_FIRSTP(lq), MDL); 2671 1.1 christos } 2672 1.1 christos } 2673 1.1 christos 2674 1.1 christos if (next) 2675 1.1 christos lease_dereference(&next, MDL); 2676 1.1 christos if (lp) 2677 1.1 christos lease_dereference(&lp, MDL); 2678 1.1 christos 2679 1.1 christos if (lts > thresh) { 2680 1.1 christos result = "IMBALANCED"; 2681 1.1 christos log_func = log_error; 2682 1.1 christos } else { 2683 1.1 christos result = "balanced"; 2684 1.1 christos log_func = log_info; 2685 1.1 christos } 2686 1.1 christos 2687 1.1 christos log_func("%s pool %lx %s total %d free %d backup %d " 2688 1.1 christos "lts %d max-misbal %d", result, (unsigned long)p, 2689 1.1 christos (p->shared_network ? 2690 1.1 christos p->shared_network->name : ""), p->lease_count, 2691 1.1 christos p->free_leases, p->backup_leases, lts, thresh); 2692 1.1 christos 2693 1.1 christos /* Recalculate next rebalance event timer. */ 2694 1.1 christos dhcp_failover_pool_check(p); 2695 1.1 christos } 2696 1.1 christos } 2697 1.1 christos 2698 1.1 christos if (leases_queued) 2699 1.1 christos commit_leases(); 2700 1.1 christos 2701 1.1 christos return leases_queued; 2702 1.1 christos } 2703 1.1 christos 2704 1.1 christos /* dhcp_failover_pool_check: Called whenever FREE or BACKUP leases change 2705 1.1 christos * states, on both servers. Check the scheduled time to rebalance the pool 2706 1.1 christos * and lower it if applicable. 2707 1.1 christos */ 2708 1.1 christos void 2709 1.1 christos dhcp_failover_pool_check(struct pool *pool) 2710 1.1 christos { 2711 1.1 christos dhcp_failover_state_t *peer; 2712 1.1 christos TIME est1, est2; 2713 1.1 christos struct timeval tv; 2714 1.1 christos struct lease *ltemp; 2715 1.1 christos 2716 1.1 christos peer = pool->failover_peer; 2717 1.1 christos 2718 1.1 christos if(!peer || peer->me.state != normal) 2719 1.1 christos return; 2720 1.1 christos 2721 1.1 christos /* Estimate the time left until lease exhaustion. 2722 1.1 christos * The first lease on the backup or free lists is also the oldest 2723 1.1 christos * lease. It is reasonable to guess that it will take at least 2724 1.1 christos * as much time for a pool to run out of leases, as the present 2725 1.1 christos * age of the oldest lease (seconds since it expired). 2726 1.1 christos * 2727 1.1 christos * Note that this isn't so sane of an assumption if the oldest 2728 1.1 christos * lease is a virgin (ends = 0), we wind up sending this against 2729 1.1 christos * the max_balance bounds check. 2730 1.1 christos */ 2731 1.1 christos ltemp = LEASE_GET_FIRST(pool->free); 2732 1.1 christos if(ltemp && ltemp->ends < cur_time) 2733 1.1 christos est1 = cur_time - ltemp->ends; 2734 1.1 christos else 2735 1.1 christos est1 = 0; 2736 1.1 christos 2737 1.1 christos ltemp = LEASE_GET_FIRST(pool->backup); 2738 1.1 christos if(ltemp && ltemp->ends < cur_time) 2739 1.1 christos est2 = cur_time - ltemp->ends; 2740 1.1 christos else 2741 1.1 christos est2 = 0; 2742 1.1 christos 2743 1.1 christos /* We don't want to schedule rebalance for when we think we'll run 2744 1.1 christos * out of leases, we want to schedule the rebalance for when we think 2745 1.1 christos * the disparity will be 'large enough' to warrant action. 2746 1.1 christos */ 2747 1.1 christos est1 = ((est1 * peer->max_lease_misbalance) + 50) / 100; 2748 1.1 christos est2 = ((est2 * peer->max_lease_misbalance) + 50) / 100; 2749 1.1 christos 2750 1.1 christos /* Guess when the local system will begin issuing POOLREQ panic 2751 1.1 christos * attacks because "max_lease_misbalance*2" has been exceeded. 2752 1.1 christos */ 2753 1.1 christos if(peer->i_am == primary) 2754 1.1 christos est1 *= 2; 2755 1.1 christos else 2756 1.1 christos est2 *= 2; 2757 1.1 christos 2758 1.1 christos /* Select the smallest time. */ 2759 1.1 christos if(est1 > est2) 2760 1.1 christos est1 = est2; 2761 1.1 christos 2762 1.1 christos /* Bounded by the maximum configured value. */ 2763 1.1 christos if(est1 > peer->max_balance) 2764 1.1 christos est1 = peer->max_balance; 2765 1.1 christos 2766 1.1 christos /* Project this time into the future. */ 2767 1.1 christos est1 += cur_time; 2768 1.1 christos 2769 1.1 christos /* Do not move the time down under the minimum. */ 2770 1.1 christos est2 = peer->last_balance + peer->min_balance; 2771 1.1 christos if(peer->last_balance && (est1 < est2)) 2772 1.1 christos est1 = est2; 2773 1.1 christos 2774 1.1 christos /* Introduce a random delay. */ 2775 1.1 christos est1 += random() % 5; 2776 1.1 christos 2777 1.1 christos /* Do not move the time forward, or reset to the same time. */ 2778 1.1 christos if(peer->sched_balance) { 2779 1.1 christos if (est1 >= peer->sched_balance) 2780 1.1 christos return; 2781 1.1 christos 2782 1.1 christos /* We are about to schedule the time down, cancel the 2783 1.1 christos * current timeout. 2784 1.1 christos */ 2785 1.1 christos cancel_timeout(dhcp_failover_pool_rebalance, peer); 2786 1.1 christos } 2787 1.1 christos 2788 1.1 christos /* The time is different, and lower, use it. */ 2789 1.1 christos peer->sched_balance = est1; 2790 1.1 christos 2791 1.1 christos #if defined(DEBUG_FAILOVER_TIMING) 2792 1.1 christos log_info("add_timeout +%d dhcp_failover_pool_rebalance", 2793 1.1 christos (int)(est1 - cur_time)); 2794 1.1 christos #endif 2795 1.1 christos tv.tv_sec = est1; 2796 1.1 christos tv.tv_usec = 0; 2797 1.1 christos add_timeout(&tv, dhcp_failover_pool_rebalance, peer, 2798 1.1 christos (tvref_t)dhcp_failover_state_reference, 2799 1.1 christos (tvunref_t)dhcp_failover_state_dereference); 2800 1.1 christos } 2801 1.1 christos 2802 1.1 christos int dhcp_failover_state_pool_check (dhcp_failover_state_t *state) 2803 1.1 christos { 2804 1.1 christos struct shared_network *s; 2805 1.1 christos struct pool *p; 2806 1.1 christos 2807 1.1 christos for (s = shared_networks; s; s = s -> next) { 2808 1.1 christos for (p = s -> pools; p; p = p -> next) { 2809 1.1 christos if (p -> failover_peer != state) 2810 1.1 christos continue; 2811 1.1 christos dhcp_failover_pool_check (p); 2812 1.1 christos } 2813 1.1 christos } 2814 1.1 christos return 0; 2815 1.1 christos } 2816 1.1 christos 2817 1.1 christos isc_result_t dhcp_failover_send_updates (dhcp_failover_state_t *state) 2818 1.1 christos { 2819 1.1 christos struct lease *lp = (struct lease *)0; 2820 1.1 christos isc_result_t status; 2821 1.1 christos 2822 1.1 christos /* Can't update peer if we're not talking to it! */ 2823 1.1 christos if (!state -> link_to_peer) 2824 1.1 christos return ISC_R_SUCCESS; 2825 1.1 christos 2826 1.1 christos /* If there are acks pending, transmit them prior to potentially 2827 1.1 christos * sending new updates for the same lease. 2828 1.1 christos */ 2829 1.1 christos if (state->toack_queue_head != NULL) 2830 1.1 christos dhcp_failover_send_acks(state); 2831 1.1 christos 2832 1.1 christos while ((state -> partner.max_flying_updates > 2833 1.1 christos state -> cur_unacked_updates) && state -> update_queue_head) { 2834 1.1 christos /* Grab the head of the update queue. */ 2835 1.1 christos lease_reference (&lp, state -> update_queue_head, MDL); 2836 1.1 christos 2837 1.1 christos /* Send the update to the peer. */ 2838 1.1 christos status = dhcp_failover_send_bind_update (state, lp); 2839 1.1 christos if (status != ISC_R_SUCCESS) { 2840 1.1 christos lease_dereference (&lp, MDL); 2841 1.1 christos return status; 2842 1.1 christos } 2843 1.1 christos lp -> flags &= ~ON_UPDATE_QUEUE; 2844 1.1 christos 2845 1.1 christos /* Take it off the head of the update queue and put the next 2846 1.1 christos item in the update queue at the head. */ 2847 1.1 christos lease_dereference (&state -> update_queue_head, MDL); 2848 1.1 christos if (lp -> next_pending) { 2849 1.1 christos lease_reference (&state -> update_queue_head, 2850 1.1 christos lp -> next_pending, MDL); 2851 1.1 christos lease_dereference (&lp -> next_pending, MDL); 2852 1.1 christos } else { 2853 1.1 christos lease_dereference (&state -> update_queue_tail, MDL); 2854 1.1 christos } 2855 1.1 christos 2856 1.1 christos if (state -> ack_queue_head) { 2857 1.1 christos lease_reference 2858 1.1 christos (&state -> ack_queue_tail -> next_pending, 2859 1.1 christos lp, MDL); 2860 1.1 christos lease_dereference (&state -> ack_queue_tail, MDL); 2861 1.1 christos } else { 2862 1.1 christos lease_reference (&state -> ack_queue_head, lp, MDL); 2863 1.1 christos } 2864 1.1 christos #if defined (POINTER_DEBUG) 2865 1.1 christos if (lp -> next_pending) { 2866 1.1 christos log_error ("ack_queue_tail: lp -> next_pending"); 2867 1.1 christos abort (); 2868 1.1 christos } 2869 1.1 christos #endif 2870 1.1 christos lease_reference (&state -> ack_queue_tail, lp, MDL); 2871 1.1 christos lp -> flags |= ON_ACK_QUEUE; 2872 1.1 christos lease_dereference (&lp, MDL); 2873 1.1 christos 2874 1.1 christos /* Count the object as an unacked update. */ 2875 1.1 christos state -> cur_unacked_updates++; 2876 1.1 christos } 2877 1.1 christos return ISC_R_SUCCESS; 2878 1.1 christos } 2879 1.1 christos 2880 1.1 christos /* Queue an update for a lease. Always returns 1 at this point - it's 2881 1.1 christos not an error for this to be called on a lease for which there's no 2882 1.1 christos failover peer. */ 2883 1.1 christos 2884 1.1 christos int dhcp_failover_queue_update (struct lease *lease, int immediate) 2885 1.1 christos { 2886 1.1 christos dhcp_failover_state_t *state; 2887 1.1 christos 2888 1.1 christos if (!lease -> pool || 2889 1.1 christos !lease -> pool -> failover_peer) 2890 1.1 christos return 1; 2891 1.1 christos 2892 1.1 christos /* If it's already on the update queue, leave it there. */ 2893 1.1 christos if (lease -> flags & ON_UPDATE_QUEUE) 2894 1.1 christos return 1; 2895 1.1 christos 2896 1.1 christos /* Get the failover state structure for this lease. */ 2897 1.1 christos state = lease -> pool -> failover_peer; 2898 1.1 christos 2899 1.1 christos /* If it's on the ack queue, take it off. */ 2900 1.1 christos if (lease -> flags & ON_ACK_QUEUE) 2901 1.1 christos dhcp_failover_ack_queue_remove (state, lease); 2902 1.1 christos 2903 1.1 christos if (state -> update_queue_head) { 2904 1.1 christos lease_reference (&state -> update_queue_tail -> next_pending, 2905 1.1 christos lease, MDL); 2906 1.1 christos lease_dereference (&state -> update_queue_tail, MDL); 2907 1.1 christos } else { 2908 1.1 christos lease_reference (&state -> update_queue_head, lease, MDL); 2909 1.1 christos } 2910 1.1 christos #if defined (POINTER_DEBUG) 2911 1.1 christos if (lease -> next_pending) { 2912 1.1 christos log_error ("next pending on update queue lease."); 2913 1.1 christos #if defined (DEBUG_RC_HISTORY) 2914 1.1 christos dump_rc_history (lease); 2915 1.1 christos #endif 2916 1.1 christos abort (); 2917 1.1 christos } 2918 1.1 christos #endif 2919 1.1 christos lease_reference (&state -> update_queue_tail, lease, MDL); 2920 1.1 christos lease -> flags |= ON_UPDATE_QUEUE; 2921 1.1 christos if (immediate) 2922 1.1 christos dhcp_failover_send_updates (state); 2923 1.1 christos return 1; 2924 1.1 christos } 2925 1.1 christos 2926 1.1 christos int dhcp_failover_send_acks (dhcp_failover_state_t *state) 2927 1.1 christos { 2928 1.1 christos failover_message_t *msg = (failover_message_t *)0; 2929 1.1 christos 2930 1.1 christos /* Must commit all leases prior to acking them. */ 2931 1.1 christos if (!commit_leases ()) 2932 1.1 christos return 0; 2933 1.1 christos 2934 1.1 christos while (state -> toack_queue_head) { 2935 1.1 christos failover_message_reference 2936 1.1 christos (&msg, state -> toack_queue_head, MDL); 2937 1.1 christos failover_message_dereference 2938 1.1 christos (&state -> toack_queue_head, MDL); 2939 1.1 christos if (msg -> next) { 2940 1.1 christos failover_message_reference 2941 1.1 christos (&state -> toack_queue_head, msg -> next, MDL); 2942 1.1 christos } 2943 1.1 christos 2944 1.1 christos dhcp_failover_send_bind_ack (state, msg, 0, (const char *)0); 2945 1.1 christos 2946 1.1 christos failover_message_dereference (&msg, MDL); 2947 1.1 christos } 2948 1.1 christos 2949 1.1 christos if (state -> toack_queue_tail) 2950 1.1 christos failover_message_dereference (&state -> toack_queue_tail, MDL); 2951 1.1 christos state -> pending_acks = 0; 2952 1.1 christos 2953 1.1 christos return 1; 2954 1.1 christos } 2955 1.1 christos 2956 1.1 christos void dhcp_failover_toack_queue_timeout (void *vs) 2957 1.1 christos { 2958 1.1 christos dhcp_failover_state_t *state = vs; 2959 1.1 christos 2960 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 2961 1.1 christos log_info ("dhcp_failover_toack_queue_timeout"); 2962 1.1 christos #endif 2963 1.1 christos 2964 1.1 christos dhcp_failover_send_acks (state); 2965 1.1 christos } 2966 1.1 christos 2967 1.1 christos /* Queue an ack for a message. There is currently no way to queue a 2968 1.1 christos negative ack -- these need to be sent directly. */ 2969 1.1 christos 2970 1.1 christos int dhcp_failover_queue_ack (dhcp_failover_state_t *state, 2971 1.1 christos failover_message_t *msg) 2972 1.1 christos { 2973 1.1 christos struct timeval tv; 2974 1.1 christos 2975 1.1 christos if (state -> toack_queue_head) { 2976 1.1 christos failover_message_reference 2977 1.1 christos (&state -> toack_queue_tail -> next, msg, MDL); 2978 1.1 christos failover_message_dereference (&state -> toack_queue_tail, MDL); 2979 1.1 christos } else { 2980 1.1 christos failover_message_reference (&state -> toack_queue_head, 2981 1.1 christos msg, MDL); 2982 1.1 christos } 2983 1.1 christos failover_message_reference (&state -> toack_queue_tail, msg, MDL); 2984 1.1 christos 2985 1.1 christos state -> pending_acks++; 2986 1.1 christos 2987 1.1 christos /* Flush the toack queue whenever we exceed half the number of 2988 1.1 christos allowed unacked updates. */ 2989 1.1 christos if (state -> pending_acks >= state -> partner.max_flying_updates / 2) { 2990 1.1 christos dhcp_failover_send_acks (state); 2991 1.1 christos } 2992 1.1 christos 2993 1.1 christos /* Schedule a timeout to flush the ack queue. */ 2994 1.1 christos if (state -> pending_acks > 0) { 2995 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 2996 1.1 christos log_info ("add_timeout +2 %s", 2997 1.1 christos "dhcp_failover_toack_queue_timeout"); 2998 1.1 christos #endif 2999 1.1 christos tv . tv_sec = cur_time + 2; 3000 1.1 christos tv . tv_usec = 0; 3001 1.1 christos add_timeout (&tv, 3002 1.1 christos dhcp_failover_toack_queue_timeout, state, 3003 1.1 christos (tvref_t)dhcp_failover_state_reference, 3004 1.1 christos (tvunref_t)dhcp_failover_state_dereference); 3005 1.1 christos } 3006 1.1 christos 3007 1.1 christos return 1; 3008 1.1 christos } 3009 1.1 christos 3010 1.1 christos void dhcp_failover_ack_queue_remove (dhcp_failover_state_t *state, 3011 1.1 christos struct lease *lease) 3012 1.1 christos { 3013 1.1 christos struct lease *lp; 3014 1.1 christos 3015 1.1 christos if (!(lease -> flags & ON_ACK_QUEUE)) 3016 1.1 christos return; 3017 1.1 christos 3018 1.1 christos if (state -> ack_queue_head == lease) { 3019 1.1 christos lease_dereference (&state -> ack_queue_head, MDL); 3020 1.1 christos if (lease -> next_pending) { 3021 1.1 christos lease_reference (&state -> ack_queue_head, 3022 1.1 christos lease -> next_pending, MDL); 3023 1.1 christos lease_dereference (&lease -> next_pending, MDL); 3024 1.1 christos } else { 3025 1.1 christos lease_dereference (&state -> ack_queue_tail, MDL); 3026 1.1 christos } 3027 1.1 christos } else { 3028 1.1 christos for (lp = state -> ack_queue_head; 3029 1.1 christos lp && lp -> next_pending != lease; 3030 1.1 christos lp = lp -> next_pending) 3031 1.1 christos ; 3032 1.1 christos 3033 1.1 christos if (!lp) 3034 1.1 christos return; 3035 1.1 christos 3036 1.1 christos lease_dereference (&lp -> next_pending, MDL); 3037 1.1 christos if (lease -> next_pending) { 3038 1.1 christos lease_reference (&lp -> next_pending, 3039 1.1 christos lease -> next_pending, MDL); 3040 1.1 christos lease_dereference (&lease -> next_pending, MDL); 3041 1.1 christos } else { 3042 1.1 christos lease_dereference (&state -> ack_queue_tail, MDL); 3043 1.1 christos if (lp -> next_pending) { 3044 1.1 christos log_error ("state -> ack_queue_tail"); 3045 1.1 christos abort (); 3046 1.1 christos } 3047 1.1 christos lease_reference (&state -> ack_queue_tail, lp, MDL); 3048 1.1 christos } 3049 1.1 christos } 3050 1.1 christos 3051 1.1 christos lease -> flags &= ~ON_ACK_QUEUE; 3052 1.1 christos /* Multiple acks on one XID is an error and may cause badness. */ 3053 1.1 christos lease->last_xid = 0; 3054 1.1 christos /* XXX: this violates draft-failover. We can't send another 3055 1.1 christos * update just because we forgot about an old one that hasn't 3056 1.1 christos * been acked yet. 3057 1.1 christos */ 3058 1.1 christos state -> cur_unacked_updates--; 3059 1.1 christos 3060 1.1 christos /* 3061 1.1 christos * When updating leases as a result of an ack, we defer the commit 3062 1.1 christos * for performance reasons. When there are no more acks pending, 3063 1.1 christos * do a commit. 3064 1.1 christos */ 3065 1.1 christos if (state -> cur_unacked_updates == 0) { 3066 1.1 christos commit_leases(); 3067 1.1 christos } 3068 1.1 christos } 3069 1.1 christos 3070 1.1 christos isc_result_t dhcp_failover_state_set_value (omapi_object_t *h, 3071 1.1 christos omapi_object_t *id, 3072 1.1 christos omapi_data_string_t *name, 3073 1.1 christos omapi_typed_data_t *value) 3074 1.1 christos { 3075 1.1 christos isc_result_t status; 3076 1.1 christos 3077 1.1 christos if (h -> type != dhcp_type_failover_state) 3078 1.1 christos return DHCP_R_INVALIDARG; 3079 1.1 christos 3080 1.1 christos /* This list of successful returns is completely wrong, but the 3081 1.1 christos fastest way to make dhcpctl do something vaguely sane when 3082 1.1 christos you try to change the local state. */ 3083 1.1 christos 3084 1.1 christos if (!omapi_ds_strcmp (name, "name")) { 3085 1.1 christos return ISC_R_SUCCESS; 3086 1.1 christos } else if (!omapi_ds_strcmp (name, "partner-address")) { 3087 1.1 christos return ISC_R_SUCCESS; 3088 1.1 christos } else if (!omapi_ds_strcmp (name, "local-address")) { 3089 1.1 christos return ISC_R_SUCCESS; 3090 1.1 christos } else if (!omapi_ds_strcmp (name, "partner-port")) { 3091 1.1 christos return ISC_R_SUCCESS; 3092 1.1 christos } else if (!omapi_ds_strcmp (name, "local-port")) { 3093 1.1 christos return ISC_R_SUCCESS; 3094 1.1 christos } else if (!omapi_ds_strcmp (name, "max-outstanding-updates")) { 3095 1.1 christos return ISC_R_SUCCESS; 3096 1.1 christos } else if (!omapi_ds_strcmp (name, "mclt")) { 3097 1.1 christos return ISC_R_SUCCESS; 3098 1.1 christos } else if (!omapi_ds_strcmp (name, "load-balance-max-secs")) { 3099 1.1 christos return ISC_R_SUCCESS; 3100 1.1 christos } else if (!omapi_ds_strcmp (name, "load-balance-hba")) { 3101 1.1 christos return ISC_R_SUCCESS; 3102 1.1 christos } else if (!omapi_ds_strcmp (name, "partner-state")) { 3103 1.1 christos return ISC_R_SUCCESS; 3104 1.1 christos } else if (!omapi_ds_strcmp (name, "local-state")) { 3105 1.1 christos unsigned long l; 3106 1.1 christos status = omapi_get_int_value (&l, value); 3107 1.1 christos if (status != ISC_R_SUCCESS) 3108 1.1 christos return status; 3109 1.1 christos return dhcp_failover_set_state ((dhcp_failover_state_t *)h, l); 3110 1.1 christos } else if (!omapi_ds_strcmp (name, "partner-stos")) { 3111 1.1 christos return ISC_R_SUCCESS; 3112 1.1 christos } else if (!omapi_ds_strcmp (name, "local-stos")) { 3113 1.1 christos return ISC_R_SUCCESS; 3114 1.1 christos } else if (!omapi_ds_strcmp (name, "hierarchy")) { 3115 1.1 christos return ISC_R_SUCCESS; 3116 1.1 christos } else if (!omapi_ds_strcmp (name, "last-packet-sent")) { 3117 1.1 christos return ISC_R_SUCCESS; 3118 1.1 christos } else if (!omapi_ds_strcmp (name, "last-timestamp-received")) { 3119 1.1 christos return ISC_R_SUCCESS; 3120 1.1 christos } else if (!omapi_ds_strcmp (name, "skew")) { 3121 1.1 christos return ISC_R_SUCCESS; 3122 1.1 christos } else if (!omapi_ds_strcmp (name, "max-response-delay")) { 3123 1.1 christos return ISC_R_SUCCESS; 3124 1.1 christos } else if (!omapi_ds_strcmp (name, "cur-unacked-updates")) { 3125 1.1 christos return ISC_R_SUCCESS; 3126 1.1 christos } 3127 1.1 christos 3128 1.1 christos if (h -> inner && h -> inner -> type -> set_value) 3129 1.1 christos return (*(h -> inner -> type -> set_value)) 3130 1.1 christos (h -> inner, id, name, value); 3131 1.1 christos return ISC_R_NOTFOUND; 3132 1.1 christos } 3133 1.1 christos 3134 1.1 christos void dhcp_failover_keepalive (void *vs) 3135 1.1 christos { 3136 1.1 christos } 3137 1.1 christos 3138 1.1 christos void dhcp_failover_reconnect (void *vs) 3139 1.1 christos { 3140 1.1 christos dhcp_failover_state_t *state = vs; 3141 1.1 christos isc_result_t status; 3142 1.1 christos struct timeval tv; 3143 1.1 christos 3144 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 3145 1.1 christos log_info ("dhcp_failover_reconnect"); 3146 1.1 christos #endif 3147 1.1 christos /* If we already connected the other way, let the connection 3148 1.1 christos recovery code initiate any retry that may be required. */ 3149 1.1 christos if (state -> link_to_peer) 3150 1.1 christos return; 3151 1.1 christos 3152 1.1 christos status = dhcp_failover_link_initiate ((omapi_object_t *)state); 3153 1.1 christos if (status != ISC_R_SUCCESS && status != DHCP_R_INCOMPLETE) { 3154 1.1 christos log_info ("failover peer %s: %s", state -> name, 3155 1.1 christos isc_result_totext (status)); 3156 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 3157 1.1 christos log_info("add_timeout +90 dhcp_failover_reconnect"); 3158 1.1 christos #endif 3159 1.1 christos tv . tv_sec = cur_time + 90; 3160 1.1 christos tv . tv_usec = 0; 3161 1.1 christos add_timeout(&tv, dhcp_failover_reconnect, state, 3162 1.1 christos (tvref_t)dhcp_failover_state_reference, 3163 1.1 christos (tvunref_t)dhcp_failover_state_dereference); 3164 1.1 christos } 3165 1.1 christos } 3166 1.1 christos 3167 1.1 christos void dhcp_failover_startup_timeout (void *vs) 3168 1.1 christos { 3169 1.1 christos dhcp_failover_state_t *state = vs; 3170 1.1 christos 3171 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 3172 1.1 christos log_info ("dhcp_failover_startup_timeout"); 3173 1.1 christos #endif 3174 1.1 christos 3175 1.1 christos dhcp_failover_state_transition (state, "disconnect"); 3176 1.1 christos } 3177 1.1 christos 3178 1.1 christos void dhcp_failover_link_startup_timeout (void *vl) 3179 1.1 christos { 3180 1.1 christos dhcp_failover_link_t *link = vl; 3181 1.1 christos omapi_object_t *p; 3182 1.1 christos 3183 1.1 christos for (p = (omapi_object_t *)link; p -> inner; p = p -> inner) 3184 1.1 christos ; 3185 1.1 christos for (; p; p = p -> outer) 3186 1.1 christos if (p -> type == omapi_type_connection) 3187 1.1 christos break; 3188 1.1 christos if (p) { 3189 1.1 christos log_info ("failover: link startup timeout"); 3190 1.1 christos omapi_disconnect (p, 1); 3191 1.1 christos } 3192 1.1 christos } 3193 1.1 christos 3194 1.1 christos void dhcp_failover_listener_restart (void *vs) 3195 1.1 christos { 3196 1.1 christos dhcp_failover_state_t *state = vs; 3197 1.1 christos isc_result_t status; 3198 1.1 christos struct timeval tv; 3199 1.1 christos 3200 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 3201 1.1 christos log_info ("dhcp_failover_listener_restart"); 3202 1.1 christos #endif 3203 1.1 christos 3204 1.1 christos status = dhcp_failover_listen ((omapi_object_t *)state); 3205 1.1 christos if (status != ISC_R_SUCCESS) { 3206 1.1 christos log_info ("failover peer %s: %s", state -> name, 3207 1.1 christos isc_result_totext (status)); 3208 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 3209 1.1 christos log_info ("add_timeout +90 %s", 3210 1.1 christos "dhcp_failover_listener_restart"); 3211 1.1 christos #endif 3212 1.1 christos tv . tv_sec = cur_time + 90; 3213 1.1 christos tv . tv_usec = 0; 3214 1.1 christos add_timeout (&tv, 3215 1.1 christos dhcp_failover_listener_restart, state, 3216 1.1 christos (tvref_t)dhcp_failover_state_reference, 3217 1.1 christos (tvunref_t)dhcp_failover_state_dereference); 3218 1.1 christos } 3219 1.1 christos } 3220 1.1 christos 3221 1.1 christos void 3222 1.1 christos dhcp_failover_auto_partner_down(void *vs) 3223 1.1 christos { 3224 1.1 christos dhcp_failover_state_t *state = vs; 3225 1.1 christos 3226 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 3227 1.1 christos log_info("dhcp_failover_auto_partner_down"); 3228 1.1 christos #endif 3229 1.1 christos 3230 1.1 christos dhcp_failover_set_state(state, partner_down); 3231 1.1 christos } 3232 1.1 christos 3233 1.1 christos isc_result_t dhcp_failover_state_get_value (omapi_object_t *h, 3234 1.1 christos omapi_object_t *id, 3235 1.1 christos omapi_data_string_t *name, 3236 1.1 christos omapi_value_t **value) 3237 1.1 christos { 3238 1.1 christos dhcp_failover_state_t *s; 3239 1.1 christos struct option_cache *oc; 3240 1.1 christos struct data_string ds; 3241 1.1 christos isc_result_t status; 3242 1.1 christos 3243 1.1 christos if (h -> type != dhcp_type_failover_state) 3244 1.1 christos return DHCP_R_INVALIDARG; 3245 1.1 christos s = (dhcp_failover_state_t *)h; 3246 1.1 christos 3247 1.1 christos if (!omapi_ds_strcmp (name, "name")) { 3248 1.1 christos if (s -> name) 3249 1.1 christos return omapi_make_string_value (value, 3250 1.1 christos name, s -> name, MDL); 3251 1.1 christos return ISC_R_NOTFOUND; 3252 1.1 christos } else if (!omapi_ds_strcmp (name, "partner-address")) { 3253 1.1 christos oc = s -> partner.address; 3254 1.1 christos getaddr: 3255 1.1 christos memset (&ds, 0, sizeof ds); 3256 1.1 christos if (!evaluate_option_cache (&ds, (struct packet *)0, 3257 1.1 christos (struct lease *)0, 3258 1.1 christos (struct client_state *)0, 3259 1.1 christos (struct option_state *)0, 3260 1.1 christos (struct option_state *)0, 3261 1.1 christos &global_scope, oc, MDL)) { 3262 1.1 christos return ISC_R_NOTFOUND; 3263 1.1 christos } 3264 1.1 christos status = omapi_make_const_value (value, 3265 1.1 christos name, ds.data, ds.len, MDL); 3266 1.1 christos /* Disgusting kludge: */ 3267 1.1 christos if (oc == s -> me.address && !s -> server_identifier.len) 3268 1.1 christos data_string_copy (&s -> server_identifier, &ds, MDL); 3269 1.1 christos data_string_forget (&ds, MDL); 3270 1.1 christos return status; 3271 1.1 christos } else if (!omapi_ds_strcmp (name, "local-address")) { 3272 1.1 christos oc = s -> me.address; 3273 1.1 christos goto getaddr; 3274 1.1 christos } else if (!omapi_ds_strcmp (name, "partner-port")) { 3275 1.1 christos return omapi_make_int_value (value, name, 3276 1.1 christos s -> partner.port, MDL); 3277 1.1 christos } else if (!omapi_ds_strcmp (name, "local-port")) { 3278 1.1 christos return omapi_make_int_value (value, 3279 1.1 christos name, s -> me.port, MDL); 3280 1.1 christos } else if (!omapi_ds_strcmp (name, "max-outstanding-updates")) { 3281 1.1 christos return omapi_make_uint_value (value, name, 3282 1.1 christos s -> me.max_flying_updates, 3283 1.1 christos MDL); 3284 1.1 christos } else if (!omapi_ds_strcmp (name, "mclt")) { 3285 1.1 christos return omapi_make_uint_value (value, name, s -> mclt, MDL); 3286 1.1 christos } else if (!omapi_ds_strcmp (name, "load-balance-max-secs")) { 3287 1.1 christos return omapi_make_int_value (value, name, 3288 1.1 christos s -> load_balance_max_secs, MDL); 3289 1.1 christos } else if (!omapi_ds_strcmp (name, "load-balance-hba")) { 3290 1.1 christos if (s -> hba) 3291 1.1 christos return omapi_make_const_value (value, name, 3292 1.1 christos s -> hba, 32, MDL); 3293 1.1 christos return ISC_R_NOTFOUND; 3294 1.1 christos } else if (!omapi_ds_strcmp (name, "partner-state")) { 3295 1.1 christos return omapi_make_uint_value (value, name, 3296 1.1 christos s -> partner.state, MDL); 3297 1.1 christos } else if (!omapi_ds_strcmp (name, "local-state")) { 3298 1.1 christos return omapi_make_uint_value (value, name, 3299 1.1 christos s -> me.state, MDL); 3300 1.1 christos } else if (!omapi_ds_strcmp (name, "partner-stos")) { 3301 1.1 christos return omapi_make_int_value (value, name, 3302 1.1 christos s -> partner.stos, MDL); 3303 1.1 christos } else if (!omapi_ds_strcmp (name, "local-stos")) { 3304 1.1 christos return omapi_make_int_value (value, name, 3305 1.1 christos s -> me.stos, MDL); 3306 1.1 christos } else if (!omapi_ds_strcmp (name, "hierarchy")) { 3307 1.1 christos return omapi_make_uint_value (value, name, s -> i_am, MDL); 3308 1.1 christos } else if (!omapi_ds_strcmp (name, "last-packet-sent")) { 3309 1.1 christos return omapi_make_int_value (value, name, 3310 1.1 christos s -> last_packet_sent, MDL); 3311 1.1 christos } else if (!omapi_ds_strcmp (name, "last-timestamp-received")) { 3312 1.1 christos return omapi_make_int_value (value, name, 3313 1.1 christos s -> last_timestamp_received, 3314 1.1 christos MDL); 3315 1.1 christos } else if (!omapi_ds_strcmp (name, "skew")) { 3316 1.1 christos return omapi_make_int_value (value, name, s -> skew, MDL); 3317 1.1 christos } else if (!omapi_ds_strcmp (name, "max-response-delay")) { 3318 1.1 christos return omapi_make_uint_value (value, name, 3319 1.1 christos s -> me.max_response_delay, 3320 1.1 christos MDL); 3321 1.1 christos } else if (!omapi_ds_strcmp (name, "cur-unacked-updates")) { 3322 1.1 christos return omapi_make_int_value (value, name, 3323 1.1 christos s -> cur_unacked_updates, MDL); 3324 1.1 christos } 3325 1.1 christos 3326 1.1 christos if (h -> inner && h -> inner -> type -> get_value) 3327 1.1 christos return (*(h -> inner -> type -> get_value)) 3328 1.1 christos (h -> inner, id, name, value); 3329 1.1 christos return ISC_R_NOTFOUND; 3330 1.1 christos } 3331 1.1 christos 3332 1.1 christos isc_result_t dhcp_failover_state_destroy (omapi_object_t *h, 3333 1.1 christos const char *file, int line) 3334 1.1 christos { 3335 1.1 christos dhcp_failover_state_t *s; 3336 1.1 christos 3337 1.1 christos if (h -> type != dhcp_type_failover_state) 3338 1.1 christos return DHCP_R_INVALIDARG; 3339 1.1 christos s = (dhcp_failover_state_t *)h; 3340 1.1 christos 3341 1.1 christos if (s -> link_to_peer) 3342 1.1 christos dhcp_failover_link_dereference (&s -> link_to_peer, file, line); 3343 1.1 christos if (s -> name) { 3344 1.1 christos dfree (s -> name, MDL); 3345 1.1 christos s -> name = (char *)0; 3346 1.1 christos } 3347 1.1 christos if (s -> partner.address) 3348 1.1 christos option_cache_dereference (&s -> partner.address, file, line); 3349 1.1 christos if (s -> me.address) 3350 1.1 christos option_cache_dereference (&s -> me.address, file, line); 3351 1.1 christos if (s -> hba) { 3352 1.1 christos dfree (s -> hba, file, line); 3353 1.1 christos s -> hba = (u_int8_t *)0; 3354 1.1 christos } 3355 1.1 christos if (s -> update_queue_head) 3356 1.1 christos lease_dereference (&s -> update_queue_head, file, line); 3357 1.1 christos if (s -> update_queue_tail) 3358 1.1 christos lease_dereference (&s -> update_queue_tail, file, line); 3359 1.1 christos if (s -> ack_queue_head) 3360 1.1 christos lease_dereference (&s -> ack_queue_head, file, line); 3361 1.1 christos if (s -> ack_queue_tail) 3362 1.1 christos lease_dereference (&s -> ack_queue_tail, file, line); 3363 1.1 christos if (s -> send_update_done) 3364 1.1 christos lease_dereference (&s -> send_update_done, file, line); 3365 1.1 christos if (s -> toack_queue_head) 3366 1.1 christos failover_message_dereference (&s -> toack_queue_head, 3367 1.1 christos file, line); 3368 1.1 christos if (s -> toack_queue_tail) 3369 1.1 christos failover_message_dereference (&s -> toack_queue_tail, 3370 1.1 christos file, line); 3371 1.1 christos return ISC_R_SUCCESS; 3372 1.1 christos } 3373 1.1 christos 3374 1.1 christos /* Write all the published values associated with the object through the 3375 1.1 christos specified connection. */ 3376 1.1 christos 3377 1.1 christos isc_result_t dhcp_failover_state_stuff (omapi_object_t *c, 3378 1.1 christos omapi_object_t *id, 3379 1.1 christos omapi_object_t *h) 3380 1.1 christos { 3381 1.1 christos /* In this function c should be a (omapi_connection_object_t *) */ 3382 1.1 christos 3383 1.1 christos dhcp_failover_state_t *s; 3384 1.1 christos isc_result_t status; 3385 1.1 christos 3386 1.1 christos if (c -> type != omapi_type_connection) 3387 1.1 christos return DHCP_R_INVALIDARG; 3388 1.1 christos 3389 1.1 christos if (h -> type != dhcp_type_failover_state) 3390 1.1 christos return DHCP_R_INVALIDARG; 3391 1.1 christos s = (dhcp_failover_state_t *)h; 3392 1.1 christos 3393 1.1 christos status = omapi_connection_put_name (c, "name"); 3394 1.1 christos if (status != ISC_R_SUCCESS) 3395 1.1 christos return status; 3396 1.1 christos status = omapi_connection_put_string (c, s -> name); 3397 1.1 christos if (status != ISC_R_SUCCESS) 3398 1.1 christos return status; 3399 1.1 christos 3400 1.1 christos status = omapi_connection_put_name (c, "partner-address"); 3401 1.1 christos if (status != ISC_R_SUCCESS) 3402 1.1 christos return status; 3403 1.1 christos status = omapi_connection_put_uint32 (c, sizeof s -> partner.address); 3404 1.1 christos if (status != ISC_R_SUCCESS) 3405 1.1 christos return status; 3406 1.1 christos status = omapi_connection_copyin (c, (u_int8_t *)&s -> partner.address, 3407 1.1 christos sizeof s -> partner.address); 3408 1.1 christos if (status != ISC_R_SUCCESS) 3409 1.1 christos return status; 3410 1.1 christos 3411 1.1 christos status = omapi_connection_put_name (c, "partner-port"); 3412 1.1 christos if (status != ISC_R_SUCCESS) 3413 1.1 christos return status; 3414 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3415 1.1 christos if (status != ISC_R_SUCCESS) 3416 1.1 christos return status; 3417 1.1 christos status = omapi_connection_put_uint32 (c, (u_int32_t)s -> partner.port); 3418 1.1 christos if (status != ISC_R_SUCCESS) 3419 1.1 christos return status; 3420 1.1 christos 3421 1.1 christos status = omapi_connection_put_name (c, "local-address"); 3422 1.1 christos if (status != ISC_R_SUCCESS) 3423 1.1 christos return status; 3424 1.1 christos status = omapi_connection_put_uint32 (c, sizeof s -> me.address); 3425 1.1 christos if (status != ISC_R_SUCCESS) 3426 1.1 christos return status; 3427 1.1 christos status = omapi_connection_copyin (c, (u_int8_t *)&s -> me.address, 3428 1.1 christos sizeof s -> me.address); 3429 1.1 christos if (status != ISC_R_SUCCESS) 3430 1.1 christos return status; 3431 1.1 christos 3432 1.1 christos status = omapi_connection_put_name (c, "local-port"); 3433 1.1 christos if (status != ISC_R_SUCCESS) 3434 1.1 christos return status; 3435 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3436 1.1 christos if (status != ISC_R_SUCCESS) 3437 1.1 christos return status; 3438 1.1 christos status = omapi_connection_put_uint32 (c, (u_int32_t)s -> me.port); 3439 1.1 christos if (status != ISC_R_SUCCESS) 3440 1.1 christos return status; 3441 1.1 christos 3442 1.1 christos status = omapi_connection_put_name (c, "max-outstanding-updates"); 3443 1.1 christos if (status != ISC_R_SUCCESS) 3444 1.1 christos return status; 3445 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3446 1.1 christos if (status != ISC_R_SUCCESS) 3447 1.1 christos return status; 3448 1.1 christos status = omapi_connection_put_uint32 (c, 3449 1.1 christos s -> me.max_flying_updates); 3450 1.1 christos if (status != ISC_R_SUCCESS) 3451 1.1 christos return status; 3452 1.1 christos 3453 1.1 christos status = omapi_connection_put_name (c, "mclt"); 3454 1.1 christos if (status != ISC_R_SUCCESS) 3455 1.1 christos return status; 3456 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3457 1.1 christos if (status != ISC_R_SUCCESS) 3458 1.1 christos return status; 3459 1.1 christos status = omapi_connection_put_uint32 (c, s -> mclt); 3460 1.1 christos if (status != ISC_R_SUCCESS) 3461 1.1 christos return status; 3462 1.1 christos 3463 1.1 christos status = omapi_connection_put_name (c, "load-balance-max-secs"); 3464 1.1 christos if (status != ISC_R_SUCCESS) 3465 1.1 christos return status; 3466 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3467 1.1 christos if (status != ISC_R_SUCCESS) 3468 1.1 christos return status; 3469 1.1 christos status = (omapi_connection_put_uint32 3470 1.1 christos (c, (u_int32_t)s -> load_balance_max_secs)); 3471 1.1 christos if (status != ISC_R_SUCCESS) 3472 1.1 christos return status; 3473 1.1 christos 3474 1.1 christos 3475 1.1 christos if (s -> hba) { 3476 1.1 christos status = omapi_connection_put_name (c, "load-balance-hba"); 3477 1.1 christos if (status != ISC_R_SUCCESS) 3478 1.1 christos return status; 3479 1.1 christos status = omapi_connection_put_uint32 (c, 32); 3480 1.1 christos if (status != ISC_R_SUCCESS) 3481 1.1 christos return status; 3482 1.1 christos status = omapi_connection_copyin (c, s -> hba, 32); 3483 1.1 christos if (status != ISC_R_SUCCESS) 3484 1.1 christos return status; 3485 1.1 christos } 3486 1.1 christos 3487 1.1 christos status = omapi_connection_put_name (c, "partner-state"); 3488 1.1 christos if (status != ISC_R_SUCCESS) 3489 1.1 christos return status; 3490 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3491 1.1 christos if (status != ISC_R_SUCCESS) 3492 1.1 christos return status; 3493 1.1 christos status = omapi_connection_put_uint32 (c, s -> partner.state); 3494 1.1 christos if (status != ISC_R_SUCCESS) 3495 1.1 christos return status; 3496 1.1 christos 3497 1.1 christos status = omapi_connection_put_name (c, "local-state"); 3498 1.1 christos if (status != ISC_R_SUCCESS) 3499 1.1 christos return status; 3500 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3501 1.1 christos if (status != ISC_R_SUCCESS) 3502 1.1 christos return status; 3503 1.1 christos status = omapi_connection_put_uint32 (c, s -> me.state); 3504 1.1 christos if (status != ISC_R_SUCCESS) 3505 1.1 christos return status; 3506 1.1 christos 3507 1.1 christos status = omapi_connection_put_name (c, "partner-stos"); 3508 1.1 christos if (status != ISC_R_SUCCESS) 3509 1.1 christos return status; 3510 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3511 1.1 christos if (status != ISC_R_SUCCESS) 3512 1.1 christos return status; 3513 1.1 christos status = omapi_connection_put_uint32 (c, 3514 1.1 christos (u_int32_t)s -> partner.stos); 3515 1.1 christos if (status != ISC_R_SUCCESS) 3516 1.1 christos return status; 3517 1.1 christos 3518 1.1 christos status = omapi_connection_put_name (c, "local-stos"); 3519 1.1 christos if (status != ISC_R_SUCCESS) 3520 1.1 christos return status; 3521 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3522 1.1 christos if (status != ISC_R_SUCCESS) 3523 1.1 christos return status; 3524 1.1 christos status = omapi_connection_put_uint32 (c, (u_int32_t)s -> me.stos); 3525 1.1 christos if (status != ISC_R_SUCCESS) 3526 1.1 christos return status; 3527 1.1 christos 3528 1.1 christos status = omapi_connection_put_name (c, "hierarchy"); 3529 1.1 christos if (status != ISC_R_SUCCESS) 3530 1.1 christos return status; 3531 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3532 1.1 christos if (status != ISC_R_SUCCESS) 3533 1.1 christos return status; 3534 1.1 christos status = omapi_connection_put_uint32 (c, s -> i_am); 3535 1.1 christos if (status != ISC_R_SUCCESS) 3536 1.1 christos return status; 3537 1.1 christos 3538 1.1 christos status = omapi_connection_put_name (c, "last-packet-sent"); 3539 1.1 christos if (status != ISC_R_SUCCESS) 3540 1.1 christos return status; 3541 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3542 1.1 christos if (status != ISC_R_SUCCESS) 3543 1.1 christos return status; 3544 1.1 christos status = (omapi_connection_put_uint32 3545 1.1 christos (c, (u_int32_t)s -> last_packet_sent)); 3546 1.1 christos if (status != ISC_R_SUCCESS) 3547 1.1 christos return status; 3548 1.1 christos 3549 1.1 christos status = omapi_connection_put_name (c, "last-timestamp-received"); 3550 1.1 christos if (status != ISC_R_SUCCESS) 3551 1.1 christos return status; 3552 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3553 1.1 christos if (status != ISC_R_SUCCESS) 3554 1.1 christos return status; 3555 1.1 christos status = (omapi_connection_put_uint32 3556 1.1 christos (c, (u_int32_t)s -> last_timestamp_received)); 3557 1.1 christos if (status != ISC_R_SUCCESS) 3558 1.1 christos return status; 3559 1.1 christos 3560 1.1 christos status = omapi_connection_put_name (c, "skew"); 3561 1.1 christos if (status != ISC_R_SUCCESS) 3562 1.1 christos return status; 3563 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3564 1.1 christos if (status != ISC_R_SUCCESS) 3565 1.1 christos return status; 3566 1.1 christos status = omapi_connection_put_uint32 (c, (u_int32_t)s -> skew); 3567 1.1 christos if (status != ISC_R_SUCCESS) 3568 1.1 christos return status; 3569 1.1 christos 3570 1.1 christos status = omapi_connection_put_name (c, "max-response-delay"); 3571 1.1 christos if (status != ISC_R_SUCCESS) 3572 1.1 christos return status; 3573 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3574 1.1 christos if (status != ISC_R_SUCCESS) 3575 1.1 christos return status; 3576 1.1 christos status = (omapi_connection_put_uint32 3577 1.1 christos (c, (u_int32_t)s -> me.max_response_delay)); 3578 1.1 christos if (status != ISC_R_SUCCESS) 3579 1.1 christos return status; 3580 1.1 christos 3581 1.1 christos status = omapi_connection_put_name (c, "cur-unacked-updates"); 3582 1.1 christos if (status != ISC_R_SUCCESS) 3583 1.1 christos return status; 3584 1.1 christos status = omapi_connection_put_uint32 (c, sizeof (u_int32_t)); 3585 1.1 christos if (status != ISC_R_SUCCESS) 3586 1.1 christos return status; 3587 1.1 christos status = (omapi_connection_put_uint32 3588 1.1 christos (c, (u_int32_t)s -> cur_unacked_updates)); 3589 1.1 christos if (status != ISC_R_SUCCESS) 3590 1.1 christos return status; 3591 1.1 christos 3592 1.1 christos if (h -> inner && h -> inner -> type -> stuff_values) 3593 1.1 christos return (*(h -> inner -> type -> stuff_values)) (c, id, 3594 1.1 christos h -> inner); 3595 1.1 christos return ISC_R_SUCCESS; 3596 1.1 christos } 3597 1.1 christos 3598 1.1 christos isc_result_t dhcp_failover_state_lookup (omapi_object_t **sp, 3599 1.1 christos omapi_object_t *id, 3600 1.1 christos omapi_object_t *ref) 3601 1.1 christos { 3602 1.1 christos omapi_value_t *tv = (omapi_value_t *)0; 3603 1.1 christos isc_result_t status; 3604 1.1 christos dhcp_failover_state_t *s; 3605 1.1 christos 3606 1.1 christos if (!ref) 3607 1.1 christos return DHCP_R_NOKEYS; 3608 1.1 christos 3609 1.1 christos /* First see if we were sent a handle. */ 3610 1.1 christos status = omapi_get_value_str (ref, id, "handle", &tv); 3611 1.1 christos if (status == ISC_R_SUCCESS) { 3612 1.1 christos status = omapi_handle_td_lookup (sp, tv -> value); 3613 1.1 christos 3614 1.1 christos omapi_value_dereference (&tv, MDL); 3615 1.1 christos if (status != ISC_R_SUCCESS) 3616 1.1 christos return status; 3617 1.1 christos 3618 1.1 christos /* Don't return the object if the type is wrong. */ 3619 1.1 christos if ((*sp) -> type != dhcp_type_failover_state) { 3620 1.1 christos omapi_object_dereference (sp, MDL); 3621 1.1 christos return DHCP_R_INVALIDARG; 3622 1.1 christos } 3623 1.1 christos } 3624 1.1 christos 3625 1.1 christos /* Look the failover state up by peer name. */ 3626 1.1 christos status = omapi_get_value_str (ref, id, "name", &tv); 3627 1.1 christos if (status == ISC_R_SUCCESS) { 3628 1.1 christos for (s = failover_states; s; s = s -> next) { 3629 1.1 christos unsigned l = strlen (s -> name); 3630 1.1 christos if (l == tv -> value -> u.buffer.len && 3631 1.1 christos !memcmp (s -> name, 3632 1.1 christos tv -> value -> u.buffer.value, l)) 3633 1.1 christos break; 3634 1.1 christos } 3635 1.1 christos omapi_value_dereference (&tv, MDL); 3636 1.1 christos 3637 1.1 christos /* If we already have a lease, and it's not the same one, 3638 1.1 christos then the query was invalid. */ 3639 1.1 christos if (*sp && *sp != (omapi_object_t *)s) { 3640 1.1 christos omapi_object_dereference (sp, MDL); 3641 1.1 christos return DHCP_R_KEYCONFLICT; 3642 1.1 christos } else if (!s) { 3643 1.1 christos if (*sp) 3644 1.1 christos omapi_object_dereference (sp, MDL); 3645 1.1 christos return ISC_R_NOTFOUND; 3646 1.1 christos } else if (!*sp) 3647 1.1 christos /* XXX fix so that hash lookup itself creates 3648 1.1 christos XXX the reference. */ 3649 1.1 christos omapi_object_reference (sp, (omapi_object_t *)s, MDL); 3650 1.1 christos } 3651 1.1 christos 3652 1.1 christos /* If we get to here without finding a lease, no valid key was 3653 1.1 christos specified. */ 3654 1.1 christos if (!*sp) 3655 1.1 christos return DHCP_R_NOKEYS; 3656 1.1 christos return ISC_R_SUCCESS; 3657 1.1 christos } 3658 1.1 christos 3659 1.1 christos isc_result_t dhcp_failover_state_create (omapi_object_t **sp, 3660 1.1 christos omapi_object_t *id) 3661 1.1 christos { 3662 1.1 christos return ISC_R_NOTIMPLEMENTED; 3663 1.1 christos } 3664 1.1 christos 3665 1.1 christos isc_result_t dhcp_failover_state_remove (omapi_object_t *sp, 3666 1.1 christos omapi_object_t *id) 3667 1.1 christos { 3668 1.1 christos return ISC_R_NOTIMPLEMENTED; 3669 1.1 christos } 3670 1.1 christos 3671 1.1 christos int dhcp_failover_state_match (dhcp_failover_state_t *state, 3672 1.1 christos u_int8_t *addr, unsigned addrlen) 3673 1.1 christos { 3674 1.1 christos struct data_string ds; 3675 1.1 christos int i; 3676 1.1 christos 3677 1.1 christos memset (&ds, 0, sizeof ds); 3678 1.1 christos if (evaluate_option_cache (&ds, (struct packet *)0, 3679 1.1 christos (struct lease *)0, 3680 1.1 christos (struct client_state *)0, 3681 1.1 christos (struct option_state *)0, 3682 1.1 christos (struct option_state *)0, 3683 1.1 christos &global_scope, 3684 1.1 christos state -> partner.address, MDL)) { 3685 1.1 christos for (i = 0; i + addrlen - 1 < ds.len; i += addrlen) { 3686 1.1 christos if (!memcmp (&ds.data [i], 3687 1.1 christos addr, addrlen)) { 3688 1.1 christos data_string_forget (&ds, MDL); 3689 1.1 christos return 1; 3690 1.1 christos } 3691 1.1 christos } 3692 1.1 christos data_string_forget (&ds, MDL); 3693 1.1 christos } 3694 1.1 christos return 0; 3695 1.1 christos } 3696 1.1 christos 3697 1.1 christos int 3698 1.1 christos dhcp_failover_state_match_by_name(state, name) 3699 1.1 christos dhcp_failover_state_t *state; 3700 1.1 christos failover_option_t *name; 3701 1.1 christos { 3702 1.1 christos if ((strlen(state->name) == name->count) && 3703 1.1 christos (memcmp(state->name, name->data, name->count) == 0)) 3704 1.1 christos return 1; 3705 1.1 christos 3706 1.1 christos return 0; 3707 1.1 christos } 3708 1.1 christos 3709 1.1 christos const char *dhcp_failover_reject_reason_print (int reason) 3710 1.1 christos { 3711 1.1 christos static char resbuf[sizeof("Undefined-255: This reason code is not defined " 3712 1.1 christos "in the protocol standard.")]; 3713 1.1 christos 3714 1.1 christos if ((reason > 0xff) || (reason < 0)) 3715 1.1 christos return "Reason code out of range."; 3716 1.1 christos 3717 1.1 christos switch (reason) { 3718 1.1 christos case FTR_ILLEGAL_IP_ADDR: 3719 1.1 christos return "Illegal IP address (not part of any address pool)."; 3720 1.1 christos 3721 1.1 christos case FTR_FATAL_CONFLICT: 3722 1.1 christos return "Fatal conflict exists: address in use by other client."; 3723 1.1 christos 3724 1.1 christos case FTR_MISSING_BINDINFO: 3725 1.1 christos return "Missing binding information."; 3726 1.1 christos 3727 1.1 christos case FTR_TIMEMISMATCH: 3728 1.1 christos return "Connection rejected, time mismatch too great."; 3729 1.1 christos 3730 1.1 christos case FTR_INVALID_MCLT: 3731 1.1 christos return "Connection rejected, invalid MCLT."; 3732 1.1 christos 3733 1.1 christos case FTR_MISC_REJECT: 3734 1.1 christos return "Connection rejected, unknown reason."; 3735 1.1 christos 3736 1.1 christos case FTR_DUP_CONNECTION: 3737 1.1 christos return "Connection rejected, duplicate connection."; 3738 1.1 christos 3739 1.1 christos case FTR_INVALID_PARTNER: 3740 1.1 christos return "Connection rejected, invalid failover partner."; 3741 1.1 christos 3742 1.1 christos case FTR_TLS_UNSUPPORTED: 3743 1.1 christos return "TLS not supported."; 3744 1.1 christos 3745 1.1 christos case FTR_TLS_UNCONFIGURED: 3746 1.1 christos return "TLS supported but not configured."; 3747 1.1 christos 3748 1.1 christos case FTR_TLS_REQUIRED: 3749 1.1 christos return "TLS required but not supported by partner."; 3750 1.1 christos 3751 1.1 christos case FTR_DIGEST_UNSUPPORTED: 3752 1.1 christos return "Message digest not supported."; 3753 1.1 christos 3754 1.1 christos case FTR_DIGEST_UNCONFIGURED: 3755 1.1 christos return "Message digest not configured."; 3756 1.1 christos 3757 1.1 christos case FTR_VERSION_MISMATCH: 3758 1.1 christos return "Protocol version mismatch."; 3759 1.1 christos 3760 1.1 christos case FTR_OUTDATED_BIND_INFO: 3761 1.1 christos return "Outdated binding information."; 3762 1.1 christos 3763 1.1 christos case FTR_LESS_CRIT_BIND_INFO: 3764 1.1 christos return "Less critical binding information."; 3765 1.1 christos 3766 1.1 christos case FTR_NO_TRAFFIC: 3767 1.1 christos return "No traffic within sufficient time."; 3768 1.1 christos 3769 1.1 christos case FTR_HBA_CONFLICT: 3770 1.1 christos return "Hash bucket assignment conflict."; 3771 1.1 christos 3772 1.1 christos case FTR_IP_NOT_RESERVED: 3773 1.1 christos return "IP not reserved on this server."; 3774 1.1 christos 3775 1.1 christos case FTR_IP_DIGEST_FAILURE: 3776 1.1 christos return "Message digest failed to compare."; 3777 1.1 christos 3778 1.1 christos case FTR_IP_MISSING_DIGEST: 3779 1.1 christos return "Missing message digest."; 3780 1.1 christos 3781 1.1 christos case FTR_UNKNOWN: 3782 1.1 christos return "Unknown Error."; 3783 1.1 christos 3784 1.1 christos default: 3785 1.1 christos sprintf(resbuf, "Undefined-%d: This reason code is not defined in the " 3786 1.1 christos "protocol standard.", reason); 3787 1.1 christos return resbuf; 3788 1.1 christos } 3789 1.1 christos } 3790 1.1 christos 3791 1.1 christos const char *dhcp_failover_state_name_print (enum failover_state state) 3792 1.1 christos { 3793 1.1 christos switch (state) { 3794 1.1 christos default: 3795 1.1 christos case unknown_state: 3796 1.1 christos return "unknown-state"; 3797 1.1 christos 3798 1.1 christos case partner_down: 3799 1.1 christos return "partner-down"; 3800 1.1 christos 3801 1.1 christos case normal: 3802 1.1 christos return "normal"; 3803 1.1 christos 3804 1.1 christos case conflict_done: 3805 1.1 christos return "conflict-done"; 3806 1.1 christos 3807 1.1 christos case communications_interrupted: 3808 1.1 christos return "communications-interrupted"; 3809 1.1 christos 3810 1.1 christos case resolution_interrupted: 3811 1.1 christos return "resolution-interrupted"; 3812 1.1 christos 3813 1.1 christos case potential_conflict: 3814 1.1 christos return "potential-conflict"; 3815 1.1 christos 3816 1.1 christos case recover: 3817 1.1 christos return "recover"; 3818 1.1 christos 3819 1.1 christos case recover_done: 3820 1.1 christos return "recover-done"; 3821 1.1 christos 3822 1.1 christos case recover_wait: 3823 1.1 christos return "recover-wait"; 3824 1.1 christos 3825 1.1 christos case shut_down: 3826 1.1 christos return "shutdown"; 3827 1.1 christos 3828 1.1 christos case paused: 3829 1.1 christos return "paused"; 3830 1.1 christos 3831 1.1 christos case startup: 3832 1.1 christos return "startup"; 3833 1.1 christos } 3834 1.1 christos } 3835 1.1 christos 3836 1.1 christos const char *dhcp_failover_message_name (unsigned type) 3837 1.1 christos { 3838 1.1 christos static char messbuf[sizeof("unknown-message-255")]; 3839 1.1 christos 3840 1.1 christos if (type > 0xff) 3841 1.1 christos return "invalid-message"; 3842 1.1 christos 3843 1.1 christos switch (type) { 3844 1.1 christos case FTM_POOLREQ: 3845 1.1 christos return "pool-request"; 3846 1.1 christos 3847 1.1 christos case FTM_POOLRESP: 3848 1.1 christos return "pool-response"; 3849 1.1 christos 3850 1.1 christos case FTM_BNDUPD: 3851 1.1 christos return "bind-update"; 3852 1.1 christos 3853 1.1 christos case FTM_BNDACK: 3854 1.1 christos return "bind-ack"; 3855 1.1 christos 3856 1.1 christos case FTM_CONNECT: 3857 1.1 christos return "connect"; 3858 1.1 christos 3859 1.1 christos case FTM_CONNECTACK: 3860 1.1 christos return "connect-ack"; 3861 1.1 christos 3862 1.1 christos case FTM_UPDREQ: 3863 1.1 christos return "update-request"; 3864 1.1 christos 3865 1.1 christos case FTM_UPDDONE: 3866 1.1 christos return "update-done"; 3867 1.1 christos 3868 1.1 christos case FTM_UPDREQALL: 3869 1.1 christos return "update-request-all"; 3870 1.1 christos 3871 1.1 christos case FTM_STATE: 3872 1.1 christos return "state"; 3873 1.1 christos 3874 1.1 christos case FTM_CONTACT: 3875 1.1 christos return "contact"; 3876 1.1 christos 3877 1.1 christos case FTM_DISCONNECT: 3878 1.1 christos return "disconnect"; 3879 1.1 christos 3880 1.1 christos default: 3881 1.1 christos sprintf(messbuf, "unknown-message-%u", type); 3882 1.1 christos return messbuf; 3883 1.1 christos } 3884 1.1 christos } 3885 1.1 christos 3886 1.1 christos const char *dhcp_failover_option_name (unsigned type) 3887 1.1 christos { 3888 1.1 christos static char optbuf[sizeof("unknown-option-65535")]; 3889 1.1 christos 3890 1.1 christos if (type > 0xffff) 3891 1.1 christos return "invalid-option"; 3892 1.1 christos 3893 1.1 christos switch (type) { 3894 1.1 christos case FTO_ADDRESSES_TRANSFERRED: 3895 1.1 christos return "addresses-transferred"; 3896 1.1 christos 3897 1.1 christos case FTO_ASSIGNED_IP_ADDRESS: 3898 1.1 christos return "assigned-ip-address"; 3899 1.1 christos 3900 1.1 christos case FTO_BINDING_STATUS: 3901 1.1 christos return "binding-status"; 3902 1.1 christos 3903 1.1 christos case FTO_CLIENT_IDENTIFIER: 3904 1.1 christos return "client-identifier"; 3905 1.1 christos 3906 1.1 christos case FTO_CHADDR: 3907 1.1 christos return "chaddr"; 3908 1.1 christos 3909 1.1 christos case FTO_CLTT: 3910 1.1 christos return "cltt"; 3911 1.1 christos 3912 1.1 christos case FTO_DDNS: 3913 1.1 christos return "ddns"; 3914 1.1 christos 3915 1.1 christos case FTO_DELAYED_SERVICE: 3916 1.1 christos return "delayed-service"; 3917 1.1 christos 3918 1.1 christos case FTO_HBA: 3919 1.1 christos return "hba"; 3920 1.1 christos 3921 1.1 christos case FTO_IP_FLAGS: 3922 1.1 christos return "ip-flags"; 3923 1.1 christos 3924 1.1 christos case FTO_LEASE_EXPIRY: 3925 1.1 christos return "lease-expiry"; 3926 1.1 christos 3927 1.1 christos case FTO_MAX_UNACKED: 3928 1.1 christos return "max-unacked"; 3929 1.1 christos 3930 1.1 christos case FTO_MCLT: 3931 1.1 christos return "mclt"; 3932 1.1 christos 3933 1.1 christos case FTO_MESSAGE: 3934 1.1 christos return "message"; 3935 1.1 christos 3936 1.1 christos case FTO_MESSAGE_DIGEST: 3937 1.1 christos return "message-digest"; 3938 1.1 christos 3939 1.1 christos case FTO_POTENTIAL_EXPIRY: 3940 1.1 christos return "potential-expiry"; 3941 1.1 christos 3942 1.1 christos case FTO_PROTOCOL_VERSION: 3943 1.1 christos return "protocol-version"; 3944 1.1 christos 3945 1.1 christos case FTO_RECEIVE_TIMER: 3946 1.1 christos return "receive-timer"; 3947 1.1 christos 3948 1.1 christos case FTO_REJECT_REASON: 3949 1.1 christos return "reject-reason"; 3950 1.1 christos 3951 1.1 christos case FTO_RELATIONSHIP_NAME: 3952 1.1 christos return "relationship-name"; 3953 1.1 christos 3954 1.1 christos case FTO_REPLY_OPTIONS: 3955 1.1 christos return "reply-options"; 3956 1.1 christos 3957 1.1 christos case FTO_REQUEST_OPTIONS: 3958 1.1 christos return "request-options"; 3959 1.1 christos 3960 1.1 christos case FTO_SERVER_FLAGS: 3961 1.1 christos return "server-flags"; 3962 1.1 christos 3963 1.1 christos case FTO_SERVER_STATE: 3964 1.1 christos return "server-state"; 3965 1.1 christos 3966 1.1 christos case FTO_STOS: 3967 1.1 christos return "stos"; 3968 1.1 christos 3969 1.1 christos case FTO_TLS_REPLY: 3970 1.1 christos return "tls-reply"; 3971 1.1 christos 3972 1.1 christos case FTO_TLS_REQUEST: 3973 1.1 christos return "tls-request"; 3974 1.1 christos 3975 1.1 christos case FTO_VENDOR_CLASS: 3976 1.1 christos return "vendor-class"; 3977 1.1 christos 3978 1.1 christos case FTO_VENDOR_OPTIONS: 3979 1.1 christos return "vendor-options"; 3980 1.1 christos 3981 1.1 christos default: 3982 1.1 christos sprintf(optbuf, "unknown-option-%u", type); 3983 1.1 christos return optbuf; 3984 1.1 christos } 3985 1.1 christos } 3986 1.1 christos 3987 1.1 christos failover_option_t *dhcp_failover_option_printf (unsigned code, 3988 1.1 christos char *obuf, 3989 1.1 christos unsigned *obufix, 3990 1.1 christos unsigned obufmax, 3991 1.1 christos const char *fmt, ...) 3992 1.1 christos { 3993 1.1 christos va_list va; 3994 1.1 christos char tbuf [256]; 3995 1.1 christos 3996 1.1 christos /* %Audit% Truncation causes panic. %2004.06.17,Revisit% 3997 1.1 christos * It is unclear what the effects of truncation here are, or 3998 1.1 christos * how that condition should be handled. It seems that this 3999 1.1 christos * function is used for formatting messages in the failover 4000 1.1 christos * command channel. For now the safest thing is for 4001 1.1 christos * overflow-truncation to cause a fatal log. 4002 1.1 christos */ 4003 1.1 christos va_start (va, fmt); 4004 1.1 christos if (vsnprintf (tbuf, sizeof tbuf, fmt, va) >= sizeof tbuf) 4005 1.1 christos log_fatal ("%s: vsnprintf would truncate", 4006 1.1 christos "dhcp_failover_make_option"); 4007 1.1 christos va_end (va); 4008 1.1 christos 4009 1.1 christos return dhcp_failover_make_option (code, obuf, obufix, obufmax, 4010 1.1 christos strlen (tbuf), tbuf); 4011 1.1 christos } 4012 1.1 christos 4013 1.1 christos failover_option_t *dhcp_failover_make_option (unsigned code, 4014 1.1 christos char *obuf, unsigned *obufix, 4015 1.1 christos unsigned obufmax, ...) 4016 1.1 christos { 4017 1.1 christos va_list va; 4018 1.1 christos struct failover_option_info *info; 4019 1.1 christos int i; 4020 1.1 christos unsigned size, count; 4021 1.1 christos unsigned val; 4022 1.1 christos u_int8_t *iaddr; 4023 1.1 christos unsigned ilen = 0; 4024 1.1 christos u_int8_t *bval; 4025 1.1 christos char *txt = NULL; 4026 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4027 1.1 christos char tbuf [256]; 4028 1.1 christos #endif 4029 1.1 christos 4030 1.1 christos /* Note that the failover_option structure is used differently on 4031 1.1 christos input than on output - on input, count is an element count, and 4032 1.1 christos on output it's the number of bytes total in the option, including 4033 1.1 christos the option code and option length. */ 4034 1.1 christos failover_option_t option, *op; 4035 1.1 christos 4036 1.1 christos 4037 1.1 christos /* Bogus option code? */ 4038 1.1 christos if (code < 1 || code > FTO_MAX || ft_options [code].type == FT_UNDEF) { 4039 1.1 christos return &null_failover_option; 4040 1.1 christos } 4041 1.1 christos info = &ft_options [code]; 4042 1.1 christos 4043 1.1 christos va_start (va, obufmax); 4044 1.1 christos 4045 1.1 christos /* Get the number of elements and the size of the buffer we need 4046 1.1 christos to allocate. */ 4047 1.1 christos if (info -> type == FT_DDNS || info -> type == FT_DDNS1) { 4048 1.1 christos count = info -> type == FT_DDNS ? 1 : 2; 4049 1.1 christos size = va_arg (va, int) + count; 4050 1.1 christos } else { 4051 1.1 christos /* Find out how many items in this list. */ 4052 1.1 christos if (info -> num_present) 4053 1.1 christos count = info -> num_present; 4054 1.1 christos else 4055 1.1 christos count = va_arg (va, int); 4056 1.1 christos 4057 1.1 christos /* Figure out size. */ 4058 1.1 christos switch (info -> type) { 4059 1.1 christos case FT_UINT8: 4060 1.1 christos case FT_BYTES: 4061 1.1 christos case FT_DIGEST: 4062 1.1 christos size = count; 4063 1.1 christos break; 4064 1.1 christos 4065 1.1 christos case FT_TEXT_OR_BYTES: 4066 1.1 christos case FT_TEXT: 4067 1.1 christos txt = va_arg (va, char *); 4068 1.1 christos size = count; 4069 1.1 christos break; 4070 1.1 christos 4071 1.1 christos case FT_IPADDR: 4072 1.1 christos ilen = va_arg (va, unsigned); 4073 1.1 christos size = count * ilen; 4074 1.1 christos break; 4075 1.1 christos 4076 1.1 christos case FT_UINT32: 4077 1.1 christos size = count * 4; 4078 1.1 christos break; 4079 1.1 christos 4080 1.1 christos case FT_UINT16: 4081 1.1 christos size = count * 2; 4082 1.1 christos break; 4083 1.1 christos 4084 1.1 christos default: 4085 1.1 christos /* shouldn't get here. */ 4086 1.1 christos log_fatal ("bogus type in failover_make_option: %d", 4087 1.1 christos info -> type); 4088 1.1 christos return &null_failover_option; 4089 1.1 christos } 4090 1.1 christos } 4091 1.1 christos 4092 1.1 christos size += 4; 4093 1.1 christos 4094 1.1 christos /* Allocate a buffer for the option. */ 4095 1.1 christos option.count = size; 4096 1.1 christos option.data = dmalloc (option.count, MDL); 4097 1.1 christos if (!option.data) { 4098 1.1 christos va_end (va); 4099 1.1 christos return &null_failover_option; 4100 1.1 christos } 4101 1.1 christos 4102 1.1 christos /* Put in the option code and option length. */ 4103 1.1 christos putUShort (option.data, code); 4104 1.1 christos putUShort (&option.data [2], size - 4); 4105 1.1 christos 4106 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4107 1.1 christos /* %Audit% Truncation causes panic. %2004.06.17,Revisit% 4108 1.1 christos * It is unclear what the effects of truncation here are, or 4109 1.1 christos * how that condition should be handled. It seems that this 4110 1.1 christos * message may be sent over the failover command channel. 4111 1.1 christos * For now the safest thing is for overflow-truncation to cause 4112 1.1 christos * a fatal log. 4113 1.1 christos */ 4114 1.1 christos if (snprintf (tbuf, sizeof tbuf, " (%s<%d>", info -> name, 4115 1.1 christos option.count) >= sizeof tbuf) 4116 1.1 christos log_fatal ("dhcp_failover_make_option: tbuf overflow"); 4117 1.1 christos failover_print (obuf, obufix, obufmax, tbuf); 4118 1.1 christos #endif 4119 1.1 christos 4120 1.1 christos /* Now put in the data. */ 4121 1.1 christos switch (info -> type) { 4122 1.1 christos case FT_UINT8: 4123 1.1 christos for (i = 0; i < count; i++) { 4124 1.1 christos val = va_arg (va, unsigned); 4125 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4126 1.1 christos /* %Audit% Cannot exceed 24 bytes. %2004.06.17,Safe% */ 4127 1.1 christos sprintf (tbuf, " %d", val); 4128 1.1 christos failover_print (obuf, obufix, obufmax, tbuf); 4129 1.1 christos #endif 4130 1.1 christos option.data [i + 4] = val; 4131 1.1 christos } 4132 1.1 christos break; 4133 1.1 christos 4134 1.1 christos case FT_IPADDR: 4135 1.1 christos for (i = 0; i < count; i++) { 4136 1.1 christos iaddr = va_arg (va, u_int8_t *); 4137 1.1 christos if (ilen != 4) { 4138 1.1 christos dfree (option.data, MDL); 4139 1.1 christos log_error ("IP addrlen=%d, should be 4.", 4140 1.1 christos ilen); 4141 1.1 christos va_end (va); 4142 1.1 christos return &null_failover_option; 4143 1.1 christos } 4144 1.1 christos 4145 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4146 1.1 christos /*%Audit% Cannot exceed 17 bytes. %2004.06.17,Safe%*/ 4147 1.1 christos sprintf (tbuf, " %u.%u.%u.%u", 4148 1.1 christos iaddr [0], iaddr [1], iaddr [2], iaddr [3]); 4149 1.1 christos failover_print (obuf, obufix, obufmax, tbuf); 4150 1.1 christos #endif 4151 1.1 christos memcpy (&option.data [4 + i * ilen], iaddr, ilen); 4152 1.1 christos } 4153 1.1 christos break; 4154 1.1 christos 4155 1.1 christos case FT_UINT32: 4156 1.1 christos for (i = 0; i < count; i++) { 4157 1.1 christos val = va_arg (va, unsigned); 4158 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4159 1.1 christos /*%Audit% Cannot exceed 24 bytes. %2004.06.17,Safe%*/ 4160 1.1 christos sprintf (tbuf, " %d", val); 4161 1.1 christos failover_print (obuf, obufix, obufmax, tbuf); 4162 1.1 christos #endif 4163 1.1 christos putULong (&option.data [4 + i * 4], val); 4164 1.1 christos } 4165 1.1 christos break; 4166 1.1 christos 4167 1.1 christos case FT_BYTES: 4168 1.1 christos case FT_DIGEST: 4169 1.1 christos bval = va_arg (va, u_int8_t *); 4170 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4171 1.1 christos for (i = 0; i < count; i++) { 4172 1.1 christos /* 23 bytes plus nul, safe. */ 4173 1.1 christos sprintf (tbuf, " %d", bval [i]); 4174 1.1 christos failover_print (obuf, obufix, obufmax, tbuf); 4175 1.1 christos } 4176 1.1 christos #endif 4177 1.1 christos memcpy (&option.data [4], bval, count); 4178 1.1 christos break; 4179 1.1 christos 4180 1.1 christos /* On output, TEXT_OR_BYTES is _always_ text, and always NUL 4181 1.1 christos terminated. Note that the caller should be careful not 4182 1.1 christos to provide a format and data that amount to more than 256 4183 1.1 christos bytes of data, since it will cause a fatal error. */ 4184 1.1 christos case FT_TEXT_OR_BYTES: 4185 1.1 christos case FT_TEXT: 4186 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4187 1.1 christos /* %Audit% Truncation causes panic. %2004.06.17,Revisit% 4188 1.1 christos * It is unclear what the effects of truncation here are, or 4189 1.1 christos * how that condition should be handled. It seems that this 4190 1.1 christos * function is used for formatting messages in the failover 4191 1.1 christos * command channel. For now the safest thing is for 4192 1.1 christos * overflow-truncation to cause a fatal log. 4193 1.1 christos */ 4194 1.1 christos if (snprintf (tbuf, sizeof tbuf, "\"%s\"", txt) >= sizeof tbuf) 4195 1.1 christos log_fatal ("dhcp_failover_make_option: tbuf overflow"); 4196 1.1 christos failover_print (obuf, obufix, obufmax, tbuf); 4197 1.1 christos #endif 4198 1.1 christos memcpy (&option.data [4], txt, count); 4199 1.1 christos break; 4200 1.1 christos 4201 1.1 christos case FT_DDNS: 4202 1.1 christos case FT_DDNS1: 4203 1.1 christos option.data [4] = va_arg (va, unsigned); 4204 1.1 christos if (count == 2) 4205 1.1 christos option.data [5] = va_arg (va, unsigned); 4206 1.1 christos bval = va_arg (va, u_int8_t *); 4207 1.1 christos memcpy (&option.data [4 + count], bval, size - count - 4); 4208 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4209 1.1 christos for (i = 4; i < size; i++) { 4210 1.1 christos /*%Audit% Cannot exceed 24 bytes. %2004.06.17,Safe%*/ 4211 1.1 christos sprintf (tbuf, " %d", option.data [i]); 4212 1.1 christos failover_print (obuf, obufix, obufmax, tbuf); 4213 1.1 christos } 4214 1.1 christos #endif 4215 1.1 christos break; 4216 1.1 christos 4217 1.1 christos case FT_UINT16: 4218 1.1 christos for (i = 0; i < count; i++) { 4219 1.1 christos val = va_arg (va, u_int32_t); 4220 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4221 1.1 christos /*%Audit% Cannot exceed 24 bytes. %2004.06.17,Safe%*/ 4222 1.1 christos sprintf (tbuf, " %d", val); 4223 1.1 christos failover_print (obuf, obufix, obufmax, tbuf); 4224 1.1 christos #endif 4225 1.1 christos putUShort (&option.data [4 + i * 2], val); 4226 1.1 christos } 4227 1.1 christos break; 4228 1.1 christos 4229 1.1 christos case FT_UNDEF: 4230 1.1 christos default: 4231 1.1 christos break; 4232 1.1 christos } 4233 1.1 christos 4234 1.1 christos #if defined DEBUG_FAILOVER_MESSAGES 4235 1.1 christos failover_print (obuf, obufix, obufmax, ")"); 4236 1.1 christos #endif 4237 1.1 christos va_end (va); 4238 1.1 christos 4239 1.1 christos /* Now allocate a place to store what we just set up. */ 4240 1.1 christos op = dmalloc (sizeof (failover_option_t), MDL); 4241 1.1 christos if (!op) { 4242 1.1 christos dfree (option.data, MDL); 4243 1.1 christos return &null_failover_option; 4244 1.1 christos } 4245 1.1 christos 4246 1.1 christos *op = option; 4247 1.1 christos return op; 4248 1.1 christos } 4249 1.1 christos 4250 1.1 christos /* Send a failover message header. */ 4251 1.1 christos 4252 1.1 christos isc_result_t dhcp_failover_put_message (dhcp_failover_link_t *link, 4253 1.1 christos omapi_object_t *connection, 4254 1.1 christos int msg_type, u_int32_t xid, ...) 4255 1.1 christos { 4256 1.1 christos unsigned size = 0; 4257 1.1 christos int bad_option = 0; 4258 1.1 christos int opix = 0; 4259 1.1 christos va_list list; 4260 1.1 christos failover_option_t *option; 4261 1.1 christos unsigned char *opbuf; 4262 1.1 christos isc_result_t status = ISC_R_SUCCESS; 4263 1.1 christos unsigned char cbuf; 4264 1.1 christos struct timeval tv; 4265 1.1 christos 4266 1.1 christos /* Run through the argument list once to compute the length of 4267 1.1 christos the option portion of the message. */ 4268 1.1 christos va_start (list, xid); 4269 1.1 christos while ((option = va_arg (list, failover_option_t *))) { 4270 1.1 christos if (option != &skip_failover_option) 4271 1.1 christos size += option -> count; 4272 1.1 christos if (option == &null_failover_option) 4273 1.1 christos bad_option = 1; 4274 1.1 christos } 4275 1.1 christos va_end (list); 4276 1.1 christos 4277 1.1 christos /* Allocate an option buffer, unless we got an error. */ 4278 1.1 christos if (!bad_option && size) { 4279 1.1 christos opbuf = dmalloc (size, MDL); 4280 1.1 christos if (!opbuf) 4281 1.1 christos status = ISC_R_NOMEMORY; 4282 1.1 christos } else 4283 1.1 christos opbuf = (unsigned char *)0; 4284 1.1 christos 4285 1.1 christos va_start (list, xid); 4286 1.1 christos while ((option = va_arg (list, failover_option_t *))) { 4287 1.1 christos if (option == &skip_failover_option) 4288 1.1 christos continue; 4289 1.1 christos if (!bad_option && opbuf) 4290 1.1 christos memcpy (&opbuf [opix], 4291 1.1 christos option -> data, option -> count); 4292 1.1 christos if (option != &null_failover_option && 4293 1.1 christos option != &skip_failover_option) { 4294 1.1 christos opix += option -> count; 4295 1.1 christos dfree (option -> data, MDL); 4296 1.1 christos dfree (option, MDL); 4297 1.1 christos } 4298 1.1 christos } 4299 1.1 christos va_end(list); 4300 1.1 christos 4301 1.1 christos if (bad_option) 4302 1.1 christos return DHCP_R_INVALIDARG; 4303 1.1 christos 4304 1.1 christos /* Now send the message header. */ 4305 1.1 christos 4306 1.1 christos /* Message length. */ 4307 1.1 christos status = omapi_connection_put_uint16 (connection, size + 12); 4308 1.1 christos if (status != ISC_R_SUCCESS) 4309 1.1 christos goto err; 4310 1.1 christos 4311 1.1 christos /* Message type. */ 4312 1.1 christos cbuf = msg_type; 4313 1.1 christos status = omapi_connection_copyin (connection, &cbuf, 1); 4314 1.1 christos if (status != ISC_R_SUCCESS) 4315 1.1 christos goto err; 4316 1.1 christos 4317 1.1 christos /* Payload offset. */ 4318 1.1 christos cbuf = 12; 4319 1.1 christos status = omapi_connection_copyin (connection, &cbuf, 1); 4320 1.1 christos if (status != ISC_R_SUCCESS) 4321 1.1 christos goto err; 4322 1.1 christos 4323 1.1 christos /* Current time. */ 4324 1.1 christos status = omapi_connection_put_uint32 (connection, (u_int32_t)cur_time); 4325 1.1 christos if (status != ISC_R_SUCCESS) 4326 1.1 christos goto err; 4327 1.1 christos 4328 1.1 christos /* Transaction ID. */ 4329 1.1 christos status = omapi_connection_put_uint32(connection, xid); 4330 1.1 christos if (status != ISC_R_SUCCESS) 4331 1.1 christos goto err; 4332 1.1 christos 4333 1.1 christos /* Payload. */ 4334 1.1 christos if (opbuf) { 4335 1.1 christos status = omapi_connection_copyin (connection, opbuf, size); 4336 1.1 christos if (status != ISC_R_SUCCESS) 4337 1.1 christos goto err; 4338 1.1 christos dfree (opbuf, MDL); 4339 1.1 christos } 4340 1.1 christos if (link -> state_object && 4341 1.1 christos link -> state_object -> link_to_peer == link) { 4342 1.1 christos #if defined (DEBUG_FAILOVER_CONTACT_TIMING) 4343 1.1 christos log_info ("add_timeout +%d %s", 4344 1.1 christos (int)(link -> state_object -> 4345 1.1 christos partner.max_response_delay) / 3, 4346 1.1 christos "dhcp_failover_send_contact"); 4347 1.1 christos #endif 4348 1.1 christos tv . tv_sec = cur_time + 4349 1.1 christos (int)(link -> state_object -> 4350 1.1 christos partner.max_response_delay) / 3; 4351 1.1 christos tv . tv_usec = 0; 4352 1.1 christos add_timeout (&tv, 4353 1.1 christos dhcp_failover_send_contact, link -> state_object, 4354 1.1 christos (tvref_t)dhcp_failover_state_reference, 4355 1.1 christos (tvunref_t)dhcp_failover_state_dereference); 4356 1.1 christos } 4357 1.1 christos return status; 4358 1.1 christos 4359 1.1 christos err: 4360 1.1 christos if (opbuf) 4361 1.1 christos dfree (opbuf, MDL); 4362 1.1 christos log_info ("dhcp_failover_put_message: something went wrong."); 4363 1.1 christos omapi_disconnect (connection, 1); 4364 1.1 christos return status; 4365 1.1 christos } 4366 1.1 christos 4367 1.1 christos void dhcp_failover_timeout (void *vstate) 4368 1.1 christos { 4369 1.1 christos dhcp_failover_state_t *state = vstate; 4370 1.1 christos dhcp_failover_link_t *link; 4371 1.1 christos 4372 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 4373 1.1 christos log_info ("dhcp_failover_timeout"); 4374 1.1 christos #endif 4375 1.1 christos 4376 1.1 christos if (!state || state -> type != dhcp_type_failover_state) 4377 1.1 christos return; 4378 1.1 christos link = state -> link_to_peer; 4379 1.1 christos if (!link || 4380 1.1 christos !link -> outer || 4381 1.1 christos link -> outer -> type != omapi_type_connection) 4382 1.1 christos return; 4383 1.1 christos 4384 1.1 christos log_error ("timeout waiting for failover peer %s", state -> name); 4385 1.1 christos 4386 1.1 christos /* If we haven't gotten a timely response, blow away the connection. 4387 1.1 christos This will cause the state to change automatically. */ 4388 1.1 christos omapi_disconnect (link -> outer, 1); 4389 1.1 christos } 4390 1.1 christos 4391 1.1 christos void dhcp_failover_send_contact (void *vstate) 4392 1.1 christos { 4393 1.1 christos dhcp_failover_state_t *state = vstate; 4394 1.1 christos dhcp_failover_link_t *link; 4395 1.1 christos isc_result_t status; 4396 1.1 christos 4397 1.1 christos #if defined(DEBUG_FAILOVER_MESSAGES) && \ 4398 1.1 christos defined(DEBUG_FAILOVER_CONTACT_MESSAGES) 4399 1.1 christos char obuf [64]; 4400 1.1 christos unsigned obufix = 0; 4401 1.1 christos 4402 1.1 christos failover_print(obuf, &obufix, sizeof(obuf), "(contact"); 4403 1.1 christos #endif 4404 1.1 christos 4405 1.1 christos #if defined (DEBUG_FAILOVER_CONTACT_TIMING) 4406 1.1 christos log_info ("dhcp_failover_send_contact"); 4407 1.1 christos #endif 4408 1.1 christos 4409 1.1 christos if (!state || state -> type != dhcp_type_failover_state) 4410 1.1 christos return; 4411 1.1 christos link = state -> link_to_peer; 4412 1.1 christos if (!link || 4413 1.1 christos !link -> outer || 4414 1.1 christos link -> outer -> type != omapi_type_connection) 4415 1.1 christos return; 4416 1.1 christos 4417 1.1 christos status = (dhcp_failover_put_message 4418 1.1 christos (link, link -> outer, 4419 1.1 christos FTM_CONTACT, link->xid++, 4420 1.1 christos (failover_option_t *)0)); 4421 1.1 christos 4422 1.1 christos #if defined(DEBUG_FAILOVER_MESSAGES) && \ 4423 1.1 christos defined(DEBUG_FAILOVER_CONTACT_MESSAGES) 4424 1.1 christos if (status != ISC_R_SUCCESS) 4425 1.1 christos failover_print(obuf, &obufix, sizeof(obuf), " (failed)"); 4426 1.1 christos failover_print(obuf, &obufix, sizeof(obuf), ")"); 4427 1.1 christos if (obufix) { 4428 1.1 christos log_debug ("%s", obuf); 4429 1.1 christos } 4430 1.1 christos #else 4431 1.1 christos IGNORE_UNUSED(status); 4432 1.1 christos #endif 4433 1.1 christos return; 4434 1.1 christos } 4435 1.1 christos 4436 1.1 christos isc_result_t dhcp_failover_send_state (dhcp_failover_state_t *state) 4437 1.1 christos { 4438 1.1 christos dhcp_failover_link_t *link; 4439 1.1 christos isc_result_t status; 4440 1.1 christos 4441 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4442 1.1 christos char obuf [64]; 4443 1.1 christos unsigned obufix = 0; 4444 1.1 christos 4445 1.1 christos # define FMA obuf, &obufix, sizeof obuf 4446 1.1 christos failover_print (FMA, "(state"); 4447 1.1 christos #else 4448 1.1 christos # define FMA (char *)0, (unsigned *)0, 0 4449 1.1 christos #endif 4450 1.1 christos 4451 1.1 christos if (!state || state -> type != dhcp_type_failover_state) 4452 1.1 christos return DHCP_R_INVALIDARG; 4453 1.1 christos link = state -> link_to_peer; 4454 1.1 christos if (!link || 4455 1.1 christos !link -> outer || 4456 1.1 christos link -> outer -> type != omapi_type_connection) 4457 1.1 christos return DHCP_R_INVALIDARG; 4458 1.1 christos 4459 1.1 christos status = (dhcp_failover_put_message 4460 1.1 christos (link, link -> outer, 4461 1.1 christos FTM_STATE, link->xid++, 4462 1.1 christos dhcp_failover_make_option (FTO_SERVER_STATE, FMA, 4463 1.1 christos (state -> me.state == startup 4464 1.1 christos ? state -> saved_state 4465 1.1 christos : state -> me.state)), 4466 1.1 christos dhcp_failover_make_option 4467 1.1 christos (FTO_SERVER_FLAGS, FMA, 4468 1.1 christos (state -> service_state == service_startup 4469 1.1 christos ? FTF_SERVER_STARTUP : 0)), 4470 1.1 christos dhcp_failover_make_option (FTO_STOS, FMA, state -> me.stos), 4471 1.1 christos (failover_option_t *)0)); 4472 1.1 christos 4473 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4474 1.1 christos if (status != ISC_R_SUCCESS) 4475 1.1 christos failover_print (FMA, " (failed)"); 4476 1.1 christos failover_print (FMA, ")"); 4477 1.1 christos if (obufix) { 4478 1.1 christos log_debug ("%s", obuf); 4479 1.1 christos } 4480 1.1 christos #else 4481 1.1 christos IGNORE_UNUSED(status); 4482 1.1 christos #endif 4483 1.1 christos return ISC_R_SUCCESS; 4484 1.1 christos } 4485 1.1 christos 4486 1.1 christos /* Send a connect message. */ 4487 1.1 christos 4488 1.1 christos isc_result_t dhcp_failover_send_connect (omapi_object_t *l) 4489 1.1 christos { 4490 1.1 christos dhcp_failover_link_t *link; 4491 1.1 christos dhcp_failover_state_t *state; 4492 1.1 christos isc_result_t status; 4493 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4494 1.1 christos char obuf [64]; 4495 1.1 christos unsigned obufix = 0; 4496 1.1 christos 4497 1.1 christos # define FMA obuf, &obufix, sizeof obuf 4498 1.1 christos failover_print (FMA, "(connect"); 4499 1.1 christos #else 4500 1.1 christos # define FMA (char *)0, (unsigned *)0, 0 4501 1.1 christos #endif 4502 1.1 christos 4503 1.1 christos if (!l || l -> type != dhcp_type_failover_link) 4504 1.1 christos return DHCP_R_INVALIDARG; 4505 1.1 christos link = (dhcp_failover_link_t *)l; 4506 1.1 christos state = link -> state_object; 4507 1.1 christos if (!l -> outer || l -> outer -> type != omapi_type_connection) 4508 1.1 christos return DHCP_R_INVALIDARG; 4509 1.1 christos 4510 1.1 christos status = 4511 1.1 christos (dhcp_failover_put_message 4512 1.1 christos (link, l -> outer, 4513 1.1 christos FTM_CONNECT, link->xid++, 4514 1.1 christos dhcp_failover_make_option(FTO_RELATIONSHIP_NAME, FMA, 4515 1.1 christos strlen(state->name), state->name), 4516 1.1 christos dhcp_failover_make_option (FTO_MAX_UNACKED, FMA, 4517 1.1 christos state -> me.max_flying_updates), 4518 1.1 christos dhcp_failover_make_option (FTO_RECEIVE_TIMER, FMA, 4519 1.1 christos state -> me.max_response_delay), 4520 1.1 christos dhcp_failover_option_printf(FTO_VENDOR_CLASS, FMA, 4521 1.1 christos "isc-%s", PACKAGE_VERSION), 4522 1.1 christos dhcp_failover_make_option (FTO_PROTOCOL_VERSION, FMA, 4523 1.1 christos DHCP_FAILOVER_VERSION), 4524 1.1 christos dhcp_failover_make_option (FTO_TLS_REQUEST, FMA, 4525 1.1 christos 0, 0), 4526 1.1 christos dhcp_failover_make_option (FTO_MCLT, FMA, 4527 1.1 christos state -> mclt), 4528 1.1 christos (state -> hba 4529 1.1 christos ? dhcp_failover_make_option (FTO_HBA, FMA, 32, state -> hba) 4530 1.1 christos : &skip_failover_option), 4531 1.1 christos (failover_option_t *)0)); 4532 1.1 christos 4533 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4534 1.1 christos if (status != ISC_R_SUCCESS) 4535 1.1 christos failover_print (FMA, " (failed)"); 4536 1.1 christos failover_print (FMA, ")"); 4537 1.1 christos if (obufix) { 4538 1.1 christos log_debug ("%s", obuf); 4539 1.1 christos } 4540 1.1 christos #endif 4541 1.1 christos return status; 4542 1.1 christos } 4543 1.1 christos 4544 1.1 christos isc_result_t dhcp_failover_send_connectack (omapi_object_t *l, 4545 1.1 christos dhcp_failover_state_t *state, 4546 1.1 christos int reason, const char *errmsg) 4547 1.1 christos { 4548 1.1 christos dhcp_failover_link_t *link; 4549 1.1 christos isc_result_t status; 4550 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4551 1.1 christos char obuf [64]; 4552 1.1 christos unsigned obufix = 0; 4553 1.1 christos 4554 1.1 christos # define FMA obuf, &obufix, sizeof obuf 4555 1.1 christos failover_print (FMA, "(connectack"); 4556 1.1 christos #else 4557 1.1 christos # define FMA (char *)0, (unsigned *)0, 0 4558 1.1 christos #endif 4559 1.1 christos 4560 1.1 christos if (!l || l -> type != dhcp_type_failover_link) 4561 1.1 christos return DHCP_R_INVALIDARG; 4562 1.1 christos link = (dhcp_failover_link_t *)l; 4563 1.1 christos if (!l -> outer || l -> outer -> type != omapi_type_connection) 4564 1.1 christos return DHCP_R_INVALIDARG; 4565 1.1 christos 4566 1.1 christos status = 4567 1.1 christos (dhcp_failover_put_message 4568 1.1 christos (link, l -> outer, 4569 1.1 christos FTM_CONNECTACK, link->imsg->xid, 4570 1.1 christos state 4571 1.1 christos ? dhcp_failover_make_option(FTO_RELATIONSHIP_NAME, FMA, 4572 1.1 christos strlen(state->name), state->name) 4573 1.1 christos : (link->imsg->options_present & FTB_RELATIONSHIP_NAME) 4574 1.1 christos ? dhcp_failover_make_option(FTO_RELATIONSHIP_NAME, FMA, 4575 1.1 christos link->imsg->relationship_name.count, 4576 1.1 christos link->imsg->relationship_name.data) 4577 1.1 christos : &skip_failover_option, 4578 1.1 christos state 4579 1.1 christos ? dhcp_failover_make_option (FTO_MAX_UNACKED, FMA, 4580 1.1 christos state -> me.max_flying_updates) 4581 1.1 christos : &skip_failover_option, 4582 1.1 christos state 4583 1.1 christos ? dhcp_failover_make_option (FTO_RECEIVE_TIMER, FMA, 4584 1.1 christos state -> me.max_response_delay) 4585 1.1 christos : &skip_failover_option, 4586 1.1 christos dhcp_failover_option_printf(FTO_VENDOR_CLASS, FMA, 4587 1.1 christos "isc-%s", PACKAGE_VERSION), 4588 1.1 christos dhcp_failover_make_option (FTO_PROTOCOL_VERSION, FMA, 4589 1.1 christos DHCP_FAILOVER_VERSION), 4590 1.1 christos (link->imsg->options_present & FTB_TLS_REQUEST) 4591 1.1 christos ? dhcp_failover_make_option(FTO_TLS_REPLY, FMA, 4592 1.1 christos 0, 0) 4593 1.1 christos : &skip_failover_option, 4594 1.1 christos reason 4595 1.1 christos ? dhcp_failover_make_option (FTO_REJECT_REASON, 4596 1.1 christos FMA, reason) 4597 1.1 christos : &skip_failover_option, 4598 1.1 christos (reason && errmsg) 4599 1.1 christos ? dhcp_failover_make_option (FTO_MESSAGE, FMA, 4600 1.1 christos strlen (errmsg), errmsg) 4601 1.1 christos : &skip_failover_option, 4602 1.1 christos (failover_option_t *)0)); 4603 1.1 christos 4604 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4605 1.1 christos if (status != ISC_R_SUCCESS) 4606 1.1 christos failover_print (FMA, " (failed)"); 4607 1.1 christos failover_print (FMA, ")"); 4608 1.1 christos if (obufix) { 4609 1.1 christos log_debug ("%s", obuf); 4610 1.1 christos } 4611 1.1 christos #endif 4612 1.1 christos return status; 4613 1.1 christos } 4614 1.1 christos 4615 1.1 christos isc_result_t dhcp_failover_send_disconnect (omapi_object_t *l, 4616 1.1 christos int reason, 4617 1.1 christos const char *message) 4618 1.1 christos { 4619 1.1 christos dhcp_failover_link_t *link; 4620 1.1 christos isc_result_t status; 4621 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4622 1.1 christos char obuf [64]; 4623 1.1 christos unsigned obufix = 0; 4624 1.1 christos 4625 1.1 christos # define FMA obuf, &obufix, sizeof obuf 4626 1.1 christos failover_print (FMA, "(disconnect"); 4627 1.1 christos #else 4628 1.1 christos # define FMA (char *)0, (unsigned *)0, 0 4629 1.1 christos #endif 4630 1.1 christos 4631 1.1 christos if (!l || l -> type != dhcp_type_failover_link) 4632 1.1 christos return DHCP_R_INVALIDARG; 4633 1.1 christos link = (dhcp_failover_link_t *)l; 4634 1.1 christos if (!l -> outer || l -> outer -> type != omapi_type_connection) 4635 1.1 christos return DHCP_R_INVALIDARG; 4636 1.1 christos 4637 1.1 christos if (!message && reason) 4638 1.1 christos message = dhcp_failover_reject_reason_print (reason); 4639 1.1 christos 4640 1.1 christos status = (dhcp_failover_put_message 4641 1.1 christos (link, l -> outer, 4642 1.1 christos FTM_DISCONNECT, link->xid++, 4643 1.1 christos dhcp_failover_make_option (FTO_REJECT_REASON, 4644 1.1 christos FMA, reason), 4645 1.1 christos (message 4646 1.1 christos ? dhcp_failover_make_option (FTO_MESSAGE, FMA, 4647 1.1 christos strlen (message), message) 4648 1.1 christos : &skip_failover_option), 4649 1.1 christos (failover_option_t *)0)); 4650 1.1 christos 4651 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4652 1.1 christos if (status != ISC_R_SUCCESS) 4653 1.1 christos failover_print (FMA, " (failed)"); 4654 1.1 christos failover_print (FMA, ")"); 4655 1.1 christos if (obufix) { 4656 1.1 christos log_debug ("%s", obuf); 4657 1.1 christos } 4658 1.1 christos #endif 4659 1.1 christos return status; 4660 1.1 christos } 4661 1.1 christos 4662 1.1 christos /* Send a Bind Update message. */ 4663 1.1 christos 4664 1.1 christos isc_result_t dhcp_failover_send_bind_update (dhcp_failover_state_t *state, 4665 1.1 christos struct lease *lease) 4666 1.1 christos { 4667 1.1 christos dhcp_failover_link_t *link; 4668 1.1 christos isc_result_t status; 4669 1.1 christos int flags = 0; 4670 1.1 christos binding_state_t transmit_state; 4671 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4672 1.1 christos char obuf [64]; 4673 1.1 christos unsigned obufix = 0; 4674 1.1 christos 4675 1.1 christos # define FMA obuf, &obufix, sizeof obuf 4676 1.1 christos failover_print (FMA, "(bndupd"); 4677 1.1 christos #else 4678 1.1 christos # define FMA (char *)0, (unsigned *)0, 0 4679 1.1 christos #endif 4680 1.1 christos 4681 1.1 christos if (!state -> link_to_peer || 4682 1.1 christos state -> link_to_peer -> type != dhcp_type_failover_link) 4683 1.1 christos return DHCP_R_INVALIDARG; 4684 1.1 christos link = (dhcp_failover_link_t *)state -> link_to_peer; 4685 1.1 christos 4686 1.1 christos if (!link -> outer || link -> outer -> type != omapi_type_connection) 4687 1.1 christos return DHCP_R_INVALIDARG; 4688 1.1 christos 4689 1.1 christos transmit_state = lease->desired_binding_state; 4690 1.1 christos if (lease->flags & RESERVED_LEASE) { 4691 1.1 christos /* If we are listing an allocable (not yet ACTIVE etc) lease 4692 1.1 christos * as reserved, toggle to the peer's 'free state', per the 4693 1.1 christos * draft. This gives the peer permission to alloc it to the 4694 1.1 christos * chaddr/uid-named client. 4695 1.1 christos */ 4696 1.1 christos if ((state->i_am == primary) && (transmit_state == FTS_FREE)) 4697 1.1 christos transmit_state = FTS_BACKUP; 4698 1.1 christos else if ((state->i_am == secondary) && 4699 1.1 christos (transmit_state == FTS_BACKUP)) 4700 1.1 christos transmit_state = FTS_FREE; 4701 1.1 christos 4702 1.1 christos flags |= FTF_IP_FLAG_RESERVE; 4703 1.1 christos } 4704 1.1 christos if (lease->flags & BOOTP_LEASE) 4705 1.1 christos flags |= FTF_IP_FLAG_BOOTP; 4706 1.1 christos 4707 1.1 christos /* last_xid == 0 is illegal, seek past zero if we hit it. */ 4708 1.1 christos if (link->xid == 0) 4709 1.1 christos link->xid = 1; 4710 1.1 christos 4711 1.1 christos lease->last_xid = link->xid++; 4712 1.1 christos 4713 1.1 christos /* 4714 1.1 christos * Our very next action is to transmit a binding update relating to 4715 1.1 christos * this lease over the wire, and although there is a BNDACK, there is 4716 1.1 christos * no BNDACKACK or BNDACKACKACK...the basic issue as we send a BNDUPD, 4717 1.1 christos * we may not receive a BNDACK. This non-reception does not imply the 4718 1.1 christos * peer did not receive and process the BNDUPD. So at this point, we 4719 1.1 christos * must divest any state that would be dangerous to retain under the 4720 1.1 christos * impression the peer has been updated. Normally state changes like 4721 1.1 christos * this are processed in supersede_lease(), but in this case we need a 4722 1.1 christos * very late binding. 4723 1.1 christos * 4724 1.1 christos * In failover rules, a server is permitted to work forward in certain 4725 1.1 christos * directions from a given lease's state; active leases may be 4726 1.1 christos * extended, so forth. There is an 'optimization' in the failover 4727 1.1 christos * draft that permits a server to 'rewind' any work they have not 4728 1.1 christos * informed the peer. Since we can't know if the peer received our 4729 1.1 christos * update but was unable to acknowledge it, we make this change on 4730 1.1 christos * transmit rather than upon receiving the acknowledgement. 4731 1.1 christos * 4732 1.1 christos * XXX: Frequent lease commits are undesirable. This should hopefully 4733 1.1 christos * only trigger when a server is sending a lease /state change/, and 4734 1.1 christos * not merely an update such as with a renewal. 4735 1.1 christos */ 4736 1.1 christos if (lease->rewind_binding_state != lease->binding_state) { 4737 1.1 christos lease->rewind_binding_state = lease->binding_state; 4738 1.1 christos 4739 1.1 christos write_lease(lease); 4740 1.1 christos commit_leases(); 4741 1.1 christos } 4742 1.1 christos 4743 1.1 christos /* Send the update. */ 4744 1.1 christos status = (dhcp_failover_put_message 4745 1.1 christos (link, link -> outer, 4746 1.1 christos FTM_BNDUPD, lease->last_xid, 4747 1.1 christos dhcp_failover_make_option (FTO_ASSIGNED_IP_ADDRESS, FMA, 4748 1.1 christos lease -> ip_addr.len, 4749 1.1 christos lease -> ip_addr.iabuf), 4750 1.1 christos dhcp_failover_make_option (FTO_BINDING_STATUS, FMA, 4751 1.1 christos lease -> desired_binding_state), 4752 1.1 christos lease -> uid_len 4753 1.1 christos ? dhcp_failover_make_option (FTO_CLIENT_IDENTIFIER, FMA, 4754 1.1 christos lease -> uid_len, 4755 1.1 christos lease -> uid) 4756 1.1 christos : &skip_failover_option, 4757 1.1 christos lease -> hardware_addr.hlen 4758 1.1 christos ? dhcp_failover_make_option (FTO_CHADDR, FMA, 4759 1.1 christos lease -> hardware_addr.hlen, 4760 1.1 christos lease -> hardware_addr.hbuf) 4761 1.1 christos : &skip_failover_option, 4762 1.1 christos dhcp_failover_make_option (FTO_LEASE_EXPIRY, FMA, 4763 1.1 christos lease -> ends), 4764 1.1 christos dhcp_failover_make_option (FTO_POTENTIAL_EXPIRY, FMA, 4765 1.1 christos lease -> tstp), 4766 1.1 christos dhcp_failover_make_option (FTO_STOS, FMA, 4767 1.1 christos lease -> starts), 4768 1.1 christos (lease->cltt != 0) ? 4769 1.1 christos dhcp_failover_make_option(FTO_CLTT, FMA, lease->cltt) : 4770 1.1 christos &skip_failover_option, /* No CLTT */ 4771 1.1 christos flags ? dhcp_failover_make_option(FTO_IP_FLAGS, FMA, 4772 1.1 christos flags) : 4773 1.1 christos &skip_failover_option, /* No IP_FLAGS */ 4774 1.1 christos &skip_failover_option, /* XXX DDNS */ 4775 1.1 christos &skip_failover_option, /* XXX request options */ 4776 1.1 christos &skip_failover_option, /* XXX reply options */ 4777 1.1 christos (failover_option_t *)0)); 4778 1.1 christos 4779 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4780 1.1 christos if (status != ISC_R_SUCCESS) 4781 1.1 christos failover_print (FMA, " (failed)"); 4782 1.1 christos failover_print (FMA, ")"); 4783 1.1 christos if (obufix) { 4784 1.1 christos log_debug ("%s", obuf); 4785 1.1 christos } 4786 1.1 christos #endif 4787 1.1 christos return status; 4788 1.1 christos } 4789 1.1 christos 4790 1.1 christos /* Send a Bind ACK message. */ 4791 1.1 christos 4792 1.1 christos isc_result_t dhcp_failover_send_bind_ack (dhcp_failover_state_t *state, 4793 1.1 christos failover_message_t *msg, 4794 1.1 christos int reason, const char *message) 4795 1.1 christos { 4796 1.1 christos dhcp_failover_link_t *link; 4797 1.1 christos isc_result_t status; 4798 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4799 1.1 christos char obuf [64]; 4800 1.1 christos unsigned obufix = 0; 4801 1.1 christos 4802 1.1 christos # define FMA obuf, &obufix, sizeof obuf 4803 1.1 christos failover_print (FMA, "(bndack"); 4804 1.1 christos #else 4805 1.1 christos # define FMA (char *)0, (unsigned *)0, 0 4806 1.1 christos #endif 4807 1.1 christos 4808 1.1 christos if (!state -> link_to_peer || 4809 1.1 christos state -> link_to_peer -> type != dhcp_type_failover_link) 4810 1.1 christos return DHCP_R_INVALIDARG; 4811 1.1 christos link = (dhcp_failover_link_t *)state -> link_to_peer; 4812 1.1 christos 4813 1.1 christos if (!link -> outer || link -> outer -> type != omapi_type_connection) 4814 1.1 christos return DHCP_R_INVALIDARG; 4815 1.1 christos 4816 1.1 christos if (!message && reason) 4817 1.1 christos message = dhcp_failover_reject_reason_print (reason); 4818 1.1 christos 4819 1.1 christos /* Send the update. */ 4820 1.1 christos status = (dhcp_failover_put_message 4821 1.1 christos (link, link -> outer, 4822 1.1 christos FTM_BNDACK, msg->xid, 4823 1.1 christos dhcp_failover_make_option (FTO_ASSIGNED_IP_ADDRESS, FMA, 4824 1.1 christos sizeof msg -> assigned_addr, 4825 1.1 christos &msg -> assigned_addr), 4826 1.1 christos #ifdef DO_BNDACK_SHOULD_NOT 4827 1.1 christos dhcp_failover_make_option (FTO_BINDING_STATUS, FMA, 4828 1.1 christos msg -> binding_status), 4829 1.1 christos (msg -> options_present & FTB_CLIENT_IDENTIFIER) 4830 1.1 christos ? dhcp_failover_make_option (FTO_CLIENT_IDENTIFIER, FMA, 4831 1.1 christos msg -> client_identifier.count, 4832 1.1 christos msg -> client_identifier.data) 4833 1.1 christos : &skip_failover_option, 4834 1.1 christos (msg -> options_present & FTB_CHADDR) 4835 1.1 christos ? dhcp_failover_make_option (FTO_CHADDR, FMA, 4836 1.1 christos msg -> chaddr.count, 4837 1.1 christos msg -> chaddr.data) 4838 1.1 christos : &skip_failover_option, 4839 1.1 christos dhcp_failover_make_option (FTO_LEASE_EXPIRY, FMA, 4840 1.1 christos msg -> expiry), 4841 1.1 christos dhcp_failover_make_option (FTO_POTENTIAL_EXPIRY, FMA, 4842 1.1 christos msg -> potential_expiry), 4843 1.1 christos dhcp_failover_make_option (FTO_STOS, FMA, 4844 1.1 christos msg -> stos), 4845 1.1 christos (msg->options_present & FTB_CLTT) ? 4846 1.1 christos dhcp_failover_make_option(FTO_CLTT, FMA, msg->cltt) : 4847 1.1 christos &skip_failover_option, /* No CLTT in the msg to ack. */ 4848 1.1 christos ((msg->options_present & FTB_IP_FLAGS) && msg->ip_flags) ? 4849 1.1 christos dhcp_failover_make_option(FTO_IP_FLAGS, FMA, 4850 1.1 christos msg->ip_flags) 4851 1.1 christos : &skip_failover_option, 4852 1.1 christos #endif /* DO_BNDACK_SHOULD_NOT */ 4853 1.1 christos reason 4854 1.1 christos ? dhcp_failover_make_option(FTO_REJECT_REASON, FMA, reason) 4855 1.1 christos : &skip_failover_option, 4856 1.1 christos (reason && message) 4857 1.1 christos ? dhcp_failover_make_option (FTO_MESSAGE, FMA, 4858 1.1 christos strlen (message), message) 4859 1.1 christos : &skip_failover_option, 4860 1.1 christos #ifdef DO_BNDACK_SHOULD_NOT 4861 1.1 christos &skip_failover_option, /* XXX DDNS */ 4862 1.1 christos &skip_failover_option, /* XXX request options */ 4863 1.1 christos &skip_failover_option, /* XXX reply options */ 4864 1.1 christos #endif /* DO_BNDACK_SHOULD_NOT */ 4865 1.1 christos (failover_option_t *)0)); 4866 1.1 christos 4867 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4868 1.1 christos if (status != ISC_R_SUCCESS) 4869 1.1 christos failover_print (FMA, " (failed)"); 4870 1.1 christos failover_print (FMA, ")"); 4871 1.1 christos if (obufix) { 4872 1.1 christos log_debug ("%s", obuf); 4873 1.1 christos } 4874 1.1 christos #endif 4875 1.1 christos return status; 4876 1.1 christos } 4877 1.1 christos 4878 1.1 christos isc_result_t dhcp_failover_send_poolreq (dhcp_failover_state_t *state) 4879 1.1 christos { 4880 1.1 christos dhcp_failover_link_t *link; 4881 1.1 christos isc_result_t status; 4882 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4883 1.1 christos char obuf [64]; 4884 1.1 christos unsigned obufix = 0; 4885 1.1 christos 4886 1.1 christos # define FMA obuf, &obufix, sizeof obuf 4887 1.1 christos failover_print (FMA, "(poolreq"); 4888 1.1 christos #else 4889 1.1 christos # define FMA (char *)0, (unsigned *)0, 0 4890 1.1 christos #endif 4891 1.1 christos 4892 1.1 christos if (!state -> link_to_peer || 4893 1.1 christos state -> link_to_peer -> type != dhcp_type_failover_link) 4894 1.1 christos return DHCP_R_INVALIDARG; 4895 1.1 christos link = (dhcp_failover_link_t *)state -> link_to_peer; 4896 1.1 christos 4897 1.1 christos if (!link -> outer || link -> outer -> type != omapi_type_connection) 4898 1.1 christos return DHCP_R_INVALIDARG; 4899 1.1 christos 4900 1.1 christos status = (dhcp_failover_put_message 4901 1.1 christos (link, link -> outer, 4902 1.1 christos FTM_POOLREQ, link->xid++, 4903 1.1 christos (failover_option_t *)0)); 4904 1.1 christos 4905 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4906 1.1 christos if (status != ISC_R_SUCCESS) 4907 1.1 christos failover_print (FMA, " (failed)"); 4908 1.1 christos failover_print (FMA, ")"); 4909 1.1 christos if (obufix) { 4910 1.1 christos log_debug ("%s", obuf); 4911 1.1 christos } 4912 1.1 christos #endif 4913 1.1 christos return status; 4914 1.1 christos } 4915 1.1 christos 4916 1.1 christos isc_result_t dhcp_failover_send_poolresp (dhcp_failover_state_t *state, 4917 1.1 christos int leases) 4918 1.1 christos { 4919 1.1 christos dhcp_failover_link_t *link; 4920 1.1 christos isc_result_t status; 4921 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4922 1.1 christos char obuf [64]; 4923 1.1 christos unsigned obufix = 0; 4924 1.1 christos 4925 1.1 christos # define FMA obuf, &obufix, sizeof obuf 4926 1.1 christos failover_print (FMA, "(poolresp"); 4927 1.1 christos #else 4928 1.1 christos # define FMA (char *)0, (unsigned *)0, 0 4929 1.1 christos #endif 4930 1.1 christos 4931 1.1 christos if (!state -> link_to_peer || 4932 1.1 christos state -> link_to_peer -> type != dhcp_type_failover_link) 4933 1.1 christos return DHCP_R_INVALIDARG; 4934 1.1 christos link = (dhcp_failover_link_t *)state -> link_to_peer; 4935 1.1 christos 4936 1.1 christos if (!link -> outer || link -> outer -> type != omapi_type_connection) 4937 1.1 christos return DHCP_R_INVALIDARG; 4938 1.1 christos 4939 1.1 christos status = (dhcp_failover_put_message 4940 1.1 christos (link, link -> outer, 4941 1.1 christos FTM_POOLRESP, link->imsg->xid, 4942 1.1 christos dhcp_failover_make_option (FTO_ADDRESSES_TRANSFERRED, FMA, 4943 1.1 christos leases), 4944 1.1 christos (failover_option_t *)0)); 4945 1.1 christos 4946 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4947 1.1 christos if (status != ISC_R_SUCCESS) 4948 1.1 christos failover_print (FMA, " (failed)"); 4949 1.1 christos failover_print (FMA, ")"); 4950 1.1 christos if (obufix) { 4951 1.1 christos log_debug ("%s", obuf); 4952 1.1 christos } 4953 1.1 christos #endif 4954 1.1 christos return status; 4955 1.1 christos } 4956 1.1 christos 4957 1.1 christos isc_result_t dhcp_failover_send_update_request (dhcp_failover_state_t *state) 4958 1.1 christos { 4959 1.1 christos dhcp_failover_link_t *link; 4960 1.1 christos isc_result_t status; 4961 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4962 1.1 christos char obuf [64]; 4963 1.1 christos unsigned obufix = 0; 4964 1.1 christos 4965 1.1 christos # define FMA obuf, &obufix, sizeof obuf 4966 1.1 christos failover_print (FMA, "(updreq"); 4967 1.1 christos #else 4968 1.1 christos # define FMA (char *)0, (unsigned *)0, 0 4969 1.1 christos #endif 4970 1.1 christos 4971 1.1 christos if (!state->link_to_peer || 4972 1.1 christos state->link_to_peer->type != dhcp_type_failover_link) 4973 1.1 christos return (DHCP_R_INVALIDARG); 4974 1.1 christos link = (dhcp_failover_link_t *)state->link_to_peer; 4975 1.1 christos 4976 1.1 christos if (!link->outer || link->outer->type != omapi_type_connection) 4977 1.1 christos return (DHCP_R_INVALIDARG); 4978 1.1 christos 4979 1.1 christos /* We allow an update to be restarted in case we requested an update 4980 1.1 christos * and were interrupted by something. If we had an ALL going we need 4981 1.1 christos * to restart that. Otherwise we simply continue with the request */ 4982 1.1 christos if (state->curUPD == FTM_UPDREQALL) { 4983 1.1 christos return (dhcp_failover_send_update_request_all(state)); 4984 1.1 christos } 4985 1.1 christos 4986 1.1 christos status = (dhcp_failover_put_message(link, link->outer, FTM_UPDREQ, 4987 1.1 christos link->xid++, NULL)); 4988 1.1 christos 4989 1.1 christos state->curUPD = FTM_UPDREQ; 4990 1.1 christos 4991 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 4992 1.1 christos if (status != ISC_R_SUCCESS) 4993 1.1 christos failover_print(FMA, " (failed)"); 4994 1.1 christos failover_print(FMA, ")"); 4995 1.1 christos if (obufix) { 4996 1.1 christos log_debug("%s", obuf); 4997 1.1 christos } 4998 1.1 christos #endif 4999 1.1 christos 5000 1.1 christos if (status == ISC_R_SUCCESS) { 5001 1.1 christos log_info("Sent update request message to %s", state->name); 5002 1.1 christos } else { 5003 1.1 christos log_error("Failed to send update request all message to %s: %s", 5004 1.1 christos state->name, isc_result_totext(status)); 5005 1.1 christos } 5006 1.1 christos return (status); 5007 1.1 christos } 5008 1.1 christos 5009 1.1 christos isc_result_t dhcp_failover_send_update_request_all (dhcp_failover_state_t 5010 1.1 christos *state) 5011 1.1 christos { 5012 1.1 christos dhcp_failover_link_t *link; 5013 1.1 christos isc_result_t status; 5014 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 5015 1.1 christos char obuf [64]; 5016 1.1 christos unsigned obufix = 0; 5017 1.1 christos 5018 1.1 christos # define FMA obuf, &obufix, sizeof obuf 5019 1.1 christos failover_print (FMA, "(updreqall"); 5020 1.1 christos #else 5021 1.1 christos # define FMA (char *)0, (unsigned *)0, 0 5022 1.1 christos #endif 5023 1.1 christos 5024 1.1 christos if (!state->link_to_peer || 5025 1.1 christos state->link_to_peer->type != dhcp_type_failover_link) 5026 1.1 christos return (DHCP_R_INVALIDARG); 5027 1.1 christos link = (dhcp_failover_link_t *)state->link_to_peer; 5028 1.1 christos 5029 1.1 christos if (!link->outer || link->outer->type != omapi_type_connection) 5030 1.1 christos return (DHCP_R_INVALIDARG); 5031 1.1 christos 5032 1.1 christos /* We allow an update to be restarted in case we requested an update 5033 1.1 christos * and were interrupted by something. 5034 1.1 christos */ 5035 1.1 christos 5036 1.1 christos status = (dhcp_failover_put_message(link, link->outer, FTM_UPDREQALL, 5037 1.1 christos link->xid++, NULL)); 5038 1.1 christos 5039 1.1 christos state->curUPD = FTM_UPDREQALL; 5040 1.1 christos 5041 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 5042 1.1 christos if (status != ISC_R_SUCCESS) 5043 1.1 christos failover_print(FMA, " (failed)"); 5044 1.1 christos failover_print(FMA, ")"); 5045 1.1 christos if (obufix) { 5046 1.1 christos log_debug("%s", obuf); 5047 1.1 christos } 5048 1.1 christos #endif 5049 1.1 christos 5050 1.1 christos if (status == ISC_R_SUCCESS) { 5051 1.1 christos log_info("Sent update request all message to %s", state->name); 5052 1.1 christos } else { 5053 1.1 christos log_error("Failed to send update request all message to %s: %s", 5054 1.1 christos state->name, isc_result_totext(status)); 5055 1.1 christos } 5056 1.1 christos return (status); 5057 1.1 christos } 5058 1.1 christos 5059 1.1 christos isc_result_t dhcp_failover_send_update_done (dhcp_failover_state_t *state) 5060 1.1 christos { 5061 1.1 christos dhcp_failover_link_t *link; 5062 1.1 christos isc_result_t status; 5063 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 5064 1.1 christos char obuf [64]; 5065 1.1 christos unsigned obufix = 0; 5066 1.1 christos 5067 1.1 christos # define FMA obuf, &obufix, sizeof obuf 5068 1.1 christos failover_print (FMA, "(upddone"); 5069 1.1 christos #else 5070 1.1 christos # define FMA (char *)0, (unsigned *)0, 0 5071 1.1 christos #endif 5072 1.1 christos 5073 1.1 christos if (!state -> link_to_peer || 5074 1.1 christos state -> link_to_peer -> type != dhcp_type_failover_link) 5075 1.1 christos return DHCP_R_INVALIDARG; 5076 1.1 christos link = (dhcp_failover_link_t *)state -> link_to_peer; 5077 1.1 christos 5078 1.1 christos if (!link -> outer || link -> outer -> type != omapi_type_connection) 5079 1.1 christos return DHCP_R_INVALIDARG; 5080 1.1 christos 5081 1.1 christos status = (dhcp_failover_put_message 5082 1.1 christos (link, link -> outer, 5083 1.1 christos FTM_UPDDONE, state->updxid, 5084 1.1 christos (failover_option_t *)0)); 5085 1.1 christos 5086 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 5087 1.1 christos if (status != ISC_R_SUCCESS) 5088 1.1 christos failover_print (FMA, " (failed)"); 5089 1.1 christos failover_print (FMA, ")"); 5090 1.1 christos if (obufix) { 5091 1.1 christos log_debug ("%s", obuf); 5092 1.1 christos } 5093 1.1 christos #endif 5094 1.1 christos 5095 1.1 christos log_info ("Sent update done message to %s", state -> name); 5096 1.1 christos 5097 1.1 christos state->updxid--; /* Paranoia, just so it mismatches. */ 5098 1.1 christos 5099 1.1 christos /* There may be uncommitted leases at this point (since 5100 1.1 christos dhcp_failover_process_bind_ack() doesn't commit leases); 5101 1.1 christos commit the lease file. */ 5102 1.1 christos commit_leases(); 5103 1.1 christos 5104 1.1 christos return status; 5105 1.1 christos } 5106 1.1 christos 5107 1.1 christos /* 5108 1.1 christos * failover_lease_is_better() compares the binding update in 'msg' with 5109 1.1 christos * the current lease in 'lease'. If the determination is that the binding 5110 1.1 christos * update shouldn't be allowed to update/crush more critical binding info 5111 1.1 christos * on the lease, the lease is preferred. A value of true is returned if the 5112 1.1 christos * local lease is preferred, or false if the remote binding update is 5113 1.1 christos * preferred. 5114 1.1 christos * 5115 1.1 christos * For now this function is hopefully simplistic and trivial. It may be that 5116 1.1 christos * a more detailed system of preferences is required, so this is something we 5117 1.1 christos * should monitor as we gain experience with these dueling events. 5118 1.1 christos */ 5119 1.1 christos static isc_boolean_t 5120 1.1 christos failover_lease_is_better(dhcp_failover_state_t *state, struct lease *lease, 5121 1.1 christos failover_message_t *msg) 5122 1.1 christos { 5123 1.1 christos binding_state_t local_state; 5124 1.1 christos TIME msg_cltt; 5125 1.1 christos 5126 1.1 christos if (lease->binding_state != lease->desired_binding_state) 5127 1.1 christos local_state = lease->desired_binding_state; 5128 1.1 christos else 5129 1.1 christos local_state = lease->binding_state; 5130 1.1 christos 5131 1.1 christos if ((msg->options_present & FTB_CLTT) != 0) 5132 1.1 christos msg_cltt = msg->cltt; 5133 1.1 christos else 5134 1.1 christos msg_cltt = 0; 5135 1.1 christos 5136 1.1 christos switch(local_state) { 5137 1.1 christos case FTS_ACTIVE: 5138 1.1 christos if (msg->binding_status == FTS_ACTIVE) { 5139 1.1 christos if (msg_cltt < lease->cltt) 5140 1.1 christos return ISC_TRUE; 5141 1.1 christos else if (msg_cltt > lease->cltt) 5142 1.1 christos return ISC_FALSE; 5143 1.1 christos else if (state->i_am == primary) 5144 1.1 christos return ISC_TRUE; 5145 1.1 christos else 5146 1.1 christos return ISC_FALSE; 5147 1.1 christos } else if (msg->binding_status == FTS_EXPIRED) { 5148 1.1 christos return ISC_FALSE; 5149 1.1 christos } 5150 1.1 christos /* FALL THROUGH */ 5151 1.1 christos 5152 1.1 christos case FTS_FREE: 5153 1.1 christos case FTS_BACKUP: 5154 1.1 christos case FTS_EXPIRED: 5155 1.1 christos case FTS_RELEASED: 5156 1.1 christos case FTS_ABANDONED: 5157 1.1 christos case FTS_RESET: 5158 1.1 christos if (msg->binding_status == FTS_ACTIVE) 5159 1.1 christos return ISC_FALSE; 5160 1.1 christos else if (state->i_am == primary) 5161 1.1 christos return ISC_TRUE; 5162 1.1 christos else 5163 1.1 christos return ISC_FALSE; 5164 1.1 christos /* FALL THROUGH to impossible condition */ 5165 1.1 christos 5166 1.1 christos default: 5167 1.1 christos log_fatal("Impossible condition at %s:%d.", MDL); 5168 1.1 christos } 5169 1.1 christos 5170 1.1 christos log_fatal("Impossible condition at %s:%d.", MDL); 5171 1.1 christos /* Silence compiler warning. */ 5172 1.1 christos return ISC_FALSE; 5173 1.1 christos } 5174 1.1 christos 5175 1.1 christos isc_result_t dhcp_failover_process_bind_update (dhcp_failover_state_t *state, 5176 1.1 christos failover_message_t *msg) 5177 1.1 christos { 5178 1.1 christos struct lease *lt = NULL, *lease = NULL; 5179 1.1 christos struct iaddr ia; 5180 1.1 christos int reason = FTR_MISC_REJECT; 5181 1.1 christos const char *message; 5182 1.1 christos int new_binding_state; 5183 1.1 christos int send_to_backup = 0; 5184 1.1 christos int required_options; 5185 1.1 christos isc_boolean_t chaddr_changed = ISC_FALSE; 5186 1.1 christos isc_boolean_t ident_changed = ISC_FALSE; 5187 1.1 christos 5188 1.1 christos /* Validate the binding update. */ 5189 1.1 christos required_options = FTB_ASSIGNED_IP_ADDRESS | FTB_BINDING_STATUS; 5190 1.1 christos if ((msg->options_present & required_options) != required_options) { 5191 1.1 christos message = "binding update lacks required options"; 5192 1.1 christos reason = FTR_MISSING_BINDINFO; 5193 1.1 christos goto bad; 5194 1.1 christos } 5195 1.1 christos 5196 1.1 christos ia.len = sizeof msg -> assigned_addr; 5197 1.1 christos memcpy (ia.iabuf, &msg -> assigned_addr, ia.len); 5198 1.1 christos 5199 1.1 christos if (!find_lease_by_ip_addr (&lease, ia, MDL)) { 5200 1.1 christos message = "unknown IP address"; 5201 1.1 christos reason = FTR_ILLEGAL_IP_ADDR; 5202 1.1 christos goto bad; 5203 1.1 christos } 5204 1.1 christos 5205 1.1 christos /* 5206 1.1 christos * If this lease is covered by a different failover peering 5207 1.1 christos * relationship, assert an error. 5208 1.1 christos */ 5209 1.1 christos if ((lease->pool == NULL) || (lease->pool->failover_peer == NULL) || 5210 1.1 christos (lease->pool->failover_peer != state)) { 5211 1.1 christos message = "IP address is covered by a different failover " 5212 1.1 christos "relationship state"; 5213 1.1 christos reason = FTR_ILLEGAL_IP_ADDR; 5214 1.1 christos goto bad; 5215 1.1 christos } 5216 1.1 christos 5217 1.1 christos /* 5218 1.1 christos * Dueling updates: This happens when both servers send a BNDUPD 5219 1.1 christos * at the same time. We want the best update to win, which means 5220 1.1 christos * we reject if we think ours is better, or cancel if we think the 5221 1.1 christos * peer's is better. We only assert a problem if the lease is on 5222 1.1 christos * the ACK queue, not on the UPDATE queue. This means that after 5223 1.1 christos * accepting this server's BNDUPD, we will send our own BNDUPD 5224 1.1 christos * /after/ sending the BNDACK (this order was recently enforced in 5225 1.1 christos * queue processing). 5226 1.1 christos */ 5227 1.1 christos if ((lease->flags & ON_ACK_QUEUE) != 0) { 5228 1.1 christos if (failover_lease_is_better(state, lease, msg)) { 5229 1.1 christos message = "incoming update is less critical than " 5230 1.1 christos "outgoing update"; 5231 1.1 christos reason = FTR_LESS_CRIT_BIND_INFO; 5232 1.1 christos goto bad; 5233 1.1 christos } else { 5234 1.1 christos /* This makes it so we ignore any spurious ACKs. */ 5235 1.1 christos dhcp_failover_ack_queue_remove(state, lease); 5236 1.1 christos } 5237 1.1 christos } 5238 1.1 christos 5239 1.1 christos /* Install the new info. Start by taking a copy to markup. */ 5240 1.1 christos if (!lease_copy (<, lease, MDL)) { 5241 1.1 christos message = "no memory"; 5242 1.1 christos goto bad; 5243 1.1 christos } 5244 1.1 christos 5245 1.1 christos if (msg -> options_present & FTB_CHADDR) { 5246 1.1 christos if (msg->binding_status == FTS_ABANDONED) { 5247 1.1 christos message = "BNDUPD to ABANDONED with a CHADDR"; 5248 1.1 christos goto bad; 5249 1.1 christos } 5250 1.1 christos if (msg -> chaddr.count > sizeof lt -> hardware_addr.hbuf) { 5251 1.1 christos message = "chaddr too long"; 5252 1.1 christos goto bad; 5253 1.1 christos } 5254 1.1 christos 5255 1.1 christos if ((lt->hardware_addr.hlen != msg->chaddr.count) || 5256 1.1 christos (memcmp(lt->hardware_addr.hbuf, msg->chaddr.data, 5257 1.1 christos msg->chaddr.count) != 0)) 5258 1.1 christos chaddr_changed = ISC_TRUE; 5259 1.1 christos 5260 1.1 christos lt -> hardware_addr.hlen = msg -> chaddr.count; 5261 1.1 christos memcpy (lt -> hardware_addr.hbuf, msg -> chaddr.data, 5262 1.1 christos msg -> chaddr.count); 5263 1.1 christos } else if (msg->binding_status == FTS_ACTIVE || 5264 1.1 christos msg->binding_status == FTS_EXPIRED || 5265 1.1 christos msg->binding_status == FTS_RELEASED) { 5266 1.1 christos message = "BNDUPD without CHADDR"; 5267 1.1 christos reason = FTR_MISSING_BINDINFO; 5268 1.1 christos goto bad; 5269 1.1 christos } else if (msg->binding_status == FTS_ABANDONED) { 5270 1.1 christos chaddr_changed = ISC_TRUE; 5271 1.1 christos lt->hardware_addr.hlen = 0; 5272 1.1 christos if (lt->scope) 5273 1.1 christos binding_scope_dereference(<->scope, MDL); 5274 1.1 christos } 5275 1.1 christos 5276 1.1 christos /* There is no explicit message content to indicate that the client 5277 1.1 christos * supplied no client-identifier. So if we don't hear of a value, 5278 1.1 christos * we discard the last one. 5279 1.1 christos */ 5280 1.1 christos if (msg->options_present & FTB_CLIENT_IDENTIFIER) { 5281 1.1 christos if (msg->binding_status == FTS_ABANDONED) { 5282 1.1 christos message = "BNDUPD to ABANDONED with client-id"; 5283 1.1 christos goto bad; 5284 1.1 christos } 5285 1.1 christos 5286 1.1 christos if ((lt->uid_len != msg->client_identifier.count) || 5287 1.1 christos (lt->uid == NULL) || /* Sanity; should never happen. */ 5288 1.1 christos (memcmp(lt->uid, msg->client_identifier.data, 5289 1.1 christos lt->uid_len) != 0)) 5290 1.1 christos ident_changed = ISC_TRUE; 5291 1.1 christos 5292 1.1 christos lt->uid_len = msg->client_identifier.count; 5293 1.1 christos 5294 1.1 christos /* Allocate the lt->uid buffer if we haven't already, or 5295 1.1 christos * re-allocate the lt-uid buffer if we have one that is not 5296 1.1 christos * large enough. Otherwise, just use the extant buffer. 5297 1.1 christos */ 5298 1.1 christos if (!lt->uid || lt->uid == lt->uid_buf || 5299 1.1 christos lt->uid_len > lt->uid_max) { 5300 1.1 christos if (lt->uid && lt->uid != lt->uid_buf) 5301 1.1 christos dfree(lt->uid, MDL); 5302 1.1 christos 5303 1.1 christos if (lt->uid_len > sizeof(lt->uid_buf)) { 5304 1.1 christos lt->uid_max = lt->uid_len; 5305 1.1 christos lt->uid = dmalloc(lt->uid_len, MDL); 5306 1.1 christos if (!lt->uid) { 5307 1.1 christos message = "no memory"; 5308 1.1 christos goto bad; 5309 1.1 christos } 5310 1.1 christos } else { 5311 1.1 christos lt->uid_max = sizeof(lt->uid_buf); 5312 1.1 christos lt->uid = lt->uid_buf; 5313 1.1 christos } 5314 1.1 christos } 5315 1.1 christos memcpy (lt -> uid, 5316 1.1 christos msg -> client_identifier.data, lt -> uid_len); 5317 1.1 christos } else if (lt->uid && msg->binding_status != FTS_RESET && 5318 1.1 christos msg->binding_status != FTS_FREE && 5319 1.1 christos msg->binding_status != FTS_BACKUP) { 5320 1.1 christos ident_changed = ISC_TRUE; 5321 1.1 christos if (lt->uid != lt->uid_buf) 5322 1.1 christos dfree (lt->uid, MDL); 5323 1.1 christos lt->uid = NULL; 5324 1.1 christos lt->uid_max = lt->uid_len = 0; 5325 1.1 christos } 5326 1.1 christos 5327 1.1 christos /* 5328 1.1 christos * A server's configuration can assign a 'binding scope'; 5329 1.1 christos * 5330 1.1 christos * set var = "value"; 5331 1.1 christos * 5332 1.1 christos * The problem with these binding scopes is that they are refreshed 5333 1.1 christos * when the server processes a client's DHCP packet. A local binding 5334 1.1 christos * scope is trash, then, when the lease has been assigned by the 5335 1.1 christos * partner server. There is no real way to detect this, a peer may 5336 1.1 christos * be updating us (as through potential conflict) with a binding we 5337 1.1 christos * sent them, but we can trivially detect the /problematic/ case; 5338 1.1 christos * 5339 1.1 christos * lease is free. 5340 1.1 christos * primary allocates lease to client A, assigns ddns name A. 5341 1.1 christos * primary fails. 5342 1.1 christos * secondary enters partner down. 5343 1.1 christos * lease expires, and is set free. 5344 1.1 christos * lease is allocated to client B and given ddns name B. 5345 1.1 christos * primary recovers. 5346 1.1 christos * 5347 1.1 christos * The binding update in this case will be active->active, but the 5348 1.1 christos * client identification on the lease will have changed. The ddns 5349 1.1 christos * update on client A will have leaked if we just remove the binding 5350 1.1 christos * scope blindly. 5351 1.1 christos */ 5352 1.1 christos if (msg->binding_status == FTS_ACTIVE && 5353 1.1 christos (chaddr_changed || ident_changed)) { 5354 1.1 christos #if defined (NSUPDATE) 5355 1.1 christos (void) ddns_removals(lease, NULL, NULL, ISC_FALSE); 5356 1.1 christos #endif /* NSUPDATE */ 5357 1.1 christos 5358 1.1 christos if (lease->scope != NULL) 5359 1.1 christos binding_scope_dereference(&lease->scope, MDL); 5360 1.1 christos } 5361 1.1 christos 5362 1.1 christos /* XXX Times may need to be adjusted based on clock skew! */ 5363 1.1 christos if (msg -> options_present & FTB_STOS) { 5364 1.1 christos lt -> starts = msg -> stos; 5365 1.1 christos } 5366 1.1 christos if (msg -> options_present & FTB_LEASE_EXPIRY) { 5367 1.1 christos lt -> ends = msg -> expiry; 5368 1.1 christos } 5369 1.1 christos if (msg->options_present & FTB_POTENTIAL_EXPIRY) { 5370 1.1 christos lt->atsfp = lt->tsfp = msg->potential_expiry; 5371 1.1 christos } 5372 1.1 christos if (msg->options_present & FTB_IP_FLAGS) { 5373 1.1 christos if (msg->ip_flags & FTF_IP_FLAG_RESERVE) { 5374 1.1 christos if ((((state->i_am == primary) && 5375 1.1 christos (lease->binding_state == FTS_FREE)) || 5376 1.1 christos ((state->i_am == secondary) && 5377 1.1 christos (lease->binding_state == FTS_BACKUP))) && 5378 1.1 christos !(lease->flags & RESERVED_LEASE)) { 5379 1.1 christos message = "Address is not reserved."; 5380 1.1 christos reason = FTR_IP_NOT_RESERVED; 5381 1.1 christos goto bad; 5382 1.1 christos } 5383 1.1 christos 5384 1.1 christos lt->flags |= RESERVED_LEASE; 5385 1.1 christos } else 5386 1.1 christos lt->flags &= ~RESERVED_LEASE; 5387 1.1 christos 5388 1.1 christos if (msg->ip_flags & FTF_IP_FLAG_BOOTP) { 5389 1.1 christos if ((((state->i_am == primary) && 5390 1.1 christos (lease->binding_state == FTS_FREE)) || 5391 1.1 christos ((state->i_am == secondary) && 5392 1.1 christos (lease->binding_state == FTS_BACKUP))) && 5393 1.1 christos !(lease->flags & BOOTP_LEASE)) { 5394 1.1 christos message = "Address is not allocated to BOOTP."; 5395 1.1 christos goto bad; 5396 1.1 christos } 5397 1.1 christos lt->flags |= BOOTP_LEASE; 5398 1.1 christos } else 5399 1.1 christos lt->flags &= ~BOOTP_LEASE; 5400 1.1 christos 5401 1.1 christos if (msg->ip_flags & ~(FTF_IP_FLAG_RESERVE | FTF_IP_FLAG_BOOTP)) 5402 1.1 christos log_info("Unknown IP-flags set in BNDUPD (0x%x).", 5403 1.1 christos msg->ip_flags); 5404 1.1 christos } else /* Flags may only not appear if the values are zero. */ 5405 1.1 christos lt->flags &= ~(RESERVED_LEASE | BOOTP_LEASE); 5406 1.1 christos 5407 1.1 christos #if defined (DEBUG_LEASE_STATE_TRANSITIONS) 5408 1.1 christos log_info ("processing state transition for %s: %s to %s", 5409 1.1 christos piaddr (lease -> ip_addr), 5410 1.1 christos binding_state_print (lease -> binding_state), 5411 1.1 christos binding_state_print (msg -> binding_status)); 5412 1.1 christos #endif 5413 1.1 christos 5414 1.1 christos /* If we're in normal state, make sure the state transition 5415 1.1 christos we got is valid. */ 5416 1.1 christos if (state -> me.state == normal) { 5417 1.1 christos new_binding_state = 5418 1.1 christos (normal_binding_state_transition_check 5419 1.1 christos (lease, state, msg -> binding_status, 5420 1.1 christos msg -> potential_expiry)); 5421 1.1 christos /* XXX if the transition the peer asked for isn't 5422 1.1 christos XXX allowed, maybe we should make the transition 5423 1.1 christos XXX into potential-conflict at this point. */ 5424 1.1 christos } else { 5425 1.1 christos new_binding_state = 5426 1.1 christos (conflict_binding_state_transition_check 5427 1.1 christos (lease, state, msg -> binding_status, 5428 1.1 christos msg -> potential_expiry)); 5429 1.1 christos } 5430 1.1 christos if (new_binding_state != msg -> binding_status) { 5431 1.1 christos char outbuf [100]; 5432 1.1 christos 5433 1.1 christos if (snprintf (outbuf, sizeof outbuf, 5434 1.1 christos "%s: invalid state transition: %s to %s", 5435 1.1 christos piaddr (lease -> ip_addr), 5436 1.1 christos binding_state_print (lease -> binding_state), 5437 1.1 christos binding_state_print (msg -> binding_status)) 5438 1.1 christos >= sizeof outbuf) 5439 1.1 christos log_fatal ("%s: impossible outbuf overflow", 5440 1.1 christos "dhcp_failover_process_bind_update"); 5441 1.1 christos 5442 1.1 christos dhcp_failover_send_bind_ack (state, msg, 5443 1.1 christos FTR_FATAL_CONFLICT, 5444 1.1 christos outbuf); 5445 1.1 christos goto out; 5446 1.1 christos } 5447 1.1 christos if (new_binding_state == FTS_EXPIRED || 5448 1.1 christos new_binding_state == FTS_RELEASED || 5449 1.1 christos new_binding_state == FTS_RESET) { 5450 1.1 christos lt -> next_binding_state = FTS_FREE; 5451 1.1 christos 5452 1.1 christos /* Mac address affinity. Assign the lease to 5453 1.1 christos * BACKUP state if we are the primary and the 5454 1.1 christos * peer is more likely to reallocate this lease 5455 1.1 christos * to a returning client. 5456 1.1 christos */ 5457 1.1 christos if ((state->i_am == primary) && 5458 1.1 christos !(lt->flags & (RESERVED_LEASE | BOOTP_LEASE))) 5459 1.1 christos send_to_backup = peer_wants_lease(lt); 5460 1.1 christos } else { 5461 1.1 christos lt -> next_binding_state = new_binding_state; 5462 1.1 christos } 5463 1.1 christos msg -> binding_status = lt -> next_binding_state; 5464 1.1 christos 5465 1.1 christos /* 5466 1.1 christos * If we accept a peer's binding update, then we can't rewind a 5467 1.1 christos * lease behind the peer's state. 5468 1.1 christos */ 5469 1.1 christos lease->rewind_binding_state = lt->next_binding_state; 5470 1.1 christos 5471 1.1 christos /* Try to install the new information. */ 5472 1.1 christos if (!supersede_lease (lease, lt, 0, 0, 0, 0) || 5473 1.1 christos !write_lease (lease)) { 5474 1.1 christos message = "database update failed"; 5475 1.1 christos bad: 5476 1.1 christos dhcp_failover_send_bind_ack (state, msg, reason, message); 5477 1.1 christos goto out; 5478 1.1 christos } else { 5479 1.1 christos dhcp_failover_queue_ack (state, msg); 5480 1.1 christos } 5481 1.1 christos 5482 1.1 christos /* If it is probably wise, assign lease to backup state if the peer 5483 1.1 christos * is not already hoarding leases. 5484 1.1 christos */ 5485 1.1 christos if (send_to_backup && secondary_not_hoarding(state, lease->pool)) { 5486 1.1 christos lease->next_binding_state = FTS_BACKUP; 5487 1.1 christos lease->tstp = cur_time; 5488 1.1 christos lease->starts = cur_time; 5489 1.1 christos 5490 1.1 christos if (!supersede_lease(lease, NULL, 0, 1, 0, 0) || 5491 1.1 christos !write_lease(lease)) 5492 1.1 christos log_error("can't commit lease %s for mac addr " 5493 1.1 christos "affinity", piaddr(lease->ip_addr)); 5494 1.1 christos 5495 1.1 christos dhcp_failover_send_updates(state); 5496 1.1 christos } 5497 1.1 christos 5498 1.1 christos out: 5499 1.1 christos if (lt) 5500 1.1 christos lease_dereference (<, MDL); 5501 1.1 christos if (lease) 5502 1.1 christos lease_dereference (&lease, MDL); 5503 1.1 christos 5504 1.1 christos return ISC_R_SUCCESS; 5505 1.1 christos } 5506 1.1 christos 5507 1.1 christos /* This was hairy enough I didn't want to do it all in an if statement. 5508 1.1 christos * 5509 1.1 christos * Returns: Truth is the secondary is allowed to get more leases based upon 5510 1.1 christos * MAC address affinity. False otherwise. 5511 1.1 christos */ 5512 1.1 christos static inline int 5513 1.1 christos secondary_not_hoarding(dhcp_failover_state_t *state, struct pool *p) { 5514 1.1 christos int total; 5515 1.1 christos int hold; 5516 1.1 christos int lts; 5517 1.1 christos 5518 1.1 christos total = p->free_leases + p->backup_leases; 5519 1.1 christos 5520 1.1 christos /* How many leases is one side or the other allowed to "hold"? */ 5521 1.1 christos hold = ((total * state->max_lease_ownership) + 50) / 100; 5522 1.1 christos 5523 1.1 christos /* If we were to send leases (or if the secondary were to send us 5524 1.1 christos * leases in the negative direction), how many would that be? 5525 1.1 christos */ 5526 1.1 christos lts = (p->free_leases - p->backup_leases) / 2; 5527 1.1 christos 5528 1.1 christos /* The peer is not hoarding leases if we would send them more leases 5529 1.1 christos * (or they would take fewer leases) than the maximum they are allowed 5530 1.1 christos * to hold (the negative hold). 5531 1.1 christos */ 5532 1.1 christos return(lts > -hold); 5533 1.1 christos } 5534 1.1 christos 5535 1.1 christos isc_result_t dhcp_failover_process_bind_ack (dhcp_failover_state_t *state, 5536 1.1 christos failover_message_t *msg) 5537 1.1 christos { 5538 1.1 christos struct lease *lease = NULL; 5539 1.1 christos struct iaddr ia; 5540 1.1 christos const char *message = "no memory"; 5541 1.1 christos u_int32_t pot_expire; 5542 1.1 christos int send_to_backup = ISC_FALSE; 5543 1.1 christos struct timeval tv; 5544 1.1 christos 5545 1.1 christos ia.len = sizeof msg -> assigned_addr; 5546 1.1 christos memcpy (ia.iabuf, &msg -> assigned_addr, ia.len); 5547 1.1 christos 5548 1.1 christos if (!find_lease_by_ip_addr (&lease, ia, MDL)) { 5549 1.1 christos message = "no such lease"; 5550 1.1 christos goto bad; 5551 1.1 christos } 5552 1.1 christos 5553 1.1 christos /* XXX check for conflicts. */ 5554 1.1 christos if (msg -> options_present & FTB_REJECT_REASON) { 5555 1.1 christos log_error ("bind update on %s from %s rejected: %.*s", 5556 1.1 christos piaddr (ia), state -> name, 5557 1.1 christos (int)((msg -> options_present & FTB_MESSAGE) 5558 1.1 christos ? msg -> message.count 5559 1.1 christos : strlen (dhcp_failover_reject_reason_print 5560 1.1 christos (msg -> reject_reason))), 5561 1.1 christos (msg -> options_present & FTB_MESSAGE) 5562 1.1 christos ? (const char *)(msg -> message.data) 5563 1.1 christos : (dhcp_failover_reject_reason_print 5564 1.1 christos (msg -> reject_reason))); 5565 1.1 christos goto unqueue; 5566 1.1 christos } 5567 1.1 christos 5568 1.1 christos /* Silently discard acks for leases we did not update (or multiple 5569 1.1 christos * acks). 5570 1.1 christos */ 5571 1.1 christos if (!lease->last_xid) 5572 1.1 christos goto unqueue; 5573 1.1 christos 5574 1.1 christos if (lease->last_xid != msg->xid) { 5575 1.1 christos message = "xid mismatch"; 5576 1.1 christos goto bad; 5577 1.1 christos } 5578 1.1 christos 5579 1.1 christos /* XXX Times may need to be adjusted based on clock skew! */ 5580 1.1 christos if (msg->options_present & FTO_POTENTIAL_EXPIRY) 5581 1.1 christos pot_expire = msg->potential_expiry; 5582 1.1 christos else 5583 1.1 christos pot_expire = lease->tstp; 5584 1.1 christos 5585 1.1 christos /* If the lease was desired to enter a binding state, we set 5586 1.1 christos * such a value upon transmitting a bndupd. We do not clear it 5587 1.1 christos * if we receive a bndupd in the meantime (or change the state 5588 1.1 christos * of the lease again ourselves), but we do set binding_state 5589 1.1 christos * if we get a bndupd. 5590 1.1 christos * 5591 1.1 christos * So desired_binding_state tells us what we sent a bndupd for, 5592 1.1 christos * and binding_state tells us what we have since determined in 5593 1.1 christos * the meantime. 5594 1.1 christos */ 5595 1.1 christos if (lease->desired_binding_state == FTS_EXPIRED || 5596 1.1 christos lease->desired_binding_state == FTS_RESET || 5597 1.1 christos lease->desired_binding_state == FTS_RELEASED) 5598 1.1 christos { 5599 1.1 christos /* It is not a problem to do this directly as we call 5600 1.1 christos * supersede_lease immediately after: the lease is requeued 5601 1.1 christos * even if its sort order (tsfp) has changed. 5602 1.1 christos */ 5603 1.1 christos lease->atsfp = lease->tsfp = pot_expire; 5604 1.1 christos if ((state->i_am == secondary) && 5605 1.1 christos (lease->flags & RESERVED_LEASE)) 5606 1.1 christos lease->next_binding_state = FTS_BACKUP; 5607 1.1 christos else 5608 1.1 christos lease->next_binding_state = FTS_FREE; 5609 1.1 christos 5610 1.1 christos /* Clear this condition for the next go-round. */ 5611 1.1 christos lease->desired_binding_state = lease->next_binding_state; 5612 1.1 christos 5613 1.1 christos /* The peer will have made this state change, so set rewind. */ 5614 1.1 christos lease->rewind_binding_state = lease->next_binding_state; 5615 1.1 christos 5616 1.1 christos supersede_lease(lease, NULL, 0, 0, 0, 0); 5617 1.1 christos write_lease(lease); 5618 1.1 christos 5619 1.1 christos /* Lease has returned to FREE state from the 5620 1.1 christos * transitional states. If the lease 'belongs' 5621 1.1 christos * to a client that would be served by the 5622 1.1 christos * peer, process a binding update now to send 5623 1.1 christos * the lease to backup state. But not if we 5624 1.1 christos * think we already have. 5625 1.1 christos */ 5626 1.1 christos if (state->i_am == primary && 5627 1.1 christos !(lease->flags & (RESERVED_LEASE | BOOTP_LEASE)) && 5628 1.1 christos peer_wants_lease(lease)) 5629 1.1 christos send_to_backup = ISC_TRUE; 5630 1.1 christos 5631 1.1 christos if (!send_to_backup && state->me.state == normal) 5632 1.1 christos commit_leases(); 5633 1.1 christos } else { 5634 1.1 christos /* XXX It could be a problem to do this directly if the lease 5635 1.1 christos * XXX is sorted by tsfp. 5636 1.1 christos */ 5637 1.1 christos lease->atsfp = lease->tsfp = pot_expire; 5638 1.1 christos if (lease->desired_binding_state != lease->binding_state) { 5639 1.1 christos lease->next_binding_state = 5640 1.1 christos lease->desired_binding_state; 5641 1.1 christos supersede_lease(lease, NULL, 0, 0, 0, 0); 5642 1.1 christos } 5643 1.1 christos write_lease(lease); 5644 1.1 christos /* Commit the lease only after a two-second timeout, 5645 1.1 christos so that if we get a bunch of acks in quick 5646 1.1 christos succession (e.g., when stealing leases from the 5647 1.1 christos secondary), we do not do an immediate commit for 5648 1.1 christos each one. */ 5649 1.1 christos tv.tv_sec = cur_time + 2; 5650 1.1 christos tv.tv_usec = 0; 5651 1.1 christos add_timeout(&tv, commit_leases_timeout, (void *)0, 0, 0); 5652 1.1 christos } 5653 1.1 christos 5654 1.1 christos unqueue: 5655 1.1 christos dhcp_failover_ack_queue_remove (state, lease); 5656 1.1 christos 5657 1.1 christos /* If we are supposed to send an update done after we send 5658 1.1 christos this lease, go ahead and send it. */ 5659 1.1 christos if (state -> send_update_done == lease) { 5660 1.1 christos lease_dereference (&state -> send_update_done, MDL); 5661 1.1 christos dhcp_failover_send_update_done (state); 5662 1.1 christos } 5663 1.1 christos 5664 1.1 christos /* Now that the lease is off the ack queue, consider putting it 5665 1.1 christos * back on the update queue for mac address affinity. 5666 1.1 christos */ 5667 1.1 christos if (send_to_backup && secondary_not_hoarding(state, lease->pool)) { 5668 1.1 christos lease->next_binding_state = FTS_BACKUP; 5669 1.1 christos lease->tstp = lease->starts = cur_time; 5670 1.1 christos 5671 1.1 christos if (!supersede_lease(lease, NULL, 0, 1, 0, 0) || 5672 1.1 christos !write_lease(lease)) 5673 1.1 christos log_error("can't commit lease %s for " 5674 1.1 christos "client affinity", piaddr(lease->ip_addr)); 5675 1.1 christos 5676 1.1 christos if (state->me.state == normal) 5677 1.1 christos commit_leases(); 5678 1.1 christos } 5679 1.1 christos 5680 1.1 christos /* If there are updates pending, we've created space to send at 5681 1.1 christos least one. */ 5682 1.1 christos dhcp_failover_send_updates (state); 5683 1.1 christos 5684 1.1 christos out: 5685 1.1 christos lease_dereference (&lease, MDL); 5686 1.1 christos return ISC_R_SUCCESS; 5687 1.1 christos 5688 1.1 christos bad: 5689 1.1 christos log_info ("bind update on %s got ack from %s: %s.", 5690 1.1 christos piaddr (ia), state -> name, message); 5691 1.1 christos goto out; 5692 1.1 christos } 5693 1.1 christos 5694 1.1 christos isc_result_t dhcp_failover_generate_update_queue (dhcp_failover_state_t *state, 5695 1.1 christos int everythingp) 5696 1.1 christos { 5697 1.1 christos struct shared_network *s; 5698 1.1 christos struct pool *p; 5699 1.1 christos struct lease *l; 5700 1.1 christos int i; 5701 1.1 christos #define FREE_LEASES 0 5702 1.1 christos #define ACTIVE_LEASES 1 5703 1.1 christos #define EXPIRED_LEASES 2 5704 1.1 christos #define ABANDONED_LEASES 3 5705 1.1 christos #define BACKUP_LEASES 4 5706 1.1 christos #define RESERVED_LEASES 5 5707 1.1 christos LEASE_STRUCT_PTR lptr[RESERVED_LEASES+1]; 5708 1.1 christos 5709 1.1 christos /* Loop through each pool in each shared network and call the 5710 1.1 christos expiry routine on the pool. */ 5711 1.1 christos for (s = shared_networks; s; s = s -> next) { 5712 1.1 christos for (p = s -> pools; p; p = p -> next) { 5713 1.1 christos if (p->failover_peer != state) 5714 1.1 christos continue; 5715 1.1 christos 5716 1.1 christos lptr[FREE_LEASES] = &p->free; 5717 1.1 christos lptr[ACTIVE_LEASES] = &p->active; 5718 1.1 christos lptr[EXPIRED_LEASES] = &p->expired; 5719 1.1 christos lptr[ABANDONED_LEASES] = &p->abandoned; 5720 1.1 christos lptr[BACKUP_LEASES] = &p->backup; 5721 1.1 christos lptr[RESERVED_LEASES] = &p->reserved; 5722 1.1 christos 5723 1.1 christos for (i = FREE_LEASES; i <= RESERVED_LEASES; i++) { 5724 1.1 christos for (l = LEASE_GET_FIRSTP(lptr[i]); 5725 1.1 christos l != NULL; 5726 1.1 christos l = LEASE_GET_NEXTP(lptr[i], l)) { 5727 1.1 christos if ((l->flags & ON_QUEUE) == 0 && 5728 1.1 christos (everythingp || 5729 1.1 christos (l->tstp > l->atsfp) || 5730 1.1 christos (i == EXPIRED_LEASES))) { 5731 1.1 christos l -> desired_binding_state = l -> binding_state; 5732 1.1 christos dhcp_failover_queue_update (l, 0); 5733 1.1 christos } 5734 1.1 christos } 5735 1.1 christos } 5736 1.1 christos } 5737 1.1 christos } 5738 1.1 christos return ISC_R_SUCCESS; 5739 1.1 christos } 5740 1.1 christos 5741 1.1 christos isc_result_t 5742 1.1 christos dhcp_failover_process_update_request (dhcp_failover_state_t *state, 5743 1.1 christos failover_message_t *msg) 5744 1.1 christos { 5745 1.1 christos if (state->send_update_done) { 5746 1.1 christos log_info("Received update request while old update still " 5747 1.1 christos "flying! Silently discarding old request."); 5748 1.1 christos lease_dereference(&state->send_update_done, MDL); 5749 1.1 christos } 5750 1.1 christos 5751 1.1 christos /* Generate a fresh update queue. */ 5752 1.1 christos dhcp_failover_generate_update_queue (state, 0); 5753 1.1 christos 5754 1.1 christos state->updxid = msg->xid; 5755 1.1 christos 5756 1.1 christos /* If there's anything on the update queue (there shouldn't be 5757 1.1 christos anything on the ack queue), trigger an update done message 5758 1.1 christos when we get an ack for that lease. */ 5759 1.1 christos if (state -> update_queue_tail) { 5760 1.1 christos lease_reference (&state -> send_update_done, 5761 1.1 christos state -> update_queue_tail, MDL); 5762 1.1 christos dhcp_failover_send_updates (state); 5763 1.1 christos log_info ("Update request from %s: sending update", 5764 1.1 christos state -> name); 5765 1.1 christos } else { 5766 1.1 christos /* Otherwise, there are no updates to send, so we can 5767 1.1 christos just send an UPDDONE message immediately. */ 5768 1.1 christos dhcp_failover_send_update_done (state); 5769 1.1 christos log_info ("Update request from %s: nothing pending", 5770 1.1 christos state -> name); 5771 1.1 christos } 5772 1.1 christos 5773 1.1 christos return ISC_R_SUCCESS; 5774 1.1 christos } 5775 1.1 christos 5776 1.1 christos isc_result_t 5777 1.1 christos dhcp_failover_process_update_request_all (dhcp_failover_state_t *state, 5778 1.1 christos failover_message_t *msg) 5779 1.1 christos { 5780 1.1 christos if (state->send_update_done) { 5781 1.1 christos log_info("Received update request while old update still " 5782 1.1 christos "flying! Silently discarding old request."); 5783 1.1 christos lease_dereference(&state->send_update_done, MDL); 5784 1.1 christos } 5785 1.1 christos 5786 1.1 christos /* Generate a fresh update queue that includes every lease. */ 5787 1.1 christos dhcp_failover_generate_update_queue (state, 1); 5788 1.1 christos 5789 1.1 christos state->updxid = msg->xid; 5790 1.1 christos 5791 1.1 christos if (state -> update_queue_tail) { 5792 1.1 christos lease_reference (&state -> send_update_done, 5793 1.1 christos state -> update_queue_tail, MDL); 5794 1.1 christos dhcp_failover_send_updates (state); 5795 1.1 christos log_info ("Update request all from %s: sending update", 5796 1.1 christos state -> name); 5797 1.1 christos } else { 5798 1.1 christos /* This should really never happen, but it could happen 5799 1.1 christos on a server that currently has no leases configured. */ 5800 1.1 christos dhcp_failover_send_update_done (state); 5801 1.1 christos log_info ("Update request all from %s: nothing pending", 5802 1.1 christos state -> name); 5803 1.1 christos } 5804 1.1 christos 5805 1.1 christos return ISC_R_SUCCESS; 5806 1.1 christos } 5807 1.1 christos 5808 1.1 christos isc_result_t 5809 1.1 christos dhcp_failover_process_update_done (dhcp_failover_state_t *state, 5810 1.1 christos failover_message_t *msg) 5811 1.1 christos { 5812 1.1 christos struct timeval tv; 5813 1.1 christos 5814 1.1 christos log_info ("failover peer %s: peer update completed.", 5815 1.1 christos state -> name); 5816 1.1 christos 5817 1.1 christos state -> curUPD = 0; 5818 1.1 christos 5819 1.1 christos switch (state -> me.state) { 5820 1.1 christos case unknown_state: 5821 1.1 christos case partner_down: 5822 1.1 christos case normal: 5823 1.1 christos case communications_interrupted: 5824 1.1 christos case resolution_interrupted: 5825 1.1 christos case shut_down: 5826 1.1 christos case paused: 5827 1.1 christos case recover_done: 5828 1.1 christos case startup: 5829 1.1 christos case recover_wait: 5830 1.1 christos break; /* shouldn't happen. */ 5831 1.1 christos 5832 1.1 christos /* We got the UPDDONE, so we can go into normal state! */ 5833 1.1 christos case potential_conflict: 5834 1.1 christos if (state->partner.state == conflict_done) { 5835 1.1 christos if (state->i_am == secondary) { 5836 1.1 christos dhcp_failover_set_state (state, normal); 5837 1.1 christos } else { 5838 1.1 christos log_error("Secondary is in conflict_done " 5839 1.1 christos "state after conflict resolution, " 5840 1.1 christos "this is illegal."); 5841 1.1 christos dhcp_failover_set_state (state, shut_down); 5842 1.1 christos } 5843 1.1 christos } else { 5844 1.1 christos if (state->i_am == primary) 5845 1.1 christos dhcp_failover_set_state (state, conflict_done); 5846 1.1 christos else 5847 1.1 christos log_error("Spurious update-done message."); 5848 1.1 christos } 5849 1.1 christos 5850 1.1 christos break; 5851 1.1 christos 5852 1.1 christos case conflict_done: 5853 1.1 christos log_error("Spurious update-done message."); 5854 1.1 christos break; 5855 1.1 christos 5856 1.1 christos case recover: 5857 1.1 christos /* Wait for MCLT to expire before moving to recover_done, 5858 1.1 christos except that if both peers come up in recover, there is 5859 1.1 christos no point in waiting for MCLT to expire - this probably 5860 1.1 christos indicates the initial startup of a newly-configured 5861 1.1 christos failover pair. */ 5862 1.1 christos if (state -> me.stos + state -> mclt > cur_time && 5863 1.1 christos state -> partner.state != recover && 5864 1.1 christos state -> partner.state != recover_done) { 5865 1.1 christos dhcp_failover_set_state (state, recover_wait); 5866 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 5867 1.1 christos log_info ("add_timeout +%d %s", 5868 1.1 christos (int)(cur_time - 5869 1.1 christos state -> me.stos + state -> mclt), 5870 1.1 christos "dhcp_failover_recover_done"); 5871 1.1 christos #endif 5872 1.1 christos tv . tv_sec = (int)(state -> me.stos + state -> mclt); 5873 1.1 christos tv . tv_usec = 0; 5874 1.1 christos add_timeout (&tv, 5875 1.1 christos dhcp_failover_recover_done, 5876 1.1 christos state, 5877 1.1 christos (tvref_t)omapi_object_reference, 5878 1.1 christos (tvunref_t) 5879 1.1 christos omapi_object_dereference); 5880 1.1 christos } else 5881 1.1 christos dhcp_failover_recover_done (state); 5882 1.1 christos } 5883 1.1 christos 5884 1.1 christos return ISC_R_SUCCESS; 5885 1.1 christos } 5886 1.1 christos 5887 1.1 christos void dhcp_failover_recover_done (void *sp) 5888 1.1 christos { 5889 1.1 christos dhcp_failover_state_t *state = sp; 5890 1.1 christos 5891 1.1 christos #if defined (DEBUG_FAILOVER_TIMING) 5892 1.1 christos log_info ("dhcp_failover_recover_done"); 5893 1.1 christos #endif 5894 1.1 christos 5895 1.1 christos dhcp_failover_set_state (state, recover_done); 5896 1.1 christos } 5897 1.1 christos 5898 1.1 christos #if defined (DEBUG_FAILOVER_MESSAGES) 5899 1.1 christos /* Print hunks of failover messages, doing line breaks as appropriate. 5900 1.1 christos Note that this assumes syslog is being used, rather than, e.g., the 5901 1.1 christos Windows NT logging facility, where just dumping the whole message in 5902 1.1 christos one hunk would be more appropriate. */ 5903 1.1 christos 5904 1.1 christos void failover_print (char *obuf, 5905 1.1 christos unsigned *obufix, unsigned obufmax, const char *s) 5906 1.1 christos { 5907 1.1 christos int len = strlen (s); 5908 1.1 christos 5909 1.1 christos while (len + *obufix + 1 >= obufmax) { 5910 1.1 christos log_debug ("%s", obuf); 5911 1.1 christos if (!*obufix) { 5912 1.1 christos log_debug ("%s", s); 5913 1.1 christos *obufix = 0; 5914 1.1 christos return; 5915 1.1 christos } 5916 1.1 christos *obufix = 0; 5917 1.1 christos } 5918 1.1 christos strcpy (&obuf [*obufix], s); 5919 1.1 christos *obufix += len; 5920 1.1 christos } 5921 1.1 christos #endif /* defined (DEBUG_FAILOVER_MESSAGES) */ 5922 1.1 christos 5923 1.1 christos /* Taken from draft-ietf-dhc-loadb-01.txt: */ 5924 1.1 christos /* A "mixing table" of 256 distinct values, in pseudo-random order. */ 5925 1.1 christos unsigned char loadb_mx_tbl[256] = { 5926 1.1 christos 251, 175, 119, 215, 81, 14, 79, 191, 103, 49, 5927 1.1 christos 181, 143, 186, 157, 0, 232, 31, 32, 55, 60, 5928 1.1 christos 152, 58, 17, 237, 174, 70, 160, 144, 220, 90, 5929 1.1 christos 57, 223, 59, 3, 18, 140, 111, 166, 203, 196, 5930 1.1 christos 134, 243, 124, 95, 222, 179, 197, 65, 180, 48, 5931 1.1 christos 36, 15, 107, 46, 233, 130, 165, 30, 123, 161, 5932 1.1 christos 209, 23, 97, 16, 40, 91, 219, 61, 100, 10, 5933 1.1 christos 210, 109, 250, 127, 22, 138, 29, 108, 244, 67, 5934 1.1 christos 207, 9, 178, 204, 74, 98, 126, 249, 167, 116, 5935 1.1 christos 34, 77, 193, 200, 121, 5, 20, 113, 71, 35, 5936 1.1 christos 128, 13, 182, 94, 25, 226, 227, 199, 75, 27, 5937 1.1 christos 41, 245, 230, 224, 43, 225, 177, 26, 155, 150, 5938 1.1 christos 212, 142, 218, 115, 241, 73, 88, 105, 39, 114, 5939 1.1 christos 62, 255, 192, 201, 145, 214, 168, 158, 221, 148, 5940 1.1 christos 154, 122, 12, 84, 82, 163, 44, 139, 228, 236, 5941 1.1 christos 205, 242, 217, 11, 187, 146, 159, 64, 86, 239, 5942 1.1 christos 195, 42, 106, 198, 118, 112, 184, 172, 87, 2, 5943 1.1 christos 173, 117, 176, 229, 247, 253, 137, 185, 99, 164, 5944 1.1 christos 102, 147, 45, 66, 231, 52, 141, 211, 194, 206, 5945 1.1 christos 246, 238, 56, 110, 78, 248, 63, 240, 189, 93, 5946 1.1 christos 92, 51, 53, 183, 19, 171, 72, 50, 33, 104, 5947 1.1 christos 101, 69, 8, 252, 83, 120, 76, 135, 85, 54, 5948 1.1 christos 202, 125, 188, 213, 96, 235, 136, 208, 162, 129, 5949 1.1 christos 190, 132, 156, 38, 47, 1, 7, 254, 24, 4, 5950 1.1 christos 216, 131, 89, 21, 28, 133, 37, 153, 149, 80, 5951 1.1 christos 170, 68, 6, 169, 234, 151 }; 5952 1.1 christos 5953 1.1 christos static unsigned char loadb_p_hash (const unsigned char *, unsigned); 5954 1.1 christos static unsigned char loadb_p_hash (const unsigned char *key, unsigned len) 5955 1.1 christos { 5956 1.1 christos unsigned char hash = len; 5957 1.1 christos int i; 5958 1.1 christos for(i = len; i > 0; ) 5959 1.1 christos hash = loadb_mx_tbl [hash ^ (key [--i])]; 5960 1.1 christos return hash; 5961 1.1 christos } 5962 1.1 christos 5963 1.1 christos int load_balance_mine (struct packet *packet, dhcp_failover_state_t *state) 5964 1.1 christos { 5965 1.1 christos struct option_cache *oc; 5966 1.1 christos struct data_string ds; 5967 1.1 christos unsigned char hbaix; 5968 1.1 christos int hm; 5969 1.1 christos u_int16_t ec; 5970 1.1 christos 5971 1.1 christos ec = ntohs(packet->raw->secs); 5972 1.1 christos 5973 1.1 christos /* 5974 1.1 christos * If desired check to see if the secs field may have been byte 5975 1.1 christos * swapped. We assume it has if the high order byte isn't cleared 5976 1.1 christos * while the low order byte is cleared. In this case we swap the 5977 1.1 christos * bytes and continue processing. 5978 1.1 christos */ 5979 1.1 christos if ((check_secs_byte_order == 1) && 5980 1.1 christos ((ec > 255) && ((ec & 0xff) == 0))) { 5981 1.1 christos ec = (ec >> 8) | (ec << 8); 5982 1.1 christos } 5983 1.1 christos 5984 1.1 christos if ((state->load_balance_max_secs == 0) || 5985 1.1 christos (state->load_balance_max_secs < ec)) { 5986 1.1 christos return (1); 5987 1.1 christos } 5988 1.1 christos 5989 1.1 christos /* If we don't have a hash bucket array, we can't tell if this 5990 1.1 christos one's ours, so we assume it's not. */ 5991 1.1 christos if (!state->hba) 5992 1.1 christos return (0); 5993 1.1 christos 5994 1.1 christos oc = lookup_option(&dhcp_universe, packet->options, 5995 1.1 christos DHO_DHCP_CLIENT_IDENTIFIER); 5996 1.1 christos memset(&ds, 0, sizeof ds); 5997 1.1 christos if (oc && 5998 1.1 christos evaluate_option_cache(&ds, packet, NULL, NULL, 5999 1.1 christos packet->options, NULL, 6000 1.1 christos &global_scope, oc, MDL)) { 6001 1.1 christos hbaix = loadb_p_hash(ds.data, ds.len); 6002 1.1 christos 6003 1.1 christos data_string_forget(&ds, MDL); 6004 1.1 christos } else { 6005 1.1 christos hbaix = loadb_p_hash(packet->raw->chaddr, 6006 1.1 christos packet->raw->hlen); 6007 1.1 christos } 6008 1.1 christos 6009 1.1 christos hm = state->hba[(hbaix >> 3) & 0x1F] & (1 << (hbaix & 0x07)); 6010 1.1 christos 6011 1.1 christos if (state->i_am == primary) 6012 1.1 christos return (hm); 6013 1.1 christos else 6014 1.1 christos return (!hm); 6015 1.1 christos } 6016 1.1 christos 6017 1.1 christos /* The inverse of load_balance_mine ("load balance theirs"). We can't 6018 1.1 christos * use the regular load_balance_mine() and invert it because of the case 6019 1.1 christos * where there might not be an HBA, and we want to indicate false here 6020 1.1 christos * in this case only. 6021 1.1 christos */ 6022 1.1 christos int 6023 1.1 christos peer_wants_lease(struct lease *lp) 6024 1.1 christos { 6025 1.1 christos dhcp_failover_state_t *state; 6026 1.1 christos unsigned char hbaix; 6027 1.1 christos int hm; 6028 1.1 christos 6029 1.1 christos if (!lp->pool) 6030 1.1 christos return 0; 6031 1.1 christos 6032 1.1 christos state = lp->pool->failover_peer; 6033 1.1 christos 6034 1.1 christos if (!state || !state->hba) 6035 1.1 christos return 0; 6036 1.1 christos 6037 1.1 christos if (lp->uid_len) 6038 1.1 christos hbaix = loadb_p_hash(lp->uid, lp->uid_len); 6039 1.1 christos else if (lp->hardware_addr.hlen > 1) 6040 1.1 christos /* Skip the first byte, which is the hardware type, and is 6041 1.1 christos * not included during actual load balancing checks above 6042 1.1 christos * since it is separate from the packet header chaddr field. 6043 1.1 christos * The remainder of the hardware address should be identical 6044 1.1 christos * to the chaddr contents. 6045 1.1 christos */ 6046 1.1 christos hbaix = loadb_p_hash(lp->hardware_addr.hbuf + 1, 6047 1.1 christos lp->hardware_addr.hlen - 1); 6048 1.1 christos else /* impossible to categorize into LBA */ 6049 1.1 christos return 0; 6050 1.1 christos 6051 1.1 christos hm = state->hba[(hbaix >> 3) & 0x1F] & (1 << (hbaix & 0x07)); 6052 1.1 christos 6053 1.1 christos if (state->i_am == primary) 6054 1.1 christos return !hm; 6055 1.1 christos else 6056 1.1 christos return hm; 6057 1.1 christos } 6058 1.1 christos 6059 1.1 christos /* This deals with what to do with bind updates when 6060 1.1 christos we're in the normal state 6061 1.1 christos 6062 1.1 christos Note that tsfp had better be set from the latest bind update 6063 1.1 christos _before_ this function is called! */ 6064 1.1 christos 6065 1.1 christos binding_state_t 6066 1.1 christos normal_binding_state_transition_check (struct lease *lease, 6067 1.1 christos dhcp_failover_state_t *state, 6068 1.1 christos binding_state_t binding_state, 6069 1.1 christos u_int32_t tsfp) 6070 1.1 christos { 6071 1.1 christos binding_state_t new_state; 6072 1.1 christos 6073 1.1 christos /* If there is no transition, it's no problem. */ 6074 1.1 christos if (binding_state == lease -> binding_state) 6075 1.1 christos return binding_state; 6076 1.1 christos 6077 1.1 christos switch (lease -> binding_state) { 6078 1.1 christos case FTS_FREE: 6079 1.1 christos case FTS_ABANDONED: 6080 1.1 christos switch (binding_state) { 6081 1.1 christos case FTS_ACTIVE: 6082 1.1 christos case FTS_ABANDONED: 6083 1.1 christos case FTS_BACKUP: 6084 1.1 christos case FTS_EXPIRED: 6085 1.1 christos case FTS_RELEASED: 6086 1.1 christos case FTS_RESET: 6087 1.1 christos /* If the lease was free, and our peer is primary, 6088 1.1 christos then it can make it active, or abandoned, or 6089 1.1 christos backup. Abandoned is treated like free in 6090 1.1 christos this case. */ 6091 1.1 christos if (state -> i_am == secondary) 6092 1.1 christos return binding_state; 6093 1.1 christos 6094 1.1 christos /* Otherwise, it can't legitimately do any sort of 6095 1.1 christos state transition. Because the lease was free, 6096 1.1 christos and the error has already been made, we allow the 6097 1.1 christos peer to change its state anyway, but log a warning 6098 1.1 christos message in hopes that the error will be fixed. */ 6099 1.1 christos case FTS_FREE: /* for compiler */ 6100 1.1 christos new_state = binding_state; 6101 1.1 christos goto out; 6102 1.1 christos 6103 1.1 christos default: 6104 1.1 christos log_fatal ("Impossible case at %s:%d.", MDL); 6105 1.1 christos return FTS_RESET; 6106 1.1 christos } 6107 1.1 christos case FTS_ACTIVE: 6108 1.1 christos /* The secondary can't change the state of an active 6109 1.1 christos lease. */ 6110 1.1 christos if (state -> i_am == primary) { 6111 1.1 christos /* Except that the client may send the DHCPRELEASE 6112 1.1 christos to the secondary. We also allow for when the 6113 1.1 christos secondary gets a DECLINE and the primary does not.*/ 6114 1.1 christos if ((binding_state == FTS_RELEASED) || 6115 1.1 christos (binding_state == FTS_ABANDONED)) 6116 1.1 christos return binding_state; 6117 1.1 christos 6118 1.1 christos new_state = lease -> binding_state; 6119 1.1 christos goto out; 6120 1.1 christos } 6121 1.1 christos 6122 1.1 christos /* So this is only for transitions made by the primary: */ 6123 1.1 christos switch (binding_state) { 6124 1.1 christos case FTS_FREE: 6125 1.1 christos case FTS_BACKUP: 6126 1.1 christos /* Can't set a lease to free or backup until the 6127 1.1 christos peer agrees that it's expired. */ 6128 1.1 christos if (tsfp > cur_time) { 6129 1.1 christos new_state = lease -> binding_state; 6130 1.1 christos goto out; 6131 1.1 christos } 6132 1.1 christos return binding_state; 6133 1.1 christos 6134 1.1 christos case FTS_EXPIRED: 6135 1.1 christos /* XXX 65 should be the clock skew between the peers 6136 1.1 christos XXX plus a fudge factor. This code will result 6137 1.1 christos XXX in problems if MCLT is really short or the 6138 1.1 christos XXX max-lease-time is really short (less than the 6139 1.1 christos XXX fudge factor. */ 6140 1.1 christos if (lease -> ends - 65 > cur_time) { 6141 1.1 christos new_state = lease -> binding_state; 6142 1.1 christos goto out; 6143 1.1 christos } 6144 1.1 christos 6145 1.1 christos case FTS_RELEASED: 6146 1.1 christos case FTS_ABANDONED: 6147 1.1 christos case FTS_RESET: 6148 1.1 christos case FTS_ACTIVE: 6149 1.1 christos return binding_state; 6150 1.1 christos 6151 1.1 christos default: 6152 1.1 christos log_fatal ("Impossible case at %s:%d.", MDL); 6153 1.1 christos return FTS_RESET; 6154 1.1 christos } 6155 1.1 christos break; 6156 1.1 christos case FTS_EXPIRED: 6157 1.1 christos switch (binding_state) { 6158 1.1 christos case FTS_BACKUP: 6159 1.1 christos case FTS_FREE: 6160 1.1 christos /* Can't set a lease to free or backup until the 6161 1.1 christos peer agrees that it's expired. */ 6162 1.1 christos if (tsfp > cur_time) { 6163 1.1 christos new_state = lease -> binding_state; 6164 1.1 christos goto out; 6165 1.1 christos } 6166 1.1 christos return binding_state; 6167 1.1 christos 6168 1.1 christos case FTS_ACTIVE: 6169 1.1 christos case FTS_RELEASED: 6170 1.1 christos case FTS_ABANDONED: 6171 1.1 christos case FTS_RESET: 6172 1.1 christos case FTS_EXPIRED: 6173 1.1 christos return binding_state; 6174 1.1 christos 6175 1.1 christos default: 6176 1.1 christos log_fatal ("Impossible case at %s:%d.", MDL); 6177 1.1 christos return FTS_RESET; 6178 1.1 christos } 6179 1.1 christos case FTS_RELEASED: 6180 1.1 christos switch (binding_state) { 6181 1.1 christos case FTS_FREE: 6182 1.1 christos case FTS_BACKUP: 6183 1.1 christos 6184 1.1 christos /* These are invalid state transitions - should we 6185 1.1 christos prevent them? */ 6186 1.1 christos case FTS_EXPIRED: 6187 1.1 christos case FTS_ABANDONED: 6188 1.1 christos case FTS_RESET: 6189 1.1 christos case FTS_ACTIVE: 6190 1.1 christos case FTS_RELEASED: 6191 1.1 christos return binding_state; 6192 1.1 christos 6193 1.1 christos default: 6194 1.1 christos log_fatal ("Impossible case at %s:%d.", MDL); 6195 1.1 christos return FTS_RESET; 6196 1.1 christos } 6197 1.1 christos case FTS_RESET: 6198 1.1 christos switch (binding_state) { 6199 1.1 christos case FTS_FREE: 6200 1.1 christos case FTS_BACKUP: 6201 1.1 christos /* Can't set a lease to free or backup until the 6202 1.1 christos peer agrees that it's expired. */ 6203 1.1 christos if (tsfp > cur_time) { 6204 1.1 christos new_state = lease -> binding_state; 6205 1.1 christos goto out; 6206 1.1 christos } 6207 1.1 christos return binding_state; 6208 1.1 christos 6209 1.1 christos case FTS_ACTIVE: 6210 1.1 christos case FTS_EXPIRED: 6211 1.1 christos case FTS_RELEASED: 6212 1.1 christos case FTS_ABANDONED: 6213 1.1 christos case FTS_RESET: 6214 1.1 christos return binding_state; 6215 1.1 christos 6216 1.1 christos default: 6217 1.1 christos log_fatal ("Impossible case at %s:%d.", MDL); 6218 1.1 christos return FTS_RESET; 6219 1.1 christos } 6220 1.1 christos case FTS_BACKUP: 6221 1.1 christos switch (binding_state) { 6222 1.1 christos case FTS_ACTIVE: 6223 1.1 christos case FTS_ABANDONED: 6224 1.1 christos case FTS_EXPIRED: 6225 1.1 christos case FTS_RELEASED: 6226 1.1 christos case FTS_RESET: 6227 1.1 christos /* If the lease was in backup, and our peer 6228 1.1 christos is secondary, then it can make it active 6229 1.1 christos or abandoned. */ 6230 1.1 christos if (state -> i_am == primary) 6231 1.1 christos return binding_state; 6232 1.1 christos 6233 1.1 christos /* Either the primary or the secondary can 6234 1.1 christos reasonably move a lease from the backup 6235 1.1 christos state to the free state. */ 6236 1.1 christos case FTS_FREE: 6237 1.1 christos return binding_state; 6238 1.1 christos 6239 1.1 christos case FTS_BACKUP: 6240 1.1 christos new_state = lease -> binding_state; 6241 1.1 christos goto out; 6242 1.1 christos 6243 1.1 christos default: 6244 1.1 christos log_fatal ("Impossible case at %s:%d.", MDL); 6245 1.1 christos return FTS_RESET; 6246 1.1 christos } 6247 1.1 christos 6248 1.1 christos default: 6249 1.1 christos log_fatal ("Impossible case at %s:%d.", MDL); 6250 1.1 christos return FTS_RESET; 6251 1.1 christos } 6252 1.1 christos out: 6253 1.1 christos return new_state; 6254 1.1 christos } 6255 1.1 christos 6256 1.1 christos /* Determine whether the state transition is okay when we're potentially 6257 1.1 christos in conflict with the peer. */ 6258 1.1 christos binding_state_t 6259 1.1 christos conflict_binding_state_transition_check (struct lease *lease, 6260 1.1 christos dhcp_failover_state_t *state, 6261 1.1 christos binding_state_t binding_state, 6262 1.1 christos u_int32_t tsfp) 6263 1.1 christos { 6264 1.1 christos binding_state_t new_state; 6265 1.1 christos 6266 1.1 christos /* If there is no transition, it's no problem. */ 6267 1.1 christos if (binding_state == lease -> binding_state) 6268 1.1 christos new_state = binding_state; 6269 1.1 christos else { 6270 1.1 christos switch (lease -> binding_state) { 6271 1.1 christos /* If we think the lease is not in use, then the 6272 1.1 christos state into which the partner put it is just fine, 6273 1.1 christos whatever it is. */ 6274 1.1 christos case FTS_FREE: 6275 1.1 christos case FTS_ABANDONED: 6276 1.1 christos case FTS_EXPIRED: 6277 1.1 christos case FTS_RELEASED: 6278 1.1 christos case FTS_RESET: 6279 1.1 christos case FTS_BACKUP: 6280 1.1 christos new_state = binding_state; 6281 1.1 christos break; 6282 1.1 christos 6283 1.1 christos /* If we think the lease *is* in use, then we're not 6284 1.1 christos going to take the partner's change if the partner 6285 1.1 christos thinks it's free. */ 6286 1.1 christos case FTS_ACTIVE: 6287 1.1 christos switch (binding_state) { 6288 1.1 christos case FTS_FREE: 6289 1.1 christos case FTS_BACKUP: 6290 1.1 christos new_state = lease -> binding_state; 6291 1.1 christos break; 6292 1.1 christos 6293 1.1 christos case FTS_EXPIRED: 6294 1.1 christos /* If we don't agree about expiry, it's 6295 1.1 christos * invalid. 65 should allow for max 6296 1.1 christos * clock skew (60) plus some fudge. 6297 1.1 christos * XXX: should we refetch cur_time? 6298 1.1 christos */ 6299 1.1 christos if ((lease->ends - 65) > cur_time) 6300 1.1 christos new_state = lease->binding_state; 6301 1.1 christos else 6302 1.1 christos new_state = binding_state; 6303 1.1 christos break; 6304 1.1 christos 6305 1.1 christos /* RELEASED, RESET, and ABANDONED indicate 6306 1.1 christos * that our partner has information about 6307 1.1 christos * this lease that we did not witness. Our 6308 1.1 christos * partner wins. 6309 1.1 christos */ 6310 1.1 christos case FTS_RELEASED: 6311 1.1 christos case FTS_RESET: 6312 1.1 christos case FTS_ABANDONED: 6313 1.1 christos new_state = binding_state; 6314 1.1 christos break; 6315 1.1 christos 6316 1.1 christos default: 6317 1.1 christos log_fatal ("Impossible case at %s:%d.", MDL); 6318 1.1 christos return FTS_RESET; 6319 1.1 christos } 6320 1.1 christos break; 6321 1.1 christos 6322 1.1 christos default: 6323 1.1 christos log_fatal ("Impossible case at %s:%d.", MDL); 6324 1.1 christos return FTS_RESET; 6325 1.1 christos } 6326 1.1 christos } 6327 1.1 christos return new_state; 6328 1.1 christos } 6329 1.1 christos 6330 1.1 christos /* We can reallocate a lease under the following circumstances: 6331 1.1 christos 6332 1.1 christos (1) It belongs to us - it's FTS_FREE, and we're primary, or it's 6333 1.1 christos FTS_BACKUP, and we're secondary. 6334 1.1 christos (2) We're in partner_down, and the lease is not active, and we 6335 1.1 christos can be sure that the other server didn't make it active. 6336 1.1 christos We can only be sure that the server didn't make it active 6337 1.1 christos when we are in the partner_down state and one of the following 6338 1.1 christos two conditions holds: 6339 1.1 christos (a) in the case that the time sent from the peer is earlier than 6340 1.1 christos the time we entered the partner_down state, at least MCLT has 6341 1.1 christos gone by since we entered partner_down, or 6342 1.1 christos (b) in the case that the time sent from the peer is later than 6343 1.1 christos the time when we entered partner_down, the current time is 6344 1.1 christos later than the time sent from the peer by at least MCLT. */ 6345 1.1 christos 6346 1.1 christos int lease_mine_to_reallocate (struct lease *lease) 6347 1.1 christos { 6348 1.1 christos dhcp_failover_state_t *peer; 6349 1.1 christos 6350 1.1 christos if (lease && lease->pool && 6351 1.1 christos (peer = lease->pool->failover_peer)) { 6352 1.1 christos /* 6353 1.1 christos * In addition to the normal rules governing wether a server 6354 1.1 christos * is allowed to operate changes on a lease, the server is 6355 1.1 christos * allowed to operate on a lease from the standpoint of the 6356 1.1 christos * most conservative guess of the peer's state for this lease. 6357 1.1 christos */ 6358 1.1 christos switch (lease->binding_state) { 6359 1.1 christos case FTS_ACTIVE: 6360 1.1 christos /* ACTIVE leases may not be reallocated. */ 6361 1.1 christos return 0; 6362 1.1 christos 6363 1.1 christos case FTS_FREE: 6364 1.1 christos case FTS_ABANDONED: 6365 1.1 christos /* FREE leases may only be allocated by the primary, 6366 1.1 christos * unless the secondary is acting in partner_down 6367 1.1 christos * state and stos+mclt or tsfp+mclt has expired, 6368 1.1 christos * whichever is greater. 6369 1.1 christos * 6370 1.1 christos * ABANDONED are treated the same as FREE for all 6371 1.1 christos * purposes here. Note that servers will only try 6372 1.1 christos * for ABANDONED leases as a last resort anyway. 6373 1.1 christos */ 6374 1.1 christos if (peer -> i_am == primary) 6375 1.1 christos return 1; 6376 1.1 christos 6377 1.1 christos return(peer->service_state == service_partner_down && 6378 1.1 christos ((lease->tsfp < peer->me.stos) ? 6379 1.1 christos (peer->me.stos + peer->mclt < cur_time) : 6380 1.1 christos (lease->tsfp + peer->mclt < cur_time))); 6381 1.1 christos 6382 1.1 christos case FTS_RELEASED: 6383 1.1 christos case FTS_EXPIRED: 6384 1.1 christos /* 6385 1.1 christos * These leases are generally untouchable until the 6386 1.1 christos * peer acknowledges their state change. However, as 6387 1.1 christos * this is impossible if the peer is offline, the 6388 1.1 christos * failover protocol permits an 'optimization' to 6389 1.1 christos * rewind the lease to a previous state that the server 6390 1.1 christos * is allowed to operate on, if that was the state that 6391 1.1 christos * was last acknowledged by the peer. 6392 1.1 christos * 6393 1.1 christos * So if a lease was free, was allocated by this 6394 1.1 christos * server, and expired without ever being transmitted 6395 1.1 christos * to the peer, it can be returned to free and given 6396 1.1 christos * to any new client legally. 6397 1.1 christos */ 6398 1.1 christos if ((peer->i_am == primary) && 6399 1.1 christos (lease->rewind_binding_state == FTS_FREE)) 6400 1.1 christos return 1; 6401 1.1 christos if ((peer->i_am == secondary) && 6402 1.1 christos (lease->rewind_binding_state == FTS_BACKUP)) 6403 1.1 christos return 1; 6404 1.1 christos 6405 1.1 christos /* FALL THROUGH (released, expired, reset) */ 6406 1.1 christos case FTS_RESET: 6407 1.1 christos /* 6408 1.1 christos * Released, expired, and reset leases go onto the 6409 1.1 christos * 'expired' queue all together. Upon entry into 6410 1.1 christos * partner-down state, this queue of leases has their 6411 1.1 christos * tsfp values modified to equal stos+mclt, the point 6412 1.1 christos * at which the server is allowed to remove them from 6413 1.1 christos * these transitional states. 6414 1.1 christos * 6415 1.1 christos * Note that although tsfp has been possibly extended 6416 1.1 christos * past the actual tsfp we received from the peer, we 6417 1.1 christos * don't have to take any special action. Since tsfp 6418 1.1 christos * will be equal to the current time when the lease 6419 1.1 christos * transitions to free, tsfp will not be used to grant 6420 1.1 christos * lease-times longer than the MCLT to clients, which 6421 1.1 christos * is the only danger for this sort of modification. 6422 1.1 christos */ 6423 1.1 christos return((peer->service_state == service_partner_down) && 6424 1.1 christos (lease->tsfp < cur_time)); 6425 1.1 christos 6426 1.1 christos case FTS_BACKUP: 6427 1.1 christos /* Only the secondary may allocate BACKUP leases, 6428 1.1 christos * unless in partner_down state in which case at 6429 1.1 christos * least TSFP+MCLT or STOS+MCLT must have expired, 6430 1.1 christos * whichever is greater. 6431 1.1 christos */ 6432 1.1 christos if (peer->i_am == secondary) 6433 1.1 christos return 1; 6434 1.1 christos 6435 1.1 christos return((peer->service_state == service_partner_down) && 6436 1.1 christos ((lease->tsfp < peer->me.stos) ? 6437 1.1 christos (peer->me.stos + peer->mclt < cur_time) : 6438 1.1 christos (lease->tsfp + peer->mclt < cur_time))); 6439 1.1 christos 6440 1.1 christos default: 6441 1.1 christos /* All lease states appear above. */ 6442 1.1 christos log_fatal("Impossible case at %s:%d.", MDL); 6443 1.1 christos break; 6444 1.1 christos } 6445 1.1 christos return 0; 6446 1.1 christos } 6447 1.1 christos if (lease) 6448 1.1 christos return(lease->binding_state == FTS_FREE || 6449 1.1 christos lease->binding_state == FTS_BACKUP); 6450 1.1 christos else 6451 1.1 christos return 0; 6452 1.1 christos } 6453 1.1 christos 6454 1.1 christos static isc_result_t failover_message_reference (failover_message_t **mp, 6455 1.1 christos failover_message_t *m, 6456 1.1 christos const char *file, int line) 6457 1.1 christos { 6458 1.1 christos *mp = m; 6459 1.1 christos m -> refcnt++; 6460 1.1 christos return ISC_R_SUCCESS; 6461 1.1 christos } 6462 1.1 christos 6463 1.1 christos static isc_result_t failover_message_dereference (failover_message_t **mp, 6464 1.1 christos const char *file, int line) 6465 1.1 christos { 6466 1.1 christos failover_message_t *m; 6467 1.1 christos m = (*mp); 6468 1.1 christos m -> refcnt--; 6469 1.1 christos if (m -> refcnt == 0) { 6470 1.1 christos if (m -> next) 6471 1.1 christos failover_message_dereference (&m -> next, 6472 1.1 christos file, line); 6473 1.1 christos if (m -> chaddr.data) 6474 1.1 christos dfree (m -> chaddr.data, file, line); 6475 1.1 christos if (m -> client_identifier.data) 6476 1.1 christos dfree (m -> client_identifier.data, file, line); 6477 1.1 christos if (m -> hba.data) 6478 1.1 christos dfree (m -> hba.data, file, line); 6479 1.1 christos if (m -> message.data) 6480 1.1 christos dfree (m -> message.data, file, line); 6481 1.1 christos if (m -> relationship_name.data) 6482 1.1 christos dfree (m -> relationship_name.data, file, line); 6483 1.1 christos if (m -> reply_options.data) 6484 1.1 christos dfree (m -> reply_options.data, file, line); 6485 1.1 christos if (m -> request_options.data) 6486 1.1 christos dfree (m -> request_options.data, file, line); 6487 1.1 christos if (m -> vendor_class.data) 6488 1.1 christos dfree (m -> vendor_class.data, file, line); 6489 1.1 christos if (m -> vendor_options.data) 6490 1.1 christos dfree (m -> vendor_options.data, file, line); 6491 1.1 christos if (m -> ddns.data) 6492 1.1 christos dfree (m -> ddns.data, file, line); 6493 1.1 christos dfree (*mp, file, line); 6494 1.1 christos } 6495 1.1 christos *mp = 0; 6496 1.1 christos return ISC_R_SUCCESS; 6497 1.1 christos } 6498 1.1 christos 6499 1.1 christos OMAPI_OBJECT_ALLOC (dhcp_failover_state, dhcp_failover_state_t, 6500 1.1 christos dhcp_type_failover_state) 6501 1.1 christos OMAPI_OBJECT_ALLOC (dhcp_failover_listener, dhcp_failover_listener_t, 6502 1.1 christos dhcp_type_failover_listener) 6503 1.1 christos OMAPI_OBJECT_ALLOC (dhcp_failover_link, dhcp_failover_link_t, 6504 1.1 christos dhcp_type_failover_link) 6505 1.1 christos #endif /* defined (FAILOVER_PROTOCOL) */ 6506 1.1 christos 6507 1.1 christos const char *binding_state_print (enum failover_state state) 6508 1.1 christos { 6509 1.1 christos switch (state) { 6510 1.1 christos case FTS_FREE: 6511 1.1 christos return "free"; 6512 1.1 christos break; 6513 1.1 christos 6514 1.1 christos case FTS_ACTIVE: 6515 1.1 christos return "active"; 6516 1.1 christos break; 6517 1.1 christos 6518 1.1 christos case FTS_EXPIRED: 6519 1.1 christos return "expired"; 6520 1.1 christos break; 6521 1.1 christos 6522 1.1 christos case FTS_RELEASED: 6523 1.1 christos return "released"; 6524 1.1 christos break; 6525 1.1 christos 6526 1.1 christos case FTS_ABANDONED: 6527 1.1 christos return "abandoned"; 6528 1.1 christos break; 6529 1.1 christos 6530 1.1 christos case FTS_RESET: 6531 1.1 christos return "reset"; 6532 1.1 christos break; 6533 1.1 christos 6534 1.1 christos case FTS_BACKUP: 6535 1.1 christos return "backup"; 6536 1.1 christos break; 6537 1.1 christos 6538 1.1 christos default: 6539 1.1 christos return "unknown"; 6540 1.1 christos break; 6541 1.1 christos } 6542 1.1 christos } 6543 1.1 christos 6544 1.1 christos 6545 1.3 christos #if defined (DEBUG_FAILOVER_MESSAGES) 6546 1.1 christos /*! 6547 1.1 christos * \brief Given a char pointer, return always return a printable value 6548 1.1 christos * 6549 1.1 christos * This function is intended to be used in within log statements, such that 6550 1.1 christos * its invocation only occurs if the logging level is enabled. 6551 1.1 christos * 6552 1.1 christos * \param value pointer the character to print 6553 1.1 christos * 6554 1.1 christos * \return If value is null, returns the string "<none>", if it contains 6555 1.1 christos * non-printable bytes, returns the string "<unsuitable for printing>", 6556 1.1 christos * otherwise it returns a const pointer to value 6557 1.1 christos */ 6558 1.2 christos static const char *printable(const char* value) { 6559 1.1 christos const char *print_value = "<none>"; 6560 1.1 christos if (value) { 6561 1.1 christos if ((strlen (value) <= 64) && 6562 1.1 christos db_printable((unsigned char*)value)) { 6563 1.1 christos print_value = value; 6564 1.1 christos } 6565 1.1 christos else { 6566 1.1 christos print_value = "<unsuitable for printing>"; 6567 1.1 christos } 6568 1.1 christos } 6569 1.1 christos 6570 1.1 christos return (print_value); 6571 1.1 christos } 6572 1.3 christos #endif 6573 1.1 christos 6574 1.1 christos /*! 6575 1.1 christos * \brief Remove information from a prior use of a lease 6576 1.1 christos * 6577 1.1 christos * Remove information from a lease that is not germain to lease affinity 6578 1.1 christos * 6579 1.1 christos * \param lease the lease to scrub 6580 1.1 christos */ 6581 1.1 christos void scrub_lease(struct lease* lease, const char *file, int line) { 6582 1.3 christos #if defined (DEBUG_FAILOVER_MESSAGES) 6583 1.3 christos /* While technically not associated with FO messaging this log statement 6584 1.3 christos * draws more questions then it helps, so we'll ifdef it out */ 6585 1.1 christos log_debug ("%s(%d):scrubbing lease for %s, hostname: %s", file, line, 6586 1.1 christos piaddr(lease->ip_addr), printable(lease->client_hostname)); 6587 1.4 christos #endif 6588 1.1 christos 6589 1.1 christos if (lease->client_hostname) { 6590 1.1 christos dfree (lease->client_hostname, MDL); 6591 1.1 christos lease->client_hostname = (char *)0; 6592 1.1 christos } 6593 1.1 christos } 6594