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>
12#include <util/io_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
22using namespace isc::util;
23using namespace isc::util::str;
24
25namespace {
26
28const size_t DUID_TYPE_LEN = 2;
29
31const size_t MIN_MAC_LEN = 6;
32
34const size_t ENTERPRISE_ID_LEN = 4;
35
37const size_t DUID_EN_IDENTIFIER_LEN = 6;
38
39}
40
41namespace isc {
42namespace dhcp {
43
44DUIDFactory::DUIDFactory(const std::string& storage_location)
45 : storage_location_(trim(storage_location)), duid_() {
46}
47
48bool
50 return (!storage_location_.empty());
51}
52
53void
54DUIDFactory::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
121void
122DUIDFactory::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
188void
189DUIDFactory::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
244void
245DUIDFactory::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
306void
307DUIDFactory::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
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
380void
381DUIDFactory::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
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
A generic exception that is thrown if a function is called in a prohibited way.
A generic exception that is thrown when an unexpected error condition occurs.
bool isStored() const
Checks if generated DUID will be stored in the file.
Definition: duid_factory.cc:49
DUIDFactory(const std::string &storage_location="")
Constructor.
Definition: duid_factory.cc:44
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
void createEN(const uint32_t enterprise_id, const std::vector< uint8_t > &identifier)
Generates DUID-EN.
DuidPtr get()
Returns current DUID.
void createLL(const uint16_t htype, const std::vector< uint8_t > &ll_identifier)
Generates DUID-LL.
static const size_t MIN_DUID_LEN
minimum duid size The minimal DUID size specified in RFC 8415 is 1.
Definition: duid.h:35
static DUID fromText(const std::string &text)
Create DUID from the textual format.
Definition: duid.cc:61
@ DUID_LL
link-layer, see RFC3315, section 11.4
Definition: duid.h:42
@ DUID_LLT
link-layer + time, see RFC3315, section 11.2
Definition: duid.h:40
@ DUID_EN
enterprise-id, see RFC3315, section 11.3
Definition: duid.h:41
const IfaceCollection & getIfaces()
Returns container with all interfaces.
Definition: iface_mgr.h:597
std::list< IfacePtr > IfaceCollection
Type that holds a list of pointers to interfaces.
Definition: iface_mgr.h:509
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition: iface_mgr.cc:53
#define DUID_TIME_EPOCH
Definition: dhcp6.h:354
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
boost::shared_ptr< Iface > IfacePtr
Definition: iface_mgr.h:457
boost::shared_ptr< DUID > DuidPtr
Definition: duid.h:21
@ HTYPE_ETHER
Ethernet 10Mbps.
Definition: dhcp4.h:56
string trim(const string &instring)
Trim Leading and Trailing Spaces.
Definition: strutil.cc:53
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
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 fillRandom(Iterator begin, Iterator end)
Fill in specified range with a random data.
bool isRangeZero(Iterator begin, Iterator end)
Checks if specified range in a container contains only zeros.
uint32_t readUint32(const uint8_t *buffer, size_t length)
Read Unsigned 32-Bit Integer from Buffer.
Definition: io_utilities.h:79
uint16_t readUint16(const void *buffer, size_t length)
Read Unsigned 16-Bit Integer from Buffer.
Definition: io_utilities.h:28
Defines the logger used by the top-level component of kea-dhcp-ddns.