1 //
2 // Copyright (C) 2015 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 #include "dhcp_client/dhcp_message.h"
18 
19 #include <net/if.h>
20 #include <net/if_arp.h>
21 #include <netinet/in.h>
22 
23 #include <memory>
24 #include <set>
25 #include <string>
26 #include <utility>
27 #include <vector>
28 
29 #include <base/logging.h>
30 
31 #include "dhcp_client/dhcp_options.h"
32 #include "dhcp_client/dhcp_options_writer.h"
33 
34 using shill::ByteString;
35 
36 namespace dhcp_client {
37 
38 namespace {
39 const int kClientHardwareAddressLength = 16;
40 const int kServerNameLength = 64;
41 const int kBootFileLength = 128;
42 const uint32_t kMagicCookie = 0x63825363;
43 const size_t kDHCPMessageMaxLength = 548;
44 const size_t kDHCPMessageMinLength = 236;
45 const uint8_t kDHCPMessageBootRequest = 1;
46 const uint8_t kDHCPMessageBootReply = 2;
47 
48 // Follow the naming in rfc2131 for this struct.
49 struct __attribute__((__packed__)) RawDHCPMessage {
50   uint8_t op;
51   uint8_t htype;
52   uint8_t hlen;
53   uint8_t hops;
54   uint32_t xid;
55   uint16_t secs;
56   uint16_t flags;
57   uint32_t ciaddr;
58   uint32_t yiaddr;
59   uint32_t siaddr;
60   uint32_t giaddr;
61   uint8_t chaddr[kClientHardwareAddressLength];
62   uint8_t sname[kServerNameLength];
63   uint8_t file[kBootFileLength];
64   uint32_t cookie;
65   uint8_t options[kDHCPOptionLength];
66 };
67 }  // namespace
68 
DHCPMessage()69 DHCPMessage::DHCPMessage()
70     : requested_ip_address_(0),
71       lease_time_(0),
72       message_type_(0),
73       server_identifier_(0),
74       renewal_time_(0),
75       rebinding_time_(0) {
76   options_map_.insert(std::make_pair(kDHCPOptionMessageType,
77       ParserContext(new UInt8Parser(), &message_type_)));
78   options_map_.insert(std::make_pair(kDHCPOptionLeaseTime,
79       ParserContext(new UInt32Parser(), &lease_time_)));
80   options_map_.insert(std::make_pair(kDHCPOptionMessage,
81       ParserContext(new StringParser(), &error_message_)));
82   options_map_.insert(std::make_pair(kDHCPOptionSubnetMask,
83       ParserContext(new UInt32Parser(), &subnet_mask_)));
84   options_map_.insert(std::make_pair(kDHCPOptionServerIdentifier,
85       ParserContext(new UInt32Parser(), &server_identifier_)));
86   options_map_.insert(std::make_pair(kDHCPOptionRenewalTime,
87       ParserContext(new UInt32Parser(), &renewal_time_)));
88   options_map_.insert(std::make_pair(kDHCPOptionRebindingTime,
89       ParserContext(new UInt32Parser(), &rebinding_time_)));
90   options_map_.insert(std::make_pair(kDHCPOptionDNSServer,
91       ParserContext(new UInt32ListParser(), &dns_server_)));
92   options_map_.insert(std::make_pair(kDHCPOptionRouter,
93       ParserContext(new UInt32ListParser(), &router_)));
94   options_map_.insert(std::make_pair(kDHCPOptionDomainName,
95       ParserContext(new StringParser(), &domain_name_)));
96   options_map_.insert(std::make_pair(kDHCPOptionVendorSpecificInformation,
97       ParserContext(new ByteArrayParser(), &vendor_specific_info_)));
98 }
99 
~DHCPMessage()100 DHCPMessage::~DHCPMessage() {}
101 
InitFromBuffer(const unsigned char * buffer,size_t length,DHCPMessage * message)102 bool DHCPMessage::InitFromBuffer(const unsigned char* buffer,
103                                  size_t length,
104                                  DHCPMessage* message) {
105   if (buffer == NULL) {
106     LOG(ERROR) << "Invalid buffer address";
107     return false;
108   }
109   if (length < kDHCPMessageMinLength || length > kDHCPMessageMaxLength) {
110     LOG(ERROR) << "Invalid DHCP message length";
111     return false;
112   }
113   const RawDHCPMessage* raw_message
114       = reinterpret_cast<const RawDHCPMessage*>(buffer);
115   size_t options_length = reinterpret_cast<const unsigned char*>(raw_message) +
116       length - reinterpret_cast<const unsigned char*>(raw_message->options) + 1;
117   message->opcode_ = raw_message->op;
118   message->hardware_address_type_ = raw_message->htype;
119   message->hardware_address_length_ = raw_message->hlen;
120   if (message->hardware_address_length_ > kClientHardwareAddressLength) {
121     LOG(ERROR) << "Invalid hardware address length";
122     return false;
123   }
124   message->relay_hops_ = raw_message->hops;
125   message->transaction_id_ = ntohl(raw_message->xid);
126   message->seconds_ = ntohs(raw_message->secs);
127   message->flags_ = ntohs(raw_message->flags);
128   message->client_ip_address_ = ntohl(raw_message->ciaddr);
129   message->your_ip_address_ = ntohl(raw_message->yiaddr);
130   message->next_server_ip_address_ = ntohl(raw_message->siaddr);
131   message->agent_ip_address_ = ntohl(raw_message->giaddr);
132   message->cookie_ = ntohl(raw_message->cookie);
133   message->client_hardware_address_ = ByteString(
134       reinterpret_cast<const char*>(raw_message->chaddr),
135       message->hardware_address_length_);
136   message->servername_.assign(reinterpret_cast<const char*>(raw_message->sname),
137                               kServerNameLength);
138   message->bootfile_.assign(reinterpret_cast<const char*>(raw_message->file),
139                             kBootFileLength);
140   // Validate the DHCP Message
141   if (!message->IsValid()) {
142     return false;
143   }
144   if (!message->ParseDHCPOptions(raw_message->options, options_length)) {
145     LOG(ERROR) << "Failed to parse DHCP options";
146     return false;
147   }
148   return true;
149 }
150 
ParseDHCPOptions(const uint8_t * options,size_t options_length)151 bool DHCPMessage::ParseDHCPOptions(const uint8_t* options,
152                                    size_t options_length) {
153   // DHCP options are in TLV format.
154   // T: tag, L: length, V: value(data)
155   // RFC 1497, RFC 1533, RFC 2132
156   const uint8_t* ptr = options;
157   const uint8_t* end_ptr = options + options_length;
158   std::set<uint8_t> options_set;
159   while (ptr < end_ptr) {
160     uint8_t option_code = *ptr++;
161     int option_code_int = static_cast<int>(option_code);
162     if (option_code == kDHCPOptionPad) {
163       continue;
164     } else if (option_code == kDHCPOptionEnd) {
165       // We reach the end of the option field.
166       // Validate the options before we return.
167       return ContainsValidOptions(options_set);
168     }
169     if (ptr >= end_ptr) {
170       LOG(ERROR) << "Failed to decode dhcp options, no option length field"
171                     " for option: " << option_code_int;
172       return false;
173     }
174     uint8_t option_length = *ptr++;
175     if (ptr + option_length >= end_ptr) {
176       LOG(ERROR) << "Failed to decode dhcp options, invalid option length field"
177                     " for option: " << option_code_int;
178       return false;
179     }
180     if (options_set.find(option_code) != options_set.end()) {
181       LOG(ERROR) << "Found repeated DHCP option: " << option_code_int;
182       return false;
183     }
184     // Here we find a valid DHCP option.
185     auto it = options_map_.find(option_code);
186     if (it != options_map_.end()) {
187       ParserContext* context = &(it->second);
188       if (!context->parser->GetOption(ptr, option_length, context->output)) {
189         return false;
190       }
191       options_set.insert(option_code);
192     } else {
193       DLOG(INFO) << "Ignore DHCP option: " << option_code_int;
194     }
195     // Move to next tag.
196     ptr += option_length;
197   }
198   // Reach the end of message without seeing kDHCPOptionEnd.
199   LOG(ERROR) << "Broken DHCP options without END tag.";
200   return false;
201 }
202 
ContainsValidOptions(const std::set<uint8_t> & options_set)203 bool DHCPMessage::ContainsValidOptions(const std::set<uint8_t>& options_set) {
204   // A DHCP message must contain option 53: DHCP Message Type.
205   if (options_set.find(kDHCPOptionMessageType) == options_set.end()) {
206     LOG(ERROR) << "Faied to find option 53: DHCP Message Type.";
207     return false;
208   }
209   if (message_type_ != kDHCPMessageTypeOffer &&
210       message_type_ != kDHCPMessageTypeAck &&
211       message_type_ != kDHCPMessageTypeNak) {
212     LOG(ERROR) << "Invalid DHCP Message Type: "
213                << static_cast<int>(message_type_);
214     return false;
215   }
216   // A DHCP Offer message must contain option 51: IP Address Lease Time.
217   if (message_type_ == kDHCPMessageTypeOffer) {
218     if (options_set.find(kDHCPOptionLeaseTime) == options_set.end()) {
219       LOG(ERROR) << "Faied to find option 51: IP Address Lease Time";
220       return false;
221     }
222   }
223   // A message from DHCP server must contain option 54: Server Identifier.
224   if (options_set.find(kDHCPOptionServerIdentifier) == options_set.end()) {
225     LOG(ERROR) << "Faied to find option 54: Server Identifier.";
226     return false;
227   }
228   return true;
229 }
230 
IsValid()231 bool DHCPMessage::IsValid() {
232   if (opcode_ != kDHCPMessageBootReply) {
233     LOG(ERROR) << "Invalid DHCP message op code";
234     return false;
235   }
236   if (hardware_address_type_ != ARPHRD_ETHER) {
237     LOG(ERROR) << "DHCP message device family id does not match";
238     return false;
239   }
240   if (hardware_address_length_ != IFHWADDRLEN) {
241     LOG(ERROR) <<
242         "DHCP message device hardware address length does not match";
243     return false;
244   }
245   // We have nothing to do with the 'hops' field.
246 
247   // The reply message from server should have the same xid we cached in client.
248   // DHCP state machine will take charge of this checking.
249 
250   // According to RFC 2131, all secs field in reply messages should be 0.
251   if (seconds_) {
252     LOG(ERROR) << "Invalid DHCP message secs";
253     return false;
254   }
255 
256   // Check broadcast flags.
257   // It should be 0 because we do not request broadcast reply.
258   if (flags_) {
259     LOG(ERROR) << "Invalid DHCP message flags";
260     return false;
261   }
262 
263   // We need to ensure the message contains the correct client hardware address.
264   // DHCP state machine will take charge of this checking.
265 
266   // We do not use the bootfile field.
267   if (cookie_ != kMagicCookie) {
268     LOG(ERROR) << "DHCP message cookie does not match";
269     return false;
270   }
271   return true;
272 }
273 
Serialize(ByteString * data) const274 bool DHCPMessage::Serialize(ByteString* data) const {
275   RawDHCPMessage raw_message;
276   raw_message.op = opcode_;
277   raw_message.htype = hardware_address_type_;
278   raw_message.hlen = hardware_address_length_;
279   raw_message.hops = relay_hops_;
280   raw_message.xid = htonl(transaction_id_);
281   raw_message.secs = htons(seconds_);
282   raw_message.flags = htons(flags_);
283   raw_message.ciaddr = htonl(client_ip_address_);
284   raw_message.yiaddr = htonl(your_ip_address_);
285   raw_message.siaddr = htonl(next_server_ip_address_);
286   raw_message.giaddr = htonl(agent_ip_address_);
287   raw_message.cookie = htonl(cookie_);
288   memcpy(raw_message.chaddr,
289          client_hardware_address_.GetConstData(),
290          hardware_address_length_);
291   if (servername_.length() >= kServerNameLength) {
292     LOG(ERROR) << "Invalid server name length: " << servername_.length();
293     return false;
294   }
295   memcpy(raw_message.sname,
296          servername_.c_str(),
297          servername_.length());
298   raw_message.sname[servername_.length()] = 0;
299   if (bootfile_.length() >= kBootFileLength) {
300     LOG(ERROR) << "Invalid boot file length: " << bootfile_.length();
301     return false;
302   }
303   memcpy(raw_message.file,
304          bootfile_.c_str(),
305          bootfile_.length());
306   raw_message.file[bootfile_.length()] = 0;
307   data->Append(ByteString(reinterpret_cast<const char*>(&raw_message),
308                           sizeof(raw_message) - kDHCPOptionLength));
309   // Append DHCP options to the message.
310   DHCPOptionsWriter* options_writer = DHCPOptionsWriter::GetInstance();
311   if (options_writer->WriteUInt8Option(data,
312                                        kDHCPOptionMessageType,
313                                        message_type_) == -1) {
314     LOG(ERROR) << "Failed to write message type option";
315     return false;
316   }
317   if (requested_ip_address_ != 0) {
318     if (options_writer->WriteUInt32Option(data,
319                                           kDHCPOptionRequestedIPAddr,
320                                           requested_ip_address_) == -1) {
321       LOG(ERROR) << "Failed to write requested ip address option";
322       return false;
323     }
324   }
325   if (lease_time_ != 0) {
326     if (options_writer->WriteUInt32Option(data,
327                                           kDHCPOptionLeaseTime,
328                                           lease_time_) == -1) {
329       LOG(ERROR) << "Failed to write lease time option";
330       return false;
331     }
332   }
333   if (server_identifier_ != 0) {
334     if (options_writer->WriteUInt32Option(data,
335                                           kDHCPOptionServerIdentifier,
336                                           server_identifier_) == -1) {
337       LOG(ERROR) << "Failed to write server identifier option";
338       return false;
339     }
340   }
341   if (error_message_.size() != 0) {
342     if (options_writer->WriteStringOption(data,
343                                           kDHCPOptionMessage,
344                                           error_message_) == -1) {
345       LOG(ERROR) << "Failed to write error message option";
346       return false;
347     }
348   }
349   if (parameter_request_list_.size() != 0) {
350     if (options_writer->WriteUInt8ListOption(data,
351                                              kDHCPOptionParameterRequestList,
352                                              parameter_request_list_) == -1) {
353       LOG(ERROR) << "Failed to write parameter request list";
354       return false;
355     }
356   }
357   // TODO(nywang): Append other options.
358   // Append end tag.
359   if (options_writer->WriteEndTag(data) == -1) {
360     LOG(ERROR) << "Failed to write DHCP options end tag";
361     return false;
362   }
363   // Ensure we do not exceed the maximum length.
364   if (data->GetLength() > kDHCPMessageMaxLength) {
365     LOG(ERROR) << "DHCP message length exceeds the limit";
366     return false;
367   }
368   return true;
369 }
370 
ComputeChecksum(const uint8_t * data,size_t len)371 uint16_t DHCPMessage::ComputeChecksum(const uint8_t* data, size_t len) {
372   uint32_t sum = 0;
373 
374   while (len > 1) {
375     sum += static_cast<uint32_t>(data[0]) << 8 | static_cast<uint32_t>(data[1]);
376     data += 2;
377     len -= 2;
378   }
379   if (len == 1) {
380     sum += static_cast<uint32_t>(*data) << 8;
381   }
382   sum = (sum >> 16) + (sum & 0xffff);
383   sum += (sum >> 16);
384 
385   return ~static_cast<uint16_t>(sum);
386 }
387 
SetClientIdentifier(const ByteString & client_identifier)388 void DHCPMessage::SetClientIdentifier(
389     const ByteString& client_identifier) {
390   client_identifier_ = client_identifier;
391 }
392 
SetClientIPAddress(uint32_t client_ip_address)393 void DHCPMessage::SetClientIPAddress(uint32_t client_ip_address) {
394   client_ip_address_ = client_ip_address;
395 }
396 
SetClientHardwareAddress(const ByteString & client_hardware_address)397 void DHCPMessage::SetClientHardwareAddress(
398     const ByteString& client_hardware_address) {
399   client_hardware_address_ = client_hardware_address;
400 }
401 
SetErrorMessage(const std::string & error_message)402 void DHCPMessage::SetErrorMessage(const std::string& error_message) {
403   error_message_ = error_message;
404 }
405 
SetLeaseTime(uint32_t lease_time)406 void DHCPMessage::SetLeaseTime(uint32_t lease_time) {
407   lease_time_ = lease_time;
408 }
409 
SetMessageType(uint8_t message_type)410 void DHCPMessage::SetMessageType(uint8_t message_type) {
411   message_type_ = message_type;
412 }
413 
SetParameterRequestList(const std::vector<uint8_t> & parameter_request_list)414 void DHCPMessage::SetParameterRequestList(
415     const std::vector<uint8_t>& parameter_request_list) {
416   parameter_request_list_ = parameter_request_list;
417 }
418 
SetRequestedIpAddress(uint32_t requested_ip_address)419 void DHCPMessage::SetRequestedIpAddress(uint32_t requested_ip_address) {
420   requested_ip_address_ = requested_ip_address;
421 }
422 
SetServerIdentifier(uint32_t server_identifier)423 void DHCPMessage::SetServerIdentifier(uint32_t server_identifier) {
424   server_identifier_ = server_identifier;
425 }
426 
SetTransactionID(uint32_t transaction_id)427 void DHCPMessage::SetTransactionID(uint32_t transaction_id) {
428   transaction_id_ = transaction_id;
429 }
430 
SetVendorSpecificInfo(const shill::ByteString & vendor_specific_info)431 void DHCPMessage::SetVendorSpecificInfo(
432     const shill::ByteString& vendor_specific_info) {
433   vendor_specific_info_ = vendor_specific_info;
434 }
435 
InitRequest(DHCPMessage * message)436 void DHCPMessage::InitRequest(DHCPMessage* message) {
437   message->opcode_ = kDHCPMessageBootRequest;
438   message->hardware_address_type_ = ARPHRD_ETHER;
439   message->hardware_address_length_ = IFHWADDRLEN;
440   message->relay_hops_ = 0;
441   // Seconds since DHCP process started.
442   // 0 is also valid according to RFC 2131.
443   message->seconds_ = 0;
444   // Only firewire (IEEE 1394) and InfiniBand interfaces
445   // require broadcast flag.
446   message->flags_ =  0;
447   // Should be zero in client's messages.
448   message->your_ip_address_ = 0;
449   // Should be zero in client's messages.
450   message->next_server_ip_address_ = 0;
451   // Should be zero in client's messages.
452   message->agent_ip_address_ = 0;
453   message->cookie_ = kMagicCookie;
454 }
455 
456 }  // namespace dhcp_client
457