/*
* Copyright 2017, 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 "message.h"
#include "dhcp.h"
#include <string.h>
#include <vector>
static uint32_t sNextTransactionId = 1;
static const ptrdiff_t kOptionOffset = 7;
// The default lease time in seconds
static const uint32_t kDefaultLeaseTime = 10 * 60;
// The parameters that the client would like to receive from the server
static const uint8_t kRequestParameters[] = { OPT_SUBNET_MASK,
OPT_GATEWAY,
OPT_DNS,
OPT_BROADCAST_ADDR,
OPT_LEASE_TIME,
OPT_T1,
OPT_T2,
OPT_MTU };
Message::Message() {
memset(&dhcpData, 0, sizeof(dhcpData));
mSize = 0;
}
Message::Message(const uint8_t* data, size_t size) {
if (size <= sizeof(dhcpData)) {
memcpy(&dhcpData, data, size);
mSize = size;
} else {
memset(&dhcpData, 0, sizeof(dhcpData));
mSize = 0;
}
}
Message Message::discover(const uint8_t (&sourceMac)[ETH_ALEN]) {
Message message(OP_BOOTREQUEST,
sourceMac,
static_cast<uint8_t>(DHCPDISCOVER));
message.addOption(OPT_PARAMETER_LIST, kRequestParameters);
message.endOptions();
return message;
}
Message Message::request(const uint8_t (&sourceMac)[ETH_ALEN],
in_addr_t requestAddress,
in_addr_t serverAddress) {
Message message(OP_BOOTREQUEST,
sourceMac,
static_cast<uint8_t>(DHCPREQUEST));
message.addOption(OPT_PARAMETER_LIST, kRequestParameters);
message.addOption(OPT_REQUESTED_IP, requestAddress);
message.addOption(OPT_SERVER_ID, serverAddress);
message.endOptions();
return message;
}
Message Message::offer(const Message& sourceMessage,
in_addr_t serverAddress,
in_addr_t offeredAddress,
in_addr_t offeredNetmask,
in_addr_t offeredGateway,
const in_addr_t* offeredDnsServers,
size_t numOfferedDnsServers) {
uint8_t macAddress[ETH_ALEN];
memcpy(macAddress, sourceMessage.dhcpData.chaddr, sizeof(macAddress));
Message message(OP_BOOTREPLY, macAddress, static_cast<uint8_t>(DHCPOFFER));
message.dhcpData.xid = sourceMessage.dhcpData.xid;
message.dhcpData.flags = sourceMessage.dhcpData.flags;
message.dhcpData.yiaddr = offeredAddress;
message.dhcpData.giaddr = sourceMessage.dhcpData.giaddr;
message.addOption(OPT_SERVER_ID, serverAddress);
message.addOption(OPT_LEASE_TIME, kDefaultLeaseTime);
message.addOption(OPT_SUBNET_MASK, offeredNetmask);
message.addOption(OPT_GATEWAY, offeredGateway);
message.addOption(OPT_DNS,
offeredDnsServers,
numOfferedDnsServers * sizeof(in_addr_t));
message.endOptions();
return message;
}
Message Message::ack(const Message& sourceMessage,
in_addr_t serverAddress,
in_addr_t offeredAddress,
in_addr_t offeredNetmask,
in_addr_t offeredGateway,
const in_addr_t* offeredDnsServers,
size_t numOfferedDnsServers) {
uint8_t macAddress[ETH_ALEN];
memcpy(macAddress, sourceMessage.dhcpData.chaddr, sizeof(macAddress));
Message message(OP_BOOTREPLY, macAddress, static_cast<uint8_t>(DHCPACK));
message.dhcpData.xid = sourceMessage.dhcpData.xid;
message.dhcpData.flags = sourceMessage.dhcpData.flags;
message.dhcpData.yiaddr = offeredAddress;
message.dhcpData.giaddr = sourceMessage.dhcpData.giaddr;
message.addOption(OPT_SERVER_ID, serverAddress);
message.addOption(OPT_LEASE_TIME, kDefaultLeaseTime);
message.addOption(OPT_SUBNET_MASK, offeredNetmask);
message.addOption(OPT_GATEWAY, offeredGateway);
message.addOption(OPT_DNS,
offeredDnsServers,
numOfferedDnsServers * sizeof(in_addr_t));
message.endOptions();
return message;
}
Message Message::nack(const Message& sourceMessage, in_addr_t serverAddress) {
uint8_t macAddress[ETH_ALEN];
memcpy(macAddress, sourceMessage.dhcpData.chaddr, sizeof(macAddress));
Message message(OP_BOOTREPLY, macAddress, static_cast<uint8_t>(DHCPNAK));
message.dhcpData.xid = sourceMessage.dhcpData.xid;
message.dhcpData.flags = sourceMessage.dhcpData.flags;
message.dhcpData.giaddr = sourceMessage.dhcpData.giaddr;
message.addOption(OPT_SERVER_ID, serverAddress);
message.endOptions();
return message;
}
bool Message::isValidDhcpMessage(uint8_t expectedOp,
uint32_t expectedXid) const {
if (!isValidDhcpMessage(expectedOp)) {
return false;
}
// Only look for message with a matching transaction ID
if (dhcpData.xid != expectedXid) {
return false;
}
return true;
}
bool Message::isValidDhcpMessage(uint8_t expectedOp) const {
// Require that there is at least enough options for the DHCP cookie
if (dhcpData.options + 4 > end()) {
return false;
}
if (dhcpData.op != expectedOp) {
return false;
}
if (dhcpData.htype != HTYPE_ETHER) {
return false;
}
if (dhcpData.hlen != ETH_ALEN) {
return false;
}
// Need to have the correct cookie in the options
if (dhcpData.options[0] != OPT_COOKIE1) {
return false;
}
if (dhcpData.options[1] != OPT_COOKIE2) {
return false;
}
if (dhcpData.options[2] != OPT_COOKIE3) {
return false;
}
if (dhcpData.options[3] != OPT_COOKIE4) {
return false;
}
return true;
}
size_t Message::optionsSize() const {
auto options = reinterpret_cast<const uint8_t*>(&dhcpData.options);
const uint8_t* msgEnd = end();
if (msgEnd <= options) {
return 0;
}
return msgEnd - options;
}
uint8_t Message::type() const {
uint8_t length = 0;
const uint8_t* opt = getOption(OPT_MESSAGE_TYPE, &length);
if (opt && length == 1) {
return *opt;
}
return 0;
}
in_addr_t Message::serverId() const {
uint8_t length = 0;
const uint8_t* opt = getOption(OPT_SERVER_ID, &length);
if (opt && length == 4) {
return *reinterpret_cast<const in_addr_t*>(opt);
}
return 0;
}
in_addr_t Message::requestedIp() const {
uint8_t length = 0;
const uint8_t* opt = getOption(OPT_REQUESTED_IP, &length);
if (opt && length == 4) {
return *reinterpret_cast<const in_addr_t*>(opt);
}
return 0;
}
Message::Message(uint8_t operation,
const uint8_t (&macAddress)[ETH_ALEN],
uint8_t type) {
memset(&dhcpData, 0, sizeof(dhcpData));
dhcpData.op = operation;
dhcpData.htype = HTYPE_ETHER;
dhcpData.hlen = ETH_ALEN;
dhcpData.hops = 0;
dhcpData.flags = htons(FLAGS_BROADCAST);
dhcpData.xid = htonl(sNextTransactionId++);
memcpy(dhcpData.chaddr, macAddress, ETH_ALEN);
uint8_t* opts = dhcpData.options;
*opts++ = OPT_COOKIE1;
*opts++ = OPT_COOKIE2;
*opts++ = OPT_COOKIE3;
*opts++ = OPT_COOKIE4;
*opts++ = OPT_MESSAGE_TYPE;
*opts++ = 1;
*opts++ = type;
updateSize(opts);
}
void Message::addOption(uint8_t type, const void* data, uint8_t size) {
uint8_t* opts = nextOption();
*opts++ = type;
*opts++ = size;
memcpy(opts, data, size);
opts += size;
updateSize(opts);
}
void Message::endOptions() {
uint8_t* opts = nextOption();
*opts++ = OPT_END;
updateSize(opts);
}
const uint8_t* Message::getOption(uint8_t expectedOptCode,
uint8_t* length) const {
size_t optsSize = optionsSize();
for (size_t i = 4; i + 2 < optsSize; ) {
uint8_t optCode = dhcpData.options[i];
uint8_t optLen = dhcpData.options[i + 1];
const uint8_t* opt = dhcpData.options + i + 2;
if (optCode == OPT_END) {
return nullptr;
}
if (optCode == expectedOptCode) {
*length = optLen;
return opt;
}
i += 2 + optLen;
}
return nullptr;
}
uint8_t* Message::nextOption() {
return reinterpret_cast<uint8_t*>(&dhcpData) + size();
}
void Message::updateSize(uint8_t* optionsEnd) {
mSize = optionsEnd - reinterpret_cast<uint8_t*>(&dhcpData);
}