| /* |
| * 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); |
| } |
| |