/**************************************************************************** * net/nat/ipv4_nat_entry.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you 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. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include "icmp/icmp.h" #include "nat/nat.h" #include "tcp/tcp.h" #include "udp/udp.h" #if defined(CONFIG_NET_NAT) && defined(CONFIG_NET_IPv4) /**************************************************************************** * Private Data ****************************************************************************/ static DECLARE_HASHTABLE(g_table_inbound, CONFIG_NET_NAT_HASH_BITS); static DECLARE_HASHTABLE(g_table_outbound, CONFIG_NET_NAT_HASH_BITS); /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: ipv4_nat_inbound_key * * Description: * Create an inbound hash key for NAT. * ****************************************************************************/ static inline uint32_t ipv4_nat_inbound_key(in_addr_t external_ip, uint16_t external_port, uint8_t protocol) { return NTOHL(external_ip) ^ /* external ip may different in higher bits. */ ((uint32_t)protocol << 16) ^ external_port; } /**************************************************************************** * Name: ipv4_nat_outbound_key * * Description: * Create an outbound hash key for NAT. * ****************************************************************************/ static inline uint32_t ipv4_nat_outbound_key(in_addr_t local_ip, uint16_t local_port, uint8_t protocol) { return NTOHL(local_ip) ^ /* NTOHL makes sure difference is in lower bits. */ ((uint32_t)protocol << 8) ^ ((uint32_t)local_port << 16); } /**************************************************************************** * Name: ipv4_nat_select_port_without_stack * * Description: * Select an available port number for TCP/UDP protocol, or id for ICMP. * Used when corresponding stack is disabled. * * Input Parameters: * protocol - The L4 protocol of the packet. * ip - The IP bind with the port (in network byte order). * portno - The local port (in network byte order), as reference. * * Returned Value: * port number on success; 0 on failure * ****************************************************************************/ #if (defined(CONFIG_NET_TCP) && defined(CONFIG_NET_TCP_NO_STACK)) || \ (defined(CONFIG_NET_UDP) && defined(CONFIG_NET_UDP_NO_STACK)) || \ (defined(CONFIG_NET_ICMP) && !defined(CONFIG_NET_ICMP_SOCKET)) static uint16_t ipv4_nat_select_port_without_stack( uint8_t protocol, in_addr_t ip, uint16_t portno) { uint16_t hport = NTOHS(portno); while (ipv4_nat_port_inuse(protocol, ip, portno)) { ++hport; if (hport >= CONFIG_NET_DEFAULT_MAX_PORT || hport < CONFIG_NET_DEFAULT_MIN_PORT) { hport = CONFIG_NET_DEFAULT_MIN_PORT; } portno = HTONS(hport); } return portno; } #endif /**************************************************************************** * Name: ipv4_nat_select_port * * Description: * Select an available port number for TCP/UDP protocol, or id for ICMP. * * Input Parameters: * dev - The device on which the packet will be sent. * protocol - The L4 protocol of the packet. * local_port - The local port of the packet, as reference. * * Returned Value: * port number on success; 0 on failure * ****************************************************************************/ static uint16_t ipv4_nat_select_port(FAR struct net_driver_s *dev, uint8_t protocol, uint16_t local_port) { switch (protocol) { #ifdef CONFIG_NET_TCP case IP_PROTO_TCP: { #ifndef CONFIG_NET_TCP_NO_STACK /* Try to select local_port first. */ int ret = tcp_selectport(PF_INET, (FAR const union ip_addr_u *)&dev->d_ipaddr, local_port); /* If failed, try select another unused port. */ if (ret < 0) { ret = tcp_selectport(PF_INET, (FAR const union ip_addr_u *)&dev->d_ipaddr, 0); } return ret > 0 ? ret : 0; #else return ipv4_nat_select_port_without_stack(IP_PROTO_TCP, dev->d_ipaddr, local_port); #endif } #endif #ifdef CONFIG_NET_UDP case IP_PROTO_UDP: { #ifndef CONFIG_NET_UDP_NO_STACK union ip_binding_u u; u.ipv4.laddr = dev->d_ipaddr; u.ipv4.raddr = INADDR_ANY; /* TODO: Try keep origin port as possible. */ return HTONS(udp_select_port(PF_INET, &u)); #else return ipv4_nat_select_port_without_stack(IP_PROTO_UDP, dev->d_ipaddr, local_port); #endif } #endif #ifdef CONFIG_NET_ICMP case IP_PROTO_ICMP: { #ifdef CONFIG_NET_ICMP_SOCKET uint16_t id = local_port; uint16_t hid = NTOHS(id); while (icmp_findconn(dev, id) || ipv4_nat_port_inuse(IP_PROTO_ICMP, dev->d_ipaddr, id)) { ++hid; if (hid >= CONFIG_NET_DEFAULT_MAX_PORT || hid < CONFIG_NET_DEFAULT_MIN_PORT) { hid = CONFIG_NET_DEFAULT_MIN_PORT; } id = HTONS(hid); } return id; #else return ipv4_nat_select_port_without_stack(IP_PROTO_ICMP, dev->d_ipaddr, local_port); #endif } #endif } /* TODO: Currently select original port for unsupported protocol, maybe * return zero to indicate failure. */ return local_port; } /**************************************************************************** * Name: ipv4_nat_entry_refresh * * Description: * Refresh a NAT entry, update its expiration time. * * Input Parameters: * entry - The entry to refresh. * ****************************************************************************/ static void ipv4_nat_entry_refresh(FAR struct ipv4_nat_entry *entry) { /* Note: May add logic here to move recent node to head side if each chain * in hashtable is still too long (with long expire time). */ switch (entry->protocol) { #ifdef CONFIG_NET_TCP case IP_PROTO_TCP: /* NOTE: According to RFC2663, Section 2.6, Page 5, we can reduce the * time to 4min if we have received FINs from both side of one * connection, and keep 24h for other TCP connections. However, full * cone NAT may have multiple connections on one entry, so this * optimization may not work and we only use one expiration time. */ entry->expire_time = TICK2SEC(clock_systime_ticks()) + CONFIG_NET_NAT_TCP_EXPIRE_SEC; break; #endif #ifdef CONFIG_NET_UDP case IP_PROTO_UDP: entry->expire_time = TICK2SEC(clock_systime_ticks()) + CONFIG_NET_NAT_UDP_EXPIRE_SEC; break; #endif #ifdef CONFIG_NET_ICMP case IP_PROTO_ICMP: entry->expire_time = TICK2SEC(clock_systime_ticks()) + CONFIG_NET_NAT_ICMP_EXPIRE_SEC; break; #endif } } /**************************************************************************** * Name: ipv4_nat_entry_create * * Description: * Create a NAT entry and insert into entry list. * * Input Parameters: * protocol - The L4 protocol of the packet. * external_ip - The external ip of the packet. * external_port - The external port of the packet. * local_ip - The local ip of the packet. * local_port - The local port of the packet. * peer_ip - The peer ip of the packet. * peer_port - The peer port of the packet. * * Returned Value: * Pointer to entry on success; null on failure * ****************************************************************************/ static FAR struct ipv4_nat_entry * ipv4_nat_entry_create(uint8_t protocol, in_addr_t external_ip, uint16_t external_port, in_addr_t local_ip, uint16_t local_port, in_addr_t peer_ip, uint16_t peer_port) { FAR struct ipv4_nat_entry *entry = kmm_malloc(sizeof(struct ipv4_nat_entry)); if (entry == NULL) { nwarn("WARNING: Failed to allocate IPv4 NAT entry\n"); return NULL; } entry->protocol = protocol; entry->external_ip = external_ip; entry->external_port = external_port; entry->local_ip = local_ip; entry->local_port = local_port; #ifdef CONFIG_NET_NAT_SYMMETRIC entry->peer_ip = peer_ip; entry->peer_port = peer_port; #endif ipv4_nat_entry_refresh(entry); hashtable_add(g_table_inbound, &entry->hash_inbound, ipv4_nat_inbound_key(external_ip, external_port, protocol)); hashtable_add(g_table_outbound, &entry->hash_outbound, ipv4_nat_outbound_key(local_ip, local_port, protocol)); return entry; } /**************************************************************************** * Name: ipv4_nat_entry_delete * * Description: * Delete a NAT entry and remove from entry list. * * Input Parameters: * entry - The entry to remove. * ****************************************************************************/ static void ipv4_nat_entry_delete(FAR struct ipv4_nat_entry *entry) { ninfo("INFO: Removing NAT entry proto=%" PRIu8 ", local=%" PRIx32 ":%" PRIu16 ", external=:%" PRIu16 "\n", entry->protocol, entry->local_ip, entry->local_port, entry->external_port); hashtable_delete(g_table_inbound, &entry->hash_inbound, ipv4_nat_inbound_key(entry->external_ip, entry->external_port, entry->protocol)); hashtable_delete(g_table_outbound, &entry->hash_outbound, ipv4_nat_outbound_key(entry->local_ip, entry->local_port, entry->protocol)); kmm_free(entry); } /**************************************************************************** * Name: ipv4_nat_reclaim_entry * * Description: * Try reclaim all expired NAT entries. * Only works after every CONFIG_NET_NAT_ENTRY_RECLAIM_SEC (low frequency). * * Although expired entries will be automatically reclaimed when matching * inbound/outbound entries, there might be some situations that entries * will be kept in memory, e.g. big hashtable with only a few connections. * * Assumptions: * NAT is initialized. * ****************************************************************************/ #if CONFIG_NET_NAT_ENTRY_RECLAIM_SEC > 0 static void ipv4_nat_reclaim_entry(int32_t current_time) { static int32_t next_reclaim_time = CONFIG_NET_NAT_ENTRY_RECLAIM_SEC; if (next_reclaim_time - current_time <= 0) { FAR hash_node_t *p; FAR hash_node_t *tmp; int count = 0; int i; ninfo("INFO: Reclaiming all expired NAT entries.\n"); hashtable_for_every_safe(g_table_inbound, p, tmp, i) { FAR struct ipv4_nat_entry *entry = container_of(p, struct ipv4_nat_entry, hash_inbound); if (entry->expire_time - current_time <= 0) { ipv4_nat_entry_delete(entry); count++; } } ninfo("INFO: %d expired NAT entries reclaimed.\n", count); next_reclaim_time = current_time + CONFIG_NET_NAT_ENTRY_RECLAIM_SEC; } } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: ipv4_nat_entry_clear * * Description: * Clear all entries related to dev. Called when NAT will be disabled on * any device. * * Input Parameters: * dev - The device on which NAT entries will be cleared. * * Assumptions: * NAT is initialized. * ****************************************************************************/ void ipv4_nat_entry_clear(FAR struct net_driver_s *dev) { FAR hash_node_t *p; FAR hash_node_t *tmp; int i; ninfo("INFO: Clearing all NAT entries for %s\n", dev->d_ifname); hashtable_for_every_safe(g_table_inbound, p, tmp, i) { FAR struct ipv4_nat_entry *entry = container_of(p, struct ipv4_nat_entry, hash_inbound); if (net_ipv4addr_cmp(entry->external_ip, dev->d_ipaddr)) { ipv4_nat_entry_delete(entry); } } } /**************************************************************************** * Name: ipv4_nat_inbound_entry_find * * Description: * Find the inbound entry in NAT entry list. * * Input Parameters: * protocol - The L4 protocol of the packet. * external_ip - The external ip of the packet, supports INADDR_ANY. * external_port - The external port of the packet. * peer_ip - The peer ip of the packet. * peer_port - The peer port of the packet. * refresh - Whether to refresh the selected entry. * * Returned Value: * Pointer to entry on success; null on failure * ****************************************************************************/ FAR struct ipv4_nat_entry * ipv4_nat_inbound_entry_find(uint8_t protocol, in_addr_t external_ip, uint16_t external_port, in_addr_t peer_ip, uint16_t peer_port, bool refresh) { FAR hash_node_t *p; FAR hash_node_t *tmp; bool skip_ip = net_ipv4addr_cmp(external_ip, INADDR_ANY); #ifdef CONFIG_NET_NAT_SYMMETRIC bool skip_peer = net_ipv4addr_cmp(peer_ip, INADDR_ANY); #endif int32_t current_time = TICK2SEC(clock_systime_ticks()); #if CONFIG_NET_NAT_ENTRY_RECLAIM_SEC > 0 ipv4_nat_reclaim_entry(current_time); #endif hashtable_for_every_possible_safe(g_table_inbound, p, tmp, ipv4_nat_inbound_key(external_ip, external_port, protocol)) { FAR struct ipv4_nat_entry *entry = container_of(p, struct ipv4_nat_entry, hash_inbound); /* Remove expired entries. */ if (entry->expire_time - current_time <= 0) { ipv4_nat_entry_delete(entry); continue; } if (entry->protocol == protocol && (skip_ip || net_ipv4addr_cmp(entry->external_ip, external_ip)) && entry->external_port == external_port #ifdef CONFIG_NET_NAT_SYMMETRIC && (skip_peer || (net_ipv4addr_cmp(entry->peer_ip, peer_ip) && entry->peer_port == peer_port)) #endif ) { if (refresh) { ipv4_nat_entry_refresh(entry); } return entry; } } if (refresh) /* false = a test of whether entry exists, no need to warn */ { nwarn("WARNING: Failed to find IPv4 inbound NAT entry for " "proto=%" PRIu8 ", external=%" PRIx32 ":%" PRIu16 "\n", protocol, external_ip, external_port); } return NULL; } /**************************************************************************** * Name: ipv4_nat_outbound_entry_find * * Description: * Find the outbound entry in NAT entry list. Create one if corresponding * entry does not exist. * * Input Parameters: * dev - The device on which the packet will be sent. * protocol - The L4 protocol of the packet. * local_ip - The local ip of the packet. * local_port - The local port of the packet. * peer_ip - The peer ip of the packet. * peer_port - The peer port of the packet. * try_create - Try create the entry if no entry found. * * Returned Value: * Pointer to entry on success; null on failure * ****************************************************************************/ FAR struct ipv4_nat_entry * ipv4_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol, in_addr_t local_ip, uint16_t local_port, in_addr_t peer_ip, uint16_t peer_port, bool try_create) { FAR hash_node_t *p; FAR hash_node_t *tmp; int32_t current_time = TICK2SEC(clock_systime_ticks()); #if CONFIG_NET_NAT_ENTRY_RECLAIM_SEC > 0 ipv4_nat_reclaim_entry(current_time); #endif hashtable_for_every_possible_safe(g_table_outbound, p, tmp, ipv4_nat_outbound_key(local_ip, local_port, protocol)) { FAR struct ipv4_nat_entry *entry = container_of(p, struct ipv4_nat_entry, hash_outbound); /* Remove expired entries. */ if (entry->expire_time - current_time <= 0) { ipv4_nat_entry_delete(entry); continue; } if (entry->protocol == protocol && net_ipv4addr_cmp(entry->external_ip, dev->d_ipaddr) && net_ipv4addr_cmp(entry->local_ip, local_ip) && entry->local_port == local_port #ifdef CONFIG_NET_NAT_SYMMETRIC && net_ipv4addr_cmp(entry->peer_ip, peer_ip) && entry->peer_port == peer_port #endif ) { ipv4_nat_entry_refresh(entry); return entry; } } if (!try_create) { return NULL; } /* Failed to find the entry, create one. */ ninfo("INFO: Failed to find IPv4 outbound NAT entry for " "proto=%" PRIu8 ", local=%" PRIx32 ":%" PRIu16 ", try create one.\n", protocol, local_ip, local_port); uint16_t external_port = ipv4_nat_select_port(dev, protocol, local_port); if (!external_port) { nwarn("WARNING: Failed to find an available port!\n"); return NULL; } return ipv4_nat_entry_create(protocol, dev->d_ipaddr, external_port, local_ip, local_port, peer_ip, peer_port); } #endif /* CONFIG_NET_NAT && CONFIG_NET_IPv4 */