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
34using shill::ByteString;
35
36namespace dhcp_client {
37
38namespace {
39const int kClientHardwareAddressLength = 16;
40const int kServerNameLength = 64;
41const int kBootFileLength = 128;
42const uint32_t kMagicCookie = 0x63825363;
43const size_t kDHCPMessageMaxLength = 548;
44const size_t kDHCPMessageMinLength = 236;
45const uint8_t kDHCPMessageBootRequest = 1;
46const uint8_t kDHCPMessageBootReply = 2;
47
48// Follow the naming in rfc2131 for this struct.
49struct __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
69DHCPMessage::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
100DHCPMessage::~DHCPMessage() {}
101
102bool 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
151bool 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
203bool 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
231bool 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
274bool 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
371uint16_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
388void DHCPMessage::SetClientIdentifier(
389    const ByteString& client_identifier) {
390  client_identifier_ = client_identifier;
391}
392
393void DHCPMessage::SetClientIPAddress(uint32_t client_ip_address) {
394  client_ip_address_ = client_ip_address;
395}
396
397void DHCPMessage::SetClientHardwareAddress(
398    const ByteString& client_hardware_address) {
399  client_hardware_address_ = client_hardware_address;
400}
401
402void DHCPMessage::SetErrorMessage(const std::string& error_message) {
403  error_message_ = error_message;
404}
405
406void DHCPMessage::SetLeaseTime(uint32_t lease_time) {
407  lease_time_ = lease_time;
408}
409
410void DHCPMessage::SetMessageType(uint8_t message_type) {
411  message_type_ = message_type;
412}
413
414void DHCPMessage::SetParameterRequestList(
415    const std::vector<uint8_t>& parameter_request_list) {
416  parameter_request_list_ = parameter_request_list;
417}
418
419void DHCPMessage::SetRequestedIpAddress(uint32_t requested_ip_address) {
420  requested_ip_address_ = requested_ip_address;
421}
422
423void DHCPMessage::SetServerIdentifier(uint32_t server_identifier) {
424  server_identifier_ = server_identifier;
425}
426
427void DHCPMessage::SetTransactionID(uint32_t transaction_id) {
428  transaction_id_ = transaction_id;
429}
430
431void DHCPMessage::SetVendorSpecificInfo(
432    const shill::ByteString& vendor_specific_info) {
433  vendor_specific_info_ = vendor_specific_info;
434}
435
436void 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