Kea  1.5.0
duid_factory.cc
Go to the documentation of this file.
1 // Copyright (C) 2015-2017 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/duid_factory.h>
10 #include <dhcp/iface_mgr.h>
11 #include <exceptions/exceptions.h>
12 #include <util/io_utilities.h>
13 #include <util/range_utilities.h>
14 #include <util/strutil.h>
15 #include <boost/foreach.hpp>
16 #include <ctime>
17 #include <fstream>
18 #include <stdlib.h>
19 #include <string>
20 #include <vector>
21 
22 using namespace isc::util;
23 using namespace isc::util::str;
24 
25 namespace {
26 
28 const size_t DUID_TYPE_LEN = 2;
29 
31 const size_t MIN_MAC_LEN = 6;
32 
34 const size_t ENTERPRISE_ID_LEN = 4;
35 
37 const size_t DUID_EN_IDENTIFIER_LEN = 6;
38 
39 }
40 
41 namespace isc {
42 namespace dhcp {
43 
44 DUIDFactory::DUIDFactory(const std::string& storage_location)
45  : storage_location_(trim(storage_location)), duid_() {
46 }
47 
48 bool
50  return (!storage_location_.empty());
51 }
52 
53 void
54 DUIDFactory::createLLT(const uint16_t htype, const uint32_t time_in,
55  const std::vector<uint8_t>& ll_identifier) {
56  // We'll need DUID stored in the file to compare it against the
57  // new configuration. If the new configuration indicates that some
58  // bits of the DUID should be generated we'll first try to use the
59  // values stored in the file to prevent DUID from changing if possible.
60  readFromFile();
61 
62  uint16_t htype_current = 0;
63  uint32_t time_current = 0;
64  std::vector<uint8_t> identifier_current;
65 
66  // If DUID exists in the file, try to use it as much as possible.
67  if (duid_) {
68  std::vector<uint8_t> duid_vec = duid_->getDuid();
69  if ((duid_->getType() == DUID::DUID_LLT) && (duid_vec.size() > 8)) {
70  htype_current = readUint16(&duid_vec[2], duid_vec.size() - 2);
71  time_current = readUint32(&duid_vec[4], duid_vec.size() - 4);
72  identifier_current.assign(duid_vec.begin() + 8, duid_vec.end());
73  }
74  }
75 
76  uint32_t time_out = time_in;
77  // If time is unspecified (ANY), then use the time from current DUID or
78  // set it to current time.
79  if (time_out == 0) {
80  time_out = (time_current != 0 ? time_current :
81  static_cast<uint32_t>(time(NULL) - DUID_TIME_EPOCH));
82  }
83 
84  std::vector<uint8_t> ll_identifier_out = ll_identifier;
85  uint16_t htype_out = htype;
86 
87  // If link layer address unspecified, use address of one of the
88  // interfaces present in the system. Also, update the link
89  // layer type accordingly.
90  if (ll_identifier_out.empty()) {
91  // If DUID doesn't exist yet, generate a new identifier.
92  if (identifier_current.empty()) {
93  createLinkLayerId(ll_identifier_out, htype_out);
94  } else {
95  // Use current identifier and hardware type.
96  ll_identifier_out = identifier_current;
97  htype_out = htype_current;
98  }
99 
100  } else if (htype_out == 0) {
101  // If link layer type unspecified and link layer address
102  // is specified, use current type or HTYPE_ETHER.
103  htype_out = ((htype_current != 0) ? htype_current :
104  static_cast<uint16_t>(HTYPE_ETHER));
105 
106  }
107 
108  // Render DUID.
109  std::vector<uint8_t> duid_out(DUID_TYPE_LEN + sizeof(time_out) +
110  sizeof(htype_out));
111  writeUint16(DUID::DUID_LLT, &duid_out[0], 2);
112  writeUint16(htype_out, &duid_out[2], 2);
113  writeUint32(time_out, &duid_out[4], 4);
114  duid_out.insert(duid_out.end(), ll_identifier_out.begin(),
115  ll_identifier_out.end());
116 
117  // Set new DUID and persist in a file.
118  set(duid_out);
119 }
120 
121 void
122 DUIDFactory::createEN(const uint32_t enterprise_id,
123  const std::vector<uint8_t>& identifier) {
124  // We'll need DUID stored in the file to compare it against the
125  // new configuration. If the new configuration indicates that some
126  // bits of the DUID should be generated we'll first try to use the
127  // values stored in the file to prvent DUID from changing if possible.
128  readFromFile();
129 
130  uint32_t enterprise_id_current = 0;
131  std::vector<uint8_t> identifier_current;
132 
133  // If DUID exists in the file, try to use it as much as possible.
134  if (duid_) {
135  std::vector<uint8_t> duid_vec = duid_->getDuid();
136  if ((duid_->getType() == DUID::DUID_EN) && (duid_vec.size() > 6)) {
137  enterprise_id_current = readUint32(&duid_vec[2], duid_vec.size() - 2);
138  identifier_current.assign(duid_vec.begin() + 6, duid_vec.end());
139  }
140  }
141 
142  // Enterprise id 0 means "unspecified". In this case, try to use existing
143  // DUID's enterprise id, or use ISC enterprise id.
144  uint32_t enterprise_id_out = enterprise_id;
145  if (enterprise_id_out == 0) {
146  if (enterprise_id_current != 0) {
147  enterprise_id_out = enterprise_id_current;
148  } else {
149  enterprise_id_out = ENTERPRISE_ID_ISC;
150  }
151  }
152 
153  // Render DUID.
154  std::vector<uint8_t> duid_out(DUID_TYPE_LEN + ENTERPRISE_ID_LEN);
155  writeUint16(DUID::DUID_EN, &duid_out[0], 2);
156  writeUint32(enterprise_id_out, &duid_out[2], ENTERPRISE_ID_LEN);
157 
158  // If no identifier specified, we'll have to use the one from the
159  // DUID file or generate new.
160  if (identifier.empty()) {
161  // No DUID file, so generate new.
162  if (identifier_current.empty()) {
163  // Identifier is empty, so we have to extend the DUID by 6 bytes
164  // to fit the random identifier.
165  duid_out.resize(DUID_TYPE_LEN + ENTERPRISE_ID_LEN +
166  DUID_EN_IDENTIFIER_LEN);
167  // Variable length identifier consists of random numbers. The generated
168  // identifier is always 6 bytes long.
169  ::srandom(time(NULL));
170  fillRandom(&duid_out[DUID_TYPE_LEN + ENTERPRISE_ID_LEN],
171  &duid_out[DUID_TYPE_LEN + ENTERPRISE_ID_LEN +
172  DUID_EN_IDENTIFIER_LEN]);
173  } else {
174  // Append existing identifier.
175  duid_out.insert(duid_out.end(), identifier_current.begin(),
176  identifier_current.end());
177  }
178 
179  } else {
180  // Append the specified identifier to the end of DUID.
181  duid_out.insert(duid_out.end(), identifier.begin(), identifier.end());
182  }
183 
184  // Set new DUID and persist in a file.
185  set(duid_out);
186 }
187 
188 void
189 DUIDFactory::createLL(const uint16_t htype,
190  const std::vector<uint8_t>& ll_identifier) {
191  // We'll need DUID stored in the file to compare it against the
192  // new configuration. If the new configuration indicates that some
193  // bits of the DUID should be generated we'll first try to use the
194  // values stored in the file to prvent DUID from changing if possible.
195  readFromFile();
196 
197  uint16_t htype_current = 0;
198  std::vector<uint8_t> identifier_current;
199 
200  // If DUID exists in the file, try to use it as much as possible.
201  if (duid_) {
202  std::vector<uint8_t> duid_vec = duid_->getDuid();
203  if ((duid_->getType() == DUID::DUID_LL) && (duid_vec.size() > 4)) {
204  htype_current = readUint16(&duid_vec[2], duid_vec.size() - 2);
205  identifier_current.assign(duid_vec.begin() + 4, duid_vec.end());
206  }
207  }
208 
209  std::vector<uint8_t> ll_identifier_out = ll_identifier;
210  uint16_t htype_out = htype;
211 
212  // If link layer address unspecified, use address of one of the
213  // interfaces present in the system. Also, update the link
214  // layer type accordingly.
215  if (ll_identifier_out.empty()) {
216  // If DUID doesn't exist yet, generate a new identifier.
217  if (identifier_current.empty()) {
218  createLinkLayerId(ll_identifier_out, htype_out);
219  } else {
220  // Use current identifier and hardware type.
221  ll_identifier_out = identifier_current;
222  htype_out = htype_current;
223  }
224 
225  } else if (htype_out == 0) {
226  // If link layer type unspecified and link layer address
227  // is specified, use current type or HTYPE_ETHER.
228  htype_out = ((htype_current != 0) ? htype_current :
229  static_cast<uint16_t>(HTYPE_ETHER));
230 
231  }
232 
233  // Render DUID.
234  std::vector<uint8_t> duid_out(DUID_TYPE_LEN + sizeof(htype_out));
235  writeUint16(DUID::DUID_LL, &duid_out[0], 2);
236  writeUint16(htype_out, &duid_out[2], 2);
237  duid_out.insert(duid_out.end(), ll_identifier_out.begin(),
238  ll_identifier_out.end());
239 
240  // Set new DUID and persist in a file.
241  set(duid_out);
242 }
243 
244 void
245 DUIDFactory::createLinkLayerId(std::vector<uint8_t>& identifier,
246  uint16_t& htype) const {
248 
249  // Let's find suitable interface.
250  BOOST_FOREACH(IfacePtr iface, ifaces) {
251  // All the following checks could be merged into one multi-condition
252  // statement, but let's keep them separated as perhaps one day
253  // we will grow knobs to selectively turn them on or off. Also,
254  // this code is used only *once* during first start on a new machine
255  // and then server-id is stored. (or at least it will be once
256  // DUID storage is implemented)
257 
258  // I wish there was a this_is_a_real_physical_interface flag...
259 
260  // MAC address should be at least 6 bytes. Although there is no such
261  // requirement in any RFC, all decent physical interfaces (Ethernet,
262  // WiFi, InfiniBand, etc.) have at least 6 bytes long MAC address.
263  // We want to/ base our DUID on real hardware address, rather than
264  // virtual interface that pretends that underlying IP address is its
265  // MAC.
266  if (iface->getMacLen() < MIN_MAC_LEN) {
267  continue;
268  }
269 
270  // Let's don't use loopback.
271  if (iface->flag_loopback_) {
272  continue;
273  }
274 
275  // Let's skip downed interfaces. It is better to use working ones.
276  if (!iface->flag_up_) {
277  continue;
278  }
279 
280  // Some interfaces (like lo on Linux) report 6-bytes long
281  // MAC address 00:00:00:00:00:00. Let's not use such weird interfaces
282  // to generate DUID.
283  if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) {
284  continue;
285  }
286 
287  // Assign link layer address and type.
288  identifier.assign(iface->getMac(), iface->getMac() + iface->getMacLen());
289  htype = iface->getHWType();
290 
291  // If it looks like an Ethernet interface we should be happy
292  if ((htype == static_cast<uint16_t>(HTYPE_ETHER)) &&
293  (iface->getMacLen() == 6)) {
294  break;
295  }
296  }
297 
298  // We failed to find an interface which link layer address could be
299  // used for generating DUID-LLT.
300  if (identifier.empty()) {
301  isc_throw(Unexpected, "unable to find suitable interface for "
302  " generating a DUID-LLT");
303  }
304 }
305 
306 void
307 DUIDFactory::set(const std::vector<uint8_t>& duid_vector) {
308  // Check the minimal length.
309  if (duid_vector.size() < DUID::MIN_DUID_LEN) {
310  isc_throw(BadValue, "generated DUID must have at least "
311  << DUID::MIN_DUID_LEN << " bytes");
312  }
313 
314  // Store DUID in a file if file location specified.
315  if (isStored()) {
316  std::ofstream ofs;
317  try {
318  ofs.open(storage_location_.c_str(), std::ofstream::out |
319  std::ofstream::trunc);
320  if (!ofs.good()) {
321  isc_throw(InvalidOperation, "unable to open DUID file "
322  << storage_location_ << " for writing");
323  }
324 
325  // Create temporary DUID object.
326  DUID duid(duid_vector);
327 
328  // Write DUID to file.
329  ofs << duid.toText();
330  if (!ofs.good()) {
331  isc_throw(InvalidOperation, "unable to write to DUID file "
332  << storage_location_);
333  }
334  } catch (...) {
335  // Close stream before leaving the function.
336  ofs.close();
337  throw;
338  }
339  ofs.close();
340  }
341 
342  duid_.reset(new DUID(duid_vector));
343 }
344 
345 DuidPtr
347  // If DUID is initialized, return it.
348  if (duid_) {
349  return (duid_);
350  }
351 
352  // Try to read DUID from file, if it exists.
353  readFromFile();
354  if (duid_) {
355  return (duid_);
356  }
357 
358  // DUID doesn't exist, so we need to create it.
359  const std::vector<uint8_t> empty_vector;
360  try {
361  // There is no file with a DUID or the DUID stored in the file is
362  // invalid. We need to generate a new DUID.
363  createLLT(0, 0, empty_vector);
364 
365  } catch (...) {
366  // It is possible that the creation of the DUID-LLT failed if there
367  // are no suitable interfaces present in the system.
368  }
369 
370  if (!duid_) {
371  // Fall back to creation of DUID enterprise. If that fails we allow
372  // for propagating exception to indicate a fatal error. This may
373  // be the case if we failed to write it to a file.
374  createEN(0, empty_vector);
375  }
376 
377  return (duid_);
378 }
379 
380 void
381 DUIDFactory::readFromFile() {
382  duid_.reset();
383 
384  std::ostringstream duid_str;
385  if (isStored()) {
386  std::ifstream ifs;
387  ifs.open(storage_location_.c_str(), std::ifstream::in);
388  if (ifs.good()) {
389  std::string read_contents;
390  while (!ifs.eof() && ifs.good()) {
391  ifs >> read_contents;
392  duid_str << read_contents;
393  }
394  }
395  ifs.close();
396 
397  // If we have read anything from the file, let's try to use it to
398  // create a DUID.
399  if (duid_str.tellp() != std::streampos(0)) {
400  try {
401  duid_.reset(new DUID(DUID::fromText(duid_str.str())));
402 
403  } catch (...) {
404  // The contents of this file don't represent a valid DUID.
405  // We'll need to generate it.
406  }
407  }
408  }
409 }
410 
411 
412 }; // end of isc::dhcp namespace
413 }; // end of isc namespace
iface_mgr.h
isc::dhcp::DUID::DUID_EN
@ DUID_EN
enterprise-id, see RFC3315, section 11.3
Definition: duid.h:41
isc::dhcp::DuidPtr
boost::shared_ptr< DUID > DuidPtr
Definition: duid.h:20
isc::util::readUint32
uint32_t readUint32(const uint8_t *buffer, size_t length)
Read Unsigned 32-Bit Integer from Buffer.
Definition: io_utilities.h:79
isc::util::str::trim
string trim(const string &instring)
Trim Leading and Trailing Spaces.
Definition: strutil.cc:53
isc::dhcp::DUID::fromText
static DUID fromText(const std::string &text)
Create DUID from the textual format.
Definition: duid.cc:61
isc::dhcp::DUIDFactory::createEN
void createEN(const uint32_t enterprise_id, const std::vector< uint8_t > &identifier)
Generates DUID-EN.
Definition: duid_factory.cc:122
isc::util::writeUint16
uint8_t * writeUint16(uint16_t value, void *buffer, size_t length)
Write Unsigned 16-Bit Integer to Buffer.
Definition: io_utilities.h:55
isc::dhcp::DUIDFactory::createLL
void createLL(const uint16_t htype, const std::vector< uint8_t > &ll_identifier)
Generates DUID-LL.
Definition: duid_factory.cc:189
isc::util
Definition: edns.h:19
isc
Defines the logger used by the top-level component of kea-dhcp-ddns.
Definition: agent_parser.cc:144
strutil.h
isc_throw
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Definition: exceptions/exceptions.h:192
isc::dhcp::DUID::DUID_LLT
@ DUID_LLT
link-layer + time, see RFC3315, section 11.2
Definition: duid.h:40
isc::dhcp::DUID::MIN_DUID_LEN
static const size_t MIN_DUID_LEN
minimum duid size The minimal DUID size specified in RFC 8415 is 1.
Definition: duid.h:35
isc::dhcp::IfaceMgr::instance
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition: iface_mgr.cc:53
isc::dhcp::IfaceMgr::getIfaces
const IfaceCollection & getIfaces()
Returns container with all interfaces.
Definition: iface_mgr.h:597
DUID_TIME_EPOCH
#define DUID_TIME_EPOCH
Definition: dhcp6.h:354
isc::util::readUint16
uint16_t readUint16(const void *buffer, size_t length)
Read Unsigned 16-Bit Integer from Buffer.
Definition: io_utilities.h:28
isc::util::writeUint32
uint8_t * writeUint32(uint32_t value, uint8_t *buffer, size_t length)
Write Unsigned 32-Bit Integer to Buffer.
Definition: io_utilities.h:136
isc::util::str
Definition: strutil.cc:36
isc::dhcp::DUIDFactory::get
DuidPtr get()
Returns current DUID.
Definition: duid_factory.cc:346
isc::dhcp::DUIDFactory::isStored
bool isStored() const
Checks if generated DUID will be stored in the file.
Definition: duid_factory.cc:49
range_utilities.h
io_utilities.h
isc::dhcp::HTYPE_ETHER
@ HTYPE_ETHER
Ethernet 10Mbps.
Definition: dhcp4.h:56
exceptions.h
isc::util::fillRandom
void fillRandom(Iterator begin, Iterator end)
Fill in specified range with a random data.
Definition: range_utilities.h:53
isc::dhcp::IfaceMgr::IfaceCollection
std::list< IfacePtr > IfaceCollection
Type that holds a list of pointers to interfaces.
Definition: iface_mgr.h:509
isc::dhcp::DUID::DUID_LL
@ DUID_LL
link-layer, see RFC3315, section 11.4
Definition: duid.h:42
duid_factory.h
isc::dhcp::DUIDFactory::createLLT
void createLLT(const uint16_t htype, const uint32_t time_in, const std::vector< uint8_t > &ll_identifier)
Generates DUID-LLT.
Definition: duid_factory.cc:54
isc::util::isRangeZero
bool isRangeZero(Iterator begin, Iterator end)
Checks if specified range in a container contains only zeros.
Definition: range_utilities.h:29
isc::dhcp::IfacePtr
boost::shared_ptr< Iface > IfacePtr
Definition: iface_mgr.h:457