1 // Copyright (C) 2011-2020 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 #include <asiolink/io_address.h>
9 #include <dhcp/dhcp4.h>
10 #include <dhcp/libdhcp++.h>
11 #include <dhcp/option_int.h>
12 #include <dhcp/pkt4.h>
13 #include <exceptions/exceptions.h>
14
15 #include <algorithm>
16 #include <iostream>
17 #include <sstream>
18
19 using namespace std;
20 using namespace isc::dhcp;
21 using namespace isc::asiolink;
22
23 namespace {
24
25 /// @brief Default address used in Pkt4 constructor
26 const IOAddress DEFAULT_ADDRESS("0.0.0.0");
27 }
28
29 namespace isc {
30 namespace dhcp {
31
Pkt4(uint8_t msg_type,uint32_t transid)32 Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
33 :Pkt(transid, DEFAULT_ADDRESS, DEFAULT_ADDRESS, DHCP4_SERVER_PORT,
34 DHCP4_CLIENT_PORT),
35 op_(DHCPTypeToBootpType(msg_type)),
36 hwaddr_(new HWAddr()),
37 hops_(0),
38 secs_(0),
39 flags_(0),
40 ciaddr_(DEFAULT_ADDRESS),
41 yiaddr_(DEFAULT_ADDRESS),
42 siaddr_(DEFAULT_ADDRESS),
43 giaddr_(DEFAULT_ADDRESS)
44 {
45 memset(sname_, 0, MAX_SNAME_LEN);
46 memset(file_, 0, MAX_FILE_LEN);
47
48 setType(msg_type);
49 }
50
Pkt4(const uint8_t * data,size_t len)51 Pkt4::Pkt4(const uint8_t* data, size_t len)
52 :Pkt(data, len, DEFAULT_ADDRESS, DEFAULT_ADDRESS, DHCP4_SERVER_PORT,
53 DHCP4_CLIENT_PORT),
54 op_(BOOTREQUEST),
55 hwaddr_(new HWAddr()),
56 hops_(0),
57 secs_(0),
58 flags_(0),
59 ciaddr_(DEFAULT_ADDRESS),
60 yiaddr_(DEFAULT_ADDRESS),
61 siaddr_(DEFAULT_ADDRESS),
62 giaddr_(DEFAULT_ADDRESS)
63 {
64
65 if (len < DHCPV4_PKT_HDR_LEN) {
66 isc_throw(OutOfRange, "Truncated DHCPv4 packet (len=" << len
67 << ") received, at least " << DHCPV4_PKT_HDR_LEN
68 << " is expected.");
69 }
70 memset(sname_, 0, MAX_SNAME_LEN);
71 memset(file_, 0, MAX_FILE_LEN);
72 }
73
74 size_t
len()75 Pkt4::len() {
76 size_t length = DHCPV4_PKT_HDR_LEN; // DHCPv4 header
77
78 // ... and sum of lengths of all options
79 for (OptionCollection::const_iterator it = options_.begin();
80 it != options_.end();
81 ++it) {
82 length += (*it).second->len();
83 }
84
85 return (length);
86 }
87
88 void
pack()89 Pkt4::pack() {
90 if (!hwaddr_) {
91 isc_throw(InvalidOperation, "Can't build Pkt4 packet. HWAddr not set.");
92 }
93
94 // Clear the output buffer to make sure that consecutive calls to pack()
95 // will not result in concatenation of multiple packet copies.
96 buffer_out_.clear();
97
98 try {
99 size_t hw_len = hwaddr_->hwaddr_.size();
100
101 buffer_out_.writeUint8(op_);
102 buffer_out_.writeUint8(hwaddr_->htype_);
103 buffer_out_.writeUint8(hw_len < MAX_CHADDR_LEN ?
104 hw_len : MAX_CHADDR_LEN);
105 buffer_out_.writeUint8(hops_);
106 buffer_out_.writeUint32(transid_);
107 buffer_out_.writeUint16(secs_);
108 buffer_out_.writeUint16(flags_);
109 buffer_out_.writeUint32(ciaddr_.toUint32());
110 buffer_out_.writeUint32(yiaddr_.toUint32());
111 buffer_out_.writeUint32(siaddr_.toUint32());
112 buffer_out_.writeUint32(giaddr_.toUint32());
113
114
115 if ((hw_len > 0) && (hw_len <= MAX_CHADDR_LEN)) {
116 // write up to 16 bytes of the hardware address (CHADDR field is 16
117 // bytes long in DHCPv4 message).
118 buffer_out_.writeData(&hwaddr_->hwaddr_[0],
119 (hw_len < MAX_CHADDR_LEN ?
120 hw_len : MAX_CHADDR_LEN) );
121 hw_len = MAX_CHADDR_LEN - hw_len;
122 } else {
123 hw_len = MAX_CHADDR_LEN;
124 }
125
126 // write (len) bytes of padding
127 if (hw_len > 0) {
128 vector<uint8_t> zeros(hw_len, 0);
129 buffer_out_.writeData(&zeros[0], hw_len);
130 }
131
132 buffer_out_.writeData(sname_, MAX_SNAME_LEN);
133 buffer_out_.writeData(file_, MAX_FILE_LEN);
134
135 // write DHCP magic cookie
136 buffer_out_.writeUint32(DHCP_OPTIONS_COOKIE);
137
138 // Call packOptions4() with parameter,"top", true. This invokes
139 // logic to emit the message type option first.
140 LibDHCP::packOptions4(buffer_out_, options_, true);
141
142 // add END option that indicates end of options
143 // (End option is very simple, just a 255 octet)
144 buffer_out_.writeUint8(DHO_END);
145 } catch(const Exception& e) {
146 // An exception is thrown and message will be written to Logger
147 isc_throw(InvalidOperation, e.what());
148 }
149 }
150
151 void
unpack()152 Pkt4::unpack() {
153
154 // input buffer (used during message reception)
155 isc::util::InputBuffer buffer_in(&data_[0], data_.size());
156
157 if (buffer_in.getLength() < DHCPV4_PKT_HDR_LEN) {
158 isc_throw(OutOfRange, "Received truncated DHCPv4 packet (len="
159 << buffer_in.getLength() << " received, at least "
160 << DHCPV4_PKT_HDR_LEN << "is expected");
161 }
162
163 op_ = buffer_in.readUint8();
164 uint8_t htype = buffer_in.readUint8();
165 uint8_t hlen = buffer_in.readUint8();
166 hops_ = buffer_in.readUint8();
167 transid_ = buffer_in.readUint32();
168 secs_ = buffer_in.readUint16();
169 flags_ = buffer_in.readUint16();
170 ciaddr_ = IOAddress(buffer_in.readUint32());
171 yiaddr_ = IOAddress(buffer_in.readUint32());
172 siaddr_ = IOAddress(buffer_in.readUint32());
173 giaddr_ = IOAddress(buffer_in.readUint32());
174
175 vector<uint8_t> hw_addr(MAX_CHADDR_LEN, 0);
176 buffer_in.readVector(hw_addr, MAX_CHADDR_LEN);
177 buffer_in.readData(sname_, MAX_SNAME_LEN);
178 buffer_in.readData(file_, MAX_FILE_LEN);
179
180 hw_addr.resize(hlen);
181
182 hwaddr_ = HWAddrPtr(new HWAddr(hw_addr, htype));
183
184 if (buffer_in.getLength() == buffer_in.getPosition()) {
185 // this is *NOT* DHCP packet. It does not have any DHCPv4 options. In
186 // particular, it does not have magic cookie, a 4 byte sequence that
187 // differentiates between DHCP and RFC 951 BOOTP packets.
188 isc_throw(InvalidOperation, "Received BOOTP packet without vendor information extensions.");
189 }
190
191 if (buffer_in.getLength() - buffer_in.getPosition() < 4) {
192 // there is not enough data to hold magic DHCP cookie
193 isc_throw(Unexpected, "Truncated or no DHCP packet.");
194 }
195
196 uint32_t magic = buffer_in.readUint32();
197 if (magic != DHCP_OPTIONS_COOKIE) {
198 isc_throw(Unexpected, "Invalid or missing DHCP magic cookie");
199 }
200
201 size_t opts_len = buffer_in.getLength() - buffer_in.getPosition();
202 vector<uint8_t> opts_buffer;
203
204 // Use readVector because a function which parses option requires
205 // a vector as an input.
206 buffer_in.readVector(opts_buffer, opts_len);
207
208 size_t offset = LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE, options_, deferred_options_, false);
209
210 // If offset is not equal to the size and there is no DHO_END,
211 // then something is wrong here. We either parsed past input
212 // buffer (bug in our code) or we haven't parsed everything
213 // (received trailing garbage or truncated option).
214 //
215 // Invoking Jon Postel's law here: be conservative in what you send, and be
216 // liberal in what you accept. There's no easy way to log something from
217 // libdhcp++ library, so we just choose to be silent about remaining
218 // bytes. We also need to quell compiler warning about unused offset
219 // variable.
220 //
221 // if ((offset != size) && (opts_buffer[offset] != DHO_END)) {
222 // isc_throw(BadValue, "Received DHCPv6 buffer of size " << size
223 // << ", were able to parse " << offset << " bytes.");
224 // }
225 (void)offset;
226
227 // No need to call check() here. There are thorough tests for this
228 // later (see Dhcp4Srv::accept()). We want to drop the packet later,
229 // so we'll be able to log more detailed drop reason.
230 }
231
getType() const232 uint8_t Pkt4::getType() const {
233 OptionPtr generic = getNonCopiedOption(DHO_DHCP_MESSAGE_TYPE);
234 if (!generic) {
235 return (DHCP_NOTYPE);
236 }
237
238 // Check if Message Type is specified as OptionInt<uint8_t>
239 boost::shared_ptr<OptionInt<uint8_t> > type_opt =
240 boost::dynamic_pointer_cast<OptionInt<uint8_t> >(generic);
241 if (type_opt) {
242 return (type_opt->getValue());
243 }
244
245 // Try to use it as generic option
246 return (generic->getUint8());
247 }
248
setType(uint8_t dhcp_type)249 void Pkt4::setType(uint8_t dhcp_type) {
250 OptionPtr opt = getNonCopiedOption(DHO_DHCP_MESSAGE_TYPE);
251 if (opt) {
252
253 // There is message type option already, update it. It seems that
254 // we do have two types of objects representing message-type option.
255 // It would be more preferable to use only one type, but there's no
256 // easy way to enforce it.
257 //
258 // One is an instance of the Option class. It stores type in
259 // Option::data_, so Option::setUint8() and Option::getUint8() can be
260 // used. The other one is an instance of OptionInt<uint8_t> and
261 // it stores message type as integer, hence
262 // OptionInt<uint8_t>::getValue() and OptionInt<uint8_t>::setValue()
263 // should be used.
264 boost::shared_ptr<OptionInt<uint8_t> > type_opt =
265 boost::dynamic_pointer_cast<OptionInt<uint8_t> >(opt);
266 if (type_opt) {
267 type_opt->setValue(dhcp_type);
268 } else {
269 opt->setUint8(dhcp_type);
270 }
271 } else {
272 // There is no message type option yet, add it
273 opt.reset(new OptionInt<uint8_t>(Option::V4, DHO_DHCP_MESSAGE_TYPE,
274 dhcp_type));
275 addOption(opt);
276 }
277 }
278
279 const char*
getName(const uint8_t type)280 Pkt4::getName(const uint8_t type) {
281 static const char* DHCPDISCOVER_NAME = "DHCPDISCOVER";
282 static const char* DHCPOFFER_NAME = "DHCPOFFER";
283 static const char* DHCPREQUEST_NAME = "DHCPREQUEST";
284 static const char* DHCPDECLINE_NAME = "DHCPDECLINE";
285 static const char* DHCPACK_NAME = "DHCPACK";
286 static const char* DHCPNAK_NAME = "DHCPNAK";
287 static const char* DHCPRELEASE_NAME = "DHCPRELEASE";
288 static const char* DHCPINFORM_NAME = "DHCPINFORM";
289 static const char* DHCPLEASEQUERY_NAME = "DHCPLEASEQUERY";
290 static const char* DHCPLEASEUNASSIGNED_NAME = "DHCPLEASEUNASSIGNED";
291 static const char* DHCPLEASEUNKNOWN_NAME = "DHCPLEASEUNKNOWN";
292 static const char* DHCPLEASEACTIVE_NAME = "DHCPLEASEACTIVE";
293 static const char* DHCPBULKLEASEQUERY_NAME = "DHCPBULKLEASEQUERY";
294 static const char* DHCPLEASEQUERYDONE_NAME = "DHCPLEASEQUERYDONE";
295 static const char* DHCPLEASEQUERYSTATUS_NAME = "DHCPLEASEQUERYSTATUS";
296 static const char* DHCPTLS_NAME = "DHCPTLS";
297 static const char* UNKNOWN_NAME = "UNKNOWN";
298
299 switch (type) {
300 case DHCPDISCOVER:
301 return (DHCPDISCOVER_NAME);
302
303 case DHCPOFFER:
304 return (DHCPOFFER_NAME);
305
306 case DHCPREQUEST:
307 return (DHCPREQUEST_NAME);
308
309 case DHCPDECLINE:
310 return (DHCPDECLINE_NAME);
311
312 case DHCPACK:
313 return (DHCPACK_NAME);
314
315 case DHCPNAK:
316 return (DHCPNAK_NAME);
317
318 case DHCPRELEASE:
319 return (DHCPRELEASE_NAME);
320
321 case DHCPINFORM:
322 return (DHCPINFORM_NAME);
323
324 case DHCPLEASEQUERY:
325 return (DHCPLEASEQUERY_NAME);
326
327 case DHCPLEASEUNASSIGNED:
328 return (DHCPLEASEUNASSIGNED_NAME);
329
330 case DHCPLEASEUNKNOWN:
331 return (DHCPLEASEUNKNOWN_NAME);
332
333 case DHCPLEASEACTIVE:
334 return (DHCPLEASEACTIVE_NAME);
335
336 case DHCPBULKLEASEQUERY:
337 return (DHCPBULKLEASEQUERY_NAME);
338
339 case DHCPLEASEQUERYDONE:
340 return (DHCPLEASEQUERYDONE_NAME);
341
342 case DHCPLEASEQUERYSTATUS:
343 return (DHCPLEASEQUERYSTATUS_NAME);
344
345 case DHCPTLS:
346 return (DHCPTLS_NAME);
347
348 default:
349 ;
350 }
351 return (UNKNOWN_NAME);
352 }
353
354 const char*
getName() const355 Pkt4::getName() const {
356 // getType() is now exception safe. Even if there's no option 53 (message
357 // type), it now returns 0 rather than throw. getName() is able to handle
358 // 0 and unknown message types.
359 return (Pkt4::getName(getType()));
360 }
361
362 std::string
getLabel() const363 Pkt4::getLabel() const {
364
365 /// @todo If and when client id is extracted into Pkt4, this method should
366 /// use the instance member rather than fetch it every time.
367 std::string suffix;
368 ClientIdPtr client_id;
369 OptionPtr client_opt = getNonCopiedOption(DHO_DHCP_CLIENT_IDENTIFIER);
370 if (client_opt) {
371 try {
372 client_id = ClientIdPtr(new ClientId(client_opt->getData()));
373 } catch (...) {
374 // ClientId may throw if the client-id is too short.
375 suffix = " (malformed client-id)";
376 }
377 }
378
379 std::ostringstream label;
380 try {
381 label << makeLabel(hwaddr_, client_id, transid_);
382 } catch (...) {
383 // This should not happen with the current code, but we may add extra
384 // sanity checks in the future that would possibly throw if
385 // the hwaddr length is 0.
386 label << " (malformed hw address)";
387 }
388
389 label << suffix;
390 return (label.str());
391 }
392
393 std::string
makeLabel(const HWAddrPtr & hwaddr,const ClientIdPtr & client_id,const uint32_t transid)394 Pkt4::makeLabel(const HWAddrPtr& hwaddr, const ClientIdPtr& client_id,
395 const uint32_t transid) {
396 // Create label with HW address and client identifier.
397 stringstream label;
398 label << makeLabel(hwaddr, client_id);
399
400 // Append transaction id.
401 label << ", tid=0x" << hex << transid << dec;
402
403 return label.str();
404 }
405
406 std::string
makeLabel(const HWAddrPtr & hwaddr,const ClientIdPtr & client_id)407 Pkt4::makeLabel(const HWAddrPtr& hwaddr, const ClientIdPtr& client_id) {
408 stringstream label;
409 label << "[" << (hwaddr ? hwaddr->toText() : "no hwaddr info")
410 << "], cid=[" << (client_id ? client_id->toText() : "no info")
411 << "]";
412
413 return label.str();
414 }
415
416 std::string
toText() const417 Pkt4::toText() const {
418 stringstream output;
419 output << "local_address=" << local_addr_ << ":" << local_port_
420 << ", remote_address=" << remote_addr_
421 << ":" << remote_port_ << ", msg_type=";
422
423 // Try to obtain message type.
424 uint8_t msg_type = getType();
425 if (msg_type != DHCP_NOTYPE) {
426 output << getName(msg_type) << " (" << static_cast<int>(msg_type) << ")";
427 } else {
428 // Message Type option is missing.
429 output << "(missing)";
430 }
431
432 output << ", transid=0x" << hex << transid_ << dec;
433
434 if (!options_.empty()) {
435 output << "," << std::endl << "options:";
436 for (isc::dhcp::OptionCollection::const_iterator opt = options_.begin();
437 opt != options_.end(); ++opt) {
438 try {
439 output << std::endl << opt->second->toText(2);
440 } catch (...) {
441 output << "(unknown)" << std::endl;
442 }
443 }
444
445 } else {
446 output << ", message contains no options";
447 }
448
449 return (output.str());
450 }
451
452 void
setHWAddr(uint8_t htype,uint8_t hlen,const std::vector<uint8_t> & mac_addr)453 Pkt4::setHWAddr(uint8_t htype, uint8_t hlen,
454 const std::vector<uint8_t>& mac_addr) {
455 setHWAddrMember(htype, hlen, mac_addr, hwaddr_);
456 }
457
458 void
setHWAddr(const HWAddrPtr & addr)459 Pkt4::setHWAddr(const HWAddrPtr& addr) {
460 if (!addr) {
461 isc_throw(BadValue, "Setting DHCPv4 chaddr field to NULL"
462 << " is forbidden");
463 }
464 hwaddr_ = addr;
465 }
466
467 void
setHWAddrMember(const uint8_t htype,const uint8_t hlen,const std::vector<uint8_t> & mac_addr,HWAddrPtr & hw_addr)468 Pkt4::setHWAddrMember(const uint8_t htype, const uint8_t hlen,
469 const std::vector<uint8_t>& mac_addr,
470 HWAddrPtr& hw_addr) {
471 /// @todo Rewrite this once support for client-identifier option
472 /// is implemented (ticket 1228?)
473 if (hlen > MAX_CHADDR_LEN) {
474 isc_throw(OutOfRange, "Hardware address (len=" << hlen
475 << " too long. Max " << MAX_CHADDR_LEN << " supported.");
476
477 } else if (mac_addr.empty() && (hlen > 0) ) {
478 isc_throw(OutOfRange, "Invalid HW Address specified");
479 }
480
481 /// @todo: what if mac_addr.size() doesn't match hlen?
482 /// We would happily copy over hardware address that is possibly
483 /// too long or doesn't match hlen value.
484 hw_addr.reset(new HWAddr(mac_addr, htype));
485 }
486
487 void
setLocalHWAddr(const uint8_t htype,const uint8_t hlen,const std::vector<uint8_t> & mac_addr)488 Pkt4::setLocalHWAddr(const uint8_t htype, const uint8_t hlen,
489 const std::vector<uint8_t>& mac_addr) {
490 setHWAddrMember(htype, hlen, mac_addr, local_hwaddr_);
491 }
492
493 void
setLocalHWAddr(const HWAddrPtr & addr)494 Pkt4::setLocalHWAddr(const HWAddrPtr& addr) {
495 if (!addr) {
496 isc_throw(BadValue, "Setting local HW address to NULL is"
497 << " forbidden.");
498 }
499 local_hwaddr_ = addr;
500 }
501
502 void
setSname(const uint8_t * sname,size_t snameLen)503 Pkt4::setSname(const uint8_t* sname, size_t snameLen /*= MAX_SNAME_LEN*/) {
504 if (snameLen > MAX_SNAME_LEN) {
505 isc_throw(OutOfRange, "sname field (len=" << snameLen
506 << ") too long, Max " << MAX_SNAME_LEN << " supported.");
507
508 } else if (sname == NULL) {
509 isc_throw(InvalidParameter, "Invalid sname specified");
510 }
511
512 std::copy(sname, (sname + snameLen), sname_);
513 if (snameLen < MAX_SNAME_LEN) {
514 std::fill((sname_ + snameLen), (sname_ + MAX_SNAME_LEN), 0);
515 }
516
517 // No need to store snameLen as any empty space is filled with 0s
518 }
519
520 void
setFile(const uint8_t * file,size_t fileLen)521 Pkt4::setFile(const uint8_t* file, size_t fileLen /*= MAX_FILE_LEN*/) {
522 if (fileLen > MAX_FILE_LEN) {
523 isc_throw(OutOfRange, "file field (len=" << fileLen
524 << ") too long, Max " << MAX_FILE_LEN << " supported.");
525
526 } else if (file == NULL) {
527 isc_throw(InvalidParameter, "Invalid file name specified");
528 }
529
530 std::copy(file, (file + fileLen), file_);
531 if (fileLen < MAX_FILE_LEN) {
532 std::fill((file_ + fileLen), (file_ + MAX_FILE_LEN), 0);
533 }
534
535 // No need to store fileLen as any empty space is filled with 0s
536 }
537
538 uint8_t
539 // cppcheck-suppress unusedFunction
DHCPTypeToBootpType(uint8_t dhcpType)540 Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
541 switch (dhcpType) {
542 case DHCPDISCOVER:
543 case DHCPREQUEST:
544 case DHCPDECLINE:
545 case DHCPRELEASE:
546 case DHCPINFORM:
547 case DHCPLEASEQUERY:
548 case DHCPBULKLEASEQUERY:
549 return (BOOTREQUEST);
550
551 case DHCPACK:
552 case DHCPNAK:
553 case DHCPOFFER:
554 case DHCPLEASEUNASSIGNED:
555 case DHCPLEASEUNKNOWN:
556 case DHCPLEASEACTIVE:
557 case DHCPLEASEQUERYDONE:
558 return (BOOTREPLY);
559
560 default:
561 isc_throw(OutOfRange, "Invalid message type: "
562 << static_cast<int>(dhcpType) );
563 }
564 }
565
566 uint8_t
getHtype() const567 Pkt4::getHtype() const {
568 if (!hwaddr_) {
569 return (HTYPE_UNDEFINED);
570 }
571 return (hwaddr_->htype_);
572 }
573
574 uint8_t
getHlen() const575 Pkt4::getHlen() const {
576 if (!hwaddr_) {
577 return (0);
578 }
579 uint8_t len = hwaddr_->hwaddr_.size();
580 return (len <= MAX_CHADDR_LEN ? len : MAX_CHADDR_LEN);
581 }
582
583 void
addOption(const OptionPtr & opt)584 Pkt4::addOption(const OptionPtr& opt) {
585 // Check for uniqueness (DHCPv4 options must be unique)
586 if (getNonCopiedOption(opt->getType())) {
587 isc_throw(BadValue, "Option " << opt->getType()
588 << " already present in this message.");
589 }
590
591 Pkt::addOption(opt);
592 }
593
594 bool
isRelayed() const595 Pkt4::isRelayed() const {
596 return (!giaddr_.isV4Zero() && !giaddr_.isV4Bcast());
597 }
598
599 } // end of namespace isc::dhcp
600
601 } // end of namespace isc
602