Kea 3.3.3
ha_impl.cc
Go to the documentation of this file.
1// Copyright (C) 2018-2026 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
11#include <cc/data.h>
12#include <dhcp/pkt4.h>
13#include <dhcp/pkt6.h>
14#include <dhcpsrv/cfgmgr.h>
15#include <dhcpsrv/lease.h>
17#include <dhcpsrv/subnet.h>
20#include <stats/stats_mgr.h>
21
22#include <ha_log.h>
23
24using namespace isc::asiolink;
25using namespace isc::config;
26using namespace isc::data;
27using namespace isc::dhcp;
28using namespace isc::hooks;
29using namespace isc::log;
30using namespace isc::stats;
31
32namespace isc {
33namespace ha {
34
38
39void
40HAImpl::configure(const ConstElementPtr& input_config) {
41 config_ = HAConfigParser::parse(input_config);
42}
43
44void
46 const HAServerType& server_type) {
47 auto configs = config_->getAll();
48 for (unsigned id = 0; id < configs.size(); ++id) {
49 // Create the HA service and crank up the state machine.
50 auto service = boost::make_shared<HAService>(id, io_service_, network_state,
51 configs[id], server_type);
52 for (auto const& peer_config : configs[id]->getAllServersConfig()) {
53 services_->map(peer_config.first, service);
54 }
55 }
56 // Schedule a start of the services. This ensures we begin after
57 // the dust has settled and Kea MT mode has been firmly established.
58 io_service_->post([&]() {
59 for (auto const& service : services_->getAll()) {
60 service->startClientAndListener();
61 }
62 });
63}
64
66 stop();
67 config_.reset();
68 services_.reset(new HAServiceMapper());
69 io_service_->stopAndPoll();
70}
71
72void
74 for (auto const& service : services_->getAll()) {
75 // Shut down the services explicitly, we need finer control
76 // than relying on destruction order.
77 service->stopClientAndListener();
78 }
79 io_service_->stopAndPoll();
80}
81
82void
84 // If there are multiple relationships, the HA-specific processing is
85 // in the subnet4_select hook point.
86 if (services_->hasMultiple()) {
87 return;
88 }
89
90 Pkt4Ptr query4;
91 callout_handle.getArgument("query4", query4);
92
95 try {
96 // We have to unpack the query to get access into HW address which is
97 // used to load balance the packet.
98 if (callout_handle.getStatus() != CalloutHandle::NEXT_STEP_SKIP) {
99 query4->unpack();
100 }
101
102 } catch (const SkipRemainingOptionsError& ex) {
103 // An option failed to unpack but we are to attempt to process it
104 // anyway. Log it and let's hope for the best.
107 .arg(ex.what());
108
109 } catch (const std::exception& ex) {
110 // Packet parsing failed. Drop the packet.
112 .arg(query4->getRemoteAddr().toText())
113 .arg(query4->getLocalAddr().toText())
114 .arg(query4->getIface())
115 .arg(ex.what());
116
117 // Increase the statistics of parse failures and dropped packets.
118 StatsMgr::instance().addValue("pkt4-parse-failed", static_cast<int64_t>(1));
119 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
121 return;
122 }
123
124 // Check if we should process this query. If not, drop it.
125 if (!services_->get()->inScope(query4)) {
127 .arg(query4->getLabel());
128 StatsMgr::instance().addValue("pkt4-not-for-us", static_cast<int64_t>(1));
129 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
131
132 } else {
133 // We have successfully parsed the query so we have to signal
134 // to the server that it must not parse it.
136 }
137}
138
139void
141 // This callout only applies in the case of multiple relationships.
142 // When there is only one relationship it has no effect because
143 // the decision if we should process the packet has been made
144 // in the buffer4_receive callout.
145 if (!services_->hasMultiple()) {
146 // Return silently. It is not an error.
147 return;
148 }
149
150 Pkt4Ptr query4;
151 callout_handle.getArgument("query4", query4);
152
153 ConstSubnet4Ptr subnet4;
154 callout_handle.getArgument("subnet4", subnet4);
155
156 // If the server failed to select the subnet this pointer is null.
157 // There is nothing we can do with this packet because we don't know
158 // which relationship it belongs to. We're even unable to check if the
159 // server is responsible for this packet.
160 if (!subnet4) {
161 // Log at debug level because that's the level at which the server
162 // logs the subnet selection failure.
164 .arg(query4->getLabel());
165 // We even do not know what is the server handling it...
166 StatsMgr::instance().addValue("pkt4-not-for-us", static_cast<int64_t>(1));
167 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
169 return;
170 }
171
172 // The subnet configuration should contain a user context
173 // and this context should contain a mapping of the subnet to a
174 // relationship. If the context doesn't exist there is no way
175 // to determine which relationship the packet belongs to.
176 std::string server_name;
177 try {
178 server_name = HAConfig::getSubnetServerName(subnet4);
179 if (server_name.empty()) {
181 .arg(query4->getLabel())
182 .arg(subnet4->toText());
183 // We even do not know what is the server handling it...
184 StatsMgr::instance().addValue("pkt4-not-for-us", static_cast<int64_t>(1));
185 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
187 return;
188 }
189
190 } catch (...) {
192 .arg(query4->getLabel())
193 .arg(subnet4->toText());
194 StatsMgr::instance().addValue("pkt4-processing-failed", static_cast<int64_t>(1));
195 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
197 return;
198 }
199
200 // Try to find a relationship matching this server name.
201 auto service = services_->get(server_name);
202 if (!service) {
204 .arg(query4->getLabel())
205 .arg(server_name);
207 StatsMgr::instance().addValue("pkt4-not-for-us", static_cast<int64_t>(1));
208 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
209 return;
210 }
211
212 // We have found appropriate relationship. Let's see if we should
213 // process the packet. We'll drop the packet if our partner is
214 // operational and is responsible for this packet.
215 if (!service->inScope(query4)) {
217 .arg(query4->getLabel())
218 .arg(server_name);
219 StatsMgr::instance().addValue("pkt4-not-for-us", static_cast<int64_t>(1));
220 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
222 return;
223 }
224
225 // Remember the server name we retrieved from the subnet. We will
226 // need it in a leases4_committed callout that doesn't have access
227 // to the subnet object.
228 callout_handle.setContext("ha-server-name", server_name);
229}
230
231void
233 Pkt4Ptr query4;
234 Lease4CollectionPtr leases4;
235 Lease4CollectionPtr deleted_leases4;
236
237 // Get all arguments available for the leases4_committed hook point.
238 // If any of these arguments is not available this is a programmatic
239 // error. An exception will be thrown which will be caught by the
240 // caller and logged.
241 callout_handle.getArgument("query4", query4);
242
243 callout_handle.getArgument("leases4", leases4);
244 callout_handle.getArgument("deleted_leases4", deleted_leases4);
245
246 // In some cases we may have no leases, e.g. DHCPNAK.
247 if (leases4->empty() && deleted_leases4->empty()) {
249 .arg(query4->getLabel());
250 return;
251 }
252
253 // Get default config and service instances.
254 HAConfigPtr config = config_->get();
255 HAServicePtr service = services_->get();
256
257 // If we have multiple relationships we need to find the one that
258 // matches our subnet.
259 if (services_->hasMultiple()) {
260 try {
261 // Retrieve the server name from the context and the respective
262 // config and service instances.
263 std::string server_name;
264 callout_handle.getContext("ha-server-name", server_name);
265 config = config_->get(server_name);
266 service = services_->get(server_name);
267
268 // This is rather impossible but let's be safe.
269 if (!config || !service) {
270 isc_throw(Unexpected, "relationship not configured for server '" << server_name << "'");
271 }
272
273 } catch (const std::exception& ex) {
275 .arg(query4->getLabel())
276 .arg(ex.what());
277 StatsMgr::instance().addValue("pkt4-not-for-us", static_cast<int64_t>(1));
278 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
280 return;
281 }
282 }
283
284 // If the hook library is configured to not send lease updates to the
285 // partner, there is nothing to do because this whole callout is
286 // currently about sending lease updates.
287 if (!config->amSendingLeaseUpdates()) {
288 // No need to log it, because it was already logged when configuration
289 // was applied.
290 return;
291 }
292
293 // Get the parking lot for this hook point. We're going to remember this
294 // pointer until we unpark the packet.
295 ParkingLotHandlePtr parking_lot = callout_handle.getParkingLotHandlePtr();
296
297 // Create a reference to the parked packet. This signals that we have a
298 // stake in unparking it.
299 parking_lot->reference(query4);
300
301 // Asynchronously send lease updates. In some cases no updates will be sent,
302 // e.g. when this server is in the partner-down state and there are no backup
303 // servers. In those cases we simply return without parking the DHCP query.
304 // The response will be sent to the client immediately.
305 try {
306 if (service->asyncSendLeaseUpdates(query4, leases4, deleted_leases4, parking_lot) == 0) {
307 // Dereference the parked packet. This releases our stake in it.
308 parking_lot->dereference(query4);
309 return;
310 }
311 } catch (...) {
312 // Make sure we dereference.
313 parking_lot->dereference(query4);
314 throw;
315 }
316
317 // The callout returns this status code to indicate to the server that it
318 // should leave the packet parked. It will be parked until each hook
319 // library with a reference, unparks the packet.
321}
322
323void
325 // Always return CONTINUE.
327 size_t peers_to_update = 0;
328
329 // If the hook library is configured to not send lease updates to the
330 // partner, there is nothing to do because this whole callout is
331 // currently about sending lease updates.
332 if (!config_->get()->amSendingLeaseUpdates()) {
333 // No need to log it, because it was already logged when configuration
334 // was applied.
335 callout_handle.setArgument("peers_to_update", peers_to_update);
336 return;
337 }
338
339 // Get all arguments available for the lease4_server_decline hook point.
340 // If any of these arguments is not available this is a programmatic
341 // error. An exception will be thrown which will be caught by the
342 // caller and logged.
343 Pkt4Ptr query4;
344 callout_handle.getArgument("query4", query4);
345
346 Lease4Ptr lease4;
347 callout_handle.getArgument("lease4", lease4);
348
349 // Asynchronously send the lease update. In some cases no updates will be sent,
350 // e.g. when this server is in the partner-down state and there are no backup
351 // servers.
352 peers_to_update = services_->get()->asyncSendSingleLeaseUpdate(query4, lease4, 0);
353 callout_handle.setArgument("peers_to_update", peers_to_update);
354}
355
356void
358 Lease4Ptr lease4;
359 callout_handle.getArgument("lease4", lease4);
360
361 // If there are multiple relationships we need to take a detour and find
362 // a subnet the lease belongs to. The subnet will contain the information
363 // required to select appropriate HA service.
364 HAServicePtr service;
365 if (services_->hasMultiple()) {
366 auto subnet4 = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease4->subnet_id_);
367 if (!subnet4) {
368 // No subnet means that we possibly have some stale leases that don't
369 // really belong to us. Therefore, there we return early and rely on the
370 // DHCP server to reclaim them. The HA hook has no jurisdiction here.
371 return;
372 }
373
374 std::string server_name;
375 try {
376 server_name = HAConfig::getSubnetServerName(subnet4);
377 if (server_name.empty()) {
378 // Again, this subnet has no hint for HA where our lease belongs.
379 // We have to rely on the server to run reclamation of this lease.
380 return;
381 }
382 } catch (...) {
383 // Someone has tried to configure the hint for HA in the subnet but
384 // it was poorly specified. We will log an error and leave again.
386 .arg(lease4->addr_.toText())
387 .arg(subnet4->toText());
388 return;
389 }
390 service = services_->get(server_name);
391
392 } else {
393 service = services_->get();
394 }
395
396 if (!service) {
397 // This is highly unlikely but better handle null pointers.
398 return;
399 }
400
401 if (!shouldReclaim(service, lease4)) {
402 // While the server is in the terminated state it has to be careful about
403 // reclaiming the leases to avoid conflicting DNS updates with a server that
404 // owns the lease. This lease apparently belongs to another server, so we
405 // should not reclaim it.
407 .arg(lease4->addr_.toText());
409 return;
410 }
411}
412
413void
415 // If there are multiple relationships, the HA-specific processing is
416 // in the subnet6_select hook point.
417 if (services_->hasMultiple()) {
418 return;
419 }
420
421 Pkt6Ptr query6;
422 callout_handle.getArgument("query6", query6);
423
426 try {
427 // We have to unpack the query to get access into DUID which is
428 // used to load balance the packet.
429 if (callout_handle.getStatus() != CalloutHandle::NEXT_STEP_SKIP) {
430 query6->unpack();
431 }
432
433 } catch (const SkipRemainingOptionsError& ex) {
434 // An option failed to unpack but we are to attempt to process it
435 // anyway. Log it and let's hope for the best.
438 .arg(ex.what());
439
440 } catch (const std::exception& ex) {
441 // Packet parsing failed. Drop the packet.
443 .arg(query6->getRemoteAddr().toText())
444 .arg(query6->getLocalAddr().toText())
445 .arg(query6->getIface())
446 .arg(ex.what());
447
448 // Increase the statistics of parse failures and dropped packets.
449 StatsMgr::instance().addValue("pkt6-parse-failed", static_cast<int64_t>(1));
450 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
451
452
454 return;
455 }
456
457 // Check if we should process this query. If not, drop it.
458 if (!services_->get()->inScope(query6)) {
460 .arg(query6->getLabel());
461 StatsMgr::instance().addValue("pkt6-not-for-us", static_cast<int64_t>(1));
462 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
464
465 } else {
466 // We have successfully parsed the query so we have to signal
467 // to the server that it must not parse it.
469 }
470}
471
472void
474 // This callout only applies in the case of multiple relationships.
475 // When there is only one relationship it has no effect because
476 // the decision if we should process the packet has been made
477 // in the buffer6_receive callout.
478 if (!services_->hasMultiple()) {
479 // Return silently. It is not an error.
480 return;
481 }
482
483 Pkt6Ptr query6;
484 callout_handle.getArgument("query6", query6);
485
486 ConstSubnet6Ptr subnet6;
487 callout_handle.getArgument("subnet6", subnet6);
488
489 // If the server failed to select the subnet this pointer is null.
490 // There is nothing we can do with this packet because we don't know
491 // which relationship it belongs to. We're even unable to check if the
492 // server is responsible for this packet.
493 if (!subnet6) {
494 // Log at debug level because that's the level at which the server
495 // logs the subnet selection failure.
497 .arg(query6->getLabel());
499 // We even do not know what is the server handling it...
500 StatsMgr::instance().addValue("pkt6-not-for-us", static_cast<int64_t>(1));
501 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
502 return;
503 }
504
505 // The subnet configuration should contain a user context
506 // and this context should contain a mapping of the subnet to a
507 // relationship. If the context doesn't exist there is no way
508 // to determine which relationship the packet belongs to.
509 std::string server_name;
510 try {
511 server_name = HAConfig::getSubnetServerName(subnet6);
512 if (server_name.empty()) {
514 .arg(query6->getLabel())
515 .arg(subnet6->toText());
517 // We even do not know what is the server handling it...
518 StatsMgr::instance().addValue("pkt6-not-for-us", static_cast<int64_t>(1));
519 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
520 return;
521 }
522
523 } catch (...) {
525 .arg(query6->getLabel())
526 .arg(subnet6->toText());
528 StatsMgr::instance().addValue("pkt6-processing-failed", static_cast<int64_t>(1));
529 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
530 return;
531 }
532
533 // Try to find a relationship matching this server name.
534 auto service = services_->get(server_name);
535 if (!service) {
537 .arg(query6->getLabel())
538 .arg(server_name);
540 StatsMgr::instance().addValue("pkt6-not-for-us", static_cast<int64_t>(1));
541 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
542 return;
543 }
544
545 // We have found appropriate relationship. Let's see if we should
546 // process the packet. We'll drop the packet if our partner is
547 // operational and is responsible for this packet.
548 if (!service->inScope(query6)) {
550 .arg(query6->getLabel())
551 .arg(server_name);
552 StatsMgr::instance().addValue("pkt6-not-for-us", static_cast<int64_t>(1));
553 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
555 return;
556 }
557
558 // Remember the server name we retrieved from the subnet. We will
559 // need it in a leases4_committed callout that doesn't have access
560 // to the subnet object.
561 callout_handle.setContext("ha-server-name", server_name);
562}
563
564void
566 Pkt6Ptr query6;
567 Lease6CollectionPtr leases6;
568 Lease6CollectionPtr deleted_leases6;
569
570 // Get all arguments available for the leases6_committed hook point.
571 // If any of these arguments is not available this is a programmatic
572 // error. An exception will be thrown which will be caught by the
573 // caller and logged.
574 callout_handle.getArgument("query6", query6);
575
576 callout_handle.getArgument("leases6", leases6);
577 callout_handle.getArgument("deleted_leases6", deleted_leases6);
578
579 // In some cases we may have no leases.
580 if (leases6->empty() && deleted_leases6->empty()) {
582 .arg(query6->getLabel());
583 return;
584 }
585
586 HAConfigPtr config = config_->get();
587 HAServicePtr service = services_->get();
588 if (services_->hasMultiple()) {
589 try {
590 std::string server_name;
591 callout_handle.getContext("ha-server-name", server_name);
592 config = config_->get(server_name);
593 service = services_->get(server_name);
594
595 if (!config || !service) {
596 isc_throw(Unexpected, "relationship not found for the ha-server-name='" << server_name << "'");
597 }
598
599 } catch (const std::exception& ex) {
601 .arg(query6->getLabel())
602 .arg(ex.what());
603 StatsMgr::instance().addValue("pkt6-not-for-us", static_cast<int64_t>(1));
604 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
606 return;
607 }
608 }
609
610 // If the hook library is configured to not send lease updates to the
611 // partner, there is nothing to do because this whole callout is
612 // currently about sending lease updates.
613 if (!config->amSendingLeaseUpdates()) {
614 // No need to log it, because it was already logged when configuration
615 // was applied.
616 return;
617 }
618
619 // Get the parking lot for this hook point. We're going to remember this
620 // pointer until we unpark the packet.
621 ParkingLotHandlePtr parking_lot = callout_handle.getParkingLotHandlePtr();
622
623 // Create a reference to the parked packet. This signals that we have a
624 // stake in unparking it.
625 parking_lot->reference(query6);
626
627 // Asynchronously send lease updates. In some cases no updates will be sent,
628 // e.g. when this server is in the partner-down state and there are no backup
629 // servers. In those cases we simply return without parking the DHCP query.
630 // The response will be sent to the client immediately.
631 try {
632 if (service->asyncSendLeaseUpdates(query6, leases6, deleted_leases6, parking_lot) == 0) {
633 // Dereference the parked packet. This releases our stake in it.
634 parking_lot->dereference(query6);
635 return;
636 }
637 } catch (...) {
638 // Make sure we dereference.
639 parking_lot->dereference(query6);
640 throw;
641 }
642
643 // The callout returns this status code to indicate to the server that it
644 // should leave the packet parked. It will be unparked until each hook
645 // library with a reference, unparks the packet.
647}
648
649void
651 Lease6Ptr lease6;
652 callout_handle.getArgument("lease6", lease6);
653
654 // If there are multiple relationships we need to take a detour and find
655 // a subnet the lease belongs to. The subnet will contain the information
656 // required to select appropriate HA service.
657 HAServicePtr service;
658 if (services_->hasMultiple()) {
659 auto subnet6 = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease6->subnet_id_);
660 if (!subnet6) {
661 // No subnet means that we possibly have some stale leases that don't
662 // really belong to us. Therefore, there we return early and rely on the
663 // DHCP server to reclaim them. The HA hook has no jurisdiction here.
664 return;
665 }
666
667 std::string server_name;
668 try {
669 server_name = HAConfig::getSubnetServerName(subnet6);
670 if (server_name.empty()) {
671 // Again, this subnet has no hint for HA where our lease belongs.
672 // We have to rely on the server to run reclamation of this lease.
673 return;
674 }
675 } catch (...) {
676 // Someone has tried to configure the hint for HA in the subnet but
677 // it was poorly specified. We will log an error and leave again.
679 .arg(lease6->addr_.toText())
680 .arg(subnet6->toText());
681 return;
682 }
683 service = services_->get(server_name);
684
685 } else {
686 service = services_->get();
687 }
688
689 if (!service) {
690 // This is highly unlikely but better handle null pointers.
691 return;
692 }
693
694 if (!shouldReclaim(service, lease6)) {
695 // While the server is in the terminated state it has to be careful about
696 // reclaiming the leases to avoid conflicting DNS updates with a server that
697 // owns the lease. This lease apparently belongs to another server, so we
698 // should not reclaim it.
700 .arg(lease6->addr_.toText());
702 return;
703 }
704}
705
706void
708 std::string command_name;
709 callout_handle.getArgument("name", command_name);
710 if (command_name == "status-get") {
711 // Get the response.
712 ConstElementPtr response;
713 callout_handle.getArgument("response", response);
714 if (!response || (response->getType() != Element::map)) {
715 return;
716 }
717 // Get the arguments item from the response.
718 ConstElementPtr resp_args = response->get("arguments");
719 if (!resp_args || (resp_args->getType() != Element::map)) {
720 return;
721 }
722 // Add the ha servers info to arguments.
723 ElementPtr mutable_resp_args =
724 boost::const_pointer_cast<Element>(resp_args);
725
726 // Process the status get command for each HA service.
727 auto ha_relationships = Element::createList();
728 for (auto const& service : services_->getAll()) {
729 auto ha_relationship = Element::createMap();
730 ConstElementPtr ha_servers = service->processStatusGet();
731 ha_relationship->set("ha-servers", ha_servers);
732 ha_relationship->set("ha-mode", Element::create(HAConfig::HAModeToString(config_->get()->getHAMode())));
733 ha_relationships->add(ha_relationship);
734 mutable_resp_args->set("high-availability", ha_relationships);
735 }
736 }
737}
738
739void
741 // Command must always be provided.
742 ConstElementPtr command;
743 callout_handle.getArgument("command", command);
744
745 // Retrieve arguments.
746 ConstElementPtr args;
747 static_cast<void>(parseCommand(args, command));
748
749 HAServicePtr service;
750 try {
751 service = getHAServiceByServerName("ha-heartbeat", args);
752
753 } catch (const std::exception& ex) {
754 // There was an error while parsing command arguments. Return an error status
755 // code to notify the user.
757 callout_handle.setArgument("response", response);
758 return;
759 }
760
761 // Command parsing was successful, so let's process the command.
762 ConstElementPtr response = service->processHeartbeat();
763 callout_handle.setArgument("response", response);
764}
765
766void
768 // Command must always be provided.
769 ConstElementPtr command;
770 callout_handle.getArgument("command", command);
771
772 // Retrieve arguments.
773 ConstElementPtr args;
774 static_cast<void>(parseCommand(args, command));
775
776 ConstElementPtr server_name;
777 unsigned int max_period_value = 0;
778
779 HAServicePtr service;
780 try {
781 // Arguments are required for the ha-sync command.
782 if (!args) {
783 isc_throw(BadValue, "arguments not found in the 'ha-sync' command");
784 }
785
786 // Arguments must be a map.
787 if (args->getType() != Element::map) {
788 isc_throw(BadValue, "arguments in the 'ha-sync' command are not a map");
789 }
790
791 // server-name is mandatory. Otherwise how can we know the server to
792 // communicate with.
793 server_name = args->get("server-name");
794 if (!server_name) {
795 isc_throw(BadValue, "'server-name' is mandatory for the 'ha-sync' command");
796 }
797
798 // server-name must obviously be a string.
799 if (server_name->getType() != Element::string) {
800 isc_throw(BadValue, "'server-name' must be a string in the 'ha-sync' command");
801 }
802
803 // max-period is optional. In fact it is optional for dhcp-disable command too.
804 ConstElementPtr max_period = args->get("max-period");
805 if (max_period) {
806 // If it is specified, it must be a positive integer.
807 if ((max_period->getType() != Element::integer) ||
808 (max_period->intValue() <= 0)) {
809 isc_throw(BadValue, "'max-period' must be a positive integer in the 'ha-sync' command");
810 }
811
812 max_period_value = static_cast<unsigned int>(max_period->intValue());
813 }
814
815 service = getHAServiceByServerName("ha-sync", args);
816
817 } catch (const std::exception& ex) {
818 // There was an error while parsing command arguments. Return an error status
819 // code to notify the user.
821 callout_handle.setArgument("response", response);
822 return;
823 }
824
825 // Command parsing was successful, so let's process the command.
826 ConstElementPtr response = service->processSynchronize(server_name->stringValue(),
827 max_period_value);
828 callout_handle.setArgument("response", response);
829}
830
831void
833 // Command must always be provided.
834 ConstElementPtr command;
835 callout_handle.getArgument("command", command);
836
837 // Retrieve arguments.
838 ConstElementPtr args;
839 static_cast<void>(parseCommand(args, command));
840
841 HAServicePtr service;
842 std::vector<std::string> scopes_vector;
843 try {
844 // Arguments must be present.
845 if (!args) {
846 isc_throw(BadValue, "arguments not found in the 'ha-scopes' command");
847 }
848
849 // Arguments must be a map.
850 if (args->getType() != Element::map) {
851 isc_throw(BadValue, "arguments in the 'ha-scopes' command are not a map");
852 }
853
854 // scopes argument is mandatory.
855 ConstElementPtr scopes = args->get("scopes");
856 if (!scopes) {
857 isc_throw(BadValue, "'scopes' is mandatory for the 'ha-scopes' command");
858 }
859
860 // It contains a list of scope names.
861 if (scopes->getType() != Element::list) {
862 isc_throw(BadValue, "'scopes' must be a list in the 'ha-scopes' command");
863 }
864
865 // Retrieve scope names from this list. The list may be empty to clear the
866 // scopes.
867 for (size_t i = 0; i < scopes->size(); ++i) {
868 ConstElementPtr scope = scopes->get(i);
869 if (!scope || scope->getType() != Element::string) {
870 isc_throw(BadValue, "scope name must be a string in the 'scopes' argument");
871 }
872 scopes_vector.push_back(scope->stringValue());
873 }
874
875 service = getHAServiceByServerName("ha-scopes", args);
876
877 } catch (const std::exception& ex) {
878 // There was an error while parsing command arguments. Return an error status
879 // code to notify the user.
881 callout_handle.setArgument("response", response);
882 return;
883 }
884
885 // Command parsing was successful, so let's process the command.
886 ConstElementPtr response = service->processScopes(scopes_vector);
887 callout_handle.setArgument("response", response);
888}
889
890void
892 // Command must always be provided.
893 ConstElementPtr command;
894 callout_handle.getArgument("command", command);
895
896 // Retrieve arguments.
897 ConstElementPtr args;
898 static_cast<void>(parseCommand(args, command));
899
900 HAServicePtr service;
901 try {
902 service = getHAServiceByServerName("ha-continue", args);
903
904 } catch (const std::exception& ex) {
905 // There was an error while parsing command arguments. Return an error status
906 // code to notify the user.
908 callout_handle.setArgument("response", response);
909 return;
910 }
911 ConstElementPtr response = service->processContinue();
912 callout_handle.setArgument("response", response);
913}
914
915void
917 // Command must always be provided.
918 ConstElementPtr command;
919 callout_handle.getArgument("command", command);
920
921 HAServicePtr service;
922 try {
923 // Retrieve arguments.
924 ConstElementPtr args;
925 static_cast<void>(parseCommandWithArgs(args, command));
926
927 ConstElementPtr cancel_op = args->get("cancel");
928 if (!cancel_op) {
929 isc_throw(BadValue, "'cancel' is mandatory for the 'ha-maintenance-notify' command");
930 }
931
932 if (cancel_op->getType() != Element::boolean) {
933 isc_throw(BadValue, "'cancel' must be a boolean in the 'ha-maintenance-notify' command");
934 }
935
936 ConstElementPtr state = args->get("state");
937 if (state && state->getType() != Element::string) {
938 isc_throw(BadValue, "'state' must be a string in the 'ha-maintenance-notify' command");
939 }
940
941 service = getHAServiceByServerName("ha-maintenance-notify", args);
942
943 ConstElementPtr response = service->processMaintenanceNotify(cancel_op->boolValue(),
944 state ? state->stringValue() : "unavailable");
945 callout_handle.setArgument("response", response);
946
947 } catch (const std::exception& ex) {
948 // There was an error while parsing command arguments. Return an error status
949 // code to notify the user.
951 callout_handle.setArgument("response", response);
952 }
953}
954
955void
957 ConstElementPtr response;
958 for (auto const& service : services_->getAll()) {
959 response = service->processMaintenanceStart();
960 int rcode = CONTROL_RESULT_SUCCESS;
961 static_cast<void>(parseAnswer(rcode, response));
962 if (rcode != CONTROL_RESULT_SUCCESS) {
963 break;
964 }
965 }
966 callout_handle.setArgument("response", response);
967}
968
969void
971 ConstElementPtr response;
972 for (auto const& service : services_->getAll()) {
973 response = service->processMaintenanceCancel();
974 }
975 callout_handle.setArgument("response", response);
976}
977
978void
980 // Command must always be provided.
981 ConstElementPtr command;
982 callout_handle.getArgument("command", command);
983
984 // Retrieve arguments.
985 ConstElementPtr args;
986 static_cast<void>(parseCommand(args, command));
987
988 HAServicePtr service;
989 try {
990 service = getHAServiceByServerName("ha-reset", args);
991
992 } catch (const std::exception& ex) {
993 // There was an error while parsing command arguments. Return an error status
994 // code to notify the user.
996 callout_handle.setArgument("response", response);
997 return;
998 }
999
1000 ConstElementPtr response = service->processHAReset();
1001 callout_handle.setArgument("response", response);
1002}
1003
1004void
1006 // Command must always be provided.
1007 ConstElementPtr command;
1008 callout_handle.getArgument("command", command);
1009
1010 // Retrieve arguments.
1011 ConstElementPtr args;
1012 static_cast<void>(parseCommand(args, command));
1013
1014 HAServicePtr service;
1015 auto origin_id_value = NetworkState::HA_REMOTE_COMMAND+1;
1016 try {
1017 if (args) {
1018 auto origin_id = args->get("origin-id");
1019 auto origin = args->get("origin");
1020 // The origin-id is a new parameter replacing the origin. However, some versions
1021 // of Kea may still send the origin parameter instead.
1022 if (origin_id) {
1023 if (origin_id->getType() != Element::integer) {
1024 isc_throw(BadValue, "'origin-id' must be an integer in the 'ha-sync-complete-notify' command");
1025 }
1026 origin_id_value = origin_id->intValue();
1027
1028 } else if (origin) {
1029 if (origin->getType() != Element::integer) {
1030 isc_throw(BadValue, "'origin' must be an integer in the 'ha-sync-complete-notify' command");
1031 }
1032 origin_id_value = origin->intValue();
1033 }
1034 }
1035
1036 service = getHAServiceByServerName("ha-sync-complete-notify", args);
1037
1038 } catch (const std::exception& ex) {
1039 // There was an error while parsing command arguments. Return an error status
1040 // code to notify the user.
1041 ConstElementPtr response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
1042 callout_handle.setArgument("response", response);
1043 return;
1044 }
1045
1046 ConstElementPtr response = service->processSyncCompleteNotify(origin_id_value);
1047 callout_handle.setArgument("response", response);
1048}
1049
1051HAImpl::getHAServiceByServerName(const std::string& command_name, ConstElementPtr args) const {
1052 HAServicePtr service;
1053 if (args) {
1054 // Arguments must be a map.
1055 if (args->getType() != Element::map) {
1056 isc_throw(BadValue, "arguments in the '" << command_name << "' command are not a map");
1057 }
1058
1059 auto server_name = args->get("server-name");
1060
1061 if (server_name) {
1062 if (server_name->getType() != Element::string) {
1063 isc_throw(BadValue, "'server-name' must be a string in the '" << command_name << "' command");
1064 }
1065 service = services_->get(server_name->stringValue());
1066 if (!service) {
1067 isc_throw(BadValue, server_name->stringValue() << " matches no configured"
1068 << " 'server-name'");
1069 }
1070 }
1071 }
1072
1073 if (!service) {
1074 service = services_->get();
1075 }
1076
1077 return (service);
1078}
1079
1080bool
1081HAImpl::shouldReclaim(const HAServicePtr& service, const dhcp::Lease4Ptr& lease4) const {
1082 return (service->shouldReclaim(lease4));
1083}
1084
1085bool
1086HAImpl::shouldReclaim(const HAServicePtr& service, const dhcp::Lease6Ptr& lease6) const {
1087 return (service->shouldReclaim(lease6));
1088}
1089
1090} // end of namespace isc::ha
1091} // end of namespace isc
@ NEXT_STEP_PARK
park the packet
@ NEXT_STEP_CONTINUE
continue normally
@ NEXT_STEP_DROP
drop the packet
@ NEXT_STEP_SKIP
skip the next processing step
static ElementPtr create(const Position &pos=ZERO_POSITION())
Create a NullElement.
Definition data.cc:300
@ map
Definition data.h:160
@ integer
Definition data.h:153
@ boolean
Definition data.h:155
@ list
Definition data.h:159
@ string
Definition data.h:157
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition data.cc:355
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition data.cc:350
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown when an unexpected error condition occurs.
static CfgMgr & instance()
returns a single instance of Configuration Manager
Definition cfgmgr.cc:29
SrvConfigPtr getCurrentCfg()
Returns a pointer to the current configuration.
Definition cfgmgr.cc:116
static const unsigned int HA_REMOTE_COMMAND
The network state is being altered by a "dhcp-disable" or "dhcp-enable" command sent by a HA partner.
Exception thrown during option unpacking This exception is thrown when an error has occurred,...
Definition option.h:53
static HAConfigMapperPtr parse(const data::ConstElementPtr &config)
Parses HA configuration.
static std::string getSubnetServerName(const dhcp::ConstSubnetPtr &subnet)
Convenience function extracting a value of the ha-server-name parameter from a subnet context.
Definition ha_config.cc:527
static std::string HAModeToString(const HAMode &ha_mode)
Returns HA mode name.
Definition ha_config.cc:236
void stop()
Stop the client and listener.
Definition ha_impl.cc:73
void scopesHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-scopes command.
Definition ha_impl.cc:832
void continueHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-continue command.
Definition ha_impl.cc:891
void lease4Expire(hooks::CalloutHandle &callout_handle)
Implementation of the "lease4_expire" callout.
Definition ha_impl.cc:357
HAConfigMapperPtr config_
Holds parsed configuration.
Definition ha_impl.h:285
void syncCompleteNotifyHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-sync-complete-notify command.
Definition ha_impl.cc:1005
HAServicePtr getHAServiceByServerName(const std::string &command_name, data::ConstElementPtr args) const
Attempts to get an HAService by server name.
Definition ha_impl.cc:1051
HAServiceMapperPtr services_
Pointer to the high availability services (state machines).
Definition ha_impl.h:288
void startServices(const dhcp::NetworkStatePtr &network_state, const HAServerType &server_type)
Creates high availability services using current configuration.
Definition ha_impl.cc:45
void subnet4Select(hooks::CalloutHandle &callout_handle)
Implementation of the "subnet4_select" callout.
Definition ha_impl.cc:140
void configure(const data::ConstElementPtr &input_config)
Parses configuration.
Definition ha_impl.cc:40
virtual ~HAImpl()
Destructor.
Definition ha_impl.cc:65
void maintenanceCancelHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-cancel command.
Definition ha_impl.cc:970
void haResetHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-reset command.
Definition ha_impl.cc:979
void maintenanceNotifyHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-notify command.
Definition ha_impl.cc:916
void synchronizeHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-sync command.
Definition ha_impl.cc:767
void maintenanceStartHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-start command.
Definition ha_impl.cc:956
void lease6Expire(hooks::CalloutHandle &callout_handle)
Implementation of the "lease6_expire" callout.
Definition ha_impl.cc:650
isc::asiolink::IOServicePtr io_service_
The hook I/O service.
Definition ha_impl.h:282
void leases4Committed(hooks::CalloutHandle &callout_handle)
Implementation of the "leases4_committed" callout.
Definition ha_impl.cc:232
virtual bool shouldReclaim(const HAServicePtr &service, const dhcp::Lease4Ptr &lease4) const
Checks if the lease should be reclaimed by this server.
Definition ha_impl.cc:1081
void buffer4Receive(hooks::CalloutHandle &callout_handle)
Implementation of the "buffer4_receive" callout.
Definition ha_impl.cc:83
void buffer6Receive(hooks::CalloutHandle &callout_handle)
Implementation of the "buffer6_receive" callout.
Definition ha_impl.cc:414
void commandProcessed(hooks::CalloutHandle &callout_handle)
Implementation of the "command_processed" callout.
Definition ha_impl.cc:707
HAImpl()
Constructor.
Definition ha_impl.cc:35
void leases6Committed(hooks::CalloutHandle &callout_handle)
Implementation of the "leases6_committed" callout.
Definition ha_impl.cc:565
void subnet6Select(hooks::CalloutHandle &callout_handle)
Implementation of the "subnet6_select" callout.
Definition ha_impl.cc:473
void lease4ServerDecline(hooks::CalloutHandle &callout_handle)
Implementation of the "lease4_server_decline" callout.
Definition ha_impl.cc:324
void heartbeatHandler(hooks::CalloutHandle &callout_handle)
Implements handle for the heartbeat command.
Definition ha_impl.cc:740
Per-packet callout handle.
void getContext(const std::string &name, T &value) const
Get context.
ParkingLotHandlePtr getParkingLotHandlePtr() const
Returns pointer to the parking lot handle for this hook point.
void setContext(const std::string &name, T value)
Set context.
CalloutNextStep getStatus() const
Returns the next processing step.
void setStatus(const CalloutNextStep next)
Sets the next processing step.
void getArgument(const std::string &name, T &value) const
Get argument.
void setArgument(const std::string &name, T value)
Set argument.
static StatsMgr & instance()
Statistics Manager accessor method.
This file contains several functions and constants that are used for handling commands and responses ...
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
void addValue(const std::string &name, const int64_t value)
Records incremental integer observation.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition macros.h:32
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
Parses a standard config/command level answer and returns arguments or text status code.
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
std::string parseCommand(ConstElementPtr &arg, ConstElementPtr command)
Parses the given command into a string containing the actual command and an ElementPtr containing the...
std::string parseCommandWithArgs(ConstElementPtr &arg, ConstElementPtr command)
Parses the given command into a string containing the command name and an ElementPtr containing the m...
ConstElementPtr createAnswer()
Creates a standard config/command level success answer message (i.e.
const int CONTROL_RESULT_SUCCESS
Status code indicating a successful operation.
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:30
boost::shared_ptr< Element > ElementPtr
Definition data.h:29
boost::shared_ptr< Lease4Collection > Lease4CollectionPtr
A shared pointer to the collection of IPv4 leases.
Definition lease.h:525
boost::shared_ptr< const Subnet6 > ConstSubnet6Ptr
A const pointer to a Subnet6 object.
Definition subnet.h:620
boost::shared_ptr< const Subnet4 > ConstSubnet4Ptr
A const pointer to a Subnet4 object.
Definition subnet.h:455
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition pkt4.h:556
boost::shared_ptr< Lease6 > Lease6Ptr
Pointer to a Lease6 structure.
Definition lease.h:530
boost::shared_ptr< NetworkState > NetworkStatePtr
Pointer to the NetworkState object.
boost::shared_ptr< Lease6Collection > Lease6CollectionPtr
A shared pointer to the collection of IPv6 leases.
Definition lease.h:698
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition pkt6.h:31
boost::shared_ptr< Lease4 > Lease4Ptr
Pointer to a Lease4 structure.
Definition lease.h:317
const isc::log::MessageID HA_BUFFER4_RECEIVE_UNPACK_FAILED
Definition ha_messages.h:14
const isc::log::MessageID HA_SUBNET4_SELECT_NO_SUBNET_SELECTED
const isc::log::MessageID HA_LEASES6_COMMITTED_NOTHING_TO_UPDATE
Definition ha_messages.h:64
const isc::log::MessageID HA_LEASES6_COMMITTED_NO_RELATIONSHIP
Definition ha_messages.h:65
const isc::log::MessageID HA_BUFFER4_RECEIVE_PACKET_OPTIONS_SKIPPED
Definition ha_messages.h:13
const isc::log::MessageID HA_BUFFER6_RECEIVE_UNPACK_FAILED
Definition ha_messages.h:18
HARelationshipMapper< HAService > HAServiceMapper
Type of an object mapping HAService to relationships.
Definition ha_service.h:43
const isc::log::MessageID HA_SUBNET6_SELECT_NO_SUBNET_SELECTED
const isc::log::MessageID HA_SUBNET6_SELECT_INVALID_HA_SERVER_NAME
const isc::log::MessageID HA_LEASES4_COMMITTED_NO_RELATIONSHIP
Definition ha_messages.h:62
isc::log::Logger ha_logger("ha-hooks")
Definition ha_log.h:17
const isc::log::MessageID HA_SUBNET6_SELECT_NOT_FOR_US
const isc::log::MessageID HA_BUFFER6_RECEIVE_PACKET_OPTIONS_SKIPPED
Definition ha_messages.h:17
boost::shared_ptr< HAConfig > HAConfigPtr
Pointer to the High Availability configuration structure.
Definition ha_config.h:39
const isc::log::MessageID HA_SUBNET4_SELECT_INVALID_HA_SERVER_NAME
HAServerType
Lists possible server types for which HA service is created.
const isc::log::MessageID HA_LEASE4_EXPIRE_INVALID_HA_SERVER_NAME
Definition ha_messages.h:54
const isc::log::MessageID HA_SUBNET4_SELECT_NO_RELATIONSHIP_FOR_SUBNET
const isc::log::MessageID HA_SUBNET4_SELECT_NOT_FOR_US
const isc::log::MessageID HA_LEASE6_EXPIRE_RECLAMATION_SKIP
Definition ha_messages.h:59
const isc::log::MessageID HA_SUBNET6_SELECT_NO_RELATIONSHIP_FOR_SUBNET
const isc::log::MessageID HA_LEASE4_EXPIRE_RECLAMATION_SKIP
Definition ha_messages.h:55
const isc::log::MessageID HA_LEASES4_COMMITTED_NOTHING_TO_UPDATE
Definition ha_messages.h:61
const isc::log::MessageID HA_SUBNET4_SELECT_NO_RELATIONSHIP_SELECTOR_FOR_SUBNET
const isc::log::MessageID HA_SUBNET6_SELECT_NO_RELATIONSHIP_SELECTOR_FOR_SUBNET
const isc::log::MessageID HA_BUFFER4_RECEIVE_NOT_FOR_US
Definition ha_messages.h:12
const isc::log::MessageID HA_BUFFER6_RECEIVE_NOT_FOR_US
Definition ha_messages.h:16
boost::shared_ptr< HAService > HAServicePtr
Pointer to the HAService class.
const isc::log::MessageID HA_LEASE6_EXPIRE_INVALID_HA_SERVER_NAME
Definition ha_messages.h:58
boost::shared_ptr< ParkingLotHandle > ParkingLotHandlePtr
Pointer to the parking lot handle.
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Defines the logger used by the top-level component of kea-lfc.