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