/** @file wlan_exp_ip_udp_arp.c * @brief Mango wlan_exp IP/UDP Library (Internal Structures / Functions) * * @copyright Copyright 2014-2019, Mango Communications. All rights reserved. * Distributed under the Mango Reference Design license (https://mangocomm.com/802.11/license) */ #include "wlan_mac_high_sw_config.h" #if WLAN_SW_CONFIG_ENABLE_WLAN_EXP #include "string.h" #include "wlan_exp_ip_udp.h" #include "wlan_exp_ip_udp_arp.h" #include "wlan_exp_transport.h" #include "wlan_mac_queue.h" #define WLAN_EXP_IP_UDP_NUM_ARP_ENTRIES 10 // Number of ARP entries (global pool) // ARP Table data structures // static arp_cache_entry_t ETH_arp_cache[WLAN_EXP_IP_UDP_NUM_ARP_ENTRIES]; void arp_reply(eth_rx_queue_buffer_t* eth_rx_queue_buffer, eth_tx_queue_buffer_t* eth_tx_queue_buffer); /*****************************************************************************/ /** * Initialize the ARP cache structure * * @param None * * @return None * ******************************************************************************/ void arp_init_cache() { u32 i; // Initialize ARP table for (i = 0; i < WLAN_EXP_IP_UDP_NUM_ARP_ENTRIES; i++) { // Zero out the entry bzero((void *)&(ETH_arp_cache[i]), sizeof(arp_cache_entry_t)); } } /*****************************************************************************/ /** * Process the ARP packet * * @param eth_rx_queue_buffer - Ethernet packet * * @return int - Always returns 0 since we don't want higher level transports * to process this packet * * @note This function assumes that both Ethernet device and buffer are valid. * *****************************************************************************/ int arp_process_packet(eth_rx_queue_buffer_t* eth_rx_queue_buffer, eth_tx_queue_buffer_t* eth_tx_queue_buffer) { arp_ipv4_packet_t* arp; u8 my_ip_addr[IP_ADDR_LEN]; arp = (arp_ipv4_packet_t*)((void*)(eth_rx_queue_buffer->pkt) + sizeof(ethernet_header_t)); eth_get_ip_addr(my_ip_addr); // Process the ARP packet // - If the ARP is a request to the node, then update the ARP table and send a reply // - If the ARP is a reply, then update the ARP table // // NOTE: The library does not currently process gratuitous ARPs since there are a limited number // of ARP table entries. However, this functionality would be easy to add in. // if ((Xil_Ntohs(arp->htype) == ARP_HTYPE_ETH) && // Hardware type is "Ethernet" (Xil_Ntohs(arp->ptype) == ETHERTYPE_IP_V4) && // Protocol type is "IP v4" (arp->hlen == ETH_ADDR_LEN) && // Hardware length is "Ethernet" (arp->plen == IP_ADDR_LEN) ) { // Protocol length is "IP v4" // // // !!! TBD !!! - Future addition: To process gratuitous ARPs, check: // - ARP Request and target_paddr == sender_paddr and target_haddr == {0, 0, 0, 0, 0, 0} // - ARP Reply and target_paddr == sender_paddr and target_haddr == sender_haddr // // // Check the ARP is for the node // NOTE: For ARP requests, the target haddr is ignored. // if ((arp->target_paddr[0] == my_ip_addr[0]) && // IP address matches node IP address (arp->target_paddr[1] == my_ip_addr[1]) && (arp->target_paddr[2] == my_ip_addr[2]) && (arp->target_paddr[3] == my_ip_addr[3]) ) { // Update the ARP table regardless of whether this is a request or a reply arp_update_cache(arp->sender_haddr, arp->sender_paddr); // Process the ARP operation // NOTE: In the case of an ARP reply, we have already updated the ARP cache, so there is nothing // further to be done. Therefore, we are just using an if statement to check if this is // an ARP request. If needed, this can be changed to a case statement to process other ARP // operations. // if (Xil_Ntohs(arp->oper) == ARP_REQUEST) { // Send an ARP reply arp_reply(eth_rx_queue_buffer, eth_tx_queue_buffer); } } } return 0; // Upper layer stacks should not process this packet so return zero bytes } /*****************************************************************************/ /** * Send an ARP Reply * * @param eth_rx_queue_buffer - received ARP packet * * @return None * * @note This function assumes that both Ethernet device and buffer are valid. * *****************************************************************************/ void arp_reply(eth_rx_queue_buffer_t* eth_rx_queue_buffer, eth_tx_queue_buffer_t* eth_tx_queue_buffer) { u32 i; arp_ipv4_packet_t* request; u8 eth_ip_addr[IP_ADDR_LEN]; u8 eth_hw_addr[ETH_ADDR_LEN]; ethernet_header_t* eth_hdr; request = (arp_ipv4_packet_t*)((void*)eth_rx_queue_buffer->pkt + sizeof(ethernet_header_t)); eth_get_ip_addr(eth_ip_addr); eth_get_hw_addr(eth_hw_addr); u32 arp_size = ETH_HEADER_LEN + arp_ipv4_packet_t_LEN; // If the packet was successfully allocated if (eth_tx_queue_buffer != NULL) { // Initialize the Ethernet header // NOTE: We will not use a UDP socket to send this packet, since this reply occurs at a // lower level in the protocol stack than a UDP socket. // eth_hdr = (ethernet_header_t*)eth_tx_queue_buffer->seg0; memcpy(eth_hdr->dest_mac_addr, request->sender_haddr, ETH_ADDR_SIZE); memcpy(eth_hdr->src_mac_addr, eth_hw_addr, ETH_ADDR_SIZE); eth_hdr->ethertype = Xil_Htons(ETHERTYPE_ARP); // Get the pointer to the ARP reply packet arp_ipv4_packet_t* arp_reply_packet = (arp_ipv4_packet_t*)(eth_tx_queue_buffer->seg0 + sizeof(ethernet_header_t)); // Populate the ARP reply arp_reply_packet->htype = Xil_Htons(ARP_HTYPE_ETH); arp_reply_packet->ptype = Xil_Htons(ETHERTYPE_IP_V4); arp_reply_packet->hlen = ETH_ADDR_LEN; arp_reply_packet->plen = IP_ADDR_LEN; arp_reply_packet->oper = Xil_Htons(ARP_REPLY); for (i = 0; i < ETH_ADDR_LEN; i++) { arp_reply_packet->sender_haddr[i] = eth_hw_addr[i]; arp_reply_packet->target_haddr[i] = request->sender_haddr[i]; } for (i = 0; i < IP_ADDR_LEN ; i++) { arp_reply_packet->sender_paddr[i] = eth_ip_addr[i]; arp_reply_packet->target_paddr[i] = request->sender_paddr[i]; } // By setting the length of segment 0, we inform the calling context we have filled // in an Ethernet frame that needs transmission eth_tx_queue_buffer->seg0_len = arp_size; } else { xil_printf("Error: Provided with NULL eth_tx_queue_buffer_t*\n"); } } /*****************************************************************************/ /** * Get the Hardware address associated with the Ethernet device / IP address from * the ARP cache. * * @param eth_dev_num - Ethernet device to match in the cache * @param hw_addr - Hardware address (to be returned from the cache) * @param ip_addr - IP address to match in the cache * * @return int - Status of the command: * WLAN_SUCCESS - Command completed successfully * WLAN_FAILURE - There was an error in the command * * @note The reason for the "strange" order of the arguments is to maintain * consistency when specifying HW address and IP address (ie all functions * required HW address then IP address when both are part of the arguments). * Since both addresses are (u8 *), the compiler cannot tell them apart * which makes it easy to get them reversed. * *****************************************************************************/ int arp_get_hw_addr(u8* hw_addr, u8* ip_addr) { int i, j; // Look through the ARP table for (i = 0; i < WLAN_EXP_IP_UDP_NUM_ARP_ENTRIES; i++) { // If an entry is in use, then check the IP address if (ETH_arp_cache[i].state == ARP_TABLE_USED) { // If the IP address / eth_dev_num matches, then copy the hardware address if ((ETH_arp_cache[i].paddr[0] == ip_addr[0]) && (ETH_arp_cache[i].paddr[1] == ip_addr[1]) && (ETH_arp_cache[i].paddr[2] == ip_addr[2]) && (ETH_arp_cache[i].paddr[3] == ip_addr[3])) { // Copy the hardware address for (j = 0; j < ETH_ADDR_LEN; j++) { hw_addr[j] = ETH_arp_cache[i].haddr[j]; } return WLAN_SUCCESS; } } } return WLAN_FAILURE; } /*****************************************************************************/ /** * Update the ARP cache * * This cache uses Ethernet device and IP address as keys to index hardware addresses. * * @param hw_addr - Hardware address * @param ip_addr - IP address * * @return int - Status of the command: * 0 - Command completed successfully * -1 - There was an error in the command * * @note This function assumes that both socket and buffer are valid. * *****************************************************************************/ int arp_update_cache(u8* hw_addr, u8* ip_addr) { int i, j; int first_unused_entry = -1; int oldest_entry = -1; int entry_age = -1; int entry_to_use = -1; // Look through the ARP table: // - Check that current IP address to see if entry already exists for the Ethernet device; // - Update the hw address // - Set age to zero // - Update the age of all entries that are being used // - Record the first unused entry // - Record the oldest entry // for (i = 0; i < WLAN_EXP_IP_UDP_NUM_ARP_ENTRIES; i++) { if (ETH_arp_cache[i].state == ARP_TABLE_USED) { // If this entry is older than the current oldest, then record it and update the age if (entry_age < (int)ETH_arp_cache[i].age) { oldest_entry = i; entry_age = ETH_arp_cache[i].age; } // Update the age of the used entry ETH_arp_cache[i].age += 1; // If the IP address / eth_dev_num matches, then copy the hardware address if ((ETH_arp_cache[i].paddr[0] == ip_addr[0]) && (ETH_arp_cache[i].paddr[1] == ip_addr[1]) && (ETH_arp_cache[i].paddr[2] == ip_addr[2]) && (ETH_arp_cache[i].paddr[3] == ip_addr[3])) { // Copy the hardware address for (j = 0; j < ETH_ADDR_LEN; j++) { ETH_arp_cache[i].haddr[j] = hw_addr[j]; } // Set age to zero ETH_arp_cache[i].age = 0; // We are done updating the table return WLAN_SUCCESS; } } else { // Record first unused entry if (first_unused_entry < 0) { first_unused_entry = i; } } } // If we reach, here we need to add the entry to the table // - If there is an unused entry, then we should use that // - If there are no unused entries, then we should use the oldest entry (LRU replacement policy) // if (first_unused_entry != -1) { entry_to_use = first_unused_entry; // Mark unused entry as used ETH_arp_cache[entry_to_use].state = ARP_TABLE_USED; } else { entry_to_use = oldest_entry; } // Copy the IP / HW addresses and eth_dev_num to entry if (entry_to_use != -1) { // Copy IP address for (i = 0; i < IP_ADDR_LEN; i++) { ETH_arp_cache[entry_to_use].paddr[i] = ip_addr[i]; } // Copy HW address for (i = 0; i < ETH_ADDR_LEN; i++) { ETH_arp_cache[entry_to_use].haddr[i] = hw_addr[i]; } } else { return WLAN_FAILURE; } return WLAN_SUCCESS; } #endif //WLAN_SW_CONFIG_ENABLE_WLAN_EXP