Home | History | Annotate | Download | only in dhcp_client
      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 
     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 
    100 DHCPMessage::~DHCPMessage() {}
    101 
    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 
    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 
    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 
    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 
    274 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 
    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 
    388 void DHCPMessage::SetClientIdentifier(
    389     const ByteString& client_identifier) {
    390   client_identifier_ = client_identifier;
    391 }
    392 
    393 void DHCPMessage::SetClientIPAddress(uint32_t client_ip_address) {
    394   client_ip_address_ = client_ip_address;
    395 }
    396 
    397 void DHCPMessage::SetClientHardwareAddress(
    398     const ByteString& client_hardware_address) {
    399   client_hardware_address_ = client_hardware_address;
    400 }
    401 
    402 void DHCPMessage::SetErrorMessage(const std::string& error_message) {
    403   error_message_ = error_message;
    404 }
    405 
    406 void DHCPMessage::SetLeaseTime(uint32_t lease_time) {
    407   lease_time_ = lease_time;
    408 }
    409 
    410 void DHCPMessage::SetMessageType(uint8_t message_type) {
    411   message_type_ = message_type;
    412 }
    413 
    414 void DHCPMessage::SetParameterRequestList(
    415     const std::vector<uint8_t>& parameter_request_list) {
    416   parameter_request_list_ = parameter_request_list;
    417 }
    418 
    419 void DHCPMessage::SetRequestedIpAddress(uint32_t requested_ip_address) {
    420   requested_ip_address_ = requested_ip_address;
    421 }
    422 
    423 void DHCPMessage::SetServerIdentifier(uint32_t server_identifier) {
    424   server_identifier_ = server_identifier;
    425 }
    426 
    427 void DHCPMessage::SetTransactionID(uint32_t transaction_id) {
    428   transaction_id_ = transaction_id;
    429 }
    430 
    431 void DHCPMessage::SetVendorSpecificInfo(
    432     const shill::ByteString& vendor_specific_info) {
    433   vendor_specific_info_ = vendor_specific_info;
    434 }
    435 
    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