普通文本  |  457行  |  15.55 KB

//
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "dhcp_client/dhcp_message.h"

#include <net/if.h>
#include <net/if_arp.h>
#include <netinet/in.h>

#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include <base/logging.h>

#include "dhcp_client/dhcp_options.h"
#include "dhcp_client/dhcp_options_writer.h"

using shill::ByteString;

namespace dhcp_client {

namespace {
const int kClientHardwareAddressLength = 16;
const int kServerNameLength = 64;
const int kBootFileLength = 128;
const uint32_t kMagicCookie = 0x63825363;
const size_t kDHCPMessageMaxLength = 548;
const size_t kDHCPMessageMinLength = 236;
const uint8_t kDHCPMessageBootRequest = 1;
const uint8_t kDHCPMessageBootReply = 2;

// Follow the naming in rfc2131 for this struct.
struct __attribute__((__packed__)) RawDHCPMessage {
  uint8_t op;
  uint8_t htype;
  uint8_t hlen;
  uint8_t hops;
  uint32_t xid;
  uint16_t secs;
  uint16_t flags;
  uint32_t ciaddr;
  uint32_t yiaddr;
  uint32_t siaddr;
  uint32_t giaddr;
  uint8_t chaddr[kClientHardwareAddressLength];
  uint8_t sname[kServerNameLength];
  uint8_t file[kBootFileLength];
  uint32_t cookie;
  uint8_t options[kDHCPOptionLength];
};
}  // namespace

DHCPMessage::DHCPMessage()
    : requested_ip_address_(0),
      lease_time_(0),
      message_type_(0),
      server_identifier_(0),
      renewal_time_(0),
      rebinding_time_(0) {
  options_map_.insert(std::make_pair(kDHCPOptionMessageType,
      ParserContext(new UInt8Parser(), &message_type_)));
  options_map_.insert(std::make_pair(kDHCPOptionLeaseTime,
      ParserContext(new UInt32Parser(), &lease_time_)));
  options_map_.insert(std::make_pair(kDHCPOptionMessage,
      ParserContext(new StringParser(), &error_message_)));
  options_map_.insert(std::make_pair(kDHCPOptionSubnetMask,
      ParserContext(new UInt32Parser(), &subnet_mask_)));
  options_map_.insert(std::make_pair(kDHCPOptionServerIdentifier,
      ParserContext(new UInt32Parser(), &server_identifier_)));
  options_map_.insert(std::make_pair(kDHCPOptionRenewalTime,
      ParserContext(new UInt32Parser(), &renewal_time_)));
  options_map_.insert(std::make_pair(kDHCPOptionRebindingTime,
      ParserContext(new UInt32Parser(), &rebinding_time_)));
  options_map_.insert(std::make_pair(kDHCPOptionDNSServer,
      ParserContext(new UInt32ListParser(), &dns_server_)));
  options_map_.insert(std::make_pair(kDHCPOptionRouter,
      ParserContext(new UInt32ListParser(), &router_)));
  options_map_.insert(std::make_pair(kDHCPOptionDomainName,
      ParserContext(new StringParser(), &domain_name_)));
  options_map_.insert(std::make_pair(kDHCPOptionVendorSpecificInformation,
      ParserContext(new ByteArrayParser(), &vendor_specific_info_)));
}

DHCPMessage::~DHCPMessage() {}

bool DHCPMessage::InitFromBuffer(const unsigned char* buffer,
                                 size_t length,
                                 DHCPMessage* message) {
  if (buffer == NULL) {
    LOG(ERROR) << "Invalid buffer address";
    return false;
  }
  if (length < kDHCPMessageMinLength || length > kDHCPMessageMaxLength) {
    LOG(ERROR) << "Invalid DHCP message length";
    return false;
  }
  const RawDHCPMessage* raw_message
      = reinterpret_cast<const RawDHCPMessage*>(buffer);
  size_t options_length = reinterpret_cast<const unsigned char*>(raw_message) +
      length - reinterpret_cast<const unsigned char*>(raw_message->options) + 1;
  message->opcode_ = raw_message->op;
  message->hardware_address_type_ = raw_message->htype;
  message->hardware_address_length_ = raw_message->hlen;
  if (message->hardware_address_length_ > kClientHardwareAddressLength) {
    LOG(ERROR) << "Invalid hardware address length";
    return false;
  }
  message->relay_hops_ = raw_message->hops;
  message->transaction_id_ = ntohl(raw_message->xid);
  message->seconds_ = ntohs(raw_message->secs);
  message->flags_ = ntohs(raw_message->flags);
  message->client_ip_address_ = ntohl(raw_message->ciaddr);
  message->your_ip_address_ = ntohl(raw_message->yiaddr);
  message->next_server_ip_address_ = ntohl(raw_message->siaddr);
  message->agent_ip_address_ = ntohl(raw_message->giaddr);
  message->cookie_ = ntohl(raw_message->cookie);
  message->client_hardware_address_ = ByteString(
      reinterpret_cast<const char*>(raw_message->chaddr),
      message->hardware_address_length_);
  message->servername_.assign(reinterpret_cast<const char*>(raw_message->sname),
                              kServerNameLength);
  message->bootfile_.assign(reinterpret_cast<const char*>(raw_message->file),
                            kBootFileLength);
  // Validate the DHCP Message
  if (!message->IsValid()) {
    return false;
  }
  if (!message->ParseDHCPOptions(raw_message->options, options_length)) {
    LOG(ERROR) << "Failed to parse DHCP options";
    return false;
  }
  return true;
}

bool DHCPMessage::ParseDHCPOptions(const uint8_t* options,
                                   size_t options_length) {
  // DHCP options are in TLV format.
  // T: tag, L: length, V: value(data)
  // RFC 1497, RFC 1533, RFC 2132
  const uint8_t* ptr = options;
  const uint8_t* end_ptr = options + options_length;
  std::set<uint8_t> options_set;
  while (ptr < end_ptr) {
    uint8_t option_code = *ptr++;
    int option_code_int = static_cast<int>(option_code);
    if (option_code == kDHCPOptionPad) {
      continue;
    } else if (option_code == kDHCPOptionEnd) {
      // We reach the end of the option field.
      // Validate the options before we return.
      return ContainsValidOptions(options_set);
    }
    if (ptr >= end_ptr) {
      LOG(ERROR) << "Failed to decode dhcp options, no option length field"
                    " for option: " << option_code_int;
      return false;
    }
    uint8_t option_length = *ptr++;
    if (ptr + option_length >= end_ptr) {
      LOG(ERROR) << "Failed to decode dhcp options, invalid option length field"
                    " for option: " << option_code_int;
      return false;
    }
    if (options_set.find(option_code) != options_set.end()) {
      LOG(ERROR) << "Found repeated DHCP option: " << option_code_int;
      return false;
    }
    // Here we find a valid DHCP option.
    auto it = options_map_.find(option_code);
    if (it != options_map_.end()) {
      ParserContext* context = &(it->second);
      if (!context->parser->GetOption(ptr, option_length, context->output)) {
        return false;
      }
      options_set.insert(option_code);
    } else {
      DLOG(INFO) << "Ignore DHCP option: " << option_code_int;
    }
    // Move to next tag.
    ptr += option_length;
  }
  // Reach the end of message without seeing kDHCPOptionEnd.
  LOG(ERROR) << "Broken DHCP options without END tag.";
  return false;
}

bool DHCPMessage::ContainsValidOptions(const std::set<uint8_t>& options_set) {
  // A DHCP message must contain option 53: DHCP Message Type.
  if (options_set.find(kDHCPOptionMessageType) == options_set.end()) {
    LOG(ERROR) << "Faied to find option 53: DHCP Message Type.";
    return false;
  }
  if (message_type_ != kDHCPMessageTypeOffer &&
      message_type_ != kDHCPMessageTypeAck &&
      message_type_ != kDHCPMessageTypeNak) {
    LOG(ERROR) << "Invalid DHCP Message Type: "
               << static_cast<int>(message_type_);
    return false;
  }
  // A DHCP Offer message must contain option 51: IP Address Lease Time.
  if (message_type_ == kDHCPMessageTypeOffer) {
    if (options_set.find(kDHCPOptionLeaseTime) == options_set.end()) {
      LOG(ERROR) << "Faied to find option 51: IP Address Lease Time";
      return false;
    }
  }
  // A message from DHCP server must contain option 54: Server Identifier.
  if (options_set.find(kDHCPOptionServerIdentifier) == options_set.end()) {
    LOG(ERROR) << "Faied to find option 54: Server Identifier.";
    return false;
  }
  return true;
}

bool DHCPMessage::IsValid() {
  if (opcode_ != kDHCPMessageBootReply) {
    LOG(ERROR) << "Invalid DHCP message op code";
    return false;
  }
  if (hardware_address_type_ != ARPHRD_ETHER) {
    LOG(ERROR) << "DHCP message device family id does not match";
    return false;
  }
  if (hardware_address_length_ != IFHWADDRLEN) {
    LOG(ERROR) <<
        "DHCP message device hardware address length does not match";
    return false;
  }
  // We have nothing to do with the 'hops' field.

  // The reply message from server should have the same xid we cached in client.
  // DHCP state machine will take charge of this checking.

  // According to RFC 2131, all secs field in reply messages should be 0.
  if (seconds_) {
    LOG(ERROR) << "Invalid DHCP message secs";
    return false;
  }

  // Check broadcast flags.
  // It should be 0 because we do not request broadcast reply.
  if (flags_) {
    LOG(ERROR) << "Invalid DHCP message flags";
    return false;
  }

  // We need to ensure the message contains the correct client hardware address.
  // DHCP state machine will take charge of this checking.

  // We do not use the bootfile field.
  if (cookie_ != kMagicCookie) {
    LOG(ERROR) << "DHCP message cookie does not match";
    return false;
  }
  return true;
}

bool DHCPMessage::Serialize(ByteString* data) const {
  RawDHCPMessage raw_message;
  raw_message.op = opcode_;
  raw_message.htype = hardware_address_type_;
  raw_message.hlen = hardware_address_length_;
  raw_message.hops = relay_hops_;
  raw_message.xid = htonl(transaction_id_);
  raw_message.secs = htons(seconds_);
  raw_message.flags = htons(flags_);
  raw_message.ciaddr = htonl(client_ip_address_);
  raw_message.yiaddr = htonl(your_ip_address_);
  raw_message.siaddr = htonl(next_server_ip_address_);
  raw_message.giaddr = htonl(agent_ip_address_);
  raw_message.cookie = htonl(cookie_);
  memcpy(raw_message.chaddr,
         client_hardware_address_.GetConstData(),
         hardware_address_length_);
  if (servername_.length() >= kServerNameLength) {
    LOG(ERROR) << "Invalid server name length: " << servername_.length();
    return false;
  }
  memcpy(raw_message.sname,
         servername_.c_str(),
         servername_.length());
  raw_message.sname[servername_.length()] = 0;
  if (bootfile_.length() >= kBootFileLength) {
    LOG(ERROR) << "Invalid boot file length: " << bootfile_.length();
    return false;
  }
  memcpy(raw_message.file,
         bootfile_.c_str(),
         bootfile_.length());
  raw_message.file[bootfile_.length()] = 0;
  data->Append(ByteString(reinterpret_cast<const char*>(&raw_message),
                          sizeof(raw_message) - kDHCPOptionLength));
  // Append DHCP options to the message.
  DHCPOptionsWriter* options_writer = DHCPOptionsWriter::GetInstance();
  if (options_writer->WriteUInt8Option(data,
                                       kDHCPOptionMessageType,
                                       message_type_) == -1) {
    LOG(ERROR) << "Failed to write message type option";
    return false;
  }
  if (requested_ip_address_ != 0) {
    if (options_writer->WriteUInt32Option(data,
                                          kDHCPOptionRequestedIPAddr,
                                          requested_ip_address_) == -1) {
      LOG(ERROR) << "Failed to write requested ip address option";
      return false;
    }
  }
  if (lease_time_ != 0) {
    if (options_writer->WriteUInt32Option(data,
                                          kDHCPOptionLeaseTime,
                                          lease_time_) == -1) {
      LOG(ERROR) << "Failed to write lease time option";
      return false;
    }
  }
  if (server_identifier_ != 0) {
    if (options_writer->WriteUInt32Option(data,
                                          kDHCPOptionServerIdentifier,
                                          server_identifier_) == -1) {
      LOG(ERROR) << "Failed to write server identifier option";
      return false;
    }
  }
  if (error_message_.size() != 0) {
    if (options_writer->WriteStringOption(data,
                                          kDHCPOptionMessage,
                                          error_message_) == -1) {
      LOG(ERROR) << "Failed to write error message option";
      return false;
    }
  }
  if (parameter_request_list_.size() != 0) {
    if (options_writer->WriteUInt8ListOption(data,
                                             kDHCPOptionParameterRequestList,
                                             parameter_request_list_) == -1) {
      LOG(ERROR) << "Failed to write parameter request list";
      return false;
    }
  }
  // TODO(nywang): Append other options.
  // Append end tag.
  if (options_writer->WriteEndTag(data) == -1) {
    LOG(ERROR) << "Failed to write DHCP options end tag";
    return false;
  }
  // Ensure we do not exceed the maximum length.
  if (data->GetLength() > kDHCPMessageMaxLength) {
    LOG(ERROR) << "DHCP message length exceeds the limit";
    return false;
  }
  return true;
}

uint16_t DHCPMessage::ComputeChecksum(const uint8_t* data, size_t len) {
  uint32_t sum = 0;

  while (len > 1) {
    sum += static_cast<uint32_t>(data[0]) << 8 | static_cast<uint32_t>(data[1]);
    data += 2;
    len -= 2;
  }
  if (len == 1) {
    sum += static_cast<uint32_t>(*data) << 8;
  }
  sum = (sum >> 16) + (sum & 0xffff);
  sum += (sum >> 16);

  return ~static_cast<uint16_t>(sum);
}

void DHCPMessage::SetClientIdentifier(
    const ByteString& client_identifier) {
  client_identifier_ = client_identifier;
}

void DHCPMessage::SetClientIPAddress(uint32_t client_ip_address) {
  client_ip_address_ = client_ip_address;
}

void DHCPMessage::SetClientHardwareAddress(
    const ByteString& client_hardware_address) {
  client_hardware_address_ = client_hardware_address;
}

void DHCPMessage::SetErrorMessage(const std::string& error_message) {
  error_message_ = error_message;
}

void DHCPMessage::SetLeaseTime(uint32_t lease_time) {
  lease_time_ = lease_time;
}

void DHCPMessage::SetMessageType(uint8_t message_type) {
  message_type_ = message_type;
}

void DHCPMessage::SetParameterRequestList(
    const std::vector<uint8_t>& parameter_request_list) {
  parameter_request_list_ = parameter_request_list;
}

void DHCPMessage::SetRequestedIpAddress(uint32_t requested_ip_address) {
  requested_ip_address_ = requested_ip_address;
}

void DHCPMessage::SetServerIdentifier(uint32_t server_identifier) {
  server_identifier_ = server_identifier;
}

void DHCPMessage::SetTransactionID(uint32_t transaction_id) {
  transaction_id_ = transaction_id;
}

void DHCPMessage::SetVendorSpecificInfo(
    const shill::ByteString& vendor_specific_info) {
  vendor_specific_info_ = vendor_specific_info;
}

void DHCPMessage::InitRequest(DHCPMessage* message) {
  message->opcode_ = kDHCPMessageBootRequest;
  message->hardware_address_type_ = ARPHRD_ETHER;
  message->hardware_address_length_ = IFHWADDRLEN;
  message->relay_hops_ = 0;
  // Seconds since DHCP process started.
  // 0 is also valid according to RFC 2131.
  message->seconds_ = 0;
  // Only firewire (IEEE 1394) and InfiniBand interfaces
  // require broadcast flag.
  message->flags_ =  0;
  // Should be zero in client's messages.
  message->your_ip_address_ = 0;
  // Should be zero in client's messages.
  message->next_server_ip_address_ = 0;
  // Should be zero in client's messages.
  message->agent_ip_address_ = 0;
  message->cookie_ = kMagicCookie;
}

}  // namespace dhcp_client