/* failover.c Failover protocol support code... */ /* * Copyright (c) 1999-2000 Internet Software Consortium. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of The Internet Software Consortium nor the names * of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This software has been written for the Internet Software Consortium * by Ted Lemon in cooperation with Vixie Enterprises and Nominum, Inc. * To learn more about the Internet Software Consortium, see * ``http://www.isc.org/''. To learn more about Vixie Enterprises, * see ``http://www.vix.com''. To learn more about Nominum, Inc., see * ``http://www.nominum.com''. */ #ifndef lint static char copyright[] = "$Id: failover.c,v 1.1.1.1 2000/04/22 07:12:05 mellon Exp $ Copyright (c) 1999-2000 The Internet Software Consortium. All rights reserved.\n"; #endif /* not lint */ #include "dhcpd.h" #include "version.h" #include #if defined (FAILOVER_PROTOCOL) static struct hash_table *failover_hash; static dhcp_failover_state_t *failover_states; static isc_result_t do_a_failover_option (omapi_object_t *, dhcp_failover_link_t *); void enter_failover_peer (peer) dhcp_failover_state_t *peer; { } isc_result_t find_failover_peer (peer, name) dhcp_failover_state_t **peer; char *name; { dhcp_failover_state_t *p; for (p = failover_states; p; p = p -> next) if (!strcmp (name, p -> name)) break; if (p) return omapi_object_reference ((omapi_object_t **)peer, (omapi_object_t *)p, MDL); return ISC_R_NOTFOUND; } /* The failover protocol has three objects associated with it. For each failover partner declaration in the dhcpd.conf file, primary or secondary, there is a failover_state object. For any primary or secondary state object that has a connection to its peer, there is also a failover_link object, which has its own input state seperate from the failover protocol state for managing the actual bytes coming in off the wire. Finally, there will be one listener object for every distinct port number associated with a secondary failover_state object. Normally all secondary failover_state objects are expected to listen on the same port number, so there need be only one listener object, but if different port numbers are specified for each failover object, there could be as many as one listener object for each secondary failover_state object. */ /* This, then, is the implemention of the failover link object. */ isc_result_t dhcp_failover_link_initiate (omapi_object_t *h) { isc_result_t status; dhcp_failover_link_t *obj; unsigned long port; omapi_value_t *value = (omapi_value_t *)0; dhcp_failover_state_t *state; omapi_object_t *o; int i; struct data_string ds; /* Find the failover state in the object chain. */ for (o = h; o -> outer; o = o -> outer) ; for (; o; o = o -> inner) { if (o -> type == dhcp_type_failover_state) break; } if (!o) return ISC_R_INVALIDARG; state = (dhcp_failover_state_t *)o; obj = (dhcp_failover_link_t *)dmalloc (sizeof *obj, MDL); if (!obj) return ISC_R_NOMEMORY; memset (obj, 0, sizeof *obj); obj -> refcnt = 1; obj -> type = dhcp_type_failover_link; option_cache_reference (&obj -> peer_address, state -> address, MDL); obj -> peer_port = state -> port; memset (&ds, 0, sizeof ds); if (!evaluate_option_cache (&ds, (struct packet *)0, (struct lease *)0, (struct option_state *)0, (struct option_state *)0, &global_scope, obj -> peer_address, MDL)) { option_cache_dereference (&obj -> peer_address, MDL); omapi_object_dereference ((omapi_object_t **)&obj, MDL); return ISC_R_UNEXPECTED; } /* Kludge around the fact that omapi_connect expects an ASCII string representing the address to which to connect. */ status = ISC_R_INVALIDARG; for (i = 0; i + 4 <= ds.len; i += 4) { char peer_name [40]; sprintf (peer_name, "%d.%d.%d.%d", ds.data [i], ds.data [i + 1], ds.data [i + 2], ds.data [i + 3]); status = omapi_connect ((omapi_object_t *)obj, peer_name, port); if (status == ISC_R_SUCCESS) break; } data_string_forget (&ds, MDL); /* If we didn't connect to any of the addresses, give up. */ if (status != ISC_R_SUCCESS) { lose: omapi_object_dereference ((omapi_object_t **)&obj, MDL); option_cache_dereference (&obj -> peer_address, MDL); return status; } status = omapi_object_reference (&h -> outer, (omapi_object_t *)obj, MDL); if (status != ISC_R_SUCCESS) goto lose; status = omapi_object_reference (&obj -> inner, h, MDL); if (status != ISC_R_SUCCESS) goto lose; /* Send the introductory message. */ status = dhcp_failover_send_connect ((omapi_object_t *)obj); if (status != ISC_R_SUCCESS) goto lose; omapi_object_dereference ((omapi_object_t **)&obj, MDL); return ISC_R_SUCCESS; } isc_result_t dhcp_failover_link_signal (omapi_object_t *h, const char *name, va_list ap) { isc_result_t status; dhcp_failover_link_t *link; omapi_object_t *c; u_int16_t nlen; u_int32_t vlen; if (h -> type != dhcp_type_failover_link) { /* XXX shouldn't happen. Put an assert here? */ return ISC_R_UNEXPECTED; } link = (dhcp_failover_link_t *)h; /* Not a signal we recognize? */ if (strcmp (name, "ready")) { if (h -> inner && h -> inner -> type -> signal_handler) return (*(h -> inner -> type -> signal_handler)) (h, name, ap); return ISC_R_NOTFOUND; } if (!h -> outer || h -> outer -> type != omapi_type_connection) return ISC_R_INVALIDARG; c = h -> outer; /* We get here because we requested that we be woken up after some number of bytes were read, and that number of bytes has in fact been read. */ switch (link -> state) { case dhcp_flink_start: link -> state = dhcp_flink_message_length_wait; if ((omapi_connection_require (c, 2)) != ISC_R_SUCCESS) break; case dhcp_flink_message_length_wait: link -> state = dhcp_flink_message_wait; link -> imsg = dmalloc (sizeof (failover_message_t), MDL); if (!link -> imsg) { dhcp_flink_fail: if (link -> imsg) { dfree (link -> imsg, MDL); link -> imsg = (failover_message_t *)0; } link -> state = dhcp_flink_disconnected; omapi_disconnect (c, 1); /* XXX just blow away the protocol state now? XXX or will disconnect blow it away? */ return ISC_R_UNEXPECTED; } memset (link -> imsg, 0, sizeof (link -> imsg)); /* Get the length: */ omapi_connection_get_uint16 (c, &link -> imsg_len); link -> imsg_count = 0; /* Bytes read. */ /* Maximum of 2048 bytes in any failover message. */ if (link -> imsg_len > DHCP_FAILOVER_MAX_MESSAGE_SIZE) goto dhcp_flink_fail; if ((omapi_connection_require (c, link -> imsg_len)) != ISC_R_SUCCESS) break; case dhcp_flink_message_wait: /* Read in the message. At this point we have the entire message in the input buffer. For each incoming value ID, set a bit in the bitmask indicating that we've gotten it. Maybe flag an error message if the bit is already set. Once we're done reading, we can check the bitmask to make sure that the required fields for each message have been included. */ link -> imsg_count += 2; /* Count the length as read. */ /* Get message type. */ omapi_connection_copyout (&link -> imsg -> type, c, 1); link -> imsg_count++; /* Get message payload offset. */ omapi_connection_copyout (&link -> imsg_payoff, c, 1); link -> imsg_count++; /* Get message time. */ omapi_connection_get_uint32 (c, &link -> imsg -> time); link -> imsg_count += 4; /* Get transaction ID. */ omapi_connection_get_uint32 (c, &link -> imsg -> xid); link -> imsg_count += 4; /* Skip over any portions of the message header that we don't understand. */ if (link -> imsg_payoff - link -> imsg_count) { omapi_connection_copyout ((unsigned char *)0, c, (link -> imsg_payoff - link -> imsg_count)); link -> imsg_count = link -> imsg_payoff; } /* Get transaction ID. */ omapi_connection_get_uint32 (c, &link -> imsg -> xid); link -> imsg_count += 4; /* Now start sucking options off the wire. */ while (link -> imsg_count < link -> imsg_len) { if (do_a_failover_option (c, link) != ISC_R_SUCCESS) goto dhcp_flink_fail; } /* Once we have the entire message, and we've validated it as best we can here, pass it to the parent. */ omapi_signal_in (h -> outer, "message", link); break; default: /* XXX should never get here. Assertion? */ break; } return ISC_R_SUCCESS; } static isc_result_t do_a_failover_option (c, link) omapi_object_t *c; dhcp_failover_link_t *link; { u_int16_t option_code; u_int16_t option_len; unsigned char *op; unsigned op_size; unsigned op_count; int i; if (link -> imsg_count + 2 > link -> imsg_len) { log_error ("FAILOVER: message overflow at option code."); return ISC_R_PROTOCOLERROR; } /* Get option code. */ omapi_connection_get_uint16 (c, &option_code); link -> imsg_count += 2; /* Get option length. */ omapi_connection_get_uint16 (c, &option_len); link -> imsg_count += 2; if (link -> imsg_count + option_len > link -> imsg_len) { log_error ("FAILOVER: message overflow at %s", " length."); return ISC_R_PROTOCOLERROR; } /* If it's an unknown code, skip over it. */ if (option_code > FTO_MAX) { #if defined (FAILOVER_PROTOCOL_DEBUG) && defined (FAILOVER_DEBUG_VERBOSE) log_debug (" option code %d len %d (not recognized)", option_code, option_len); #endif omapi_connection_copyout ((unsigned char *)0, c, option_len); link -> imsg_count += option_len; return ISC_R_SUCCESS; } /* If it's the digest, do it now. */ if (ft_options [option_code].type == FT_DIGEST) { link -> imsg_count += option_len; if (link -> imsg_count != link -> imsg_len) { log_error ("FAILOVER: digest not at end of message"); return ISC_R_PROTOCOLERROR; } #if defined (FAILOVER_PROTOCOL_DEBUG) && defined (FAILOVER_DEBUG_VERBOSE) log_debug (" option %s len %d", ft_options [option_code].name, option_len); #endif /* For now, just dump it. */ omapi_connection_copyout ((unsigned char *)0, c, option_len); return ISC_R_SUCCESS; } /* Only accept an option once. */ if (link -> imsg -> options_present & ft_options [option_code].bit) { log_error ("FAILOVER: duplicate option %s", ft_options [option_code].name); return ISC_R_PROTOCOLERROR; } /* Make sure the option is appropriate for this type of message. Really, any option is generally allowed for any message, and the cases where this is not true are too complicated to represent in this way - what this code is doing is to just avoid saving the value of an option we don't have any way to use, which allows us to make the failover_message structure smaller. */ if (ft_options [option_code].bit && !(fto_allowed [option_code] & ft_options [option_code].bit)) { omapi_connection_copyout ((unsigned char *)0, c, option_len); link -> imsg_count += option_len; return ISC_R_SUCCESS; } /* Figure out how many elements, how big they are, and where to store them. */ if (ft_options [option_code].num_present) { /* If this option takes a fixed number of elements, we expect the space for them to be preallocated, and we can just read the data in. */ op = ((unsigned char *)&link -> imsg) + ft_options [option_code].offset; op_size = ft_sizes [ft_options [option_code].type]; op_count = ft_options [option_code].num_present; if (option_len != op_size * op_count) { log_error ("FAILOVER: option size (%d:%d), option %s", option_len, (ft_sizes [ft_options [option_code].type] * ft_options [option_code].num_present), ft_options [option_code].name); return ISC_R_PROTOCOLERROR; } } else { failover_option_t *fo; /* FT_DDNS* are special - one or two bytes of status followed by the client FQDN. */ if (ft_options [option_code].type == FT_DDNS1 || ft_options [option_code].type == FT_DDNS1) { ddns_fqdn_t *ddns = ((ddns_fqdn_t *) (((char *)&link -> imsg) + ft_options [option_code].offset)); op_count = (ft_options [option_code].type == FT_DDNS1 ? 1 : 2); omapi_connection_copyout (&ddns -> codes [0], c, op_count); if (op_count == 1) ddns -> codes [1] = 0; op_size = 1; op_count = option_len - op_count; ddns -> length = op_count; ddns -> data = dmalloc (op_count, MDL); if (!ddns -> data) { log_error ("FAILOVER: no memory getting%s(%d)", " DNS data ", op_count); /* Actually, NO_MEMORY, but if we lose here we have to drop the connection. */ return ISC_R_PROTOCOLERROR; } omapi_connection_copyout (ddns -> data, c, op_count); goto out; } /* A zero for num_present means that any number of elements can appear, so we have to figure out how many we got from the length of the option, and then fill out a failover_option structure describing the data. */ op_size = ft_sizes [ft_options [option_code].type]; /* Make sure that option data length is a multiple of the size of the data type being sent. */ if (op_size > 1 && option_len % op_size) { log_error ("FAILOVER: option_len %d not %s%d", option_len, "multiple of ", op_size); return ISC_R_PROTOCOLERROR; } op_count = option_len / op_size; fo = ((failover_option_t *) (((char *)&link -> imsg) + ft_options [option_code].offset)); fo -> count = op_count; fo -> data = dmalloc (option_len, MDL); if (!fo -> data) { log_error ("FAILOVER: no memory getting %s (%d)", "option data", op_count); return ISC_R_PROTOCOLERROR; } op = fo -> data; } /* For single-byte message values and multi-byte values that don't need swapping, just read them in all at once. */ if (op_size == 1 || ft_options [option_code].type == FT_IPADDR) { omapi_connection_copyout ((unsigned char *)op, c, option_len); goto out; } /* For values that require swapping, read them in one at a time using routines that swap bytes. */ for (i = 0; i < op_count; i++) { switch (ft_options [option_code].type) { case FT_UINT32: omapi_connection_get_uint32 (c, (u_int32_t *)op); op += 4; break; case FT_UINT16: omapi_connection_get_uint16 (c, (u_int16_t *)op); op += 2; break; default: /* Everything else should have been handled already. */ log_error ("FAILOVER: option %s: bad type %d", ft_options [option_code].name, ft_options [option_code].type); return ISC_R_PROTOCOLERROR; } } out: /* Remember that we got this option. */ link -> imsg -> options_present |= ft_options [option_code].bit; return ISC_R_SUCCESS; } isc_result_t dhcp_failover_link_set_value (omapi_object_t *h, omapi_object_t *id, omapi_data_string_t *name, omapi_typed_data_t *value) { if (h -> type != omapi_type_protocol) return ISC_R_INVALIDARG; /* Never valid to set these. */ if (!omapi_ds_strcmp (name, "link-port") || !omapi_ds_strcmp (name, "link-name") || !omapi_ds_strcmp (name, "link-state")) return ISC_R_NOPERM; if (h -> inner && h -> inner -> type -> set_value) return (*(h -> inner -> type -> set_value)) (h -> inner, id, name, value); return ISC_R_NOTFOUND; } isc_result_t dhcp_failover_link_get_value (omapi_object_t *h, omapi_object_t *id, omapi_data_string_t *name, omapi_value_t **value) { dhcp_failover_link_t *link; if (h -> type != omapi_type_protocol) return ISC_R_INVALIDARG; link = (dhcp_failover_link_t *)h; if (!omapi_ds_strcmp (name, "link-port")) { return omapi_make_int_value (value, name, (int)link -> peer_port, MDL); } else if (!omapi_ds_strcmp (name, "link-state")) { if (link -> state < 0 || link -> state >= dhcp_flink_state_max) return omapi_make_string_value (value, name, "invalid link state", MDL); return omapi_make_string_value (value, name, dhcp_flink_state_names [link -> state], MDL); } if (h -> inner && h -> inner -> type -> get_value) return (*(h -> inner -> type -> get_value)) (h -> inner, id, name, value); return ISC_R_NOTFOUND; } isc_result_t dhcp_failover_link_destroy (omapi_object_t *h, const char *file, int line) { dhcp_failover_link_t *link; if (h -> type != dhcp_type_failover_link) return ISC_R_INVALIDARG; link = (dhcp_failover_link_t *)h; if (link -> imsg) { dfree (link -> imsg, file, line); link -> imsg = (failover_message_t *)0; } return ISC_R_SUCCESS; } /* Write all the published values associated with the object through the specified connection. */ isc_result_t dhcp_failover_link_stuff_values (omapi_object_t *c, omapi_object_t *id, omapi_object_t *l) { dhcp_failover_link_t *link; isc_result_t status; if (l -> type != dhcp_type_failover_link) return ISC_R_INVALIDARG; link = (dhcp_failover_link_t *)l; status = omapi_connection_put_name (c, "link-port"); if (status != ISC_R_SUCCESS) return status; status = omapi_connection_put_uint32 (c, sizeof (int)); if (status != ISC_R_SUCCESS) return status; status = omapi_connection_put_uint32 (c, link -> peer_port); if (status != ISC_R_SUCCESS) return status; status = omapi_connection_put_name (c, "link-state"); if (status != ISC_R_SUCCESS) return status; if (link -> state < 0 || link -> state >= dhcp_flink_state_max) status = omapi_connection_put_string (c, "invalid link state"); else status = (omapi_connection_put_string (c, dhcp_flink_state_names [link -> state])); if (status != ISC_R_SUCCESS) return status; if (link -> inner && link -> inner -> type -> stuff_values) return (*(link -> inner -> type -> stuff_values)) (c, id, link -> inner); return ISC_R_SUCCESS; } /* Set up a listener for the omapi protocol. The handle stored points to a listener object, not a protocol object. */ isc_result_t dhcp_failover_listen (omapi_object_t *h) { isc_result_t status; dhcp_failover_listener_t *obj; unsigned long port; omapi_value_t *value = (omapi_value_t *)0; status = omapi_get_value_str (h, (omapi_object_t *)0, "local-port", &value); if (status != ISC_R_SUCCESS) return status; if (!value -> value) { omapi_value_dereference (&value, MDL); return ISC_R_INVALIDARG; } status = omapi_get_int_value (&port, value -> value); omapi_value_dereference (&value, MDL); if (status != ISC_R_SUCCESS) return status; obj = (dhcp_failover_listener_t *)dmalloc (sizeof *obj, MDL); if (!obj) return ISC_R_NOMEMORY; memset (obj, 0, sizeof *obj); obj -> refcnt = 1; obj -> type = dhcp_type_failover_listener; obj -> local_port = port; status = omapi_listen ((omapi_object_t *)obj, port, 1); omapi_object_dereference ((omapi_object_t **)&obj, MDL); if (status != ISC_R_SUCCESS) return status; status = omapi_object_reference (&h -> outer, (omapi_object_t *)obj, MDL); if (status != ISC_R_SUCCESS) { omapi_object_dereference ((omapi_object_t **)&obj, MDL); return status; } status = omapi_object_reference (&obj -> inner, h, MDL); if (status != ISC_R_SUCCESS) { omapi_object_dereference ((omapi_object_t **)&obj, MDL); return status; } return status; } /* Signal handler for protocol listener - if we get a connect signal, create a new protocol connection, otherwise pass the signal down. */ isc_result_t dhcp_failover_listener_signal (omapi_object_t *o, const char *name, va_list ap) { isc_result_t status; omapi_connection_object_t *c; dhcp_failover_link_t *obj; dhcp_failover_listener_t *p; dhcp_failover_state_t *state; if (!o || o -> type != dhcp_type_failover_listener) return ISC_R_INVALIDARG; p = (dhcp_failover_listener_t *)o; /* Not a signal we recognize? */ if (strcmp (name, "connect")) { if (p -> inner && p -> inner -> type -> signal_handler) return (*(p -> inner -> type -> signal_handler)) (p -> inner, name, ap); return ISC_R_NOTFOUND; } c = va_arg (ap, omapi_connection_object_t *); if (!c || c -> type != omapi_type_connection) return ISC_R_INVALIDARG; /* See if we can find a secondary failover_state object that matches this connection. */ for (state = failover_states; state; state = state -> next) { int i; struct data_string ds; memset (&ds, 0, sizeof ds); if (evaluate_option_cache (&ds, (struct packet *)0, (struct lease *)0, (struct option_state *)0, (struct option_state *)0, &global_scope, state -> address, MDL)) { for (i = 0; i < ds.len; i += 4) { if (!memcmp (&ds.data [i], &c -> remote_addr, 4)) break; } } } /* If we can't find a failover protocol state for this remote host, drop the connection */ if (!state) { /* XXX Send a refusal message first? XXX Look in protocol spec for guidance. */ /* XXX An error message from a connect signal should XXX drop the connection - make sure this is what XXX actually happens! */ return ISC_R_INVALIDARG; } obj = (dhcp_failover_link_t *)dmalloc (sizeof *obj, MDL); if (!obj) return ISC_R_NOMEMORY; memset (obj, 0, sizeof *obj); obj -> refcnt = 1; obj -> type = dhcp_type_failover_link; option_cache_reference (&obj -> peer_address, state -> address, MDL); obj -> peer_port = ntohs (c -> remote_addr.sin_port); status = omapi_object_reference (&obj -> outer, (omapi_object_t *)c, MDL); if (status != ISC_R_SUCCESS) { lose: omapi_object_dereference ((omapi_object_t **)&obj, MDL); omapi_disconnect ((omapi_object_t *)c, 1); return status; } status = omapi_object_reference (&c -> inner, (omapi_object_t *)obj, MDL); if (status != ISC_R_SUCCESS) goto lose; /* Notify the master state machine of the arrival of a new connection. */ status = omapi_signal_in ((omapi_object_t *)state, "connect", obj); if (status != ISC_R_SUCCESS) goto lose; omapi_object_dereference ((omapi_object_t **)&obj, MDL); return status; } isc_result_t dhcp_failover_listener_set_value (omapi_object_t *h, omapi_object_t *id, omapi_data_string_t *name, omapi_typed_data_t *value) { if (h -> type != dhcp_type_failover_listener) return ISC_R_INVALIDARG; if (h -> inner && h -> inner -> type -> set_value) return (*(h -> inner -> type -> set_value)) (h -> inner, id, name, value); return ISC_R_NOTFOUND; } isc_result_t dhcp_failover_listener_get_value (omapi_object_t *h, omapi_object_t *id, omapi_data_string_t *name, omapi_value_t **value) { if (h -> type != dhcp_type_failover_listener) return ISC_R_INVALIDARG; if (h -> inner && h -> inner -> type -> get_value) return (*(h -> inner -> type -> get_value)) (h -> inner, id, name, value); return ISC_R_NOTFOUND; } isc_result_t dhcp_failover_listener_destroy (omapi_object_t *h, const char *file, int line) { if (h -> type != dhcp_type_failover_listener) return ISC_R_INVALIDARG; return ISC_R_SUCCESS; } /* Write all the published values associated with the object through the specified connection. */ isc_result_t dhcp_failover_listener_stuff (omapi_object_t *c, omapi_object_t *id, omapi_object_t *p) { int i; if (p -> type != dhcp_type_failover_listener) return ISC_R_INVALIDARG; if (p -> inner && p -> inner -> type -> stuff_values) return (*(p -> inner -> type -> stuff_values)) (c, id, p -> inner); return ISC_R_SUCCESS; } /* Set up master state machine for the failover protocol. */ isc_result_t dhcp_failover_register (omapi_object_t *h) { isc_result_t status; dhcp_failover_state_t *obj; unsigned long port; omapi_value_t *value = (omapi_value_t *)0; status = omapi_get_value_str (h, (omapi_object_t *)0, "local-port", &value); if (status != ISC_R_SUCCESS) return status; if (!value -> value) { omapi_value_dereference (&value, MDL); return ISC_R_INVALIDARG; } status = omapi_get_int_value (&port, value -> value); omapi_value_dereference (&value, MDL); if (status != ISC_R_SUCCESS) return status; obj = (dhcp_failover_state_t *)dmalloc (sizeof *obj, MDL); if (!obj) return ISC_R_NOMEMORY; memset (obj, 0, sizeof *obj); obj -> refcnt = 1; obj -> type = dhcp_type_failover_state; obj -> listen_port = port; status = omapi_listen ((omapi_object_t *)obj, port, 1); omapi_object_dereference ((omapi_object_t **)&obj, MDL); if (status != ISC_R_SUCCESS) return status; status = omapi_object_reference (&h -> outer, (omapi_object_t *)obj, MDL); if (status != ISC_R_SUCCESS) { omapi_object_dereference ((omapi_object_t **)&obj, MDL); return status; } status = omapi_object_reference (&obj -> inner, h, MDL); if (status != ISC_R_SUCCESS) { omapi_object_dereference ((omapi_object_t **)&obj, MDL); return status; } return status; } /* Signal handler for protocol state machine. */ isc_result_t dhcp_failover_state_signal (omapi_object_t *o, const char *name, va_list ap) { isc_result_t status; omapi_connection_object_t *c; omapi_protocol_object_t *obj; dhcp_failover_state_t *state; char *peer_name; if (!o || o -> type != dhcp_type_failover_state) return ISC_R_INVALIDARG; state = (dhcp_failover_state_t *)o; /* Not a signal we recognize? */ if (strcmp (name, "connect") && strcmp (name, "disconnect") && strcmp (name, "message")) { if (state -> inner && state -> inner -> type -> signal_handler) return (*(state -> inner -> type -> signal_handler)) (state -> inner, name, ap); return ISC_R_NOTFOUND; } /* Handle all the events we care about... */ return ISC_R_SUCCESS; } isc_result_t dhcp_failover_state_set_value (omapi_object_t *h, omapi_object_t *id, omapi_data_string_t *name, omapi_typed_data_t *value) { if (h -> type != dhcp_type_failover_state) return ISC_R_INVALIDARG; if (h -> inner && h -> inner -> type -> set_value) return (*(h -> inner -> type -> set_value)) (h -> inner, id, name, value); return ISC_R_NOTFOUND; } isc_result_t dhcp_failover_state_get_value (omapi_object_t *h, omapi_object_t *id, omapi_data_string_t *name, omapi_value_t **value) { if (h -> type != dhcp_type_failover_state) return ISC_R_INVALIDARG; if (h -> inner && h -> inner -> type -> get_value) return (*(h -> inner -> type -> get_value)) (h -> inner, id, name, value); return ISC_R_NOTFOUND; } isc_result_t dhcp_failover_state_destroy (omapi_object_t *h, const char *file, int line) { if (h -> type != dhcp_type_failover_state) return ISC_R_INVALIDARG; return ISC_R_SUCCESS; } /* Write all the published values associated with the object through the specified connection. */ isc_result_t dhcp_failover_state_stuff (omapi_object_t *c, omapi_object_t *id, omapi_object_t *p) { int i; if (p -> type != dhcp_type_failover_state) return ISC_R_INVALIDARG; if (p -> inner && p -> inner -> type -> stuff_values) return (*(p -> inner -> type -> stuff_values)) (c, id, p -> inner); return ISC_R_SUCCESS; } isc_result_t dhcp_failover_state_lookup (omapi_object_t **sp, omapi_object_t *id, omapi_object_t *ref) { omapi_value_t *tv = (omapi_value_t *)0; isc_result_t status; dhcp_failover_state_t *s; /* First see if we were sent a handle. */ status = omapi_get_value_str (ref, id, "handle", &tv); if (status == ISC_R_SUCCESS) { status = omapi_handle_td_lookup (sp, tv -> value); omapi_value_dereference (&tv, MDL); if (status != ISC_R_SUCCESS) return status; /* Don't return the object if the type is wrong. */ if ((*sp) -> type != dhcp_type_failover_state) { omapi_object_dereference (sp, MDL); return ISC_R_INVALIDARG; } } /* Look the failover state up by peer name. */ status = omapi_get_value_str (ref, id, "peer_name", &tv); if (status == ISC_R_SUCCESS) { for (s = failover_states; s; s = s -> next) { unsigned l = strlen (s -> name); if (l == tv -> value -> u.buffer.len || !memcmp (s -> name, tv -> value -> u.buffer.value, l)) break; } omapi_value_dereference (&tv, MDL); /* If we already have a lease, and it's not the same one, then the query was invalid. */ if (*sp && *sp != (omapi_object_t *)s) { omapi_object_dereference (sp, MDL); return ISC_R_KEYCONFLICT; } else if (!s) { if (*sp) omapi_object_dereference (sp, MDL); return ISC_R_NOTFOUND; } else if (!*sp) /* XXX fix so that hash lookup itself creates XXX the reference. */ omapi_object_reference (sp, (omapi_object_t *)s, MDL); } /* If we get to here without finding a lease, no valid key was specified. */ if (!*sp) return ISC_R_NOKEYS; return ISC_R_SUCCESS; } isc_result_t dhcp_failover_state_create (omapi_object_t **sp, omapi_object_t *id) { return ISC_R_NOTIMPLEMENTED; } isc_result_t dhcp_failover_state_remove (omapi_object_t *sp, omapi_object_t *id) { return ISC_R_NOTIMPLEMENTED; } failover_option_t *dhcp_failover_make_option (unsigned code, char *obuf, unsigned *obufix, unsigned obufmax, ...) { va_list va; struct failover_option_info *info; int i; unsigned size, count; unsigned val; struct iaddr *ival; u_int8_t *bval; char *fmt; #if defined (DEBUG_FAILOVER_MESSAGES) char tbuf [256]; #endif /* Note that the failover_option structure is used differently on input than on output - on input, count is an element count, and on output it's the number of bytes total in the option, including the option code and option length. */ failover_option_t option, *op; /* Bogus option code? */ if (code < 1 || code > FTO_MAX || ft_options [code].type == FT_UNDEF) { return &null_failover_option; } info = &ft_options [code]; va_start (va, obufmax); /* Get the number of elements and the size of the buffer we need to allocate. */ if (info -> type == FT_DDNS || info -> type == FT_DDNS1) { count = info -> type == FT_DDNS ? 1 : 2; size = va_arg (va, int) + count; } else { /* Find out how many items in this list. */ if (info -> num_present) count = info -> num_present; else count = va_arg (va, int); /* Figure out size. */ switch (info -> type) { case FT_UINT8: case FT_BYTES: case FT_DIGEST: size = count; break; case FT_TEXT_OR_BYTES: case FT_TEXT: fmt = va_arg (va, char *); #if defined (HAVE_SNPRINTF) /* Presumably if we have snprintf, we also have vsnprintf. */ vsnprintf (tbuf, sizeof tbuf, fmt, va); #else vsprintf (tbuf, fmt, va); #endif size = strlen (tbuf); count = size; break; case FT_IPADDR: case FT_UINT32: size = count * 4; break; case FT_UINT16: size = count * 2; break; default: /* shouldn't get here. */ log_fatal ("bogus type in failover_make_option: %d", info -> type); break; } } size += 4; /* Allocate a buffer for the option. */ option.count = size; option.data = dmalloc (option.count, MDL); if (!option.data) return &null_failover_option; /* Put in the option code and option length. */ putUShort (option.data, code); putUShort (&option.data [4], size - 4); #if defined (DEBUG_FAILOVER_MESSAGES) sprintf (tbuf, " (%s<%d>", info -> name, option.count); failover_print (obuf, obufix, obufmax, tbuf); #endif /* Now put in the data. */ switch (info -> type) { case FT_UINT8: for (i = 0; i < count; i++) { val = va_arg (va, unsigned); #if defined (DEBUG_FAILOVER_MESSAGES) sprintf (tbuf, " %d", val); failover_print (obuf, obufix, obufmax, tbuf); #endif option.data [i + 4] = val; } break; case FT_IPADDR: for (i = 0; i < count; i++) { ival = va_arg (va, struct iaddr *); if (ival -> len != 4) { dfree (option.data, MDL); log_error ("IP addrlen=%d, should be 4.", ival -> len); return &null_failover_option; } #if defined (DEBUG_FAILOVER_MESSAGES) sprintf (tbuf, " %s", piaddr (*ival)); failover_print (obuf, obufix, obufmax, tbuf); #endif memcpy (&option.data [4 + i * 4], ival -> iabuf, 4); } break; case FT_UINT32: for (i = 0; i < count; i++) { val = va_arg (va, unsigned); #if defined (DEBUG_FAILOVER_MESSAGES) sprintf (tbuf, " %d", val); failover_print (obuf, obufix, obufmax, tbuf); #endif putULong (&option.data [4 + i * 4], val); } break; case FT_BYTES: case FT_DIGEST: bval = va_arg (va, u_int8_t *); #if defined (DEBUG_FAILOVER_MESSAGES) for (i = 0; i < count; i++) { sprintf (tbuf, " %d", bval [i]); failover_print (obuf, obufix, obufmax, tbuf); } #endif memcpy (&option.data [4], bval, count); break; /* On output, TEXT_OR_BYTES is _always_ text, and always NUL terminated. Note that the caller should be careful not to provide a format and data that amount to more than 256 bytes of data, since it will be truncated on platforms that support snprintf, and will mung the stack on those platforms that do not support snprintf. Also, callers should not pass data acquired from the network without specifically checking it to make sure it won't bash the stack. */ case FT_TEXT_OR_BYTES: case FT_TEXT: #if defined (DEBUG_FAILOVER_MESSAGES) failover_print (obuf, obufix, obufmax, " \""); failover_print (obuf, obufix, obufmax, tbuf); failover_print (obuf, obufix, obufmax, "\""); #endif memcpy (&option.data [4], tbuf, count); break; case FT_DDNS: case FT_DDNS1: option.data [4] = va_arg (va, unsigned); if (count == 2) option.data [5] = va_arg (va, unsigned); bval = va_arg (va, u_int8_t *); memcpy (&option.data [4 + count], bval, size - count - 4); #if defined (DEBUG_FAILOVER_MESSAGES) for (i = 4; i < size; i++) { sprintf (tbuf, " %d", option.data [i]); failover_print (obuf, obufix, obufmax, tbuf); } #endif break; case FT_UINT16: for (i = 0; i < count; i++) { val = va_arg (va, u_int32_t); #if defined (DEBUG_FAILOVER_MESSAGES) sprintf (tbuf, " %d", val); failover_print (obuf, obufix, obufmax, tbuf); #endif putUShort (&option.data [4 + i * 2], val); } break; case FT_UNDEF: default: } failover_print (obuf, obufix, obufmax, ")"); /* Now allocate a place to store what we just set up. */ op = dmalloc (sizeof (failover_option_t), MDL); if (!op) { dfree (option.data, MDL); return &null_failover_option; } *op = option; return op; } /* Send a failover message header. */ isc_result_t dhcp_failover_put_message (dhcp_failover_link_t *link, omapi_object_t *connection, int msg_type, ...) { unsigned count = 0; unsigned size = 0; int bad_option = 0; int opix = 0; va_list list; failover_option_t *option; unsigned char *opbuf; isc_result_t status = ISC_R_SUCCESS; unsigned char cbuf; /* Run through the argument list once to compute the length of the option portion of the message. */ va_start (list, msg_type); while ((option = va_arg (list, failover_option_t *))) { if (option != &skip_failover_option) size += option -> count; if (option == &null_failover_option) bad_option = 1; } va_end (list); /* Allocate an option buffer, unless we got an error. */ if (!bad_option) { opbuf = dmalloc (size, MDL); if (!opbuf) status = ISC_R_NOMEMORY; } va_start (list, msg_type); while ((option = va_arg (list, failover_option_t *))) { if (option == &skip_failover_option) continue; if (!bad_option && opbuf) memcpy (&opbuf [opix], option -> data, option -> count); opix += option -> count; dfree (option -> data, MDL); dfree (option, MDL); } if (bad_option || !opbuf) return status; /* Now send the message header. */ /* Message length. */ status = omapi_connection_put_uint16 (connection, size + 12); if (status != ISC_R_SUCCESS) goto err; /* Message type. */ cbuf = msg_type; status = omapi_connection_copyin (connection, &cbuf, 1); if (status != ISC_R_SUCCESS) goto err; /* Payload offset. */ cbuf = 12; status = omapi_connection_copyin (connection, &cbuf, 1); if (status != ISC_R_SUCCESS) goto err; /* Current time. */ status = omapi_connection_put_uint32 (connection, (u_int32_t)cur_time); if (status != ISC_R_SUCCESS) goto err; /* Transaction ID. */ status = omapi_connection_put_uint32 (connection, link -> xid++); if (status != ISC_R_SUCCESS) goto err; /* Payload. */ status = omapi_connection_copyin (connection, opbuf, size); if (status != ISC_R_SUCCESS) goto err; dfree (opbuf, MDL); return status; err: dfree (opbuf, MDL); omapi_disconnect (connection, 1); return status; } /* Send a connect message. */ isc_result_t dhcp_failover_send_connect (omapi_object_t *l) { dhcp_failover_link_t *link; dhcp_failover_state_t *state; isc_result_t status; #if defined (DEBUG_FAILOVER_MESSAGES) char obuf [64]; unsigned obufix = 0; # define FMA obuf, &obufix, sizeof obuf failover_print (FMA, "(connect"); #else # define FMA (unsigned char *)0, (unsigned *)0, 0 #endif if (!l || l -> type != dhcp_type_failover_link) return ISC_R_INVALIDARG; link = (dhcp_failover_link_t *)l; state = link -> state_object; if (!l -> outer || l -> outer -> type != omapi_type_connection) return ISC_R_INVALIDARG; status = (dhcp_failover_put_message (link, l -> outer, FTM_CONNECT, dhcp_failover_make_option (FTO_SERVER_ADDR, FMA, &state -> server_addr), dhcp_failover_make_option (FTO_MAX_UNACKED, FMA, state -> max_flying_updates), dhcp_failover_make_option (FTO_RECEIVE_TIMER, FMA, state -> max_response_delay), dhcp_failover_make_option (FTO_VENDOR_CLASS, FMA, "isc-%s", DHCP_VERSION), dhcp_failover_make_option (FTO_PROTOCOL_VERSION, FMA, DHCP_FAILOVER_VERSION), dhcp_failover_make_option (FTO_TLS_REQUEST, FMA, 0, 0), dhcp_failover_make_option (FTO_MCLT, FMA, state -> mclt), dhcp_failover_make_option (FTO_HBA, FMA, state -> hba), (failover_option_t *)0)); #if defined (DEBUG_FAILOVER_MESSAGES) if (status != ISC_R_SUCCESS) failover_print (FMA, " (failed)"); failover_print (FMA, ")"); if (obufix) { log_debug ("%s", obuf); } #endif return status; } /* Send a Bind Update message. */ isc_result_t dhcp_failover_update_peer (struct lease *lease, int releasep) { dhcp_failover_link_t *link; dhcp_failover_state_t *state; isc_result_t status; #if defined (DEBUG_FAILOVER_MESSAGES) char obuf [64]; unsigned obufix = 0; # define FMA obuf, &obufix, sizeof obuf failover_print (FMA, "(bndupd"); #else # define FMA (unsigned char *)0, (unsigned *)0, 0 #endif if (!lease -> pool || !lease -> pool -> failover_peer || !lease -> pool -> failover_peer -> outer) return ISC_R_INVALIDARG; state = lease -> pool -> failover_peer; if (state -> outer -> type != dhcp_type_failover_link) return ISC_R_INVALIDARG; link = (dhcp_failover_link_t *)state -> outer; if (!link -> outer || link -> outer -> type != omapi_type_connection) return ISC_R_INVALIDARG; status = (dhcp_failover_put_message (link, link -> outer, FTM_BNDUPD, dhcp_failover_make_option (FTO_ASSIGNED_IP_ADDRESS, FMA, lease -> ip_addr.iabuf), dhcp_failover_make_option (FTO_BINDING_STATUS, FMA, 0 /* ??? */), lease -> uid_len ? dhcp_failover_make_option (FTO_CLIENT_IDENTIFIER, FMA, lease -> uid, lease -> uid_len) : &skip_failover_option, dhcp_failover_make_option (FTO_CHADDR, FMA, lease -> hardware_addr.hlen + 1, lease -> hardware_addr.hbuf), dhcp_failover_make_option (FTO_LEASE_EXPIRY, FMA, lease -> ends), dhcp_failover_make_option (FTO_POTENTIAL_EXPIRY, FMA, lease -> tstp), dhcp_failover_make_option (FTO_STOS, FMA, lease -> starts), dhcp_failover_make_option (FTO_CLTT, FMA, lease -> cltt), &skip_failover_option, /* XXX DDNS */ &skip_failover_option, /* XXX request options */ &skip_failover_option, /* XXX reply options */ (failover_option_t *)0)); #if defined (DEBUG_FAILOVER_MESSAGES) if (status != ISC_R_SUCCESS) failover_print (FMA, " (failed)"); failover_print (FMA, ")"); if (obufix) { log_debug ("%s", obuf); } #endif return status; } #if defined (DEBUG_FAILOVER_MESSAGES) /* Print hunks of failover messages, doing line breaks as appropriate. Note that this assumes syslog is being used, rather than, e.g., the Windows NT logging facility, where just dumping the whole message in one hunk would be more appropriate. */ void failover_print (char *obuf, unsigned *obufix, unsigned obufmax, const char *s) { int len = strlen (s); if (len + *obufix + 1 >= obufmax) { if (!*obufix) { log_debug ("%s", s); return; } log_debug ("%s", obuf); *obufix = 0; } else { strcpy (&obuf [*obufix], s); obufix += len; } } #endif /* defined (DEBUG_FAILOVER_MESSAGES) */ void update_partner (struct lease *lease) { } #endif /* defined (FAILOVER_PROTOCOL) */