/* * Copyright (c) 2018-2019 ARM Limited * SPDX-License-Identifier: Apache-2.0 * * 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 "CyDhcpServer.h" #include "cy_utils.h" #include "Callback.h" #include "def.h" #include "whd_types.h" #ifdef DHCP_EXTENSIVE_DEBUG extern "C" void dhcp_server_print_header_info(dhcp_packet_t *header, uint32_t datalen, const char *title); #endif /* UDP port numbers for DHCP server and client */ #define IP_PORT_DHCP_SERVER (67) #define IP_PORT_DHCP_CLIENT (68) /* BOOTP operations */ #define BOOTP_OP_REQUEST (1) #define BOOTP_OP_REPLY (2) /* DCHP message types */ #define DHCP_MSG_TYPE_DISCOVER (1) #define DHCP_MSG_TYPE_OFFER (2) #define DHCP_MSG_TYPE_REQUEST (3) #define DHCP_MSG_TYPE_DECLINE (4) #define DHCP_MSG_TYPE_ACK (5) #define DHCP_MSG_TYPE_NACK (6) #define DHCP_MSG_TYPE_RELEASE (7) #define DHCP_MSG_TYPE_INFORM (8) #define DHCP_MSG_TYPE_INVALID (255) #define DHCP_MSG_MAGIC_COOKIE (0x63825363) #define DHCP_STACK_SIZE (8*1024) /********************* Options manipulation functions ***********************************/ static void addOption(dhcp_packet_t *dhcp, uint32_t &index, uint8_t optype) { if (index + sizeof(dhcp_packet_t) - 1 + 1 >= DHCP_PACKET_SIZE) { printf("DHCP ERROR: Option index %d (Optype: %d) written to exceeds size of the packet", (int)index, (int)optype); return; } dhcp->Options[index++] = optype; return; } static void addOption(dhcp_packet_t *dhcp, uint32_t &index, uint8_t optype, uint8_t value) { if (index + sizeof(dhcp_packet_t) - 1 + 3 >= DHCP_PACKET_SIZE) { printf("DHCP ERROR: Option index %d (Optype: %d) written to exceeds size of the packet", (int)index, (int)optype); return; } dhcp->Options[index++] = optype; dhcp->Options[index++] = 0x01; dhcp->Options[index++] = value; return; } static void addOption(dhcp_packet_t *dhcp, uint32_t &index, uint8_t optype, uint16_t value) { if (index + sizeof(dhcp_packet_t) - 1 + 4 >= DHCP_PACKET_SIZE) { printf("DHCP ERROR: Option index %d (Optype: %d) written to exceeds size of the packet", (int)index, (int)optype); return; } dhcp->Options[index++] = optype; dhcp->Options[index++] = 0x02; dhcp->Options[index++] = static_cast<uint8_t>((value >> 0) & 0xFF); dhcp->Options[index++] = static_cast<uint8_t>((value >> 8) & 0xFF); return; } static void addOption(dhcp_packet_t *dhcp, uint32_t &index, uint8_t optype, uint32_t value) { if (index + sizeof(dhcp_packet_t) - 1 + 6 >= DHCP_PACKET_SIZE) { printf("DHCP ERROR: Option index %d (Optype: %d) written to exceeds size of the packet", (int)index, (int)optype); return; } dhcp->Options[index++] = optype; dhcp->Options[index++] = 0x04; dhcp->Options[index++] = static_cast<uint8_t>((value >> 0) & 0xFF); dhcp->Options[index++] = static_cast<uint8_t>((value >> 8) & 0xFF); dhcp->Options[index++] = static_cast<uint8_t>((value >> 16) & 0xFF); dhcp->Options[index++] = static_cast<uint8_t>((value >> 24) & 0xFF); return; } static void addOption(dhcp_packet_t *dhcp, uint32_t &index, uint8_t optype, uint8_t *value, uint32_t size) { if (index + sizeof(dhcp_packet_t) - 1 + 2 + size >= DHCP_PACKET_SIZE) { printf("DHCP ERROR: Option index %d (Optype: %d) written to exceeds size of the packet", (int)index, (int)optype); return; } dhcp->Options[index++] = optype; dhcp->Options[index++] = size; memcpy(&dhcp->Options[index], value, size); index += size; return; } static const uint8_t *findOption(const dhcp_packet_t *request, uint8_t option_num) { const uint8_t *option_ptr = request->Options; while ((option_ptr[0] != DHCP_END_OPTION_CODE) && (option_ptr[0] != option_num) && (option_ptr < ((const uint8_t *)request) + DHCP_PACKET_SIZE)) { option_ptr += option_ptr[1] + 2; } /* Was the option found? */ if (option_ptr[0] == option_num) { return &option_ptr[2]; } return NULL; } static void addCommonOptions(dhcp_packet_t *dhcp, uint32_t &index, const uint32_t server_addr, const uint32_t netmask) { /* Prepare the Web proxy auto discovery URL */ char wpad_sample_url[] = "http://xxx.xxx.xxx.xxx/wpad.dat"; char ip_str[16]; ipv4_to_string(ip_str, htonl(server_addr)); memcpy(&wpad_sample_url[7], &ip_str[0], 15); /* Server identifier */ addOption(dhcp, index, DHCP_SERVER_IDENTIFIER_OPTION_CODE, server_addr); /* Lease Time */ addOption(dhcp, index, DHCP_LEASETIME_OPTION_CODE, static_cast<uint32_t>(0x00015180)); /* Subnet Mask */ addOption(dhcp, index, DHCP_SUBNETMASK_OPTION_CODE, htonl(netmask)); /* Web proxy auto discovery URL */ addOption(dhcp, index, DHCP_WPAD_OPTION_CODE, (uint8_t *)&wpad_sample_url[0], strlen(wpad_sample_url)); /* Router (gateway) */ addOption(dhcp, index, DHCP_ROUTER_OPTION_CODE, htonl(server_addr)); /* DNS server */ addOption(dhcp, index, DHCP_DNS_SERVER_OPTION_CODE, htonl(server_addr)); /* Interface MTU */ addOption(dhcp, index, DHCP_MTU_OPTION_CODE, static_cast<uint16_t>(WHD_PAYLOAD_MTU)); } static void sendPacket(UDPSocket *socket, dhcp_packet_t *dhcp, uint32_t size) { nsapi_size_or_error_t err; uint32_t broadcast_ip = 0xFFFFFFFF; char string_addr[16]; ipv4_to_string(string_addr, htonl(broadcast_ip)); SocketAddress sock_addr(string_addr, IP_PORT_DHCP_CLIENT); err = socket->sendto(sock_addr, reinterpret_cast<uint8_t *>(dhcp), size); if (err < 0) { printf("DHCP ERROR: Packet send failure with error %d.", err); } else if (err != (int)size) { printf("DHCP ERROR: Could not send entire packet. Only %d bytes were sent.", err); } } /********************* Cache utility functions ***********************************/ void CyDhcpServer::setAddress(const cy_mac_addr_t &mac_id, const cy_ip_addr_t &addr) { uint32_t a; uint32_t first_empty_slot; uint32_t cached_slot; char empty_cache[NSAPI_IPv6_SIZE] = ""; /* Search for empty slot in cache */ for (a = 0, first_empty_slot = DHCP_IP_ADDRESS_CACHE_MAX, cached_slot = DHCP_IP_ADDRESS_CACHE_MAX; a < DHCP_IP_ADDRESS_CACHE_MAX; a++) { /* Check for matching MAC address */ if (memcmp(&_mac_addr_cache[a], &mac_id, sizeof(mac_id)) == 0) { /* Cached device found */ cached_slot = a; break; } else if (first_empty_slot == DHCP_IP_ADDRESS_CACHE_MAX && memcmp(&_mac_addr_cache[a], &empty_cache, sizeof(cy_mac_addr_t)) == 0) { /* Device not found in cache. Return the first empty slot */ first_empty_slot = a; } } if (cached_slot != DHCP_IP_ADDRESS_CACHE_MAX) { /* Update IP address of cached device */ _ip_addr_cache[cached_slot] = addr; } else if (first_empty_slot != DHCP_IP_ADDRESS_CACHE_MAX) { /* Add device to the first empty slot */ _mac_addr_cache[first_empty_slot] = mac_id; _ip_addr_cache[first_empty_slot] = addr; } else { /* Cache is full. Add device to slot 0 */ _mac_addr_cache[0] = mac_id; _ip_addr_cache [0] = addr; } } bool CyDhcpServer::lookupAddress(const cy_mac_addr_t &mac_id, cy_ip_addr_t &addr) { /* Check whether device is already cached */ for (uint32_t a = 0; a < DHCP_IP_ADDRESS_CACHE_MAX; a++) { if (memcmp(&_mac_addr_cache[a], &mac_id, sizeof(mac_id)) == 0) { addr = _ip_addr_cache[a]; return true; } } return false; } void CyDhcpServer::freeAddress(const cy_mac_addr_t &mac_id) { /* Check whether device is already cached */ for (uint32_t a = 0; a < DHCP_IP_ADDRESS_CACHE_MAX; a++) { if (memcmp(&_mac_addr_cache[a], &mac_id, sizeof(mac_id)) == 0) { memset(&_mac_addr_cache[a], 0, sizeof(_mac_addr_cache[a])); memset(&_ip_addr_cache[a], 0, sizeof(_ip_addr_cache[a])); } } } void CyDhcpServer::handleDiscover(dhcp_packet_t *dhcp) { #ifdef DHCP_EXTENSIVE_DEBUG dhcp_server_print_header_info(dhcp, DHCP_PACKET_SIZE, "\n\nDHCP DISCOVER RECEIVED"); #endif uint32_t index; cy_mac_addr_t client_mac; cy_ip_addr_t client_ip; memcpy(&client_mac, dhcp->ClientHwAddr, sizeof(client_mac)); if (!lookupAddress(client_mac, client_ip)) { client_ip = _available_addr; } memset(&dhcp->Legacy, 0, sizeof(dhcp->Legacy)); memset(&dhcp->Options[0], 0, DHCP_PACKET_SIZE - sizeof(dhcp_packet_t) + 3); dhcp->Opcode = BOOTP_OP_REPLY; dhcp->YourIpAddr = htonl(client_ip.addrv4.addr); dhcp->MagicCookie = htonl(static_cast<uint32_t>(DHCP_MSG_MAGIC_COOKIE)); /* Add options */ index = 0; addOption(dhcp, index, DHCP_MESSAGETYPE_OPTION_CODE, static_cast<uint8_t>(DHCP_MSG_TYPE_OFFER)); addCommonOptions(dhcp, index, _server_addr.addrv4.addr, _netmask.addrv4.addr); addOption(dhcp, index, static_cast<uint8_t>(DHCP_END_OPTION_CODE)); uint32_t size = sizeof(dhcp_packet_t) + index - 1; CY_ASSERT(size <= DHCP_PACKET_SIZE); #ifdef DHCP_EXTENSIVE_DEBUG dhcp_server_print_header_info(dhcp, size, "\n\nDHCP OFFER SENT"); #endif sendPacket(&_socket, dhcp, size); } void CyDhcpServer::handleRequest(dhcp_packet_t *dhcp) { #ifdef DHCP_EXTENSIVE_DEBUG dhcp_server_print_header_info(dhcp, DHCP_PACKET_SIZE, "\n\nDHCP REQUEST RECEIVED"); #endif cy_mac_addr_t client_mac; cy_ip_addr_t client_ip; cy_ip_addr_t req_ip; bool increment = false; uint32_t index; /* Check that the REQUEST is for this server */ uint32_t *server_id_req = (uint32_t *)findOption(dhcp, DHCP_SERVER_IDENTIFIER_OPTION_CODE); if ((server_id_req == NULL) || ((server_id_req != NULL) && (_server_addr.addrv4.addr != *server_id_req))) { return; /* Server ID was not found or does not match local IP address */ } /* Locate the requested address in the options and keep requested address */ req_ip.addrv4.addr = ntohl(*(uint32_t *)findOption(dhcp, DHCP_REQUESTED_IP_ADDRESS_OPTION_CODE)); memcpy(&client_mac, dhcp->ClientHwAddr, sizeof(client_mac)); if (!lookupAddress(client_mac, client_ip)) { client_ip = _available_addr; increment = true; } memset(&dhcp->Legacy, 0, sizeof(dhcp->Legacy)); memset(&dhcp->Options[0], 0, DHCP_PACKET_SIZE - sizeof(dhcp_packet_t) + 3); dhcp->Opcode = BOOTP_OP_REPLY; dhcp->MagicCookie = htonl(static_cast<uint32_t>(DHCP_MSG_MAGIC_COOKIE)); index = 0; /* Check if the requested IP address matches one we have assigned */ if (req_ip.addrv4.addr != client_ip.addrv4.addr) { /* Request is not for the assigned IP - force client to take next available IP by sending NAK */ addOption(dhcp, index, DHCP_MESSAGETYPE_OPTION_CODE, static_cast<uint8_t>(DHCP_MSG_TYPE_NACK)); addOption(dhcp, index, DHCP_SERVER_IDENTIFIER_OPTION_CODE, _server_addr.addrv4.addr); printf("\n\nDHCP_THREAD: %d REQUEST NAK\n", __LINE__); } else { dhcp->YourIpAddr = htonl(client_ip.addrv4.addr); addOption(dhcp, index, DHCP_MESSAGETYPE_OPTION_CODE, static_cast<uint8_t>(DHCP_MSG_TYPE_ACK)); addCommonOptions(dhcp, index, _server_addr.addrv4.addr, _netmask.addrv4.addr); if (increment) { uint32_t ip_mask = ~(_netmask.addrv4.addr); uint32_t subnet = _server_addr.addrv4.addr & _netmask.addrv4.addr; do { _available_addr.addrv4.addr = subnet | ((_available_addr.addrv4.addr + 1) & ip_mask); } while (_available_addr.addrv4.addr == _server_addr.addrv4.addr); } setAddress(client_mac, client_ip); } addOption(dhcp, index, static_cast<uint8_t>(DHCP_END_OPTION_CODE)); uint32_t size = sizeof(dhcp_packet_t) + index - 1; CY_ASSERT(size <= DHCP_PACKET_SIZE); #ifdef DHCP_EXTENSIVE_DEBUG dhcp_server_print_header_info(dhcp, DHCP_PACKET_SIZE, "\n\nDHCP REQUEST REPLY SENT"); #endif sendPacket(&_socket, dhcp, size); } void CyDhcpServer::runServer(void) { nsapi_size_or_error_t err_or_size; _running = true; /* Create receive DHCP socket */ _socket.open(_nstack); char iface_name[8] = {0}; _niface->get_interface_name(iface_name); _socket.setsockopt(NSAPI_SOCKET, NSAPI_BIND_TO_DEVICE, iface_name, strlen(iface_name)); _socket.bind((uint16_t)IP_PORT_DHCP_SERVER); /* Save the current netmask to be sent in DHCP packets as the 'subnet mask option' */ SocketAddress sock_addr; _niface->get_ip_address(&sock_addr); _server_addr.addrv4.addr = string_to_ipv4(sock_addr.get_ip_address()); _niface->get_netmask(&sock_addr); _netmask.addrv4.addr = string_to_ipv4(sock_addr.get_ip_address()); #ifdef DHCP_EXTENSIVE_DEBUG printf("DHCP Server started.\n"); printf("DHCP Server: IP : %s\n", _niface->get_ip_address()); printf("DHCP Server: Netmask: %s\n", _niface->get_netmask()); printf("DHCP Server: Gateway: %s\n", _niface->get_gateway()); printf("DHCP Server: MAC : %s\n\n", _niface->get_mac_address()); #endif /* Calculate the first available IP address which will be served - based on the netmask and the local IP */ uint32_t ip_mask = ~(_netmask.addrv4.addr); uint32_t subnet = _server_addr.addrv4.addr & _netmask.addrv4.addr; _available_addr.addrv4.addr = subnet | ((_server_addr.addrv4.addr + 1) & ip_mask); while (_running) { /* Sleep until data is received from socket. */ err_or_size = _socket.recv(_buff, DHCP_PACKET_SIZE); /* Options field in DHCP header is variable length. We are looking for option "DHCP Message Type" that is 3 octets in size (code, length and type) */ /* If the return value is <0, it is an error; if it is >=0, it is the received length */ if (err_or_size < 0 || err_or_size < (int32_t)sizeof(dhcp_packet_t)) { continue; } dhcp_packet_t *dhcp = reinterpret_cast<dhcp_packet_t *>(_buff); /* Check if the option in the dhcp header is "DHCP Message Type", code value for option "DHCP Message Type" is 53 as per rfc2132 */ if (dhcp->Options[0] != DHCP_MESSAGETYPE_OPTION_CODE) { printf("%d: %s received option code wrong: %d != %d\n", __LINE__, __func__, dhcp->Options[0], DHCP_MESSAGETYPE_OPTION_CODE); continue; } uint8_t msg_type = dhcp->Options[2]; switch (msg_type) { case DHCP_MSG_TYPE_DISCOVER: handleDiscover(dhcp); break; case DHCP_MSG_TYPE_REQUEST: handleRequest(dhcp); break; default: printf("DHCP ERROR: Unhandled dhcp packet type, %d", msg_type); break; } } } void CyDhcpServer::threadWrapper(CyDhcpServer *obj) { obj->runServer(); } CyDhcpServer::CyDhcpServer(NetworkStack *nstack, NetworkInterface *niface) : _nstack(nstack), _niface(niface), _thread(osPriorityNormal, DHCP_STACK_SIZE, NULL, "DHCPserver") {} CyDhcpServer::~CyDhcpServer() { stop(); } cy_rslt_t CyDhcpServer::start(void) { cy_rslt_t result = CY_RSLT_SUCCESS; if (!_running) { CY_ASSERT(_nstack != NULL); /* Clear cache */ memset(_mac_addr_cache, 0, sizeof(_mac_addr_cache)); memset(_ip_addr_cache, 0, sizeof(_ip_addr_cache)); /* Start DHCP server */ if (osOK != _thread.start(mbed::callback(threadWrapper, this))) { result = CY_DHCP_THREAD_CREATION_FAILED; } } return result; } cy_rslt_t CyDhcpServer::stop(void) { cy_rslt_t result = CY_RSLT_SUCCESS; if (_running) { _running = false; if (NSAPI_ERROR_OK != _socket.close()) { printf("DHCP ERROR: DHCP socket closure failed.\n"); result = CY_DHCP_STOP_FAILED; } if (osOK != _thread.join()) { printf("DHCP ERROR: DHCP thread join failed.\n"); result = CY_DHCP_STOP_FAILED; } } return result; }