456 lines
16 KiB
C++
456 lines
16 KiB
C++
//
|
|
// 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
|