Kea  2.3.7
query_filter.cc
Go to the documentation of this file.
1 // Copyright (C) 2018-2022 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_log.h>
10 #include <query_filter.h>
11 #include <dhcp/dhcp4.h>
12 #include <dhcp/dhcp6.h>
13 #include <dhcp/option.h>
14 #include <exceptions/exceptions.h>
16 
17 #include <array>
18 #include <iostream>
19 #include <sstream>
20 
21 using namespace isc::dhcp;
22 using namespace isc::log;
23 using namespace isc::util;
24 
25 namespace {
26 
30 std::array<uint8_t, 256> loadb_mx_tbl = { {
31  251, 175, 119, 215, 81, 14, 79, 191, 103, 49, 181, 143, 186, 157, 0,
32  232, 31, 32, 55, 60, 152, 58, 17, 237, 174, 70, 160, 144, 220, 90, 57,
33  223, 59, 3, 18, 140, 111, 166, 203, 196, 134, 243, 124, 95, 222, 179,
34  197, 65, 180, 48, 36, 15, 107, 46, 233, 130, 165, 30, 123, 161, 209, 23,
35  97, 16, 40, 91, 219, 61, 100, 10, 210, 109, 250, 127, 22, 138, 29, 108,
36  244, 67, 207, 9, 178, 204, 74, 98, 126, 249, 167, 116, 34, 77, 193,
37  200, 121, 5, 20, 113, 71, 35, 128, 13, 182, 94, 25, 226, 227, 199, 75,
38  27, 41, 245, 230, 224, 43, 225, 177, 26, 155, 150, 212, 142, 218, 115,
39  241, 73, 88, 105, 39, 114, 62, 255, 192, 201, 145, 214, 168, 158, 221,
40  148, 154, 122, 12, 84, 82, 163, 44, 139, 228, 236, 205, 242, 217, 11,
41  187, 146, 159, 64, 86, 239, 195, 42, 106, 198, 118, 112, 184, 172, 87,
42  2, 173, 117, 176, 229, 247, 253, 137, 185, 99, 164, 102, 147, 45, 66,
43  231, 52, 141, 211, 194, 206, 246, 238, 56, 110, 78, 248, 63, 240, 189,
44  93, 92, 51, 53, 183, 19, 171, 72, 50, 33, 104, 101, 69, 8, 252, 83, 120,
45  76, 135, 85, 54, 202, 125, 188, 213, 96, 235, 136, 208, 162, 129, 190,
46  132, 156, 38, 47, 1, 7, 254, 24, 4, 216, 131, 89, 21, 28, 133, 37, 153,
47  149, 80, 170, 68, 6, 169, 234, 151 }
48 };
49 
51 std::array<bool, DHCP_TYPES_EOF> v4_ha_types = {
52  false, // DHCP_NOTYPE = 0
53  true, // DHCPDISCOVER = 1
54  false, // DHCPOFFER = 2
55  true, // DHCPREQUEST = 3
56  true, // DHCPDECLINE = 4
57  false, // DHCPACK = 5
58  false, // DHCPNAK = 6
59  true, // DHCPRELEASE = 7
60  true, // DHCPINFORM = 8
61  false, // DHCPFORCERENEW = 9
62  false, // DHCPLEASEQUERY = 10
63  false, // DHCPLEASEUNASSIGNED = 11
64  false, // DHCPLEASEUNKNOWN = 12
65  false, // DHCPLEASEACTIVE = 13
66  false, // DHCPBULKLEASEQUERY = 14
67  false, // DHCPLEASEQUERYDONE = 15
68  false, // DHCPACTIVELEASEQUERY = 16
69  false, // DHCPLEASEQUERYSTATUS = 17
70  false // DHCPTLS = 18
71 };
72 
74 std::array<bool, DHCPV6_TYPES_EOF> v6_ha_types = {
75  false, // DHCPV6_NOTYPE = 0
76  true, // DHCPV6_SOLICIT = 1
77  false, // DHCPV6_ADVERTISE = 2
78  true, // DHCPV6_REQUEST = 3
79  true, // DHCPV6_CONFIRM = 4
80  true, // DHCPV6_RENEW = 5
81  true, // DHCPV6_REBIND = 6
82  false, // DHCPV6_REPLY = 7
83  true, // DHCPV6_RELEASE = 8
84  true, // DHCPV6_DECLINE = 9
85  false, // DHCPV6_RECONFIGURE = 10
86  false, // DHCPV6_INFORMATION_REQUEST = 11
87  false, // DHCPV6_RELAY_FORW = 12
88  false, // DHCPV6_RELAY_REPL = 13
89  false, // DHCPV6_LEASEQUERY = 14
90  false, // DHCPV6_LEASEQUERY_REPLY = 15
91  false, // DHCPV6_LEASEQUERY_DONE = 16
92  false, // DHCPV6_LEASEQUERY_DATA = 17
93  false, // DHCPV6_RECONFIGURE_REQUEST = 18
94  false, // DHCPV6_RECONFIGURE_REPLY = 19
98  false, // DHCPV6_DHCPV4_QUERY = 20
99  false, // DHCPV6_DHCPV4_RESPONSE = 21
100  false, // DHCPV6_ACTIVELEASEQUERY = 22
101  false, // DHCPV6_STARTTLS = 23
102  false, // DHCPV6_BNDUPD = 24
103  false, // DHCPV6_BNDREPLY = 25
104  false, // DHCPV6_POOLREQ = 26
105  false, // DHCPV6_POOLRESP = 27
106  false, // DHCPV6_UPDREQ = 28
107  false, // DHCPV6_UPDREQALL = 29
108  false, // DHCPV6_UPDDONE = 30
109  false, // DHCPV6_CONNECT = 31
110  false, // DHCPV6_CONNECTREPLY = 32
111  false, // DHCPV6_DISCONNECT = 33
112  false, // DHCPV6_STATE = 34
113  false // DHCPV6_CONTACT = 35
114 };
115 
116 } // end of anonymous namespace
117 
118 namespace isc {
119 namespace ha {
120 
121 QueryFilter::QueryFilter(const HAConfigPtr& config)
122  : config_(config), peers_(), scopes_(), active_servers_(0),
123  mutex_(new std::mutex) {
124 
125  // Make sure that the configuration is valid. We make certain
126  // assumptions about the availability of the servers' configurations
127  // in the config_ structure.
128  config_->validate();
129 
130  HAConfig::PeerConfigMap peers_map = config->getAllServersConfig();
131  std::vector<HAConfig::PeerConfigPtr> backup_peers;
132 
133  // The returned configurations are not ordered. Let's iterate over them
134  // and put them in the desired order.
135  for (auto peer_pair = peers_map.begin(); peer_pair != peers_map.end(); ++peer_pair) {
136  auto peer = peer_pair->second;
137  // The primary server is always first on the list.
138  if (peer->getRole() == HAConfig::PeerConfig::PRIMARY) {
139  peers_.insert(peers_.begin(), peer);
140  ++active_servers_;
141 
142  // The secondary server is always behind the primary server.
143  } else if ((peer->getRole() == HAConfig::PeerConfig::SECONDARY) ||
144  (peer->getRole() == HAConfig::PeerConfig::STANDBY)) {
145  peers_.push_back(peer);
146 
147  // If this is a secondary server, we're in the load balancing
148  // mode, in which case we have two active servers.
149  if (peer->getRole() == HAConfig::PeerConfig::SECONDARY) {
150  ++active_servers_;
151  }
152 
153  // If this is neither primary nor secondary/standby, it is a backup.
154  } else {
155  backup_peers.push_back(peer);
156  }
157  }
158 
159  // Append backup servers to the list.
160  if (!backup_peers.empty()) {
161  peers_.insert(peers_.end(), backup_peers.begin(), backup_peers.end());
162  }
163 
164  // The query filter is initially setup to serve default scopes, i.e. for the
165  // load balancing case the primary and secondary are responsible for their
166  // own scopes. The backup servers are not responding to any queries. In the
167  // hot standby mode, the primary server is responsible for the entire traffic.
168  // The standby server is not responding.
170 }
171 
172 void
173 QueryFilter::serveScope(const std::string& scope_name) {
174  if (MultiThreadingMgr::instance().getMode()) {
175  std::lock_guard<std::mutex> lock(*mutex_);
176  serveScopeInternal(scope_name);
177  } else {
178  serveScopeInternal(scope_name);
179  }
180 }
181 
182 void
183 QueryFilter::serveScopeInternal(const std::string& scope_name) {
184  validateScopeName(scope_name);
185  scopes_[scope_name] = true;
186 }
187 
188 void
189 QueryFilter::serveScopeOnly(const std::string& scope_name) {
190  if (MultiThreadingMgr::instance().getMode()) {
191  std::lock_guard<std::mutex> lock(*mutex_);
192  serveScopeOnlyInternal(scope_name);
193  } else {
194  serveScopeOnlyInternal(scope_name);
195  }
196 }
197 
198 void
199 QueryFilter::serveScopeOnlyInternal(const std::string& scope_name) {
200  validateScopeName(scope_name);
201  serveNoScopesInternal();
202  serveScopeInternal(scope_name);
203 }
204 
205 void
206 QueryFilter::serveScopes(const std::vector<std::string>& scopes) {
207  if (MultiThreadingMgr::instance().getMode()) {
208  std::lock_guard<std::mutex> lock(*mutex_);
209  serveScopesInternal(scopes);
210  } else {
211  serveScopesInternal(scopes);
212  }
213 }
214 
215 void
216 QueryFilter::serveScopesInternal(const std::vector<std::string>& scopes) {
217  // Remember currently enabled scopes in case we fail to process
218  // the provided list of scopes.
219  auto current_scopes = scopes_;
220  try {
221  serveNoScopesInternal();
222  for (size_t i = 0; i < scopes.size(); ++i) {
223  serveScopeInternal(scopes[i]);
224  }
225 
226  } catch (...) {
227  // There was an error processing scopes list. Need to revert
228  // to the previous configuration.
229  scopes_ = current_scopes;
230  throw;
231  }
232 }
233 
234 void
236  if (MultiThreadingMgr::instance().getMode()) {
237  std::lock_guard<std::mutex> lock(*mutex_);
238  serveDefaultScopesInternal();
239  } else {
240  serveDefaultScopesInternal();
241  }
242 }
243 
244 void
245 QueryFilter::serveDefaultScopesInternal() {
246  // Get this server instance configuration.
247  HAConfig::PeerConfigPtr my_config = config_->getThisServerConfig();
248  HAConfig::PeerConfig::Role my_role = my_config->getRole();
249 
250  // Clear scopes.
251  serveNoScopesInternal();
252 
253  // If I am primary or secondary, then I am only responsible for my own
254  // scope. If I am standby, I am not responsible for any scope.
255  if ((my_role == HAConfig::PeerConfig::PRIMARY) ||
256  (my_role == HAConfig::PeerConfig::SECONDARY)) {
257  serveScopeInternal(my_config->getName());
258  }
259 }
260 
261 void
263  if (MultiThreadingMgr::instance().getMode()) {
264  std::lock_guard<std::mutex> lock(*mutex_);
265  serveFailoverScopesInternal();
266  } else {
267  serveFailoverScopesInternal();
268  }
269 }
270 
271 void
272 QueryFilter::serveFailoverScopesInternal() {
273  // Clear scopes.
274  serveNoScopesInternal();
275 
276  // Iterate over the roles of all servers to see which scope should
277  // be enabled.
278  for (auto peer = peers_.begin(); peer != peers_.end(); ++peer) {
279  // The scope of the primary server must always be served. If we're
280  // doing load balancing, the scope of the secondary server also
281  // has to be served. Regardless if I am primary or secondary,
282  // I will start serving queries from both scopes. If I am a
283  // standby server, I will start serving the scope of the primary
284  // server.
285  if (((*peer)->getRole() == HAConfig::PeerConfig::PRIMARY) ||
286  ((*peer)->getRole() == HAConfig::PeerConfig::SECONDARY)) {
287  serveScopeInternal((*peer)->getName());
288  }
289  }
290 }
291 
292 void
294  if (MultiThreadingMgr::instance().getMode()) {
295  std::lock_guard<std::mutex> lock(*mutex_);
296  serveNoScopesInternal();
297  } else {
298  serveNoScopesInternal();
299  }
300 }
301 
302 void
303 QueryFilter::serveNoScopesInternal() {
304  scopes_.clear();
305 
306  // Disable scope for each peer in the configuration.
307  for (auto peer = peers_.begin(); peer != peers_.end(); ++peer) {
308  scopes_[(*peer)->getName()] = false;
309  }
310 }
311 
312 bool
313 QueryFilter::amServingScope(const std::string& scope_name) const {
314  if (MultiThreadingMgr::instance().getMode()) {
315  std::lock_guard<std::mutex> lock(*mutex_);
316  return (amServingScopeInternal(scope_name));
317  } else {
318  return (amServingScopeInternal(scope_name));
319  }
320 }
321 
322 bool
323 QueryFilter::amServingScopeInternal(const std::string& scope_name) const {
324  auto scope = scopes_.find(scope_name);
325  return ((scope == scopes_.end()) || (scope->second));
326 }
327 
328 std::set<std::string>
330  if (MultiThreadingMgr::instance().getMode()) {
331  std::lock_guard<std::mutex> lock(*mutex_);
332  return (getServedScopesInternal());
333  } else {
334  return (getServedScopesInternal());
335  }
336 }
337 
338 std::set<std::string>
339 QueryFilter::getServedScopesInternal() const {
340  std::set<std::string> scope_set;
341  for (auto scope : scopes_) {
342  if (scope.second) {
343  scope_set.insert(scope.first);
344  }
345  }
346  return (scope_set);
347 }
348 
349 bool
351  auto msg_type = query4->getType();
352  return (msg_type < v4_ha_types.size() && v4_ha_types[msg_type]);
353 }
354 
355 bool
357  auto msg_type = query->getType();
358  return (msg_type < v6_ha_types.size() && v6_ha_types[msg_type]);
359 }
360 
361 
362 bool
363 QueryFilter::inScope(const dhcp::Pkt4Ptr& query4, std::string& scope_class) const {
364  if (MultiThreadingMgr::instance().getMode()) {
365  std::lock_guard<std::mutex> lock(*mutex_);
366  return (inScopeInternal(query4, scope_class));
367  } else {
368  return (inScopeInternal(query4, scope_class));
369  }
370 }
371 
372 bool
373 QueryFilter::inScope(const dhcp::Pkt6Ptr& query6, std::string& scope_class) const {
374  if (MultiThreadingMgr::instance().getMode()) {
375  std::lock_guard<std::mutex> lock(*mutex_);
376  return (inScopeInternal(query6, scope_class));
377  } else {
378  return (inScopeInternal(query6, scope_class));
379  }
380 }
381 
382 template<typename QueryPtrType>
383 bool
384 QueryFilter::inScopeInternal(const QueryPtrType& query,
385  std::string& scope_class) const {
386  if (!query) {
387  isc_throw(BadValue, "query must not be null");
388  }
389 
390 
391  // If it's not a type HA cares about, it's in scope for this peer.
392  if (!isHaType(query)) {
393  auto scope = peers_[0]->getName();
394  scope_class = makeScopeClass(scope);
395  return (true);
396  }
397 
398  int candidate_server = 0;
399 
400  // If we're doing load balancing we have to check if this query
401  // belongs to us or the partner. If it belongs to a partner but
402  // we're configured to serve this scope, we should accept it.
403  if (config_->getHAMode() == HAConfig::LOAD_BALANCING) {
404  candidate_server = loadBalance(query);
405  // Malformed query received.
406  if (candidate_server < 0) {
407  return (false);
408  }
409  }
410 
411  auto scope = peers_[candidate_server]->getName();
412  scope_class = makeScopeClass(scope);
413  return ((candidate_server >= 0) && amServingScopeInternal(scope));
414 }
415 
416 int
418  uint8_t lb_hash = 0;
419  // Try to compute the hash by client identifier if the client
420  // identifier has been specified.
421  OptionPtr opt_client_id = query4->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
422  if (opt_client_id && !opt_client_id->getData().empty()) {
423  const auto& client_id_key = opt_client_id->getData();
424  lb_hash = loadBalanceHash(&client_id_key[0], client_id_key.size());
425 
426  } else {
427  // No client identifier available. Use the HW address instead.
428  HWAddrPtr hwaddr = query4->getHWAddr();
429  if (hwaddr && !hwaddr->hwaddr_.empty()) {
430  lb_hash = loadBalanceHash(&hwaddr->hwaddr_[0], hwaddr->hwaddr_.size());
431 
432  } else {
433  // No client identifier and no HW address. Indicate an
434  // error.
435  std::stringstream xid;
436  xid << "0x" << std::hex << query4->getTransid() << std::dec;
438  .arg(xid.str());
439  return (-1);
440  }
441  }
442 
443  // The hash value modulo number of active servers gives an index
444  // of the server to process the packet.
445  return (active_servers_ > 0 ? static_cast<int>(lb_hash % active_servers_) : -1);
446 }
447 
448 int
450  uint8_t lb_hash = 0;
451  // Compute the hash by DUID if the DUID.
452  OptionPtr opt_duid = query6->getOption(D6O_CLIENTID);
453  if (opt_duid && !opt_duid->getData().empty()) {
454  const auto& duid_key = opt_duid->getData();
455  lb_hash = loadBalanceHash(&duid_key[0], duid_key.size());
456 
457  } else {
458  // No DUID. Indicate an error.
459  std::stringstream xid;
460  xid << "0x" << std::hex << query6->getTransid() << std::dec;
462  .arg(xid.str());
463  return (-1);
464  }
465 
466  // The hash value modulo number of active servers gives an index
467  // of the server to process the packet.
468  return (active_servers_ > 0 ? static_cast<int>(lb_hash % active_servers_) : -1);
469 }
470 
471 uint8_t
472 QueryFilter::loadBalanceHash(const uint8_t* key, const size_t key_len) const {
473  uint8_t hash = static_cast<uint8_t>(key_len);
474 
475  for (auto i = key_len; i > 0;) {
476  hash = loadb_mx_tbl[hash ^ key[--i]];
477  }
478 
479  return (hash);
480 }
481 
482 void
483 QueryFilter::validateScopeName(const std::string& scope_name) const {
484  try {
485  // If there is no such server, the scope name is invalid.
486  static_cast<void>(config_->getPeerConfig(scope_name));
487 
488  } catch (...) {
489  isc_throw(BadValue, "invalid server name specified '" << scope_name
490  << "' while enabling/disabling HA scopes");
491  }
492 }
493 
494 std::string
495 QueryFilter::makeScopeClass(const std::string& scope_name) const {
496  return (std::string("HA_") + scope_name);
497 }
498 
499 } // end of namespace isc::ha
500 } // end of namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
Role
Server's role in the High Availability setup.
Definition: ha_config.h:70
std::map< std::string, PeerConfigPtr > PeerConfigMap
Map of the servers' configurations.
Definition: ha_config.h:232
boost::shared_ptr< PeerConfig > PeerConfigPtr
Pointer to the server's configuration.
Definition: ha_config.h:229
void serveScopes(const std::vector< std::string > &scopes)
Enables selected scopes.
bool inScope(const dhcp::Pkt4Ptr &query4, std::string &scope_class) const
Checks if this server should process the DHCPv4 query.
bool amServingScope(const std::string &scope_name) const
Checks if this server instance is configured to process traffic belonging to a particular scope.
std::string makeScopeClass(const std::string &scope_name) const
Returns scope class name for the specified scope name.
boost::scoped_ptr< std::mutex > mutex_
Mutex to protect the internal state.
Definition: query_filter.h:369
void serveFailoverScopes()
Enable scopes required in failover case.
uint8_t loadBalanceHash(const uint8_t *key, const size_t key_len) const
Compute load balancing hash.
void validateScopeName(const std::string &scope_name) const
Checks if the scope name matches a name of any of the configured servers.
int active_servers_
Number of the active servers in the given HA mode.
Definition: query_filter.h:366
HAConfigPtr config_
Pointer to the HA configuration.
Definition: query_filter.h:356
void serveScopeOnly(const std::string &scope_name)
Enable scope and disable all other scopes.
int loadBalance(const dhcp::Pkt4Ptr &query4) const
Performs load balancing of the DHCPv4 queries.
static bool isHaType(const dhcp::Pkt4Ptr &query4)
Determines if a DHCPv4 query is a message type HA should process.
void serveDefaultScopes()
Serve default scopes for the given HA mode.
void serveNoScopes()
Disables all scopes.
std::set< std::string > getServedScopes() const
Returns served scopes.
void serveScope(const std::string &scope_name)
Enable scope.
std::vector< HAConfig::PeerConfigPtr > peers_
Vector of HA peers configurations.
Definition: query_filter.h:359
std::map< std::string, bool > scopes_
Holds mapping of the scope names to the flag which indicates if the scopes are enabled or disabled.
Definition: query_filter.h:363
@ D6O_CLIENTID
Definition: dhcp6.h:21
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
@ DHO_DHCP_CLIENT_IDENTIFIER
Definition: dhcp4.h:130
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition: pkt4.h:547
boost::shared_ptr< HWAddr > HWAddrPtr
Shared pointer to a hardware address structure.
Definition: hwaddr.h:154
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition: pkt6.h:28
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
isc::log::Logger ha_logger("ha-hooks")
Definition: ha_log.h:17
boost::shared_ptr< HAConfig > HAConfigPtr
Pointer to the High Availability configuration structure.
Definition: ha_config.h:820
const isc::log::MessageID HA_LOAD_BALANCING_DUID_MISSING
Definition: ha_messages.h:76
const isc::log::MessageID HA_LOAD_BALANCING_IDENTIFIER_MISSING
Definition: ha_messages.h:77
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:69
Definition: edns.h:19
Defines the logger used by the top-level component of kea-lfc.