failover.c revision 1.4 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