Kea  2.3.4-git
pkt6.cc
Go to the documentation of this file.
1 // Copyright (C) 2011-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 <dhcp/dhcp6.h>
10 #include <dhcp/libdhcp++.h>
11 #include <dhcp/option.h>
12 #include <dhcp/option_space.h>
14 #include <dhcp/option_vendor.h>
15 #include <dhcp/pkt6.h>
17 #include <util/io_utilities.h>
18 #include <exceptions/exceptions.h>
19 #include <dhcp/duid.h>
20 #include <dhcp/iface_mgr.h>
21 
22 #include <iterator>
23 #include <iostream>
24 #include <sstream>
25 
26 using namespace std;
27 using namespace isc::asiolink;
28 
30 const IOAddress DEFAULT_ADDRESS6("::");
31 
32 namespace isc {
33 namespace dhcp {
34 
35 Pkt6::RelayInfo::RelayInfo()
36  : msg_type_(0), hop_count_(0), linkaddr_(DEFAULT_ADDRESS6),
37  peeraddr_(DEFAULT_ADDRESS6), relay_msg_len_(0) {
38 }
39 
40 std::string Pkt6::RelayInfo::toText() const {
41  stringstream tmp;
42  tmp << "msg-type=" << static_cast<int>(msg_type_) << "(" << getName(msg_type_)
43  << "), hop-count=" << static_cast<int>(hop_count_) << "," << endl
44  << "link-address=" << linkaddr_.toText()
45  << ", peer-address=" << peeraddr_.toText() << ", "
46  << options_.size() << " option(s)" << endl;
47  for (const auto& option : options_) {
48  tmp << option.second->toText() << endl;
49  }
50  return (tmp.str());
51 }
52 
53 Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */)
54  : Pkt(buf, buf_len, DEFAULT_ADDRESS6, DEFAULT_ADDRESS6, 0, 0), proto_(proto),
55  msg_type_(0) {
56 }
57 
58 Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/)
59  : Pkt(transid, DEFAULT_ADDRESS6, DEFAULT_ADDRESS6, 0, 0), proto_(proto),
60  msg_type_(msg_type) {
61 }
62 
63 size_t Pkt6::len() {
64  if (relay_info_.empty()) {
65  return (directLen());
66  } else {
67  // Unfortunately we need to re-calculate relay size every time, because
68  // we need to make sure that once a new option is added, its extra size
69  // is reflected in Pkt6::len().
71  return (relay_info_[0].relay_msg_len_ + getRelayOverhead(relay_info_[0]));
72  }
73 }
74 
75 void
76 Pkt6::prepareGetAnyRelayOption(const RelaySearchOrder& order,
77  int& start, int& end, int& direction) const {
78  switch (order) {
80  // Search backwards
81  start = relay_info_.size() - 1;
82  end = 0;
83  direction = -1;
84  break;
86  // Search forward
87  start = 0;
88  end = relay_info_.size() - 1;
89  direction = 1;
90  break;
91  case RELAY_GET_FIRST:
92  // Look at the innermost relay only
93  start = relay_info_.size() - 1;
94  end = start;
95  direction = 1;
96  break;
97  case RELAY_GET_LAST:
98  // Look at the outermost relay only
99  start = 0;
100  end = 0;
101  direction = 1;
102  }
103 }
104 
105 
106 OptionPtr
107 Pkt6::getNonCopiedAnyRelayOption(const uint16_t option_code,
108  const RelaySearchOrder& order) const {
109  if (relay_info_.empty()) {
110  // There's no relay info, this is a direct message
111  return (OptionPtr());
112  }
113 
114  int start = 0; // First relay to check
115  int end = 0; // Last relay to check
116  int direction = 0; // How we going to iterate: forward or backward?
117 
118  prepareGetAnyRelayOption(order, start, end, direction);
119 
120  // This is a tricky loop. It must go from start to end, but it must work in
121  // both directions (start > end; or start < end). We can't use regular
122  // exit condition, because we don't know whether to use i <= end or i >= end.
123  // That's why we check if in the next iteration we would go past the
124  // list (end + direction). It is similar to STL concept of end pointing
125  // to a place after the last element
126  for (int i = start; i != end + direction; i += direction) {
127  OptionPtr opt = getNonCopiedRelayOption(option_code, i);
128  if (opt) {
129  return (opt);
130  }
131  }
132 
133  // We iterated over specified relays and haven't found what we were
134  // looking for
135  return (OptionPtr());
136 }
137 
138 OptionPtr
139 Pkt6::getAnyRelayOption(const uint16_t option_code,
140  const RelaySearchOrder& order) {
141 
142  if (relay_info_.empty()) {
143  // There's no relay info, this is a direct message
144  return (OptionPtr());
145  }
146 
147  int start = 0; // First relay to check
148  int end = 0; // Last relay to check
149  int direction = 0; // How we going to iterate: forward or backward?
150 
151  prepareGetAnyRelayOption(order, start, end, direction);
152 
153  // This is a tricky loop. It must go from start to end, but it must work in
154  // both directions (start > end; or start < end). We can't use regular
155  // exit condition, because we don't know whether to use i <= end or i >= end.
156  // That's why we check if in the next iteration we would go past the
157  // list (end + direction). It is similar to STL concept of end pointing
158  // to a place after the last element
159  for (int i = start; i != end + direction; i += direction) {
160  OptionPtr opt = getRelayOption(option_code, i);
161  if (opt) {
162  return (opt);
163  }
164  }
165 
166  // We iterated over specified relays and haven't found what we were
167  // looking for
168  return (OptionPtr());
169 }
170 
172 Pkt6::getNonCopiedAllRelayOptions(const uint16_t option_code,
173  const RelaySearchOrder& order) const {
174  if (relay_info_.empty()) {
175  // There's no relay info, this is a direct message
176  return (OptionCollection());
177  }
178 
179  int start = 0; // First relay to check
180  int end = 0; // Last relay to check
181  int direction = 0; // How we going to iterate: forward or backward?
182 
183  prepareGetAnyRelayOption(order, start, end, direction);
184 
185  // This is a tricky loop. It must go from start to end, but it must work in
186  // both directions (start > end; or start < end). We can't use regular
187  // exit condition, because we don't know whether to use i <= end or i >= end.
188  // That's why we check if in the next iteration we would go past the
189  // list (end + direction). It is similar to STL concept of end pointing
190  // to a place after the last element
191  OptionCollection opts;
192  for (int i = start; i != end + direction; i += direction) {
193  std::pair<OptionCollection::const_iterator,
194  OptionCollection::const_iterator> range =
195  relay_info_[i].options_.equal_range(option_code);
196  opts.insert(range.first, range.second);
197  }
198  return (opts);
199 }
200 
202 Pkt6::getAllRelayOptions(const uint16_t option_code,
203  const RelaySearchOrder& order) {
204 
205  if (relay_info_.empty()) {
206  // There's no relay info, this is a direct message
207  return (OptionCollection());
208  }
209 
210  int start = 0; // First relay to check
211  int end = 0; // Last relay to check
212  int direction = 0; // How we going to iterate: forward or backward?
213 
214  prepareGetAnyRelayOption(order, start, end, direction);
215 
216  // This is a tricky loop. It must go from start to end, but it must work in
217  // both directions (start > end; or start < end). We can't use regular
218  // exit condition, because we don't know whether to use i <= end or i >= end.
219  // That's why we check if in the next iteration we would go past the
220  // list (end + direction). It is similar to STL concept of end pointing
221  // to a place after the last element
222  OptionCollection opts;
223  for (int i = start; i != end + direction; i += direction) {
224  std::pair<OptionCollection::iterator,
225  OptionCollection::iterator> range =
226  relay_info_[i].options_.equal_range(option_code);
227  // If options should be copied on retrieval, we should now iterate over
228  // matching options, copy them and replace the original ones with new
229  // instances.
231  for (OptionCollection::iterator opt_it = range.first;
232  opt_it != range.second; ++opt_it) {
233  OptionPtr option_copy = opt_it->second->clone();
234  opt_it->second = option_copy;
235  }
236  }
237  opts.insert(range.first, range.second);
238  }
239  return (opts);
240 }
241 
242 OptionPtr
243 Pkt6::getNonCopiedRelayOption(const uint16_t opt_type,
244  const uint8_t relay_level) const {
245  if (relay_level >= relay_info_.size()) {
246  isc_throw(OutOfRange, "This message was relayed "
247  << relay_info_.size() << " time(s)."
248  << " There is no info about "
249  << relay_level + 1 << " relay.");
250  }
251 
252  OptionCollection::const_iterator x = relay_info_[relay_level].options_.find(opt_type);
253  if (x != relay_info_[relay_level].options_.end()) {
254  return (x->second);
255  }
256 
257  return (OptionPtr());
258 }
259 
260 OptionPtr
261 Pkt6::getRelayOption(const uint16_t opt_type, const uint8_t relay_level) {
262  if (relay_level >= relay_info_.size()) {
263  isc_throw(OutOfRange, "This message was relayed "
264  << relay_info_.size() << " time(s)."
265  << " There is no info about "
266  << relay_level + 1 << " relay.");
267  }
268 
269  OptionCollection::iterator x = relay_info_[relay_level].options_.find(opt_type);
270  if (x != relay_info_[relay_level].options_.end()) {
272  OptionPtr relay_option_copy = x->second->clone();
273  x->second = relay_option_copy;
274  }
275  return (x->second);
276  }
277 
278  return (OptionPtr());
279 }
280 
282 Pkt6::getNonCopiedRelayOptions(const uint16_t opt_type,
283  const uint8_t relay_level) const {
284  if (relay_level >= relay_info_.size()) {
285  isc_throw(OutOfRange, "This message was relayed "
286  << relay_info_.size() << " time(s)."
287  << " There is no info about "
288  << relay_level + 1 << " relay.");
289  }
290 
291  std::pair<OptionCollection::const_iterator,
292  OptionCollection::const_iterator> range =
293  relay_info_[relay_level].options_.equal_range(opt_type);
294  return (OptionCollection(range.first, range.second));
295 }
296 
298 Pkt6::getRelayOptions(const uint16_t opt_type,
299  const uint8_t relay_level) {
300  if (relay_level >= relay_info_.size()) {
301  isc_throw(OutOfRange, "This message was relayed "
302  << relay_info_.size() << " time(s)."
303  << " There is no info about "
304  << relay_level + 1 << " relay.");
305  }
306 
307  OptionCollection options_copy;
308 
309  std::pair<OptionCollection::iterator,
310  OptionCollection::iterator> range =
311  relay_info_[relay_level].options_.equal_range(opt_type);
312  // If options should be copied on retrieval, we should now iterate over
313  // matching options, copy them and replace the original ones with new
314  // instances.
316  for (OptionCollection::iterator opt_it = range.first;
317  opt_it != range.second; ++opt_it) {
318  OptionPtr option_copy = opt_it->second->clone();
319  opt_it->second = option_copy;
320  }
321  }
322  // Finally, return updated options. This can also be empty in some cases.
323  return (OptionCollection(range.first, range.second));
324 }
325 
327 Pkt6::getRelay6LinkAddress(uint8_t relay_level) const {
328  if (relay_level >= relay_info_.size()) {
329  isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
330  << " There is no info about " << relay_level + 1 << " relay.");
331  }
332 
333  return (relay_info_[relay_level].linkaddr_);
334 }
335 
337 Pkt6::getRelay6PeerAddress(uint8_t relay_level) const {
338  if (relay_level >= relay_info_.size()) {
339  isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
340  << " There is no info about " << relay_level + 1 << " relay.");
341  }
342 
343  return (relay_info_[relay_level].peeraddr_);
344 }
345 
346 uint16_t Pkt6::getRelayOverhead(const RelayInfo& relay) const {
347  uint16_t len = DHCPV6_RELAY_HDR_LEN // fixed header
348  + Option::OPTION6_HDR_LEN; // header of the relay-msg option
349 
350  for (const auto& opt : relay.options_) {
351  len += (opt.second)->len();
352  }
353 
354  return (len);
355 }
356 
358 
359  uint16_t len = directLen(); // start with length of all options
360 
361  for (int relay_index = relay_info_.size(); relay_index > 0; --relay_index) {
362  relay_info_[relay_index - 1].relay_msg_len_ = len;
363  len += getRelayOverhead(relay_info_[relay_index - 1]);
364  }
365 
366  return (len);
367 }
368 
369 uint16_t Pkt6::directLen() const {
370  uint16_t length = DHCPV6_PKT_HDR_LEN; // DHCPv6 header
371 
372  for (const auto& it : options_) {
373  length += it.second->len();
374  }
375 
376  return (length);
377 }
378 
379 
380 void
382  switch (proto_) {
383  case UDP:
384  packUDP();
385  break;
386  case TCP:
387  packTCP();
388  break;
389  default:
390  isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)");
391  }
392 }
393 
394 void
396  try {
397  // Make sure that the buffer is empty before we start writing to it.
398  buffer_out_.clear();
399 
400  // is this a relayed packet?
401  if (!relay_info_.empty()) {
402 
403  // calculate size needed for each relay (if there is only one relay,
404  // then it will be equal to "regular" length + relay-forw header +
405  // size of relay-msg option header + possibly size of interface-id
406  // option (if present). If there is more than one relay, the whole
407  // process is called iteratively for each relay.
409 
410  // Now for each relay, we need to...
411  for (vector<RelayInfo>::iterator relay = relay_info_.begin();
412  relay != relay_info_.end(); ++relay) {
413 
414  // build relay-forw/relay-repl header (see RFC 8415, section 9)
415  buffer_out_.writeUint8(relay->msg_type_);
416  buffer_out_.writeUint8(relay->hop_count_);
417  buffer_out_.writeData(&(relay->linkaddr_.toBytes()[0]),
418  isc::asiolink::V6ADDRESS_LEN);
419  buffer_out_.writeData(&relay->peeraddr_.toBytes()[0],
420  isc::asiolink::V6ADDRESS_LEN);
421 
422  // store every option in this relay scope. Usually that will be
423  // only interface-id, but occasionally other options may be
424  // present here as well (vendor-opts for Cable modems,
425  // subscriber-id, remote-id, options echoed back from Echo
426  // Request Option, etc.)
427  for (const auto& opt : relay->options_) {
428  (opt.second)->pack(buffer_out_);
429  }
430 
431  // and include header relay-msg option. Its payload will be
432  // generated in the next iteration (if there are more relays)
433  // or outside the loop (if there are no more relays and the
434  // payload is a direct message)
436  buffer_out_.writeUint16(relay->relay_msg_len_);
437  }
438 
439  }
440 
441  // DHCPv6 header: message-type (1 octet) + transaction id (3 octets)
443  // store 3-octet transaction-id
444  buffer_out_.writeUint8( (transid_ >> 16) & 0xff );
445  buffer_out_.writeUint8( (transid_ >> 8) & 0xff );
446  buffer_out_.writeUint8( (transid_) & 0xff );
447 
448  // the rest are options
450  }
451  catch (const Exception& e) {
452  // An exception is thrown and message will be written to Logger
454  }
455 }
456 
457 void
460  isc_throw(NotImplemented, "DHCPv6 over TCP (bulk leasequery and failover)"
461  " not implemented yet.");
462 }
463 
464 void
466  switch (proto_) {
467  case UDP:
468  return unpackUDP();
469  case TCP:
470  return unpackTCP();
471  default:
472  isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)");
473  }
474 }
475 
476 void
478  if (data_.size() < 4) {
479  isc_throw(BadValue, "Received truncated UDP DHCPv6 packet of size "
480  << data_.size() << ", DHCPv6 header alone has 4 bytes.");
481  }
482  msg_type_ = data_[0];
483  switch (msg_type_) {
484  case DHCPV6_SOLICIT:
485  case DHCPV6_ADVERTISE:
486  case DHCPV6_REQUEST:
487  case DHCPV6_CONFIRM:
488  case DHCPV6_RENEW:
489  case DHCPV6_REBIND:
490  case DHCPV6_REPLY:
491  case DHCPV6_DECLINE:
492  case DHCPV6_RECONFIGURE:
494  case DHCPV6_DHCPV4_QUERY:
496  default: // assume that unknown messages are not using relay format
497  {
498  return (unpackMsg(data_.begin(), data_.end()));
499  }
500  case DHCPV6_RELAY_FORW:
501  case DHCPV6_RELAY_REPL:
502  return (unpackRelayMsg());
503  }
504 }
505 
506 void
507 Pkt6::unpackMsg(OptionBuffer::const_iterator begin,
508  OptionBuffer::const_iterator end) {
509  size_t size = std::distance(begin, end);
510  if (size < 4) {
511  // truncated message (less than 4 bytes)
512  isc_throw(BadValue, "Received truncated UDP DHCPv6 packet of size "
513  << data_.size() << ", DHCPv6 header alone has 4 bytes.");
514  }
515 
516  msg_type_ = *begin++;
517 
518  transid_ = ( (*begin++) << 16 ) +
519  ((*begin++) << 8) + (*begin++);
520  transid_ = transid_ & 0xffffff;
521 
522  // See below about invoking Postel's law, as we aren't using
523  // size we don't need to update it. If we do so in the future
524  // perhaps for stats gathering we can uncomment this.
525  // size -= sizeof(uint32_t); // We just parsed 4 bytes header
526 
527  OptionBuffer opt_buffer(begin, end);
528 
529  // If custom option parsing function has been set, use this function
530  // to parse options. Otherwise, use standard function from libdhcp.
531  size_t offset = LibDHCP::unpackOptions6(opt_buffer, DHCP6_OPTION_SPACE, options_);
532 
533  // If offset is not equal to the size, then something is wrong here. We
534  // either parsed past input buffer (bug in our code) or we haven't parsed
535  // everything (received trailing garbage or truncated option).
536  //
537  // Invoking Jon Postel's law here: be conservative in what you send, and be
538  // liberal in what you accept. There's no easy way to log something from
539  // libdhcp++ library, so we just choose to be silent about remaining
540  // bytes. We also need to quell compiler warning about unused offset
541  // variable.
542  //
543  // if (offset != size) {
544  // isc_throw(BadValue, "Received DHCPv6 buffer of size " << size
545  // << ", were able to parse " << offset << " bytes.");
546  // }
547  (void)offset;
548 }
549 
550 void
552 
553  // we use offset + bufsize, because we want to avoid creating unnecessary
554  // copies. There may be up to 32 relays. While using InputBuffer would
555  // be probably a bit cleaner, copying data up to 32 times is unacceptable
556  // price here. Hence a single buffer with offsets and lengths.
557  size_t bufsize = data_.size();
558  size_t offset = 0;
559 
560  while (bufsize >= DHCPV6_RELAY_HDR_LEN) {
561 
562  RelayInfo relay;
563 
564  size_t relay_msg_offset = 0;
565  size_t relay_msg_len = 0;
566 
567  // parse fixed header first (first 34 bytes)
568  relay.msg_type_ = data_[offset++];
569  relay.hop_count_ = data_[offset++];
570  relay.linkaddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
571  offset += isc::asiolink::V6ADDRESS_LEN;
572  relay.peeraddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
573  offset += isc::asiolink::V6ADDRESS_LEN;
574  bufsize -= DHCPV6_RELAY_HDR_LEN; // 34 bytes (1+1+16+16)
575 
576  // parse the rest as options
577  OptionBuffer opt_buffer(&data_[offset], &data_[offset] + bufsize);
578 
579  // If custom option parsing function has been set, use this function
580  // to parse options. Otherwise, use standard function from libdhcp.
582  &relay_msg_offset, &relay_msg_len);
583 
585  //relay.interface_id_ = options->getOption(D6O_INTERFACE_ID);
586  //relay.subscriber_id_ = options->getOption(D6O_SUBSCRIBER_ID);
587  //relay.remote_id_ = options->getOption(D6O_REMOTE_ID);
588 
589  if (relay_msg_offset == 0 || relay_msg_len == 0) {
590  isc_throw(BadValue, "Mandatory relay-msg option missing");
591  }
592 
593  // store relay information parsed so far
594  addRelayInfo(relay);
595 
597 
598  if (relay_msg_len >= bufsize) {
599  // length of the relay_msg option extends beyond end of the message
600  isc_throw(Unexpected, "Relay-msg option is truncated.");
601  }
602  uint8_t inner_type = data_[offset + relay_msg_offset];
603  offset += relay_msg_offset; // offset is relative
604  bufsize = relay_msg_len; // length is absolute
605 
606  if ( (inner_type != DHCPV6_RELAY_FORW) &&
607  (inner_type != DHCPV6_RELAY_REPL)) {
608  // Ok, the inner message is not encapsulated, let's decode it
609  // directly
610  return (unpackMsg(data_.begin() + offset, data_.begin() + offset
611  + relay_msg_len));
612  }
613 
614  // Oh well, there's inner relay-forw or relay-repl inside. Let's
615  // unpack it as well. The next loop iteration will take care
616  // of that.
617  }
618 
619  if ( (offset == data_.size()) && (bufsize == 0) ) {
620  // message has been parsed completely
621  return;
622  }
623 
625 }
626 
627 void
629  if (relay_info_.size() > HOP_COUNT_LIMIT) {
630  isc_throw(BadValue, "Massage cannot be encapsulated more than 32 times");
631  }
632 
634  relay_info_.push_back(relay);
635 }
636 
637 void
639  isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) "
640  "not implemented yet.");
641 }
642 
643 HWAddrPtr
645  HWAddrPtr mac;
647  if (!opt_duid) {
648  return (mac);
649  }
650 
651  uint8_t hlen = opt_duid->getData().size();
652  if (!hlen) {
653  return (mac);
654  }
655  vector<uint8_t> hw_addr(hlen, 0);
656  std::vector<unsigned char> duid_data = opt_duid->getData();
657 
658  // Read the first two bytes. That duid type.
659  uint16_t duid_type = util::readUint16(&duid_data[0], duid_data.size());
660 
661  switch (duid_type) {
662  case DUID::DUID_LL:
663  {
664  // 2 bytes of duid type, 2 bytes of hardware type and at least
665  // 1 byte of actual identification
666  if (duid_data.size() >= 5) {
667  uint16_t hwtype = util::readUint16(&duid_data[2],
668  duid_data.size() - 2);
669  mac.reset(new HWAddr(&duid_data[4], duid_data.size() - 4, hwtype));
670  }
671  break;
672  }
673  case DUID::DUID_LLT:
674  {
675  // 2 bytes of duid type, 2 bytes of hardware, 4 bytes for timestamp,
676  // and at least 1 byte of actual identification
677  if (duid_data.size() >= 9) {
678  uint16_t hwtype = util::readUint16(&duid_data[2],
679  duid_data.size() - 2);
680  mac.reset(new HWAddr(&duid_data[8], duid_data.size() - 8, hwtype));
681  }
682  break;
683  }
684  default:
685  break;
686  }
687 
688  if (mac) {
689  mac->source_ = HWAddr::HWADDR_SOURCE_DUID;
690  }
691 
692  return (mac);
693 }
694 
695 std::string
696 Pkt6::makeLabel(const DuidPtr duid, const uint32_t transid,
697  const HWAddrPtr& hwaddr) {
698  // Create label with DUID and HW address.
699  std::stringstream label;
700  label << makeLabel(duid, hwaddr);
701 
702  // Append transaction id.
703  label << ", tid=0x" << std::hex << transid << std::dec;
704 
705  return (label.str());
706 }
707 
708 std::string
709 Pkt6::makeLabel(const DuidPtr duid, const HWAddrPtr& hwaddr) {
710  std::stringstream label;
711  // DUID should be present at all times, so explicitly inform when
712  // it is no present (no info).
713  label << "duid=[" << (duid ? duid->toText() : "no info")
714  << "]";
715 
716  // HW address is typically not carried in the DHCPv6 messages
717  // and can be extracted using various, but not fully reliable,
718  // techniques. If it is not present, don't print anything.
719  if (hwaddr) {
720  label << ", [" << hwaddr->toText() << "]";
721  }
722 
723  return (label.str());
724 }
725 
726 std::string
727 Pkt6::getLabel() const {
732  return (makeLabel(getClientId(), getTransid(), HWAddrPtr()));}
733 
734 std::string
735 Pkt6::toText() const {
736  stringstream tmp;
737 
738  // First print the basics
739  tmp << "localAddr=[" << local_addr_ << "]:" << local_port_
740  << " remoteAddr=[" << remote_addr_ << "]:" << remote_port_ << endl;
741  tmp << "msgtype=" << static_cast<int>(msg_type_) << "(" << getName(msg_type_)
742  << "), transid=0x" <<
743  hex << transid_ << dec << endl;
744 
745  // Then print the options
746  for (const auto& opt : options_) {
747  tmp << opt.second->toText() << std::endl;
748  }
749 
750  // Finally, print the relay information (if present)
751  if (!relay_info_.empty()) {
752  tmp << relay_info_.size() << " relay(s):" << endl;
753  int cnt = 0;
754  for (const auto& relay : relay_info_) {
755  tmp << "relay[" << cnt++ << "]: " << relay.toText();
756  }
757  } else {
758  tmp << "No relays traversed." << endl;
759  }
760  return tmp.str();
761 }
762 
763 DuidPtr
766  try {
767  // This will throw if the DUID length is larger than 128 bytes
768  // or is too short.
769  return (opt_duid ? DuidPtr(new DUID(opt_duid->getData())) : DuidPtr());
770  } catch (...) {
771  // Do nothing. This method is used only by getLabel(), which is
772  // used for logging purposes. We should not throw, but rather
773  // report no DUID. We should not log anything, as we're in the
774  // process of logging something for this packet. So the only
775  // choice left is to return an empty pointer.
776  }
777  return (DuidPtr());
778 }
779 
781 Pkt6::getNonCopiedOptions(const uint16_t opt_type) const {
782  std::pair<OptionCollection::const_iterator,
783  OptionCollection::const_iterator> range = options_.equal_range(opt_type);
784  return (OptionCollection(range.first, range.second));
785 }
786 
788 Pkt6::getOptions(const uint16_t opt_type) {
789  OptionCollection options_copy;
790 
791  std::pair<OptionCollection::iterator,
792  OptionCollection::iterator> range = options_.equal_range(opt_type);
793  // If options should be copied on retrieval, we should now iterate over
794  // matching options, copy them and replace the original ones with new
795  // instances.
797  for (OptionCollection::iterator opt_it = range.first;
798  opt_it != range.second; ++opt_it) {
799  OptionPtr option_copy = opt_it->second->clone();
800  opt_it->second = option_copy;
801  }
802  }
803  // Finally, return updated options. This can also be empty in some cases.
804  return (OptionCollection(range.first, range.second));
805 }
806 
807 const char*
808 Pkt6::getName(const uint8_t type) {
809  static const char* ADVERTISE = "ADVERTISE";
810  static const char* CONFIRM = "CONFIRM";
811  static const char* DECLINE = "DECLINE";
812  static const char* INFORMATION_REQUEST = "INFORMATION_REQUEST";
813  static const char* LEASEQUERY = "LEASEQUERY";
814  static const char* LEASEQUERY_REPLY = "LEASEQUERY_REPLY";
815  static const char* REBIND = "REBIND";
816  static const char* RECONFIGURE = "RECONFIGURE";
817  static const char* RELAY_FORW = "RELAY_FORWARD";
818  static const char* RELAY_REPL = "RELAY_REPLY";
819  static const char* RELEASE = "RELEASE";
820  static const char* RENEW = "RENEW";
821  static const char* REPLY = "REPLY";
822  static const char* REQUEST = "REQUEST";
823  static const char* SOLICIT = "SOLICIT";
824  static const char* DHCPV4_QUERY = "DHCPV4_QUERY";
825  static const char* DHCPV4_RESPONSE = "DHCPV4_RESPONSE";
826  static const char* UNKNOWN = "UNKNOWN";
827 
828  switch (type) {
829  case DHCPV6_ADVERTISE:
830  return (ADVERTISE);
831 
832  case DHCPV6_CONFIRM:
833  return (CONFIRM);
834 
835  case DHCPV6_DECLINE:
836  return (DECLINE);
837 
839  return (INFORMATION_REQUEST);
840 
841  case DHCPV6_LEASEQUERY:
842  return (LEASEQUERY);
843 
845  return (LEASEQUERY_REPLY);
846 
847  case DHCPV6_REBIND:
848  return (REBIND);
849 
850  case DHCPV6_RECONFIGURE:
851  return (RECONFIGURE);
852 
853  case DHCPV6_RELAY_FORW:
854  return (RELAY_FORW);
855 
856  case DHCPV6_RELAY_REPL:
857  return (RELAY_REPL);
858 
859  case DHCPV6_RELEASE:
860  return (RELEASE);
861 
862  case DHCPV6_RENEW:
863  return (RENEW);
864 
865  case DHCPV6_REPLY:
866  return (REPLY);
867 
868  case DHCPV6_REQUEST:
869  return (REQUEST);
870 
871  case DHCPV6_SOLICIT:
872  return (SOLICIT);
873 
874  case DHCPV6_DHCPV4_QUERY:
875  return (DHCPV4_QUERY);
876 
878  return (DHCPV4_RESPONSE);
879 
880  default:
881  ;
882  }
883  return (UNKNOWN);
884 }
885 
886 const char* Pkt6::getName() const {
887  return (getName(getType()));
888 }
889 
890 void Pkt6::copyRelayInfo(const Pkt6Ptr& question) {
891 
892  // We use index rather than iterator, because we need that as a parameter
893  // passed to getNonCopiedRelayOption()
894  for (size_t i = 0; i < question->relay_info_.size(); ++i) {
895  RelayInfo info;
897  info.hop_count_ = question->relay_info_[i].hop_count_;
898  info.linkaddr_ = question->relay_info_[i].linkaddr_;
899  info.peeraddr_ = question->relay_info_[i].peeraddr_;
900 
901  // Is there an interface-id option in this nesting level?
902  // If there is, we need to echo it back
903  OptionPtr opt = question->getNonCopiedRelayOption(D6O_INTERFACE_ID, i);
904  // taken from question->RelayInfo_[i].options_
905  if (opt) {
906  info.options_.insert(make_pair(opt->getType(), opt));
907  }
908 
909  // Same for relay-source-port option
910  opt = question->getNonCopiedRelayOption(D6O_RELAY_SOURCE_PORT, i);
911  if (opt) {
912  info.options_.insert(make_pair(opt->getType(), opt));
913  }
914 
916 
917  // Add this relay-forw info (client's message) to our relay-repl
918  // message (server's response)
919  relay_info_.push_back(info);
920  }
921 }
922 
923 HWAddrPtr
925  if (relay_info_.empty()) {
926  // This is a direct message, use source address
927  return (getMACFromIPv6(remote_addr_));
928  }
929 
930  // This is a relayed message, get the peer-addr from the first relay-forw
931  return (getMACFromIPv6(relay_info_[relay_info_.size() - 1].peeraddr_));
932 }
933 
934 HWAddrPtr
936  HWAddrPtr mac;
937 
938  // This is not a direct message
939  if (!relay_info_.empty()) {
940  // RFC6969 Section 6: Look for the client_linklayer_addr option on the
941  // relay agent closest to the client
944  if (opt) {
945  const OptionBuffer data = opt->getData();
946  // This client link address option is supposed to be
947  // 2 bytes of link-layer type followed by link-layer address.
948  if (data.size() >= 3) {
949  // +2, -2 means to skip the initial 2 bytes which are
950  // hwaddress type
951  mac.reset(new HWAddr(&data[0] + 2, data.size() - 2,
952  opt->getUint16()));
953 
955  }
956  }
957  }
958 
959  return mac;
960 }
961 
962 HWAddrPtr
964  HWAddrPtr mac;
965  OptionVendorPtr vendor;
966  for (auto opt : getNonCopiedOptions(D6O_VENDOR_OPTS)) {
967  if (opt.first != D6O_VENDOR_OPTS) {
968  continue;
969  }
970  vendor = boost::dynamic_pointer_cast< OptionVendor>(opt.second);
971  // Check if this is indeed DOCSIS3 environment
972  if (!vendor || vendor->getVendorId() != VENDOR_ID_CABLE_LABS) {
973  continue;
974  }
975  // If it is, try to get device-id option
976  OptionPtr device_id = vendor->getOption(DOCSIS3_V6_DEVICE_ID);
977  if (device_id) {
978  // If the option contains any data, use it as MAC address
979  if (!device_id->getData().empty()) {
980  mac.reset(new HWAddr(device_id->getData(), HTYPE_DOCSIS));
981  mac->source_ = HWAddr::HWADDR_SOURCE_DOCSIS_MODEM;
982  break;
983  }
984  }
985  }
986 
987  return mac;
988 }
989 
990 HWAddrPtr
992  if (relay_info_.empty()) {
993  return (HWAddrPtr());
994  }
995 
996  // If the message passed through a CMTS, there'll
997  // CMTS-specific options in it.
998  HWAddrPtr mac;
999  OptionVendorPtr vendor;
1000  for (auto opt : getAllRelayOptions(D6O_VENDOR_OPTS,
1002  if (opt.first != D6O_VENDOR_OPTS) {
1003  continue;
1004  }
1005  vendor = boost::dynamic_pointer_cast< OptionVendor>(opt.second);
1006  // Check if this is indeed DOCSIS3 environment
1007  if (!vendor || vendor->getVendorId() != VENDOR_ID_CABLE_LABS) {
1008  continue;
1009  }
1010  // Try to get cable modem mac
1011  OptionPtr cm_mac = vendor->getOption(DOCSIS3_V6_CMTS_CM_MAC);
1012 
1013  // If the option contains any data, use it as MAC address
1014  if (cm_mac && !cm_mac->getData().empty()) {
1015  mac.reset(new HWAddr(cm_mac->getData(), HTYPE_DOCSIS));
1016  mac->source_ = HWAddr::HWADDR_SOURCE_DOCSIS_CMTS;
1017  break;
1018  }
1019  }
1020 
1021  return (mac);
1022 }
1023 
1024 HWAddrPtr
1026  HWAddrPtr mac;
1027 
1028  // If this is relayed message
1029  if (!relay_info_.empty()) {
1030  // Get remote-id option from a relay agent closest to the client
1032  if (opt) {
1033  const OptionBuffer data = opt->getData();
1034  // This remote-id option is supposed to be 4 bytes of
1035  // of enterprise-number followed by remote-id.
1036  if (data.size() >= 5) {
1037  // Let's get the interface this packet was received on.
1038  // We need it to get the hardware type.
1040  uint16_t hwtype = 0; // not specified
1041 
1042  // If we get the interface HW type, great! If not,
1043  // let's not panic.
1044  if (iface) {
1045  hwtype = iface->getHWType();
1046  }
1047 
1048  size_t len = data.size() - 4;
1049 
1050  if (len > HWAddr::MAX_HWADDR_LEN) {
1051  len = HWAddr::MAX_HWADDR_LEN;
1052  }
1053 
1054  // Skip the initial 4 bytes which are enterprise-number.
1055  mac.reset(new HWAddr(&data[0] + 4, len, hwtype));
1056  mac->source_ = HWAddr::HWADDR_SOURCE_REMOTE_ID;
1057  }
1058  }
1059  }
1060 
1061  return (mac);
1062 }
1063 
1064 } // end of namespace isc::dhcp
1065 } // end of namespace isc
#define DOCSIS3_V6_CMTS_CM_MAC
IfacePtr getIface(int ifindex)
Returns interface specified interface index.
Definition: iface_mgr.cc:902
boost::shared_ptr< DUID > DuidPtr
Definition: duid.h:20
isc::asiolink::IOAddress local_addr_
Local IP (v4 or v6) address.
Definition: pkt.h:776
virtual std::string toText() const
Returns text representation of the packet.
Definition: pkt6.cc:735
HWAddrPtr getMACFromIPv6(const isc::asiolink::IOAddress &addr)
Attempts to convert IPv6 address into MAC.
Definition: pkt.cc:235
OptionBuffer data_
Unparsed data (in received packets).
Definition: pkt.h:334
A generic exception that is thrown when a function is not implemented.
const isc::asiolink::IOAddress & getRelay6LinkAddress(uint8_t relay_level) const
return the link address field from a relay option
Definition: pkt6.cc:327
std::string iface_
Name of the network interface the packet was received/to be sent over.
Definition: pkt.h:763
static const uint32_t HWADDR_SOURCE_DOCSIS_MODEM
A cable modem (acting as DHCP client) that supports DOCSIS standard can insert DOCSIS options that co...
Definition: hwaddr.h:79
static void packOptions6(isc::util::OutputBuffer &buf, const isc::dhcp::OptionCollection &options)
Stores DHCPv6 options in a buffer.
Definition: libdhcp++.cc:1070
boost::shared_ptr< HWAddr > HWAddrPtr
Shared pointer to a hardware address structure.
Definition: hwaddr.h:154
isc::dhcp::OptionCollection options_
options received from a specified relay, except relay-msg option
Definition: pkt6.h:104
OptionCollection getRelayOptions(uint16_t option_code, uint8_t nesting_level)
Returns options inserted by relay.
Definition: pkt6.cc:298
link-layer + time, see RFC3315, section 11.2
Definition: duid.h:40
void packUDP()
Builds on wire packet for UDP transmission.
Definition: pkt6.cc:395
static const size_t MAX_HWADDR_LEN
Maximum size of a hardware address.
Definition: hwaddr.h:27
uint32_t transid_
Transaction-id (32 bits for v4, 24 bits for v6)
Definition: pkt.h:760
boost::shared_ptr< Iface > IfacePtr
Type definition for the pointer to an Iface object.
Definition: iface_mgr.h:487
Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto=UDP)
Constructor, used in replying to a message.
Definition: pkt6.cc:58
const IOAddress DEFAULT_ADDRESS6("::")
Default address used in Pkt6 constructor.
Base class for classes representing DHCP messages.
Definition: pkt.h:90
void copyRelayInfo(const Pkt6Ptr &question)
copies relay information from client&#39;s packet to server&#39;s response
Definition: pkt6.cc:890
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
OptionPtr getNonCopiedAnyRelayOption(const uint16_t option_code, const RelaySearchOrder &order) const
Returns pointer to an instance of specified option.
Definition: pkt6.cc:107
void unpackRelayMsg()
Unpacks relayed message (RELAY-FORW or RELAY-REPL).
Definition: pkt6.cc:551
STL namespace.
#define DOCSIS3_V6_DEVICE_ID
OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level)
Returns option inserted by relay.
Definition: pkt6.cc:261
OptionCollection getNonCopiedRelayOptions(const uint16_t opt_type, const uint8_t relay_level) const
Returns all option instances inserted by relay agent.
Definition: pkt6.cc:282
virtual HWAddrPtr getMACFromDocsisCMTS()
Attempts to extract MAC/Hardware address from DOCSIS options.
Definition: pkt6.cc:991
virtual std::string getLabel() const
Returns text representation of the primary packet identifiers.
Definition: pkt6.cc:727
virtual HWAddrPtr getMACFromSrcLinkLocalAddr()
Attempts to generate MAC/Hardware address from IPv6 link-local address.
Definition: pkt6.cc:924
static const uint32_t HWADDR_SOURCE_REMOTE_ID
A relay can insert remote-id.
Definition: hwaddr.h:63
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
const isc::asiolink::IOAddress & getRelay6PeerAddress(uint8_t relay_level) const
return the peer address field from a relay option
Definition: pkt6.cc:337
std::vector< RelayInfo > relay_info_
Relay information.
Definition: pkt6.h:500
std::vector< uint8_t > OptionBuffer
buffer types used in DHCP code.
Definition: option.h:24
The traffic captures we have from cable modems as well as this list by IANA: http://www.iana.org/assignments/ arp-parameters/arp-parameters.xhtml suggest that Ethernet (1) should be used in DOCSIS environment.
Definition: dhcp4.h:57
void unpackUDP()
Parses on-wire form of UDP DHCPv6 packet.
Definition: pkt6.cc:477
Holds DUID (DHCPv6 Unique Identifier)
Definition: duid.h:27
static const uint32_t HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION
Get it from RFC6939 option.
Definition: hwaddr.h:59
static size_t unpackOptions6(const OptionBuffer &buf, const std::string &option_space, isc::dhcp::OptionCollection &options, size_t *relay_msg_offset=0, size_t *relay_msg_len=0)
Parses provided buffer as DHCPv6 options and creates Option objects.
Definition: libdhcp++.cc:309
void writeData(const void *data, size_t len)
Copy an arbitrary length of data into the buffer.
Definition: buffer.h:550
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition: pkt6.h:28
structure that describes a single relay information
Definition: pkt6.h:85
static const size_t DHCPV6_RELAY_HDR_LEN
specifies relay DHCPv6 packet header length (over UDP)
Definition: pkt6.h:50
A generic exception that is thrown when an unexpected error condition occurs.
virtual void unpack()
Dispatch method that handles binary packet parsing.
Definition: pkt6.cc:465
virtual size_t len()
Returns length of the packet.
Definition: pkt6.cc:63
const char * getName() const
Returns name of the DHCPv6 message.
Definition: pkt6.cc:886
OptionPtr getOption(uint16_t type) const
Returns shared_ptr to suboption of specific type.
Definition: option.cc:199
virtual HWAddrPtr getMACFromDocsisModem()
Attempts to extract MAC/Hardware address from DOCSIS options inserted by the modem itself...
Definition: pkt6.cc:963
uint8_t msg_type_
DHCPv6 message type.
Definition: pkt6.h:647
void clear()
Clear buffer content.
Definition: buffer.h:451
DHCPv6Proto
DHCPv6 transport protocol.
Definition: pkt6.h:53
std::multimap< unsigned int, OptionPtr > OptionCollection
A collection of DHCP (v4 or v6) options.
Definition: option.h:40
static const size_t DHCPV6_PKT_HDR_LEN
specifies non-relayed DHCPv6 packet header length (over UDP)
Definition: pkt6.h:47
virtual void pack()
Prepares on-wire format.
Definition: pkt6.cc:381
link-layer, see RFC3315, section 11.4
Definition: duid.h:42
virtual HWAddrPtr getMACFromIPv6RelayOpt()
Extract MAC/Hardware address from client link-layer address.
Definition: pkt6.cc:935
OptionCollection getNonCopiedAllRelayOptions(const uint16_t option_code, const RelaySearchOrder &order) const
Returns pointers to instances of specified option.
Definition: pkt6.cc:172
bool copy_retrieved_options_
Indicates if a copy of the retrieved option should be returned when Pkt::getOption is called...
Definition: pkt.h:804
uint16_t remote_port_
remote TCP or UDP port
Definition: pkt.h:788
static const uint32_t HWADDR_SOURCE_DOCSIS_CMTS
A CMTS (acting as DHCP relay agent) that supports DOCSIS standard can insert DOCSIS options that cont...
Definition: hwaddr.h:73
void packTCP()
Builds on wire packet for TCP transmission.
Definition: pkt6.cc:458
This is a base class for exceptions thrown from the DNS library module.
Defines the logger used by the top-level component of kea-lfc.
static std::string makeLabel(const DuidPtr duid, const uint32_t transid, const HWAddrPtr &hwaddr)
Returns text representation of the given packet identifiers.
Definition: pkt6.cc:696
void unpackMsg(OptionBuffer::const_iterator begin, OptionBuffer::const_iterator end)
Unpacks direct (non-relayed) message.
Definition: pkt6.cc:507
RelaySearchOrder
defines relay search pattern
Definition: pkt6.h:74
uint16_t getRelayOverhead(const RelayInfo &relay) const
Calculates overhead introduced in specified relay.
Definition: pkt6.cc:346
uint16_t local_port_
local TDP or UDP port
Definition: pkt.h:785
#define HOP_COUNT_LIMIT
Definition: dhcp6.h:329
uint8_t hop_count_
number of traversed relays (up to 32)
Definition: pkt6.h:95
std::string toText() const
Returns printable representation of the relay information.
Definition: pkt6.cc:40
OptionPtr getAnyRelayOption(const uint16_t option_code, const RelaySearchOrder &order)
Return first instance of a specified option.
Definition: pkt6.cc:139
OptionCollection getAllRelayOptions(const uint16_t option_code, const RelaySearchOrder &order)
Return first instances of a specified option.
Definition: pkt6.cc:202
uint16_t readUint16(const void *buffer, size_t length)
Read Unsigned 16-Bit Integer from Buffer.
Definition: io_utilities.h:28
isc::asiolink::IOAddress remote_addr_
Remote IP address.
Definition: pkt.h:782
#define DHCP6_OPTION_SPACE
OptionPtr getNonCopiedOption(const uint16_t type) const
Returns the first option of specified type without copying.
Definition: pkt.cc:46
uint16_t directLen() const
Calculates size of the message as if it was not relayed at all.
Definition: pkt6.cc:369
void writeUint8(uint8_t data)
Write an unsigned 8-bit integer into the buffer.
Definition: buffer.h:466
A generic exception that is thrown if a function is called in a prohibited way.
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition: iface_mgr.cc:53
OptionPtr getNonCopiedRelayOption(const uint16_t opt_type, const uint8_t relay_level) const
Returns pointer to an option inserted by relay agent.
Definition: pkt6.cc:243
Hardware type that represents information from DHCPv4 packet.
Definition: hwaddr.h:20
void writeUint16(uint16_t data)
Write an unsigned 16-bit integer in host byte order into the buffer in network byte order...
Definition: buffer.h:490
virtual uint8_t getType() const
Returns message type (e.g.
Definition: pkt6.h:220
OptionCollection getNonCopiedOptions(const uint16_t opt_type) const
Returns all option instances of specified type without copying.
Definition: pkt6.cc:781
isc::dhcp::OptionCollection getOptions(const uint16_t type)
Returns all instances of specified type.
Definition: pkt6.cc:788
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
virtual HWAddrPtr getMACFromDUID()
Extract MAC/Hardware address from client-id.
Definition: pkt6.cc:644
isc::asiolink::IOAddress linkaddr_
fixed field in relay-forw/relay-reply
Definition: pkt6.h:96
DHCPv6Proto proto_
UDP (usually) or TCP (bulk leasequery or failover)
Definition: pkt6.h:644
DuidPtr getClientId() const
Retrieves the DUID from the Client Identifier option.
Definition: pkt6.cc:764
isc::asiolink::IOAddress peeraddr_
fixed field in relay-forw/relay-reply
Definition: pkt6.h:97
void unpackTCP()
Parses on-wire form of TCP DHCPv6 packet.
Definition: pkt6.cc:638
isc::util::OutputBuffer buffer_out_
Output buffer (used during message transmission)
Definition: pkt.h:798
uint16_t calculateRelaySizes()
Calculates overhead for all relays defined for this message.
Definition: pkt6.cc:357
boost::shared_ptr< OptionVendor > OptionVendorPtr
Pointer to a vendor option.
uint32_t getTransid() const
Returns value of transaction-id field.
Definition: pkt.h:270
#define VENDOR_ID_CABLE_LABS
void addRelayInfo(const RelayInfo &relay)
add information about one traversed relay
Definition: pkt6.cc:628
isc::dhcp::OptionCollection options_
Collection of options present in this message.
Definition: pkt.h:648
static const uint32_t HWADDR_SOURCE_DUID
Extracted from DUID-LL or DUID-LLT (not 100% reliable as the client can send fake DUID)...
Definition: hwaddr.h:48
This class represents vendor-specific information option.
Definition: option_vendor.h:30
virtual HWAddrPtr getMACFromRemoteIdRelayOption()
Attempts to obtain MAC address from remote-id relay option.
Definition: pkt6.cc:1025
static const size_t OPTION6_HDR_LEN
length of any DHCPv6 option header
Definition: option.h:80
uint8_t msg_type_
message type (RELAY-FORW oro RELAY-REPL)
Definition: pkt6.h:94