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