nuttx/net/nat/ipv6_nat_entry.c
Zhe Weng f3b34c84c2 net/nat: Support IPv6 Masquerading (NAT66)
Notes:
1. This version of NAT66 is a stateful one like NAT44, corresponding to Linux's MASQUERADE target of ip6tables.  We can support stateless NAT66 & NPTv6 later by slightly modify the address & port selection logic (maybe just match the rules and skip the entry find).
2. We're using same flag `IFF_NAT` for both NAT44 & NAT66 to make control easier.  Which means, if we enable NAT, both NAT44 & NAT66 will be enabled.  If we don't want one of them, we can just disable that one in Kconfig.
3. Maybe we can accelerate the checksum adjustment by pre-calculate a difference of checksum, and apply it to each packet, instead of calling `net_chksum_adjust` each time.  Just a thought, maybe do it later.
4. IP fragment segments on NAT66 connections are not supported yet.

Signed-off-by: Zhe Weng <wengzhe@xiaomi.com>
2024-04-11 22:23:29 +08:00

438 lines
14 KiB
C

/****************************************************************************
* net/nat/ipv6_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 <nuttx/config.h>
#include <debug.h>
#include <stdint.h>
#include <nuttx/clock.h>
#include <nuttx/hashtable.h>
#include <nuttx/kmalloc.h>
#include <nuttx/nuttx.h>
#include "inet/inet.h"
#include "nat/nat.h"
#ifdef CONFIG_NET_NAT66
/****************************************************************************
* Private Data
****************************************************************************/
static DECLARE_HASHTABLE(g_nat66_inbound, CONFIG_NET_NAT_HASH_BITS);
static DECLARE_HASHTABLE(g_nat66_outbound, CONFIG_NET_NAT_HASH_BITS);
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ipv6_nat_hash_key
*
* Description:
* Create a hash key for NAT66.
*
****************************************************************************/
static inline uint32_t ipv6_nat_hash_key(const net_ipv6addr_t ip,
uint16_t port, uint8_t protocol)
{
uint32_t key = (((uint32_t)ip[0] << 16) | ip[1]) ^
(((uint32_t)ip[2] << 16) | ip[3]) ^
(((uint32_t)ip[4] << 16) | ip[5]) ^
(((uint32_t)ip[6] << 16) | ip[7]);
return key ^ ((uint32_t)protocol << 16) ^ port;
}
/****************************************************************************
* Name: ipv6_nat_entry_refresh
*
* Description:
* Refresh a NAT entry, update its expiration time.
*
* Input Parameters:
* entry - The entry to refresh.
*
****************************************************************************/
static void ipv6_nat_entry_refresh(FAR struct ipv6_nat_entry *entry)
{
entry->expire_time = nat_expire_time(entry->protocol);
}
/****************************************************************************
* Name: ipv6_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 ipv6_nat_entry *
ipv6_nat_entry_create(uint8_t protocol, const net_ipv6addr_t external_ip,
uint16_t external_port, const net_ipv6addr_t local_ip,
uint16_t local_port, const net_ipv6addr_t peer_ip,
uint16_t peer_port)
{
FAR struct ipv6_nat_entry *entry =
kmm_malloc(sizeof(struct ipv6_nat_entry));
if (entry == NULL)
{
nwarn("WARNING: Failed to allocate IPv6 NAT entry\n");
return NULL;
}
entry->protocol = protocol;
entry->external_port = external_port;
entry->local_port = local_port;
#ifdef CONFIG_NET_NAT66_SYMMETRIC
entry->peer_port = peer_port;
#endif
net_ipv6addr_copy(entry->external_ip, external_ip);
net_ipv6addr_copy(entry->local_ip, local_ip);
#ifdef CONFIG_NET_NAT66_SYMMETRIC
net_ipv6addr_copy(entry->peer_ip, peer_ip);
#endif
ipv6_nat_entry_refresh(entry);
hashtable_add(g_nat66_inbound, &entry->hash_inbound,
ipv6_nat_hash_key(external_ip, external_port, protocol));
hashtable_add(g_nat66_outbound, &entry->hash_outbound,
ipv6_nat_hash_key(local_ip, local_port, protocol));
return entry;
}
/****************************************************************************
* Name: ipv6_nat_entry_delete
*
* Description:
* Delete a NAT entry and remove from entry list.
*
* Input Parameters:
* entry - The entry to remove.
*
****************************************************************************/
static void ipv6_nat_entry_delete(FAR struct ipv6_nat_entry *entry)
{
ninfo("INFO: Removing NAT66 entry proto=%" PRIu8
", local=%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x:%" PRIu16
", external=:%" PRIu16 "\n",
entry->protocol, entry->local_ip[0], entry->local_ip[1],
entry->local_ip[2], entry->local_ip[3], entry->local_ip[4],
entry->local_ip[5], entry->local_ip[6], entry->local_ip[7],
entry->local_port, entry->external_port);
hashtable_delete(g_nat66_inbound, &entry->hash_inbound,
ipv6_nat_hash_key(entry->external_ip,
entry->external_port,
entry->protocol));
hashtable_delete(g_nat66_outbound, &entry->hash_outbound,
ipv6_nat_hash_key(entry->local_ip,
entry->local_port,
entry->protocol));
kmm_free(entry);
}
/****************************************************************************
* Name: ipv6_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 ipv6_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 NAT66 entries.\n");
hashtable_for_every_safe(g_nat66_inbound, p, tmp, i)
{
FAR struct ipv6_nat_entry *entry =
container_of(p, struct ipv6_nat_entry, hash_inbound);
if (entry->expire_time - current_time <= 0)
{
ipv6_nat_entry_delete(entry);
count++;
}
}
ninfo("INFO: %d expired NAT66 entries reclaimed.\n", count);
next_reclaim_time = current_time + CONFIG_NET_NAT_ENTRY_RECLAIM_SEC;
}
}
#else
# define ipv6_nat_reclaim_entry(t)
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: ipv6_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 ipv6_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 NAT66 entries for %s\n", dev->d_ifname);
hashtable_for_every_safe(g_nat66_inbound, p, tmp, i)
{
FAR struct ipv6_nat_entry *entry =
container_of(p, struct ipv6_nat_entry, hash_inbound);
if (NETDEV_IS_MY_V6ADDR(dev, entry->external_ip))
{
ipv6_nat_entry_delete(entry);
}
}
}
/****************************************************************************
* Name: ipv6_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 ipv6_nat_entry *
ipv6_nat_inbound_entry_find(uint8_t protocol,
const net_ipv6addr_t external_ip,
uint16_t external_port,
const net_ipv6addr_t peer_ip,
uint16_t peer_port, bool refresh)
{
FAR hash_node_t *p;
FAR hash_node_t *tmp;
bool skip_ip = net_ipv6addr_cmp(external_ip, g_ipv6_unspecaddr);
#ifdef CONFIG_NET_NAT66_SYMMETRIC
bool skip_peer = net_ipv6addr_cmp(peer_ip, g_ipv6_unspecaddr);
#endif
int32_t current_time = TICK2SEC(clock_systime_ticks());
ipv6_nat_reclaim_entry(current_time);
hashtable_for_every_possible_safe(g_nat66_inbound, p, tmp,
ipv6_nat_hash_key(external_ip, external_port, protocol))
{
FAR struct ipv6_nat_entry *entry =
container_of(p, struct ipv6_nat_entry, hash_inbound);
/* Remove expired entries. */
if (entry->expire_time - current_time <= 0)
{
ipv6_nat_entry_delete(entry);
continue;
}
if (entry->protocol == protocol &&
(skip_ip || net_ipv6addr_cmp(entry->external_ip, external_ip)) &&
entry->external_port == external_port
#ifdef CONFIG_NET_NAT66_SYMMETRIC
&& (skip_peer || (net_ipv6addr_cmp(entry->peer_ip, peer_ip) &&
entry->peer_port == peer_port))
#endif
)
{
if (refresh)
{
ipv6_nat_entry_refresh(entry);
}
return entry;
}
}
if (refresh) /* false = a test of whether entry exists, no need to warn */
{
nwarn("WARNING: Failed to find IPv6 inbound NAT entry for proto="
"%" PRIu8 ",external=[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]:"
"%" PRIu16 "\n",
protocol, external_ip[0], external_ip[1], external_ip[2],
external_ip[3], external_ip[4], external_ip[5], external_ip[6],
external_ip[7], external_port);
}
return NULL;
}
/****************************************************************************
* Name: ipv6_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 ipv6_nat_entry *
ipv6_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
const net_ipv6addr_t local_ip,
uint16_t local_port,
const net_ipv6addr_t peer_ip,
uint16_t peer_port, bool try_create)
{
FAR hash_node_t *p;
FAR hash_node_t *tmp;
FAR union ip_addr_u *external_ip;
uint16_t external_port;
int32_t current_time = TICK2SEC(clock_systime_ticks());
ipv6_nat_reclaim_entry(current_time);
hashtable_for_every_possible_safe(g_nat66_outbound, p, tmp,
ipv6_nat_hash_key(local_ip, local_port, protocol))
{
FAR struct ipv6_nat_entry *entry =
container_of(p, struct ipv6_nat_entry, hash_outbound);
/* Remove expired entries. */
if (entry->expire_time - current_time <= 0)
{
ipv6_nat_entry_delete(entry);
continue;
}
if (entry->protocol == protocol &&
NETDEV_IS_MY_V6ADDR(dev, entry->external_ip) &&
net_ipv6addr_cmp(entry->local_ip, local_ip) &&
entry->local_port == local_port
#ifdef CONFIG_NET_NAT66_SYMMETRIC
&& net_ipv6addr_cmp(entry->peer_ip, peer_ip) &&
entry->peer_port == peer_port
#endif
)
{
ipv6_nat_entry_refresh(entry);
return entry;
}
}
if (!try_create)
{
return NULL;
}
/* Failed to find the entry, create one. */
ninfo("INFO: Failed to find IPv6 outbound NAT entry for proto=%" PRIu8
", local=[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]:%" PRIu16
", try create one.\n",
protocol, local_ip[0], local_ip[1], local_ip[2], local_ip[3],
local_ip[4], local_ip[5], local_ip[6], local_ip[7], local_port);
external_ip = (FAR union ip_addr_u *)netdev_ipv6_srcaddr(dev, peer_ip);
external_port = nat_port_select(dev, PF_INET6, protocol,
external_ip, local_port);
if (!external_port)
{
nwarn("WARNING: Failed to find an available port!\n");
return NULL;
}
return ipv6_nat_entry_create(protocol, external_ip->ipv6, external_port,
local_ip, local_port, peer_ip, peer_port);
}
#endif /* CONFIG_NET_NAT66 */