blob: b17dec6a0d4a1b2dcd82351a3d0bbd47f5062388 [file] [log] [blame] [edit]
//
// 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