Kea 2.7.5
duid_factory.cc
Go to the documentation of this file.
1// Copyright (C) 2015-2024 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.h>
14#include <util/str.h>
15#include <ctime>
16#include <fstream>
17#include <stdlib.h>
18#include <string>
19#include <vector>
20
21using namespace isc::util;
22using namespace isc::util::str;
23
24namespace {
25
27const size_t DUID_TYPE_LEN = 2;
28
30const size_t MIN_MAC_LEN = 6;
31
33const size_t ENTERPRISE_ID_LEN = 4;
34
36const size_t DUID_EN_IDENTIFIER_LEN = 6;
37
38}
39
40namespace isc {
41namespace dhcp {
42
43DUIDFactory::DUIDFactory(const std::string& storage_location)
44 : storage_location_(trim(storage_location)), duid_() {
45}
46
47bool
49 return (!storage_location_.empty());
50}
51
52void
53DUIDFactory::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
120void
121DUIDFactory::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], DUID_TYPE_LEN);
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
187void
188DUIDFactory::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
243void
244DUIDFactory::createLinkLayerId(std::vector<uint8_t>& identifier,
245 uint16_t& htype) const {
246 // Let's find suitable interface.
247 for (auto const& 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
303void
304DUIDFactory::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
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
377void
378DUIDFactory::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} // end of isc::dhcp namespace
409} // 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.
DUIDFactory(const std::string &storage_location="")
Constructor.
void createLLT(const uint16_t htype, const uint32_t time_in, const std::vector< uint8_t > &ll_identifier)
Generates DUID-LLT.
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 constexpr size_t MIN_DUID_LEN
minimum duid size
Definition duid.h:149
static DUID fromText(const std::string &text)
Create DUID from the textual format.
Definition duid.cc:50
@ DUID_LL
link-layer, see RFC3315, section 11.4
Definition duid.h:162
@ DUID_LLT
link-layer + time, see RFC3315, section 11.2
Definition duid.h:160
@ DUID_EN
enterprise-id, see RFC3315, section 11.3
Definition duid.h:161
Handles network interfaces, transmission and reception.
Definition iface_mgr.h:656
#define DUID_TIME_EPOCH
Definition dhcp6.h:337
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
boost::shared_ptr< DUID > DuidPtr
Definition duid.h:136
@ HTYPE_ETHER
Ethernet 10Mbps.
Definition dhcp4.h:56
string trim(const string &input)
Trim leading and trailing spaces.
Definition str.cc:32
void fillRandom(Iterator begin, Iterator end)
Fill in specified range with a random data.
uint8_t * writeUint32(uint32_t const value, void *const buffer, size_t const length)
uint32_t wrapper over writeUint.
Definition io.h:100
uint16_t readUint16(void const *const buffer, size_t const length)
uint16_t wrapper over readUint.
Definition io.h:76
bool isRangeZero(Iterator begin, Iterator end)
Checks if specified range in a container contains only zeros.
uint8_t * writeUint16(uint16_t const value, void *const buffer, size_t const length)
uint16_t wrapper over writeUint.
Definition io.h:94
uint32_t readUint32(void const *const buffer, size_t const length)
uint32_t wrapper over readUint.
Definition io.h:82
Defines the logger used by the top-level component of kea-lfc.