mirror of
https://github.com/apache/nuttx.git
synced 2026-05-21 04:52:02 +08:00
d619ee6541
Make nat_port_select() return an error code and report the selected external port through an output parameter. This separates selection failure from the valid ICMP echo identifier value zero, so outbound NAT entry creation no longer rejects ICMP echo requests that use id zero. Signed-off-by: Shunchao Hu <ankohuu@gmail.com>
472 lines
15 KiB
C
472 lines
15 KiB
C
/****************************************************************************
|
|
* net/nat/ipv4_nat_entry.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* 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 <nuttx/config.h>
|
|
|
|
#include <nuttx/debug.h>
|
|
#include <stdint.h>
|
|
|
|
#include <nuttx/clock.h>
|
|
#include <nuttx/hashtable.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/nuttx.h>
|
|
|
|
#include "nat/nat.h"
|
|
#include "netlink/netlink.h"
|
|
|
|
#ifdef CONFIG_NET_NAT44
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static DECLARE_HASHTABLE(g_nat44_inbound, CONFIG_NET_NAT_HASH_BITS);
|
|
static DECLARE_HASHTABLE(g_nat44_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_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 ipv4_nat_entry_t *entry)
|
|
{
|
|
entry->expire_time = nat_expire_time(entry->protocol);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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 ipv4_nat_entry_t *
|
|
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 ipv4_nat_entry_t *entry = kmm_malloc(sizeof(ipv4_nat_entry_t));
|
|
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_NAT44_SYMMETRIC
|
|
entry->peer_ip = peer_ip;
|
|
entry->peer_port = peer_port;
|
|
#endif
|
|
|
|
ipv4_nat_entry_refresh(entry);
|
|
|
|
hashtable_add(g_nat44_inbound, &entry->hash_inbound,
|
|
ipv4_nat_inbound_key(external_ip, external_port, protocol));
|
|
hashtable_add(g_nat44_outbound, &entry->hash_outbound,
|
|
ipv4_nat_outbound_key(local_ip, local_port, protocol));
|
|
|
|
#ifdef CONFIG_NETLINK_NETFILTER
|
|
netlink_conntrack_notify(IPCTNL_MSG_CT_NEW, PF_INET, entry);
|
|
#endif
|
|
|
|
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 ipv4_nat_entry_t *entry)
|
|
{
|
|
ninfo("INFO: Removing NAT44 entry proto=%" PRIu8
|
|
", local=%" PRIx32 ":%" PRIu16 ", external=:%" PRIu16 "\n",
|
|
entry->protocol, entry->local_ip, entry->local_port,
|
|
entry->external_port);
|
|
|
|
hashtable_delete(g_nat44_inbound, &entry->hash_inbound,
|
|
ipv4_nat_inbound_key(entry->external_ip,
|
|
entry->external_port,
|
|
entry->protocol));
|
|
hashtable_delete(g_nat44_outbound, &entry->hash_outbound,
|
|
ipv4_nat_outbound_key(entry->local_ip,
|
|
entry->local_port,
|
|
entry->protocol));
|
|
|
|
#ifdef CONFIG_NETLINK_NETFILTER
|
|
netlink_conntrack_notify(IPCTNL_MSG_CT_DELETE, PF_INET, entry);
|
|
#endif
|
|
|
|
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_cb(FAR ipv4_nat_entry_t *entry,
|
|
FAR void *arg)
|
|
{
|
|
int32_t current_time = *(FAR int32_t *)arg;
|
|
|
|
if (entry->expire_time - current_time <= 0)
|
|
{
|
|
ipv4_nat_entry_delete(entry);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
ninfo("INFO: Reclaiming all expired NAT44 entries.\n");
|
|
ipv4_nat_entry_foreach(ipv4_nat_reclaim_entry_cb, ¤t_time);
|
|
next_reclaim_time = current_time + CONFIG_NET_NAT_ENTRY_RECLAIM_SEC;
|
|
}
|
|
}
|
|
#else
|
|
# define ipv4_nat_reclaim_entry(t)
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: ipv4_nat_entry_clear_cb
|
|
*
|
|
* Description:
|
|
* Clear an entry related to dev. Called when NAT will be disabled on
|
|
* any device.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void ipv4_nat_entry_clear_cb(FAR ipv4_nat_entry_t *entry,
|
|
FAR void *arg)
|
|
{
|
|
FAR struct net_driver_s *dev = arg;
|
|
|
|
if (net_ipv4addr_cmp(entry->external_ip, dev->d_ipaddr))
|
|
{
|
|
ipv4_nat_entry_delete(entry);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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)
|
|
{
|
|
ninfo("INFO: Clearing all NAT44 entries for %s\n", dev->d_ifname);
|
|
ipv4_nat_entry_foreach(ipv4_nat_entry_clear_cb, dev);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ipv4_nat_entry_foreach
|
|
*
|
|
* Description:
|
|
* Call the callback function for each NAT entry.
|
|
*
|
|
* Input Parameters:
|
|
* cb - The callback function.
|
|
* arg - The argument to pass to the callback function.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void ipv4_nat_entry_foreach(ipv4_nat_entry_cb_t cb, FAR void *arg)
|
|
{
|
|
FAR hash_node_t *p;
|
|
FAR hash_node_t *tmp;
|
|
int i;
|
|
|
|
hashtable_for_every_safe(g_nat44_inbound, p, tmp, i)
|
|
{
|
|
FAR ipv4_nat_entry_t *entry =
|
|
container_of(p, ipv4_nat_entry_t, hash_inbound);
|
|
|
|
cb(entry, arg);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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 ipv4_nat_entry_t *
|
|
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_NAT44_SYMMETRIC
|
|
bool skip_peer = net_ipv4addr_cmp(peer_ip, INADDR_ANY);
|
|
#endif
|
|
int32_t current_time = TICK2SEC(clock_systime_ticks());
|
|
|
|
ipv4_nat_reclaim_entry(current_time);
|
|
|
|
hashtable_for_every_possible_safe(g_nat44_inbound, p, tmp,
|
|
ipv4_nat_inbound_key(external_ip, external_port, protocol))
|
|
{
|
|
FAR ipv4_nat_entry_t *entry =
|
|
container_of(p, ipv4_nat_entry_t, 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_NAT44_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 ipv4_nat_entry_t *
|
|
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;
|
|
uint16_t external_port;
|
|
int32_t current_time = TICK2SEC(clock_systime_ticks());
|
|
int ret;
|
|
|
|
ipv4_nat_reclaim_entry(current_time);
|
|
|
|
hashtable_for_every_possible_safe(g_nat44_outbound, p, tmp,
|
|
ipv4_nat_outbound_key(local_ip, local_port, protocol))
|
|
{
|
|
FAR ipv4_nat_entry_t *entry =
|
|
container_of(p, ipv4_nat_entry_t, 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_NAT44_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);
|
|
|
|
ret = nat_port_select(dev, PF_INET, protocol,
|
|
(FAR union ip_addr_u *)&dev->d_ipaddr, local_port,
|
|
&external_port);
|
|
if (ret < 0)
|
|
{
|
|
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_NAT44 */
|