Kea 2.5.5
communication_state.cc
Go to the documentation of this file.
1// Copyright (C) 2018-2023 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
10#include <ha_log.h>
11#include <ha_service_states.h>
12#include <cc/data.h>
14#include <dhcp/dhcp4.h>
15#include <dhcp/dhcp6.h>
16#include <dhcp/option_int.h>
17#include <dhcp/pkt4.h>
18#include <dhcp/pkt6.h>
19#include <http/date_time.h>
22
23#include <boost/pointer_cast.hpp>
24
25#include <ctime>
26#include <functional>
27#include <limits>
28#include <sstream>
29#include <utility>
30
31using namespace isc::asiolink;
32using namespace isc::data;
33using namespace isc::dhcp;
34using namespace isc::http;
35using namespace isc::log;
36using namespace isc::util;
37
38using namespace boost::posix_time;
39using namespace std;
40
41namespace {
42
44constexpr long WARN_CLOCK_SKEW = 30;
45
47constexpr long TERM_CLOCK_SKEW = 60;
48
50constexpr long MIN_TIME_SINCE_CLOCK_SKEW_WARN = 60;
51
52}
53
54namespace isc {
55namespace ha {
56
58 const HAConfigPtr& config)
59 : io_service_(io_service), config_(config), timer_(), interval_(0),
60 poke_time_(boost::posix_time::microsec_clock::universal_time()),
61 heartbeat_impl_(0), partner_state_(-1), partner_scopes_(),
62 clock_skew_(0, 0, 0, 0), last_clock_skew_warn_(),
63 my_time_at_skew_(), partner_time_at_skew_(),
64 analyzed_messages_count_(0), unsent_update_count_(0),
65 partner_unsent_update_count_{0, 0}, mutex_(new mutex()) {
66}
67
70}
71
72void
74 if (MultiThreadingMgr::instance().getMode()) {
75 std::lock_guard<std::mutex> lk(*mutex_);
76 poke_time_ += boost::posix_time::seconds(secs);
77 } else {
78 poke_time_ += boost::posix_time::seconds(secs);
79 }
80}
81
82int
84 if (MultiThreadingMgr::instance().getMode()) {
85 std::lock_guard<std::mutex> lk(*mutex_);
86 return (partner_state_);
87 } else {
88 return (partner_state_);
89 }
90}
91
92void
93CommunicationState::setPartnerState(const std::string& state) {
94 if (MultiThreadingMgr::instance().getMode()) {
95 std::lock_guard<std::mutex> lk(*mutex_);
96 setPartnerStateInternal(state);
97 } else {
98 setPartnerStateInternal(state);
99 }
100}
101
102void
104 if (MultiThreadingMgr::instance().getMode()) {
105 std::lock_guard<std::mutex> lk(*mutex_);
106 setPartnerStateInternal("unavailable");
107 resetPartnerTimeInternal();
108 } else {
109 setPartnerStateInternal("unavailable");
110 resetPartnerTimeInternal();
111 }
112}
113
114void
115CommunicationState::setPartnerStateInternal(const std::string& state) {
116 try {
118 } catch (...) {
119 isc_throw(BadValue, "unsupported HA partner state returned "
120 << state);
121 }
122}
123
124std::set<std::string>
126 if (MultiThreadingMgr::instance().getMode()) {
127 std::lock_guard<std::mutex> lk(*mutex_);
128 return (partner_scopes_);
129 } else {
130 return (partner_scopes_);
131 }
132}
133
134void
136 if (MultiThreadingMgr::instance().getMode()) {
137 std::lock_guard<std::mutex> lk(*mutex_);
138 setPartnerScopesInternal(new_scopes);
139 } else {
140 setPartnerScopesInternal(new_scopes);
141 }
142}
143
144void
145CommunicationState::setPartnerScopesInternal(ConstElementPtr new_scopes) {
146 if (!new_scopes || (new_scopes->getType() != Element::list)) {
147 isc_throw(BadValue, "unable to record partner's HA scopes because"
148 " the received value is not a valid JSON list");
149 }
150
151 std::set<std::string> partner_scopes;
152 for (auto i = 0; i < new_scopes->size(); ++i) {
153 auto scope = new_scopes->get(i);
154 if (scope->getType() != Element::string) {
155 isc_throw(BadValue, "unable to record partner's HA scopes because"
156 " the received scope value is not a valid JSON string");
157 }
158 auto scope_str = scope->stringValue();
159 if (!scope_str.empty()) {
160 partner_scopes.insert(scope_str);
161 }
162 }
163 partner_scopes_ = partner_scopes;
164}
165
166void
168 const std::function<void()>& heartbeat_impl) {
169 if (MultiThreadingMgr::instance().getMode()) {
170 std::lock_guard<std::mutex> lk(*mutex_);
171 startHeartbeatInternal(interval, heartbeat_impl);
172 } else {
173 startHeartbeatInternal(interval, heartbeat_impl);
174 }
175}
176
177void
178CommunicationState::startHeartbeatInternal(const long interval,
179 const std::function<void()>& heartbeat_impl) {
180 bool settings_modified = false;
181
182 // If we're setting the heartbeat for the first time, it should
183 // be non-null.
184 if (heartbeat_impl) {
185 settings_modified = true;
186 heartbeat_impl_ = heartbeat_impl;
187
188 } else if (!heartbeat_impl_) {
189 // The heartbeat is re-scheduled but we have no historic implementation
190 // pointer we could re-use. This is a programmatic issue.
191 isc_throw(BadValue, "unable to start heartbeat when pointer"
192 " to the heartbeat implementation is not specified");
193 }
194
195 // If we're setting the heartbeat for the first time, the interval
196 // should be greater than 0.
197 if (interval != 0) {
198 settings_modified |= (interval_ != interval);
199 interval_ = interval;
200
201 } else if (interval_ <= 0) {
202 // The heartbeat is re-scheduled but we have no historic interval
203 // which we could re-use. This is a programmatic issue.
204 heartbeat_impl_ = 0;
205 isc_throw(BadValue, "unable to start heartbeat when interval"
206 " for the heartbeat timer is not specified");
207 }
208
209 if (!timer_) {
210 timer_.reset(new IntervalTimer(*io_service_));
211 }
212
213 if (settings_modified) {
214 timer_->setup(heartbeat_impl_, interval_, IntervalTimer::ONE_SHOT);
215 }
216}
217
218void
220 if (MultiThreadingMgr::instance().getMode()) {
221 std::lock_guard<std::mutex> lk(*mutex_);
222 stopHeartbeatInternal();
223 } else {
224 stopHeartbeatInternal();
225 }
226}
227
228void
229CommunicationState::stopHeartbeatInternal() {
230 if (timer_) {
231 timer_->cancel();
232 timer_.reset();
233 interval_ = 0;
234 heartbeat_impl_ = 0;
235 }
236}
237
238bool
240 if (MultiThreadingMgr::instance().getMode()) {
241 std::lock_guard<std::mutex> lk(*mutex_);
242 return (static_cast<bool>(timer_));
243 } else {
244 return (static_cast<bool>(timer_));
245 }
246}
247
248boost::posix_time::time_duration
250 if (MultiThreadingMgr::instance().getMode()) {
251 std::lock_guard<std::mutex> lk(*mutex_);
252 return (updatePokeTimeInternal());
253 } else {
254 return (updatePokeTimeInternal());
255 }
256}
257
258boost::posix_time::time_duration
259CommunicationState::updatePokeTimeInternal() {
260 // Remember previous poke time.
261 boost::posix_time::ptime prev_poke_time = poke_time_;
262 // Set poke time to the current time.
263 poke_time_ = boost::posix_time::microsec_clock::universal_time();
264 return (poke_time_ - prev_poke_time);
265}
266
267void
269 if (MultiThreadingMgr::instance().getMode()) {
270 std::lock_guard<std::mutex> lk(*mutex_);
271 pokeInternal();
272 } else {
273 pokeInternal();
274 }
275}
276
277void
278CommunicationState::pokeInternal() {
279 // Update poke time and compute duration.
280 boost::posix_time::time_duration duration_since_poke = updatePokeTimeInternal();
281
282 // If we have been tracking the DHCP messages directed to the partner,
283 // we need to clear any gathered information because the connection
284 // seems to be (re)established.
287
288 if (timer_) {
289 // Check the duration since last poke. If it is less than a second, we don't
290 // want to reschedule the timer. In order to avoid the overhead of
291 // re-scheduling the timer too frequently we reschedule it only if the
292 // duration is 1s or more. This matches the time resolution for heartbeats.
293 if (duration_since_poke.total_seconds() > 0) {
294 // A poke causes the timer to be re-scheduled to prevent it
295 // from triggering a heartbeat shortly after confirming the
296 // connection is ok.
297 startHeartbeatInternal();
298 }
299 }
300}
301
302int64_t
304 if (MultiThreadingMgr::instance().getMode()) {
305 std::lock_guard<std::mutex> lk(*mutex_);
306 return (getDurationInMillisecsInternal());
307 } else {
308 return (getDurationInMillisecsInternal());
309 }
310}
311
312int64_t
313CommunicationState::getDurationInMillisecsInternal() const {
314 ptime now = boost::posix_time::microsec_clock::universal_time();
315 time_duration duration = now - poke_time_;
316 return (duration.total_milliseconds());
317}
318
319bool
321 return (getDurationInMillisecs() > config_->getMaxResponseDelay());
322}
323
324std::vector<uint8_t>
326 const uint16_t option_type) {
327 std::vector<uint8_t> client_id;
328 OptionPtr opt_client_id = message->getOption(option_type);
329 if (opt_client_id) {
330 client_id = opt_client_id->getData();
331 }
332 return (client_id);
333}
334
335size_t
338}
339
340size_t
342 if (MultiThreadingMgr::instance().getMode()) {
343 std::lock_guard<std::mutex> lk(*mutex_);
345 } else {
347 }
348}
349
350bool
352 const uint32_t lifetime) {
353 if (MultiThreadingMgr::instance().getMode()) {
354 std::lock_guard<std::mutex> lk(*mutex_);
355 return (reportRejectedLeaseUpdateInternal(message, lifetime));
356 } else {
357 return (reportRejectedLeaseUpdateInternal(message, lifetime));
358 }
359}
360
361bool
363 if (MultiThreadingMgr::instance().getMode()) {
364 std::lock_guard<std::mutex> lk(*mutex_);
365 return (reportSuccessfulLeaseUpdateInternal(message));
366 } else {
367 return (reportSuccessfulLeaseUpdateInternal(message));
368 }
369}
370
371void
373 if (MultiThreadingMgr::instance().getMode()) {
374 std::lock_guard<std::mutex> lk(*mutex_);
376 } else {
378 }
379}
380
381bool
383 if (MultiThreadingMgr::instance().getMode()) {
384 std::lock_guard<std::mutex> lk(*mutex_);
385 return (clockSkewShouldWarnInternal());
386 } else {
387 return (clockSkewShouldWarnInternal());
388 }
389}
390
391bool
392CommunicationState::clockSkewShouldWarnInternal() {
393 // First check if the clock skew is beyond the threshold.
394 if (isClockSkewGreater(WARN_CLOCK_SKEW)) {
395
396 // In order to prevent to frequent warnings we provide a gating mechanism
397 // which doesn't allow for issuing a warning earlier than 60 seconds after
398 // the previous one.
399
400 // Find the current time and the duration since last warning.
401 ptime now = boost::posix_time::microsec_clock::universal_time();
402 time_duration since_warn_duration = now - last_clock_skew_warn_;
403
404 // If the last warning was issued more than 60 seconds ago or it is a
405 // first warning, we need to update the last warning timestamp and return
406 // true to indicate that new warning should be issued.
407 if (last_clock_skew_warn_.is_not_a_date_time() ||
408 (since_warn_duration.total_seconds() > MIN_TIME_SINCE_CLOCK_SKEW_WARN)) {
411 .arg(config_->getThisServerName())
412 .arg(logFormatClockSkewInternal());
413 return (true);
414 }
415 }
416
417 // The warning should not be issued.
418 return (false);
419}
420
421bool
423 if (MultiThreadingMgr::instance().getMode()) {
424 std::lock_guard<std::mutex> lk(*mutex_);
425 // Issue a warning if the clock skew is greater than 60s.
426 return (clockSkewShouldTerminateInternal());
427 } else {
428 return (clockSkewShouldTerminateInternal());
429 }
430}
431
432bool
433CommunicationState::clockSkewShouldTerminateInternal() {
434 if (isClockSkewGreater(TERM_CLOCK_SKEW)) {
436 .arg(config_->getThisServerName())
437 .arg(logFormatClockSkewInternal());
438 return (true);
439 }
440 return (false);
441}
442
443bool
445 if (MultiThreadingMgr::instance().getMode()) {
446 std::lock_guard<std::mutex> lk(*mutex_);
447 return (rejectedLeaseUpdatesShouldTerminateInternal());
448 } else {
449 return (rejectedLeaseUpdatesShouldTerminateInternal());
450 }
451}
452
453bool
454CommunicationState::rejectedLeaseUpdatesShouldTerminateInternal() {
455 if (config_->getMaxRejectedLeaseUpdates() &&
456 (config_->getMaxRejectedLeaseUpdates() <= getRejectedLeaseUpdatesCountInternal())) {
458 .arg(config_->getThisServerName());
459 return (true);
460 }
461 return (false);
462}
463
464bool
465CommunicationState::isClockSkewGreater(const long seconds) const {
466 return ((clock_skew_.total_seconds() > seconds) ||
467 (clock_skew_.total_seconds() < -seconds));
468}
469
470void
471CommunicationState::setPartnerTime(const std::string& time_text) {
472 if (MultiThreadingMgr::instance().getMode()) {
473 std::lock_guard<std::mutex> lk(*mutex_);
474 setPartnerTimeInternal(time_text);
475 } else {
476 setPartnerTimeInternal(time_text);
477 }
478}
479
480void
481CommunicationState::setPartnerTimeInternal(const std::string& time_text) {
485}
486
487void
488CommunicationState::resetPartnerTimeInternal() {
489 clock_skew_ = boost::posix_time::time_duration(0, 0, 0, 0);
490 last_clock_skew_warn_ = boost::posix_time::ptime();
491 my_time_at_skew_ = boost::posix_time::ptime();
492 partner_time_at_skew_ = boost::posix_time::ptime();
493}
494
495std::string
497 if (MultiThreadingMgr::instance().getMode()) {
498 std::lock_guard<std::mutex> lk(*mutex_);
499 return (logFormatClockSkewInternal());
500 } else {
501 return (logFormatClockSkewInternal());
502 }
503}
504
505std::string
506CommunicationState::logFormatClockSkewInternal() const {
507 std::ostringstream os;
508
509 if ((my_time_at_skew_.is_not_a_date_time()) ||
510 (partner_time_at_skew_.is_not_a_date_time())) {
511 // Guard against being called before times have been set.
512 // Otherwise we'll get out-range exceptions.
513 return ("skew not initialized");
514 }
515
516 // Note HttpTime resolution is only to seconds, so we use fractional
517 // precision of zero when logging.
518 os << "my time: " << util::ptimeToText(my_time_at_skew_, 0)
519 << ", partner's time: " << util::ptimeToText(partner_time_at_skew_, 0)
520 << ", partner's clock is ";
521
522 // If negative clock skew, the partner's time is behind our time.
523 if (clock_skew_.is_negative()) {
524 os << clock_skew_.invert_sign().total_seconds() << "s behind";
525 } else {
526 // Partner's time is ahead of ours.
527 os << clock_skew_.total_seconds() << "s ahead";
528 }
529
530 return (os.str());
531}
532
535 auto report = Element::createMap();
536
537 auto in_touch = (getPartnerState() > 0);
538 report->set("in-touch", Element::create(in_touch));
539
540 auto age = in_touch ? static_cast<long long int>(getDurationInMillisecs() / 1000) : 0;
541 report->set("age", Element::create(age));
542
543 try {
544 report->set("last-state", Element::create(stateToString(getPartnerState())));
545
546 } catch (...) {
547 report->set("last-state", Element::create(std::string()));
548 }
549
550 auto list = Element::createList();
551 for (auto scope : getPartnerScopes()) {
552 list->add(Element::create(scope));
553 }
554 report->set("last-scopes", list);
555 report->set("communication-interrupted",
556 Element::create(isCommunicationInterrupted()));
557 report->set("connecting-clients", Element::create(static_cast<long long>(getConnectingClientsCount())));
558 report->set("unacked-clients", Element::create(static_cast<long long>(getUnackedClientsCount())));
559
560 long long unacked_clients_left = 0;
561 if (isCommunicationInterrupted() && (config_->getMaxUnackedClients() >= getUnackedClientsCount())) {
562 unacked_clients_left = static_cast<long long>(config_->getMaxUnackedClients() -
564 }
565 report->set("unacked-clients-left", Element::create(unacked_clients_left));
566 report->set("analyzed-packets", Element::create(static_cast<long long>(getAnalyzedMessagesCount())));
567
568 return (report);
569}
570
571uint64_t
573 if (MultiThreadingMgr::instance().getMode()) {
574 std::lock_guard<std::mutex> lk(*mutex_);
575 return (unsent_update_count_);
576 } else {
577 return (unsent_update_count_);
578 }
579}
580
581void
583 if (MultiThreadingMgr::instance().getMode()) {
584 std::lock_guard<std::mutex> lk(*mutex_);
585 increaseUnsentUpdateCountInternal();
586 } else {
587 increaseUnsentUpdateCountInternal();
588 }
589}
590
591void
592CommunicationState::increaseUnsentUpdateCountInternal() {
593 // Protect against setting the incremented value to zero.
594 // The zero value is reserved for a server startup.
595 if (unsent_update_count_ < std::numeric_limits<uint64_t>::max()) {
597 } else {
599 }
600}
601
602bool
604 if (MultiThreadingMgr::instance().getMode()) {
605 std::lock_guard<std::mutex> lk(*mutex_);
606 return (hasPartnerNewUnsentUpdatesInternal());
607 } else {
608 return (hasPartnerNewUnsentUpdatesInternal());
609 }
610}
611
612bool
613CommunicationState::hasPartnerNewUnsentUpdatesInternal() const {
614 return (partner_unsent_update_count_.second > 0 &&
616}
617
618void
620 if (MultiThreadingMgr::instance().getMode()) {
621 std::lock_guard<std::mutex> lk(*mutex_);
622 setPartnerUnsentUpdateCountInternal(unsent_update_count);
623 } else {
624 setPartnerUnsentUpdateCountInternal(unsent_update_count);
625 }
626}
627
628void
629CommunicationState::setPartnerUnsentUpdateCountInternal(uint64_t unsent_update_count) {
631 partner_unsent_update_count_.second = unsent_update_count;
632}
633
635 const HAConfigPtr& config)
636 : CommunicationState(io_service, config), connecting_clients_(),
637 rejected_clients_() {
638}
639
640void
641CommunicationState4::analyzeMessage(const boost::shared_ptr<dhcp::Pkt>& message) {
642 if (MultiThreadingMgr::instance().getMode()) {
643 std::lock_guard<std::mutex> lk(*mutex_);
644 analyzeMessageInternal(message);
645 } else {
646 analyzeMessageInternal(message);
647 }
648}
649
650void
652 // The DHCP message must successfully cast to a Pkt4 object.
653 Pkt4Ptr msg = boost::dynamic_pointer_cast<Pkt4>(message);
654 if (!msg) {
655 isc_throw(BadValue, "DHCP message to be analyzed is not a DHCPv4 message");
656 }
657
659
660 // Check value of the "secs" field by comparing it with the configured
661 // threshold.
662 uint16_t secs = msg->getSecs();
663
664 // It was observed that some Windows clients may send swapped bytes in the
665 // "secs" field. When the second byte is 0 and the first byte is non-zero
666 // we consider bytes to be swapped and so we correct them.
667 if ((secs > 255) && ((secs & 0xFF) == 0)) {
668 secs = ((secs >> 8) | (secs << 8));
669 }
670
671 // Check the value of the "secs" field. The "secs" field holds a value in
672 // seconds, hence we have to multiple by 1000 to get a value in milliseconds.
673 // If the secs value is above the threshold, it means that the current
674 // client should be considered unacked.
675 auto unacked = (secs * 1000 > config_->getMaxAckDelay());
676
677 // Client identifier will be stored together with the hardware address. It
678 // may remain empty if the client hasn't specified it.
679 auto client_id = getClientId(message, DHO_DHCP_CLIENT_IDENTIFIER);
680 bool log_unacked = false;
681
682 // Check if the given client was already recorded.
683 auto& idx = connecting_clients_.get<0>();
684 auto existing_request = idx.find(boost::make_tuple(msg->getHWAddr()->hwaddr_, client_id));
685 if (existing_request != idx.end()) {
686 // If the client was recorded and was not considered unacked
687 // but it should be considered unacked as a result of processing
688 // this packet, let's update the recorded request to mark the
689 // client unacked.
690 if (!existing_request->unacked_ && unacked) {
691 ConnectingClient4 connecting_client{ msg->getHWAddr()->hwaddr_, client_id, unacked };
692 idx.replace(existing_request, connecting_client);
693 log_unacked = true;
694 }
695
696 } else {
697 // This is the first time we see the packet from this client. Let's
698 // record it.
699 ConnectingClient4 connecting_client{ msg->getHWAddr()->hwaddr_, client_id, unacked };
700 idx.insert(connecting_client);
701 log_unacked = unacked;
702
703 if (!unacked) {
704 // This is the first time we see this client after getting into the
705 // communication interrupted state. But, this client hasn't been
706 // yet trying log enough to be considered unacked.
708 .arg(config_->getThisServerName())
709 .arg(message->getLabel());
710 }
711 }
712
713 // Only log the first time we detect a client is unacked.
714 if (log_unacked) {
715 unsigned unacked_left = 0;
716 unsigned unacked_total = connecting_clients_.get<1>().count(true);
717 if (config_->getMaxUnackedClients() >= unacked_total) {
718 unacked_left = config_->getMaxUnackedClients() - unacked_total + 1;
719 }
721 .arg(config_->getThisServerName())
722 .arg(message->getLabel())
723 .arg(unacked_total)
724 .arg(unacked_left);
725 }
726}
727
728bool
730 if (MultiThreadingMgr::instance().getMode()) {
731 std::lock_guard<std::mutex> lk(*mutex_);
732 return (failureDetectedInternal());
733 } else {
734 return (failureDetectedInternal());
735 }
736}
737
738bool
740 return ((config_->getMaxUnackedClients() == 0) ||
741 (connecting_clients_.get<1>().count(true) >
742 config_->getMaxUnackedClients()));
743}
744
745size_t
747 if (MultiThreadingMgr::instance().getMode()) {
748 std::lock_guard<std::mutex> lk(*mutex_);
749 return (connecting_clients_.size());
750 } else {
751 return (connecting_clients_.size());
752 }
753}
754
755size_t
757 if (MultiThreadingMgr::instance().getMode()) {
758 std::lock_guard<std::mutex> lk(*mutex_);
759 return (connecting_clients_.get<1>().count(true));
760 } else {
761 return (connecting_clients_.get<1>().count(true));
762 }
763}
764
765void
767 connecting_clients_.clear();
768}
769
770size_t
773}
774
775bool
776CommunicationState4::reportRejectedLeaseUpdateInternal(const PktPtr& message, const uint32_t lifetime) {
777 Pkt4Ptr msg = boost::dynamic_pointer_cast<Pkt4>(message);
778 if (!msg) {
779 isc_throw(BadValue, "DHCP message for which the lease update was rejected is not a DHCPv4 message");
780 }
781 auto client_id = getClientId(message, DHO_DHCP_CLIENT_IDENTIFIER);
782 RejectedClient4 client{ msg->getHWAddr()->hwaddr_, client_id, time(NULL) + lifetime };
783 auto existing_client = rejected_clients_.find(boost::make_tuple(msg->getHWAddr()->hwaddr_, client_id));
784 if (existing_client == rejected_clients_.end()) {
785 rejected_clients_.insert(client);
786 return (true);
787 }
788 rejected_clients_.replace(existing_client, client);
789 return (false);
790}
791
792bool
794 // Early check if there is anything to do.
796 return (false);
797 }
798 Pkt4Ptr msg = boost::dynamic_pointer_cast<Pkt4>(message);
799 if (!msg) {
800 isc_throw(BadValue, "DHCP message for which the lease update was successful is not a DHCPv4 message");
801 }
802 auto client_id = getClientId(msg, DHO_DHCP_CLIENT_IDENTIFIER);
803 auto existing_client = rejected_clients_.find(boost::make_tuple(msg->getHWAddr()->hwaddr_, client_id));
804 if (existing_client != rejected_clients_.end()) {
805 rejected_clients_.erase(existing_client);
806 return (true);
807 }
808 return (false);
809}
810
811void
813 rejected_clients_.clear();
814}
815
817 const HAConfigPtr& config)
818 : CommunicationState(io_service, config), connecting_clients_(),
819 rejected_clients_() {
820}
821
822void
823CommunicationState6::analyzeMessage(const boost::shared_ptr<dhcp::Pkt>& message) {
824 if (MultiThreadingMgr::instance().getMode()) {
825 std::lock_guard<std::mutex> lk(*mutex_);
826 analyzeMessageInternal(message);
827 } else {
828 analyzeMessageInternal(message);
829 }
830}
831
832void
833CommunicationState6::analyzeMessageInternal(const boost::shared_ptr<dhcp::Pkt>& message) {
834 // The DHCP message must successfully cast to a Pkt6 object.
835 Pkt6Ptr msg = boost::dynamic_pointer_cast<Pkt6>(message);
836 if (!msg) {
837 isc_throw(BadValue, "DHCP message to be analyzed is not a DHCPv6 message");
838 }
839
841
842 // Check the value of the "elapsed time" option. If it is below the threshold
843 // there is nothing to do. The "elapsed time" option holds the time in
844 // 1/100 of second, hence we have to multiply by 10 to get a value in milliseconds.
845 OptionUint16Ptr elapsed_time = boost::dynamic_pointer_cast<
846 OptionUint16>(msg->getOption(D6O_ELAPSED_TIME));
847 auto unacked = (elapsed_time && elapsed_time->getValue() * 10 > config_->getMaxAckDelay());
848
849 // Get the DUID of the client to see if it hasn't been recorded already.
850 auto duid = getClientId(msg, D6O_CLIENTID);
851 if (duid.empty()) {
852 return;
853 }
854
855 bool log_unacked = false;
856
857 // Check if the given client was already recorded.
858 auto& idx = connecting_clients_.get<0>();
859 auto existing_request = idx.find(duid);
860 if (existing_request != idx.end()) {
861 // If the client was recorded and was not considered unacked
862 // but it should be considered unacked as a result of processing
863 // this packet, let's update the recorded request to mark the
864 // client unacked.
865 if (!existing_request->unacked_ && unacked) {
866 ConnectingClient6 connecting_client{ duid, unacked };
867 idx.replace(existing_request, connecting_client);
868 log_unacked = true;
869 }
870
871 } else {
872 // This is the first time we see the packet from this client. Let's
873 // record it.
874 ConnectingClient6 connecting_client{ duid, unacked };
875 idx.insert(connecting_client);
876 log_unacked = unacked;
877
878 if (!unacked) {
879 // This is the first time we see this client after getting into the
880 // communication interrupted state. But, this client hasn't been
881 // yet trying log enough to be considered unacked.
883 .arg(config_->getThisServerName())
884 .arg(message->getLabel());
885 }
886 }
887
888 // Only log the first time we detect a client is unacked.
889 if (log_unacked) {
890 unsigned unacked_left = 0;
891 unsigned unacked_total = connecting_clients_.get<1>().count(true);
892 if (config_->getMaxUnackedClients() >= unacked_total) {
893 unacked_left = config_->getMaxUnackedClients() - unacked_total + 1;
894 }
896 .arg(config_->getThisServerName())
897 .arg(message->getLabel())
898 .arg(unacked_total)
899 .arg(unacked_left);
900 }
901}
902
903bool
905 if (MultiThreadingMgr::instance().getMode()) {
906 std::lock_guard<std::mutex> lk(*mutex_);
907 return (failureDetectedInternal());
908 } else {
909 return (failureDetectedInternal());
910 }
911}
912
913bool
915 return ((config_->getMaxUnackedClients() == 0) ||
916 (connecting_clients_.get<1>().count(true) >
917 config_->getMaxUnackedClients()));
918}
919
920size_t
922 if (MultiThreadingMgr::instance().getMode()) {
923 std::lock_guard<std::mutex> lk(*mutex_);
924 return (connecting_clients_.size());
925 } else {
926 return (connecting_clients_.size());
927 }
928}
929
930size_t
932 if (MultiThreadingMgr::instance().getMode()) {
933 std::lock_guard<std::mutex> lk(*mutex_);
934 return (connecting_clients_.get<1>().count(true));
935 } else {
936 return (connecting_clients_.get<1>().count(true));
937 }
938}
939
940void
942 connecting_clients_.clear();
943}
944
945size_t
948}
949
950bool
951CommunicationState6::reportRejectedLeaseUpdateInternal(const PktPtr& message, const uint32_t lifetime) {
952 Pkt6Ptr msg = boost::dynamic_pointer_cast<Pkt6>(message);
953 if (!msg) {
954 isc_throw(BadValue, "DHCP message for which the lease update was rejected is not a DHCPv6 message");
955 }
956 auto duid = getClientId(msg, D6O_CLIENTID);
957 if (duid.empty()) {
958 return (false);
959 }
960 RejectedClient6 client{ duid, time(NULL) + lifetime };
961 auto existing_client = rejected_clients_.find(duid);
962 if (existing_client == rejected_clients_.end()) {
963 rejected_clients_.insert(client);
964 return (true);
965 }
966 rejected_clients_.replace(existing_client, client);
967 return (false);
968}
969
970bool
972 // Early check if there is anything to do.
974 return (false);
975 }
976 Pkt6Ptr msg = boost::dynamic_pointer_cast<Pkt6>(message);
977 if (!msg) {
978 isc_throw(BadValue, "DHCP message for which the lease update was successful is not a DHCPv6 message");
979 }
980 auto duid = getClientId(msg, D6O_CLIENTID);
981 if (duid.empty()) {
982 return (false);
983 }
984 auto existing_client = rejected_clients_.find(duid);
985 if (existing_client != rejected_clients_.end()) {
986 rejected_clients_.erase(existing_client);
987 return (true);
988 }
989 return (false);
990}
991
992void
994 rejected_clients_.clear();
995}
996
997} // end of namespace isc::ha
998} // end of namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
Forward declaration to OptionInt.
Definition: option_int.h:49
virtual bool reportRejectedLeaseUpdateInternal(const dhcp::PktPtr &message, const uint32_t lifetime)
Marks that the lease update failed due to a conflict for the specified DHCP message.
virtual size_t getRejectedLeaseUpdatesCountInternal()
Returns the number of lease updates rejected by the partner.
virtual bool reportSuccessfulLeaseUpdateInternal(const dhcp::PktPtr &message)
Marks the lease update successful.
virtual size_t getUnackedClientsCount() const
Returns the current number of clients which haven't gotten a lease from the partner server.
virtual void clearRejectedLeaseUpdatesInternal()
Clears rejected client leases.
virtual void analyzeMessageInternal(const boost::shared_ptr< dhcp::Pkt > &message)
Checks if the DHCPv4 message appears to be unanswered.
virtual size_t getConnectingClientsCount() const
Returns the current number of clients which attempted to get a lease from the partner server.
virtual void analyzeMessage(const boost::shared_ptr< dhcp::Pkt > &message)
Checks if the DHCPv4 message appears to be unanswered.
RejectedClients4 rejected_clients_
Holds information about the clients for whom lease updates have been rejected by the partner.
virtual bool failureDetectedInternal() const
Checks if the partner failure has been detected based on the DHCP traffic analysis.
ConnectingClients4 connecting_clients_
Holds information about the clients attempting to contact the partner server while the servers are in...
virtual bool failureDetected() const
Checks if the partner failure has been detected based on the DHCP traffic analysis.
virtual void clearConnectingClients()
Removes information about the clients the partner server should respond to while communication with t...
CommunicationState4(const asiolink::IOServicePtr &io_service, const HAConfigPtr &config)
Constructor.
virtual void analyzeMessage(const boost::shared_ptr< dhcp::Pkt > &message)
Checks if the DHCPv6 message appears to be unanswered.
RejectedClients6 rejected_clients_
Holds information about the clients for whom lease updates have been rejected by the partner.
virtual size_t getRejectedLeaseUpdatesCountInternal()
Returns the number of lease updates rejected by the partner.
ConnectingClients6 connecting_clients_
Holds information about the clients attempting to contact the partner server while the servers are in...
CommunicationState6(const asiolink::IOServicePtr &io_service, const HAConfigPtr &config)
Constructor.
virtual bool reportSuccessfulLeaseUpdateInternal(const dhcp::PktPtr &message)
Marks the lease update successful.
virtual void clearConnectingClients()
Removes information about the clients the partner server should respond to while communication with t...
virtual bool failureDetected() const
Checks if the partner failure has been detected based on the DHCP traffic analysis.
virtual size_t getUnackedClientsCount() const
Returns the current number of clients which haven't gotten a lease from the partner server.
virtual bool failureDetectedInternal() const
Checks if the partner failure has been detected based on the DHCP traffic analysis.
virtual void analyzeMessageInternal(const boost::shared_ptr< dhcp::Pkt > &message)
Checks if the DHCPv6 message appears to be unanswered.
virtual bool reportRejectedLeaseUpdateInternal(const dhcp::PktPtr &message, const uint32_t lifetime=86400)
Marks that the lease update failed due to a conflict for the specified DHCP message.
virtual size_t getConnectingClientsCount() const
Returns the current number of clients which attempted to get a lease from the partner server.
virtual void clearRejectedLeaseUpdatesInternal()
Clears rejected client leases.
Holds communication state between the two HA peers.
virtual size_t getConnectingClientsCount() const =0
Returns the current number of clients which attempted to get a lease from the partner server.
virtual bool reportRejectedLeaseUpdateInternal(const dhcp::PktPtr &message, const uint32_t lifetime)=0
Marks that the lease update failed due to a conflict for the specified DHCP message.
virtual void clearRejectedLeaseUpdatesInternal()=0
Clears rejected client leases.
virtual size_t getUnackedClientsCount() const =0
Returns the current number of clients which haven't got the lease from the partner server.
virtual void clearConnectingClients()=0
Removes information about the clients the partner server should respond to while communication with t...
void clearRejectedLeaseUpdates()
Clears rejected client leases (MT safe).
void startHeartbeat(const long interval, const std::function< void()> &heartbeat_impl)
Starts recurring heartbeat (public interface).
uint64_t unsent_update_count_
Total number of unsent lease updates.
bool isCommunicationInterrupted() const
Checks if communication with the partner is interrupted.
void setPartnerScopes(data::ConstElementPtr new_scopes)
Sets partner scopes.
int getPartnerState() const
Returns last known state of the partner.
bool clockSkewShouldWarn()
Issues a warning about high clock skew between the active servers if one is warranted.
std::string logFormatClockSkew() const
Returns current clock skew value in the logger friendly format.
void setPartnerUnsentUpdateCount(uint64_t unsent_update_count)
Saves new total number of unsent lease updates from the partner.
void setPartnerState(const std::string &state)
Sets partner state.
bool clockSkewShouldTerminate()
Indicates whether the HA service should enter "terminated" state as a result of the clock skew exceed...
std::pair< uint64_t, uint64_t > partner_unsent_update_count_
Previous and current total number of unsent lease updates from the partner.
std::set< std::string > getPartnerScopes() const
Returns scopes served by the partner server.
virtual ~CommunicationState()
Destructor.
HAConfigPtr config_
High availability configuration.
bool isHeartbeatRunning() const
Checks if recurring heartbeat is running.
static size_t getRejectedLeaseUpdatesCountFromContainer(RejectedClientsType &rejected_clients)
Extracts the number of lease updates rejected by the partner from the specified container.
long interval_
Interval specified for the heartbeat.
void setPartnerUnavailable()
Sets partner state unavailable.
void stopHeartbeat()
Stops recurring heartbeat.
void increaseUnsentUpdateCount()
Increases a total number of unsent lease updates by 1.
void setPartnerTime(const std::string &time_text)
Provide partner's notion of time so the new clock skew can be calculated.
bool hasPartnerNewUnsentUpdates() const
Checks if the partner allocated new leases for which it hasn't sent any lease updates.
virtual bool reportSuccessfulLeaseUpdateInternal(const dhcp::PktPtr &message)=0
Marks the lease update successful.
asiolink::IOServicePtr io_service_
Pointer to the common IO service instance.
virtual size_t getRejectedLeaseUpdatesCountInternal()=0
Returns the number of lease updates rejected by the partner.
void modifyPokeTime(const long secs)
Modifies poke time by adding seconds to it.
const boost::scoped_ptr< std::mutex > mutex_
The mutex used to protect internal state.
data::ElementPtr getReport() const
Returns the report about current communication state.
boost::posix_time::time_duration clock_skew_
Clock skew between the active servers.
size_t getAnalyzedMessagesCount() const
Returns the number of analyzed messages while being in the communications interrupted state.
size_t analyzed_messages_count_
Total number of analyzed messages to be responded by partner.
std::function< void()> heartbeat_impl_
Pointer to the function providing heartbeat implementation.
boost::posix_time::ptime poke_time_
Last poke time.
boost::posix_time::time_duration updatePokeTime()
Update the poke time and compute the duration.
bool reportSuccessfulLeaseUpdate(const dhcp::PktPtr &message)
Marks the lease update successful (MT safe).
boost::posix_time::ptime partner_time_at_skew_
Partner reported time when skew was calculated.
CommunicationState(const asiolink::IOServicePtr &io_service, const HAConfigPtr &config)
Constructor.
int partner_state_
Last known state of the partner server.
boost::posix_time::ptime last_clock_skew_warn_
Holds a time when last warning about too high clock skew was issued.
std::set< std::string > partner_scopes_
Last known set of scopes served by the partner server.
static std::vector< uint8_t > getClientId(const dhcp::PktPtr &message, const uint16_t option_type)
Convenience function attempting to retrieve client identifier from the DHCP message.
uint64_t getUnsentUpdateCount() const
Returns a total number of unsent lease updates.
bool rejectedLeaseUpdatesShouldTerminate()
Indicates whether the HA service should enter "terminated" state due to excessive number of rejected ...
boost::posix_time::ptime my_time_at_skew_
My time when skew was calculated.
int64_t getDurationInMillisecs() const
Returns duration between the poke time and current time.
bool reportRejectedLeaseUpdate(const dhcp::PktPtr &message, const uint32_t lifetime=86400)
Marks that the lease update failed due to a conflict for the specified DHCP message (MT safe).
size_t getRejectedLeaseUpdatesCount()
Returns the number of lease updates rejected by the partner (MT safe).
asiolink::IntervalTimerPtr timer_
Interval timer triggering heartbeat commands.
void poke()
Pokes the communication state.
This class parses and generates time values used in HTTP.
Definition: date_time.h:41
boost::posix_time::ptime getPtime() const
Returns time encapsulated by this class.
Definition: date_time.h:59
static HttpDateTime fromRfc1123(const std::string &time_string)
Creates an instance from a string containing time value formatted as specified in RFC 1123.
Definition: date_time.cc:45
@ D6O_CLIENTID
Definition: dhcp6.h:21
@ D6O_ELAPSED_TIME
Definition: dhcp6.h:28
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
boost::shared_ptr< OptionUint16 > OptionUint16Ptr
Definition: option_int.h:33
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition: macros.h:26
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:29
boost::shared_ptr< Element > ElementPtr
Definition: data.h:28
boost::shared_ptr< isc::dhcp::Pkt > PktPtr
A pointer to either Pkt4 or Pkt6 packet.
Definition: pkt.h:864
@ DHO_DHCP_CLIENT_IDENTIFIER
Definition: dhcp4.h:130
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition: pkt4.h:555
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition: pkt6.h:31
boost::shared_ptr< Option > OptionPtr
Definition: option.h:37
const isc::log::MessageID HA_COMMUNICATION_INTERRUPTED_CLIENT4_UNACKED
Definition: ha_messages.h:22
const isc::log::MessageID HA_COMMUNICATION_INTERRUPTED_CLIENT6
Definition: ha_messages.h:23
isc::log::Logger ha_logger("ha-hooks")
Definition: ha_log.h:17
const isc::log::MessageID HA_HIGH_CLOCK_SKEW_CAUSED_TERMINATION
Definition: ha_messages.h:48
const isc::log::MessageID HA_LEASE_UPDATE_REJECTS_CAUSED_TERMINATION
Definition: ha_messages.h:76
boost::shared_ptr< HAConfig > HAConfigPtr
Pointer to the High Availability configuration structure.
Definition: ha_config.h:36
const isc::log::MessageID HA_COMMUNICATION_INTERRUPTED_CLIENT6_UNACKED
Definition: ha_messages.h:24
std::string stateToString(int state)
Returns state name.
const isc::log::MessageID HA_COMMUNICATION_INTERRUPTED_CLIENT4
Definition: ha_messages.h:21
int stringToState(const std::string &state_name)
Returns state for a given name.
const isc::log::MessageID HA_HIGH_CLOCK_SKEW
Definition: ha_messages.h:47
Definition: edns.h:19
std::string ptimeToText(boost::posix_time::ptime t, size_t fsecs_precision=MAX_FSECS_PRECISION)
Converts ptime structure to text.
Defines the logger used by the top-level component of kea-lfc.
Structure holding information about the client which has sent the packet being analyzed.
Structure holding information about the client who has a rejected lease update.
Structure holding information about a client which sent a packet being analyzed.
Structure holding information about the client who has a rejected lease update.