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