/* * 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 "dhcpserver.h" #include "dhcp.h" #include "log.h" #include "message.h" #include <arpa/inet.h> #include <errno.h> #include <linux/sockios.h> #include <net/if.h> #include <netinet/in.h> #include <poll.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <cutils/properties.h> static const int kMaxDnsServers = 4; DhcpServer::DhcpServer(unsigned int excludeInterface) : mExcludeInterface(excludeInterface) { } Result DhcpServer::init() { Result res = mSocket.open(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (!res) { return res; } res = mSocket.enableOption(SOL_IP, IP_PKTINFO); if (!res) { return res; } res = mSocket.enableOption(SOL_SOCKET, SO_BROADCAST); if (!res) { return res; } res = mSocket.bindIp(INADDR_ANY, PORT_BOOTP_SERVER); if (!res) { return res; } return Result::success(); } Result DhcpServer::run() { // Block all signals while we're running. This way we don't have to deal // with things like EINTR. We then uses ppoll to set the original mask while // polling. This way polling can be interrupted but socket writing, reading // and ioctl remain interrupt free. If a signal arrives while we're blocking // it it will be placed in the signal queue and handled once ppoll sets the // original mask. This way no signals are lost. sigset_t blockMask, originalMask; int status = ::sigfillset(&blockMask); if (status != 0) { return Result::error("Unable to fill signal set: %s", strerror(errno)); } status = ::sigprocmask(SIG_SETMASK, &blockMask, &originalMask); if (status != 0) { return Result::error("Unable to set signal mask: %s", strerror(errno)); } struct pollfd fds; fds.fd = mSocket.get(); fds.events = POLLIN; Message message; while ((status = ::ppoll(&fds, 1, nullptr, &originalMask)) >= 0) { if (status == 0) { // Timeout continue; } unsigned int interfaceIndex = 0; Result res = mSocket.receiveFromInterface(&message, &interfaceIndex); if (!res) { ALOGE("Failed to recieve on socket: %s", res.c_str()); continue; } if (interfaceIndex == 0 || mExcludeInterface == interfaceIndex) { // Received packet on unknown or unwanted interface, drop it continue; } if (!message.isValidDhcpMessage(OP_BOOTREQUEST)) { // Not a DHCP request, drop it continue; } switch (message.type()) { case DHCPDISCOVER: // Someone is trying to find us, let them know we exist sendDhcpOffer(message, interfaceIndex); break; case DHCPREQUEST: // Someone wants a lease based on an offer if (isValidDhcpRequest(message, interfaceIndex)) { // The request matches our offer, acknowledge it sendAck(message, interfaceIndex); } else { // Request for something other than we offered, denied sendNack(message, interfaceIndex); } break; } } // Polling failed, exit return Result::error("Polling failed: %s", strerror(errno)); } Result DhcpServer::sendMessage(unsigned int interfaceIndex, in_addr_t /*sourceAddress*/, const Message& message) { return mSocket.sendOnInterface(interfaceIndex, INADDR_BROADCAST, PORT_BOOTP_CLIENT, message); } void DhcpServer::sendDhcpOffer(const Message& message, unsigned int interfaceIndex ) { updateDnsServers(); in_addr_t offerAddress; in_addr_t netmask; in_addr_t gateway; Result res = getOfferAddress(interfaceIndex, message.dhcpData.chaddr, &offerAddress, &netmask, &gateway); if (!res) { ALOGE("Failed to get address for offer: %s", res.c_str()); return; } in_addr_t serverAddress; res = getInterfaceAddress(interfaceIndex, &serverAddress); if (!res) { ALOGE("Failed to get address for interface %u: %s", interfaceIndex, res.c_str()); return; } Message offer = Message::offer(message, serverAddress, offerAddress, netmask, gateway, mDnsServers.data(), mDnsServers.size()); res = sendMessage(interfaceIndex, serverAddress, offer); if (!res) { ALOGE("Failed to send DHCP offer: %s", res.c_str()); } } void DhcpServer::sendAck(const Message& message, unsigned int interfaceIndex) { updateDnsServers(); in_addr_t offerAddress; in_addr_t netmask; in_addr_t gateway; in_addr_t serverAddress; Result res = getOfferAddress(interfaceIndex, message.dhcpData.chaddr, &offerAddress, &netmask, &gateway); if (!res) { ALOGE("Failed to get address for offer: %s", res.c_str()); return; } res = getInterfaceAddress(interfaceIndex, &serverAddress); if (!res) { ALOGE("Failed to get address for interface %u: %s", interfaceIndex, res.c_str()); return; } Message ack = Message::ack(message, serverAddress, offerAddress, netmask, gateway, mDnsServers.data(), mDnsServers.size()); res = sendMessage(interfaceIndex, serverAddress, ack); if (!res) { ALOGE("Failed to send DHCP ack: %s", res.c_str()); } } void DhcpServer::sendNack(const Message& message, unsigned int interfaceIndex) { in_addr_t serverAddress; Result res = getInterfaceAddress(interfaceIndex, &serverAddress); if (!res) { ALOGE("Failed to get address for interface %u: %s", interfaceIndex, res.c_str()); return; } Message nack = Message::nack(message, serverAddress); res = sendMessage(interfaceIndex, serverAddress, nack); if (!res) { ALOGE("Failed to send DHCP nack: %s", res.c_str()); } } bool DhcpServer::isValidDhcpRequest(const Message& message, unsigned int interfaceIndex) { in_addr_t offerAddress; in_addr_t netmask; in_addr_t gateway; Result res = getOfferAddress(interfaceIndex, message.dhcpData.chaddr, &offerAddress, &netmask, &gateway); if (!res) { ALOGE("Failed to get address for offer: %s", res.c_str()); return false; } if (message.requestedIp() != offerAddress) { ALOGE("Client requested a different IP address from the offered one"); return false; } return true; } void DhcpServer::updateDnsServers() { char key[64]; char value[PROPERTY_VALUE_MAX]; mDnsServers.clear(); for (int i = 1; i <= kMaxDnsServers; ++i) { snprintf(key, sizeof(key), "net.eth0.dns%d", i); if (property_get(key, value, nullptr) > 0) { struct in_addr address; if (::inet_pton(AF_INET, value, &address) > 0) { mDnsServers.push_back(address.s_addr); } } } } Result DhcpServer::getInterfaceData(unsigned int interfaceIndex, unsigned long type, struct ifreq* response) { char interfaceName[IF_NAMESIZE + 1]; if (if_indextoname(interfaceIndex, interfaceName) == nullptr) { return Result::error("Failed to get interface name for index %u: %s", interfaceIndex, strerror(errno)); } memset(response, 0, sizeof(*response)); response->ifr_addr.sa_family = AF_INET; strncpy(response->ifr_name, interfaceName, IFNAMSIZ - 1); if (::ioctl(mSocket.get(), type, response) == -1) { return Result::error("Failed to get data for interface %s: %s", interfaceName, strerror(errno)); } return Result::success(); } Result DhcpServer::getInterfaceAddress(unsigned int interfaceIndex, in_addr_t* address) { struct ifreq data; Result res = getInterfaceData(interfaceIndex, SIOCGIFADDR, &data); if (res.isSuccess()) { auto inAddr = reinterpret_cast<struct sockaddr_in*>(&data.ifr_addr); *address = inAddr->sin_addr.s_addr; } return res; } Result DhcpServer::getInterfaceNetmask(unsigned int interfaceIndex, in_addr_t* address) { struct ifreq data; Result res = getInterfaceData(interfaceIndex, SIOCGIFNETMASK, &data); if (res.isSuccess()) { auto inAddr = reinterpret_cast<struct sockaddr_in*>(&data.ifr_addr); *address = inAddr->sin_addr.s_addr; } return res; } static bool isValidHost(const in_addr_t address, const in_addr_t interfaceAddress, const in_addr_t netmask) { // If the bits outside of the netmask are all zero it's a network address, // don't use this. bool isNetworkAddress = (address & ~netmask) == 0; // If all bits outside of the netmask are set then it's a broadcast address, // don't use this either. bool isBroadcastAddress = (address & ~netmask) == ~netmask; // Don't assign the interface address to a host bool isInterfaceAddress = address == interfaceAddress; return !isNetworkAddress && !isBroadcastAddress && !isInterfaceAddress; } static bool addressInRange(const in_addr_t address, const in_addr_t interfaceAddress, const in_addr_t netmask) { if (address <= (interfaceAddress & netmask)) { return false; } if (address >= (interfaceAddress | ~netmask)) { return false; } return true; } Result DhcpServer::getOfferAddress(unsigned int interfaceIndex, const uint8_t* macAddress, in_addr_t* address, in_addr_t* netmask, in_addr_t* gateway) { // The interface address will be the gateway and will be used to determine // the range of valid addresses (along with the netmask) for the client. in_addr_t interfaceAddress = 0; Result res = getInterfaceAddress(interfaceIndex, &interfaceAddress); if (!res) { return res; } // The netmask of the interface will be the netmask for the client as well // as used to determine network range. in_addr_t mask = 0; res = getInterfaceNetmask(interfaceIndex, &mask); if (!res) { return res; } // Assign these values now before they are modified below *gateway = interfaceAddress; *netmask = mask; Lease key(interfaceIndex, macAddress); // Find or create entry, if it's created it will be zero and we update it in_addr_t& value = mLeases[key]; if (value == 0) { // Addresses are stored in network byte order so when doing math on them // they have to be converted to host byte order interfaceAddress = ntohl(interfaceAddress); mask = ntohl(mask); // Get a reference to the offset so we can use it and increase it at the // same time. If the entry does not exist it will be created with a // value of zero. in_addr_t& offset = mNextAddressOffsets[interfaceIndex]; if (offset == 0) { // Increase if zero to avoid assigning network address ++offset; } // Start out at the first address in the range as determined by netmask in_addr_t nextAddress = (interfaceAddress & mask) + offset; // Ensure the address is valid while (!isValidHost(nextAddress, interfaceAddress, mask) && addressInRange(nextAddress, interfaceAddress, mask)) { ++nextAddress; ++offset; } if (addressInRange(nextAddress, interfaceAddress, mask)) { // Convert back to network byte order value = htonl(nextAddress); ++offset; } else { // Ran out of addresses return Result::error("DHCP server is out of addresses"); } } *address = value; return Result::success(); }