Kea 3.1.5
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
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));
114 return;
115 }
116
117 // Check if we should process this query. If not, drop it.
118 if (!services_->get()->inScope(query4)) {
120 .arg(query4->getLabel());
121 StatsMgr::instance().addValue("pkt4-not-for-us", static_cast<int64_t>(1));
122 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
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());
158 // We even do not know what is the server handling it...
159 StatsMgr::instance().addValue("pkt4-not-for-us", static_cast<int64_t>(1));
160 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
162 return;
163 }
164
165 // The subnet configuration should contain a user context
166 // and this context should contain a mapping of the subnet to a
167 // relationship. If the context doesn't exist there is no way
168 // to determine which relationship the packet belongs to.
169 std::string server_name;
170 try {
171 server_name = HAConfig::getSubnetServerName(subnet4);
172 if (server_name.empty()) {
174 .arg(query4->getLabel())
175 .arg(subnet4->toText());
176 // We even do not know what is the server handling it...
177 StatsMgr::instance().addValue("pkt4-not-for-us", static_cast<int64_t>(1));
178 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
180 return;
181 }
182
183 } catch (...) {
185 .arg(query4->getLabel())
186 .arg(subnet4->toText());
187 StatsMgr::instance().addValue("pkt4-processing-failed", static_cast<int64_t>(1));
188 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
190 return;
191 }
192
193 // Try to find a relationship matching this server name.
194 auto service = services_->get(server_name);
195 if (!service) {
197 .arg(query4->getLabel())
198 .arg(server_name);
200 StatsMgr::instance().addValue("pkt4-not-for-us", static_cast<int64_t>(1));
201 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
202 return;
203 }
204
205 // We have found appropriate relationship. Let's see if we should
206 // process the packet. We'll drop the packet if our partner is
207 // operational and is responsible for this packet.
208 if (!service->inScope(query4)) {
210 .arg(query4->getLabel())
211 .arg(server_name);
212 StatsMgr::instance().addValue("pkt4-not-for-us", static_cast<int64_t>(1));
213 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
215 return;
216 }
217
218 // Remember the server name we retrieved from the subnet. We will
219 // need it in a leases4_committed callout that doesn't have access
220 // to the subnet object.
221 callout_handle.setContext("ha-server-name", server_name);
222}
223
224void
226 Pkt4Ptr query4;
227 Lease4CollectionPtr leases4;
228 Lease4CollectionPtr deleted_leases4;
229
230 // Get all arguments available for the leases4_committed hook point.
231 // If any of these arguments is not available this is a programmatic
232 // error. An exception will be thrown which will be caught by the
233 // caller and logged.
234 callout_handle.getArgument("query4", query4);
235
236 callout_handle.getArgument("leases4", leases4);
237 callout_handle.getArgument("deleted_leases4", deleted_leases4);
238
239 // In some cases we may have no leases, e.g. DHCPNAK.
240 if (leases4->empty() && deleted_leases4->empty()) {
242 .arg(query4->getLabel());
243 return;
244 }
245
246 // Get default config and service instances.
247 HAConfigPtr config = config_->get();
248 HAServicePtr service = services_->get();
249
250 // If we have multiple relationships we need to find the one that
251 // matches our subnet.
252 if (services_->hasMultiple()) {
253 try {
254 // Retrieve the server name from the context and the respective
255 // config and service instances.
256 std::string server_name;
257 callout_handle.getContext("ha-server-name", server_name);
258 config = config_->get(server_name);
259 service = services_->get(server_name);
260
261 // This is rather impossible but let's be safe.
262 if (!config || !service) {
263 isc_throw(Unexpected, "relationship not configured for server '" << server_name << "'");
264 }
265
266 } catch (const std::exception& ex) {
268 .arg(query4->getLabel())
269 .arg(ex.what());
270 StatsMgr::instance().addValue("pkt4-not-for-us", static_cast<int64_t>(1));
271 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
273 return;
274 }
275 }
276
277 // If the hook library is configured to not send lease updates to the
278 // partner, there is nothing to do because this whole callout is
279 // currently about sending lease updates.
280 if (!config->amSendingLeaseUpdates()) {
281 // No need to log it, because it was already logged when configuration
282 // was applied.
283 return;
284 }
285
286 // Get the parking lot for this hook point. We're going to remember this
287 // pointer until we unpark the packet.
288 ParkingLotHandlePtr parking_lot = callout_handle.getParkingLotHandlePtr();
289
290 // Create a reference to the parked packet. This signals that we have a
291 // stake in unparking it.
292 parking_lot->reference(query4);
293
294 // Asynchronously send lease updates. In some cases no updates will be sent,
295 // e.g. when this server is in the partner-down state and there are no backup
296 // servers. In those cases we simply return without parking the DHCP query.
297 // The response will be sent to the client immediately.
298 try {
299 if (service->asyncSendLeaseUpdates(query4, leases4, deleted_leases4, parking_lot) == 0) {
300 // Dereference the parked packet. This releases our stake in it.
301 parking_lot->dereference(query4);
302 return;
303 }
304 } catch (...) {
305 // Make sure we dereference.
306 parking_lot->dereference(query4);
307 throw;
308 }
309
310 // The callout returns this status code to indicate to the server that it
311 // should leave the packet parked. It will be parked until each hook
312 // library with a reference, unparks the packet.
314}
315
316void
318 // Always return CONTINUE.
320 size_t peers_to_update = 0;
321
322 // If the hook library is configured to not send lease updates to the
323 // partner, there is nothing to do because this whole callout is
324 // currently about sending lease updates.
325 if (!config_->get()->amSendingLeaseUpdates()) {
326 // No need to log it, because it was already logged when configuration
327 // was applied.
328 callout_handle.setArgument("peers_to_update", peers_to_update);
329 return;
330 }
331
332 // Get all arguments available for the lease4_server_decline hook point.
333 // If any of these arguments is not available this is a programmatic
334 // error. An exception will be thrown which will be caught by the
335 // caller and logged.
336 Pkt4Ptr query4;
337 callout_handle.getArgument("query4", query4);
338
339 Lease4Ptr lease4;
340 callout_handle.getArgument("lease4", lease4);
341
342 // Asynchronously send the lease update. In some cases no updates will be sent,
343 // e.g. when this server is in the partner-down state and there are no backup
344 // servers.
345 peers_to_update = services_->get()->asyncSendSingleLeaseUpdate(query4, lease4, 0);
346 callout_handle.setArgument("peers_to_update", peers_to_update);
347}
348
349void
351 Lease4Ptr lease4;
352 callout_handle.getArgument("lease4", lease4);
353
354 // If there are multiple relationships we need to take a detour and find
355 // a subnet the lease belongs to. The subnet will contain the information
356 // required to select appropriate HA service.
357 HAServicePtr service;
358 if (services_->hasMultiple()) {
359 auto subnet4 = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease4->subnet_id_);
360 if (!subnet4) {
361 // No subnet means that we possibly have some stale leases that don't
362 // really belong to us. Therefore, there we return early and rely on the
363 // DHCP server to reclaim them. The HA hook has no jurisdiction here.
364 return;
365 }
366
367 std::string server_name;
368 try {
369 server_name = HAConfig::getSubnetServerName(subnet4);
370 if (server_name.empty()) {
371 // Again, this subnet has no hint for HA where our lease belongs.
372 // We have to rely on the server to run reclamation of this lease.
373 return;
374 }
375 } catch (...) {
376 // Someone has tried to configure the hint for HA in the subnet but
377 // it was poorly specified. We will log an error and leave again.
379 .arg(lease4->addr_.toText())
380 .arg(subnet4->toText());
381 return;
382 }
383 service = services_->get(server_name);
384
385 } else {
386 service = services_->get();
387 }
388
389 if (!service) {
390 // This is highly unlikely but better handle null pointers.
391 return;
392 }
393
394 if (!shouldReclaim(service, lease4)) {
395 // While the server is in the terminated state it has to be careful about
396 // reclaiming the leases to avoid conflicting DNS updates with a server that
397 // owns the lease. This lease apparently belongs to another server, so we
398 // should not reclaim it.
400 .arg(lease4->addr_.toText());
402 return;
403 }
404}
405
406void
408 // If there are multiple relationships, the HA-specific processing is
409 // in the subnet6_select hook point.
410 if (services_->hasMultiple()) {
411 return;
412 }
413
414 Pkt6Ptr query6;
415 callout_handle.getArgument("query6", query6);
416
419 try {
420 // We have to unpack the query to get access into DUID which is
421 // used to load balance the packet.
422 if (callout_handle.getStatus() != CalloutHandle::NEXT_STEP_SKIP) {
423 query6->unpack();
424 }
425
426 } catch (const SkipRemainingOptionsError& ex) {
427 // An option failed to unpack but we are to attempt to process it
428 // anyway. Log it and let's hope for the best.
431 .arg(ex.what());
432
433 } catch (const std::exception& ex) {
434 // Packet parsing failed. Drop the packet.
436 .arg(query6->getRemoteAddr().toText())
437 .arg(query6->getLocalAddr().toText())
438 .arg(query6->getIface())
439 .arg(ex.what());
440
441 // Increase the statistics of parse failures and dropped packets.
442 StatsMgr::instance().addValue("pkt6-parse-failed", static_cast<int64_t>(1));
443 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
444
445
447 return;
448 }
449
450 // Check if we should process this query. If not, drop it.
451 if (!services_->get()->inScope(query6)) {
453 .arg(query6->getLabel());
454 StatsMgr::instance().addValue("pkt6-not-for-us", static_cast<int64_t>(1));
455 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
457
458 } else {
459 // We have successfully parsed the query so we have to signal
460 // to the server that it must not parse it.
462 }
463}
464
465void
467 // This callout only applies in the case of multiple relationships.
468 // When there is only one relationship it has no effect because
469 // the decision if we should process the packet has been made
470 // in the buffer6_receive callout.
471 if (!services_->hasMultiple()) {
472 // Return silently. It is not an error.
473 return;
474 }
475
476 Pkt6Ptr query6;
477 callout_handle.getArgument("query6", query6);
478
479 ConstSubnet6Ptr subnet6;
480 callout_handle.getArgument("subnet6", subnet6);
481
482 // If the server failed to select the subnet this pointer is null.
483 // There is nothing we can do with this packet because we don't know
484 // which relationship it belongs to. We're even unable to check if the
485 // server is responsible for this packet.
486 if (!subnet6) {
487 // Log at debug level because that's the level at which the server
488 // logs the subnet selection failure.
490 .arg(query6->getLabel());
492 // We even do not know what is the server handling it...
493 StatsMgr::instance().addValue("pkt6-not-for-us", static_cast<int64_t>(1));
494 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
495 return;
496 }
497
498 // The subnet configuration should contain a user context
499 // and this context should contain a mapping of the subnet to a
500 // relationship. If the context doesn't exist there is no way
501 // to determine which relationship the packet belongs to.
502 std::string server_name;
503 try {
504 server_name = HAConfig::getSubnetServerName(subnet6);
505 if (server_name.empty()) {
507 .arg(query6->getLabel())
508 .arg(subnet6->toText());
510 // We even do not know what is the server handling it...
511 StatsMgr::instance().addValue("pkt6-not-for-us", static_cast<int64_t>(1));
512 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
513 return;
514 }
515
516 } catch (...) {
518 .arg(query6->getLabel())
519 .arg(subnet6->toText());
521 StatsMgr::instance().addValue("pkt6-processing-failed", static_cast<int64_t>(1));
522 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
523 return;
524 }
525
526 // Try to find a relationship matching this server name.
527 auto service = services_->get(server_name);
528 if (!service) {
530 .arg(query6->getLabel())
531 .arg(server_name);
533 StatsMgr::instance().addValue("pkt6-not-for-us", static_cast<int64_t>(1));
534 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
535 return;
536 }
537
538 // We have found appropriate relationship. Let's see if we should
539 // process the packet. We'll drop the packet if our partner is
540 // operational and is responsible for this packet.
541 if (!service->inScope(query6)) {
543 .arg(query6->getLabel())
544 .arg(server_name);
545 StatsMgr::instance().addValue("pkt6-not-for-us", static_cast<int64_t>(1));
546 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
548 return;
549 }
550
551 // Remember the server name we retrieved from the subnet. We will
552 // need it in a leases4_committed callout that doesn't have access
553 // to the subnet object.
554 callout_handle.setContext("ha-server-name", server_name);
555}
556
557void
559 Pkt6Ptr query6;
560 Lease6CollectionPtr leases6;
561 Lease6CollectionPtr deleted_leases6;
562
563 // Get all arguments available for the leases6_committed hook point.
564 // If any of these arguments is not available this is a programmatic
565 // error. An exception will be thrown which will be caught by the
566 // caller and logged.
567 callout_handle.getArgument("query6", query6);
568
569 callout_handle.getArgument("leases6", leases6);
570 callout_handle.getArgument("deleted_leases6", deleted_leases6);
571
572 // In some cases we may have no leases.
573 if (leases6->empty() && deleted_leases6->empty()) {
575 .arg(query6->getLabel());
576 return;
577 }
578
579 HAConfigPtr config = config_->get();
580 HAServicePtr service = services_->get();
581 if (services_->hasMultiple()) {
582 try {
583 std::string server_name;
584 callout_handle.getContext("ha-server-name", server_name);
585 config = config_->get(server_name);
586 service = services_->get(server_name);
587
588 if (!config || !service) {
589 isc_throw(Unexpected, "relationship not found for the ha-server-name='" << server_name << "'");
590 }
591
592 } catch (const std::exception& ex) {
594 .arg(query6->getLabel())
595 .arg(ex.what());
596 StatsMgr::instance().addValue("pkt6-not-for-us", static_cast<int64_t>(1));
597 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
599 return;
600 }
601 }
602
603 // If the hook library is configured to not send lease updates to the
604 // partner, there is nothing to do because this whole callout is
605 // currently about sending lease updates.
606 if (!config->amSendingLeaseUpdates()) {
607 // No need to log it, because it was already logged when configuration
608 // was applied.
609 return;
610 }
611
612 // Get the parking lot for this hook point. We're going to remember this
613 // pointer until we unpark the packet.
614 ParkingLotHandlePtr parking_lot = callout_handle.getParkingLotHandlePtr();
615
616 // Create a reference to the parked packet. This signals that we have a
617 // stake in unparking it.
618 parking_lot->reference(query6);
619
620 // Asynchronously send lease updates. In some cases no updates will be sent,
621 // e.g. when this server is in the partner-down state and there are no backup
622 // servers. In those cases we simply return without parking the DHCP query.
623 // The response will be sent to the client immediately.
624 try {
625 if (service->asyncSendLeaseUpdates(query6, leases6, deleted_leases6, parking_lot) == 0) {
626 // Dereference the parked packet. This releases our stake in it.
627 parking_lot->dereference(query6);
628 return;
629 }
630 } catch (...) {
631 // Make sure we dereference.
632 parking_lot->dereference(query6);
633 throw;
634 }
635
636 // The callout returns this status code to indicate to the server that it
637 // should leave the packet parked. It will be unparked until each hook
638 // library with a reference, unparks the packet.
640}
641
642void
644 Lease6Ptr lease6;
645 callout_handle.getArgument("lease6", lease6);
646
647 // If there are multiple relationships we need to take a detour and find
648 // a subnet the lease belongs to. The subnet will contain the information
649 // required to select appropriate HA service.
650 HAServicePtr service;
651 if (services_->hasMultiple()) {
652 auto subnet6 = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease6->subnet_id_);
653 if (!subnet6) {
654 // No subnet means that we possibly have some stale leases that don't
655 // really belong to us. Therefore, there we return early and rely on the
656 // DHCP server to reclaim them. The HA hook has no jurisdiction here.
657 return;
658 }
659
660 std::string server_name;
661 try {
662 server_name = HAConfig::getSubnetServerName(subnet6);
663 if (server_name.empty()) {
664 // Again, this subnet has no hint for HA where our lease belongs.
665 // We have to rely on the server to run reclamation of this lease.
666 return;
667 }
668 } catch (...) {
669 // Someone has tried to configure the hint for HA in the subnet but
670 // it was poorly specified. We will log an error and leave again.
672 .arg(lease6->addr_.toText())
673 .arg(subnet6->toText());
674 return;
675 }
676 service = services_->get(server_name);
677
678 } else {
679 service = services_->get();
680 }
681
682 if (!service) {
683 // This is highly unlikely but better handle null pointers.
684 return;
685 }
686
687 if (!shouldReclaim(service, lease6)) {
688 // While the server is in the terminated state it has to be careful about
689 // reclaiming the leases to avoid conflicting DNS updates with a server that
690 // owns the lease. This lease apparently belongs to another server, so we
691 // should not reclaim it.
693 .arg(lease6->addr_.toText());
695 return;
696 }
697}
698
699void
701 std::string command_name;
702 callout_handle.getArgument("name", command_name);
703 if (command_name == "status-get") {
704 // Get the response.
705 ConstElementPtr response;
706 callout_handle.getArgument("response", response);
707 if (!response || (response->getType() != Element::map)) {
708 return;
709 }
710 // Get the arguments item from the response.
711 ConstElementPtr resp_args = response->get("arguments");
712 if (!resp_args || (resp_args->getType() != Element::map)) {
713 return;
714 }
715 // Add the ha servers info to arguments.
716 ElementPtr mutable_resp_args =
717 boost::const_pointer_cast<Element>(resp_args);
718
719 // Process the status get command for each HA service.
720 auto ha_relationships = Element::createList();
721 for (auto const& service : services_->getAll()) {
722 auto ha_relationship = Element::createMap();
723 ConstElementPtr ha_servers = service->processStatusGet();
724 ha_relationship->set("ha-servers", ha_servers);
725 ha_relationship->set("ha-mode", Element::create(HAConfig::HAModeToString(config_->get()->getHAMode())));
726 ha_relationships->add(ha_relationship);
727 mutable_resp_args->set("high-availability", ha_relationships);
728 }
729 }
730}
731
732void
734 // Command must always be provided.
735 ConstElementPtr command;
736 callout_handle.getArgument("command", command);
737
738 // Retrieve arguments.
739 ConstElementPtr args;
740 static_cast<void>(parseCommand(args, command));
741
742 HAServicePtr service;
743 try {
744 service = getHAServiceByServerName("ha-heartbeat", args);
745
746 } catch (const std::exception& ex) {
747 // There was an error while parsing command arguments. Return an error status
748 // code to notify the user.
750 callout_handle.setArgument("response", response);
751 return;
752 }
753
754 // Command parsing was successful, so let's process the command.
755 ConstElementPtr response = service->processHeartbeat();
756 callout_handle.setArgument("response", response);
757}
758
759void
761 // Command must always be provided.
762 ConstElementPtr command;
763 callout_handle.getArgument("command", command);
764
765 // Retrieve arguments.
766 ConstElementPtr args;
767 static_cast<void>(parseCommand(args, command));
768
769 ConstElementPtr server_name;
770 unsigned int max_period_value = 0;
771
772 HAServicePtr service;
773 try {
774 // Arguments are required for the ha-sync command.
775 if (!args) {
776 isc_throw(BadValue, "arguments not found in the 'ha-sync' command");
777 }
778
779 // Arguments must be a map.
780 if (args->getType() != Element::map) {
781 isc_throw(BadValue, "arguments in the 'ha-sync' command are not a map");
782 }
783
784 // server-name is mandatory. Otherwise how can we know the server to
785 // communicate with.
786 server_name = args->get("server-name");
787 if (!server_name) {
788 isc_throw(BadValue, "'server-name' is mandatory for the 'ha-sync' command");
789 }
790
791 // server-name must obviously be a string.
792 if (server_name->getType() != Element::string) {
793 isc_throw(BadValue, "'server-name' must be a string in the 'ha-sync' command");
794 }
795
796 // max-period is optional. In fact it is optional for dhcp-disable command too.
797 ConstElementPtr max_period = args->get("max-period");
798 if (max_period) {
799 // If it is specified, it must be a positive integer.
800 if ((max_period->getType() != Element::integer) ||
801 (max_period->intValue() <= 0)) {
802 isc_throw(BadValue, "'max-period' must be a positive integer in the 'ha-sync' command");
803 }
804
805 max_period_value = static_cast<unsigned int>(max_period->intValue());
806 }
807
808 service = getHAServiceByServerName("ha-sync", args);
809
810 } catch (const std::exception& ex) {
811 // There was an error while parsing command arguments. Return an error status
812 // code to notify the user.
814 callout_handle.setArgument("response", response);
815 return;
816 }
817
818 // Command parsing was successful, so let's process the command.
819 ConstElementPtr response = service->processSynchronize(server_name->stringValue(),
820 max_period_value);
821 callout_handle.setArgument("response", response);
822}
823
824void
826 // Command must always be provided.
827 ConstElementPtr command;
828 callout_handle.getArgument("command", command);
829
830 // Retrieve arguments.
831 ConstElementPtr args;
832 static_cast<void>(parseCommand(args, command));
833
834 HAServicePtr service;
835 std::vector<std::string> scopes_vector;
836 try {
837 // Arguments must be present.
838 if (!args) {
839 isc_throw(BadValue, "arguments not found in the 'ha-scopes' command");
840 }
841
842 // Arguments must be a map.
843 if (args->getType() != Element::map) {
844 isc_throw(BadValue, "arguments in the 'ha-scopes' command are not a map");
845 }
846
847 // scopes argument is mandatory.
848 ConstElementPtr scopes = args->get("scopes");
849 if (!scopes) {
850 isc_throw(BadValue, "'scopes' is mandatory for the 'ha-scopes' command");
851 }
852
853 // It contains a list of scope names.
854 if (scopes->getType() != Element::list) {
855 isc_throw(BadValue, "'scopes' must be a list in the 'ha-scopes' command");
856 }
857
858 // Retrieve scope names from this list. The list may be empty to clear the
859 // scopes.
860 for (size_t i = 0; i < scopes->size(); ++i) {
861 ConstElementPtr scope = scopes->get(i);
862 if (!scope || scope->getType() != Element::string) {
863 isc_throw(BadValue, "scope name must be a string in the 'scopes' argument");
864 }
865 scopes_vector.push_back(scope->stringValue());
866 }
867
868 service = getHAServiceByServerName("ha-scopes", args);
869
870 } catch (const std::exception& ex) {
871 // There was an error while parsing command arguments. Return an error status
872 // code to notify the user.
874 callout_handle.setArgument("response", response);
875 return;
876 }
877
878 // Command parsing was successful, so let's process the command.
879 ConstElementPtr response = service->processScopes(scopes_vector);
880 callout_handle.setArgument("response", response);
881}
882
883void
885 // Command must always be provided.
886 ConstElementPtr command;
887 callout_handle.getArgument("command", command);
888
889 // Retrieve arguments.
890 ConstElementPtr args;
891 static_cast<void>(parseCommand(args, command));
892
893 HAServicePtr service;
894 try {
895 service = getHAServiceByServerName("ha-continue", args);
896
897 } catch (const std::exception& ex) {
898 // There was an error while parsing command arguments. Return an error status
899 // code to notify the user.
901 callout_handle.setArgument("response", response);
902 return;
903 }
904 ConstElementPtr response = service->processContinue();
905 callout_handle.setArgument("response", response);
906}
907
908void
910 // Command must always be provided.
911 ConstElementPtr command;
912 callout_handle.getArgument("command", command);
913
914 HAServicePtr service;
915 try {
916 // Retrieve arguments.
917 ConstElementPtr args;
918 static_cast<void>(parseCommandWithArgs(args, command));
919
920 ConstElementPtr cancel_op = args->get("cancel");
921 if (!cancel_op) {
922 isc_throw(BadValue, "'cancel' is mandatory for the 'ha-maintenance-notify' command");
923 }
924
925 if (cancel_op->getType() != Element::boolean) {
926 isc_throw(BadValue, "'cancel' must be a boolean in the 'ha-maintenance-notify' command");
927 }
928
929 ConstElementPtr state = args->get("state");
930 if (state && state->getType() != Element::string) {
931 isc_throw(BadValue, "'state' must be a string in the 'ha-maintenance-notify' command");
932 }
933
934 service = getHAServiceByServerName("ha-maintenance-notify", args);
935
936 ConstElementPtr response = service->processMaintenanceNotify(cancel_op->boolValue(),
937 state ? state->stringValue() : "unavailable");
938 callout_handle.setArgument("response", response);
939
940 } catch (const std::exception& ex) {
941 // There was an error while parsing command arguments. Return an error status
942 // code to notify the user.
944 callout_handle.setArgument("response", response);
945 }
946}
947
948void
950 ConstElementPtr response;
951 for (auto const& service : services_->getAll()) {
952 response = service->processMaintenanceStart();
953 int rcode = CONTROL_RESULT_SUCCESS;
954 static_cast<void>(parseAnswer(rcode, response));
955 if (rcode != CONTROL_RESULT_SUCCESS) {
956 break;
957 }
958 }
959 callout_handle.setArgument("response", response);
960}
961
962void
964 ConstElementPtr response;
965 for (auto const& service : services_->getAll()) {
966 response = service->processMaintenanceCancel();
967 }
968 callout_handle.setArgument("response", response);
969}
970
971void
973 // Command must always be provided.
974 ConstElementPtr command;
975 callout_handle.getArgument("command", command);
976
977 // Retrieve arguments.
978 ConstElementPtr args;
979 static_cast<void>(parseCommand(args, command));
980
981 HAServicePtr service;
982 try {
983 service = getHAServiceByServerName("ha-reset", args);
984
985 } catch (const std::exception& ex) {
986 // There was an error while parsing command arguments. Return an error status
987 // code to notify the user.
989 callout_handle.setArgument("response", response);
990 return;
991 }
992
993 ConstElementPtr response = service->processHAReset();
994 callout_handle.setArgument("response", response);
995}
996
997void
999 // Command must always be provided.
1000 ConstElementPtr command;
1001 callout_handle.getArgument("command", command);
1002
1003 // Retrieve arguments.
1004 ConstElementPtr args;
1005 static_cast<void>(parseCommand(args, command));
1006
1007 HAServicePtr service;
1008 auto origin_id_value = NetworkState::HA_REMOTE_COMMAND+1;
1009 try {
1010 if (args) {
1011 auto origin_id = args->get("origin-id");
1012 auto origin = args->get("origin");
1013 // The origin-id is a new parameter replacing the origin. However, some versions
1014 // of Kea may still send the origin parameter instead.
1015 if (origin_id) {
1016 if (origin_id->getType() != Element::integer) {
1017 isc_throw(BadValue, "'origin-id' must be an integer in the 'ha-sync-complete-notify' command");
1018 }
1019 origin_id_value = origin_id->intValue();
1020
1021 } else if (origin) {
1022 if (origin->getType() != Element::integer) {
1023 isc_throw(BadValue, "'origin' must be an integer in the 'ha-sync-complete-notify' command");
1024 }
1025 origin_id_value = origin->intValue();
1026 }
1027 }
1028
1029 service = getHAServiceByServerName("ha-sync-complete-notify", args);
1030
1031 } catch (const std::exception& ex) {
1032 // There was an error while parsing command arguments. Return an error status
1033 // code to notify the user.
1034 ConstElementPtr response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
1035 callout_handle.setArgument("response", response);
1036 return;
1037 }
1038
1039 ConstElementPtr response = service->processSyncCompleteNotify(origin_id_value);
1040 callout_handle.setArgument("response", response);
1041}
1042
1044HAImpl::getHAServiceByServerName(const std::string& command_name, ConstElementPtr args) const {
1045 HAServicePtr service;
1046 if (args) {
1047 // Arguments must be a map.
1048 if (args->getType() != Element::map) {
1049 isc_throw(BadValue, "arguments in the '" << command_name << "' command are not a map");
1050 }
1051
1052 auto server_name = args->get("server-name");
1053
1054 if (server_name) {
1055 if (server_name->getType() != Element::string) {
1056 isc_throw(BadValue, "'server-name' must be a string in the '" << command_name << "' command");
1057 }
1058 service = services_->get(server_name->stringValue());
1059 if (!service) {
1060 isc_throw(BadValue, server_name->stringValue() << " matches no configured"
1061 << " 'server-name'");
1062 }
1063 }
1064 }
1065
1066 if (!service) {
1067 service = services_->get();
1068 }
1069
1070 return (service);
1071}
1072
1073bool
1074HAImpl::shouldReclaim(const HAServicePtr& service, const dhcp::Lease4Ptr& lease4) const {
1075 return (service->shouldReclaim(lease4));
1076}
1077
1078bool
1079HAImpl::shouldReclaim(const HAServicePtr& service, const dhcp::Lease6Ptr& lease6) const {
1080 return (service->shouldReclaim(lease6));
1081}
1082
1083} // end of namespace isc::ha
1084} // 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:299
@ 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:354
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition data.cc:349
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: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:825
void continueHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-continue command.
Definition ha_impl.cc:884
void lease4Expire(hooks::CalloutHandle &callout_handle)
Implementation of the "lease4_expire" callout.
Definition ha_impl.cc:350
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:998
HAServicePtr getHAServiceByServerName(const std::string &command_name, data::ConstElementPtr args) const
Attempts to get an HAService by server name.
Definition ha_impl.cc:1044
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:963
void haResetHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-reset command.
Definition ha_impl.cc:972
void maintenanceNotifyHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-notify command.
Definition ha_impl.cc:909
void synchronizeHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-sync command.
Definition ha_impl.cc:760
void maintenanceStartHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-start command.
Definition ha_impl.cc:949
void lease6Expire(hooks::CalloutHandle &callout_handle)
Implementation of the "lease6_expire" callout.
Definition ha_impl.cc:643
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:225
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:1074
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:407
void commandProcessed(hooks::CalloutHandle &callout_handle)
Implementation of the "command_processed" callout.
Definition ha_impl.cc:700
HAImpl()
Constructor.
Definition ha_impl.cc:34
void leases6Committed(hooks::CalloutHandle &callout_handle)
Implementation of the "leases6_committed" callout.
Definition ha_impl.cc:558
void subnet6Select(hooks::CalloutHandle &callout_handle)
Implementation of the "subnet6_select" callout.
Definition ha_impl.cc:466
void lease4ServerDecline(hooks::CalloutHandle &callout_handle)
Implementation of the "lease4_server_decline" callout.
Definition ha_impl.cc:317
void heartbeatHandler(hooks::CalloutHandle &callout_handle)
Implements handle for the heartbeat command.
Definition ha_impl.cc:733
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: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:556
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.