nuttx/net/nat/nat.c
Zhe Weng e543a8086e net: Optimize TCP/UDP port selection
Optimize TCP/UDP port selection, and fix possibly dead loop.

Finish discussion in https://github.com/apache/nuttx/pull/12116#discussion_r1560851977

Note:
Linux also uses EADDRINUSE for failing in finding a portno, according to https://man7.org/linux/man-pages/man2/bind.2.html

Signed-off-by: Zhe Weng <wengzhe@xiaomi.com>
2024-04-12 15:46:29 +08:00

405 lines
11 KiB
C

/****************************************************************************
* net/nat/nat.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 "icmp/icmp.h"
#include "icmpv6/icmpv6.h"
#include "inet/inet.h"
#include "nat/nat.h"
#include "tcp/tcp.h"
#include "udp/udp.h"
#include "utils/utils.h"
#ifdef CONFIG_NET_NAT
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: nat_port_select_without_stack
*
* Description:
* Select an available port number for TCP/UDP protocol, or id for ICMP.
* Used when corresponding stack is disabled.
*
* Input Parameters:
* domain - The domain of the packet.
* protocol - The L4 protocol of the packet.
* ip - The IP bind with the port (in network byte order).
* local_port - 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)) || \
(defined(CONFIG_NET_ICMPv6) && !defined(CONFIG_NET_ICMPv6_SOCKET))
static uint16_t nat_port_select_without_stack(
uint8_t domain, uint8_t protocol, FAR const union ip_addr_u *ip,
uint16_t local_port)
{
uint16_t portno = local_port;
uint16_t hport = NTOHS(portno);
while (nat_port_inuse(domain, protocol, ip, portno))
{
NET_PORT_NEXT_NH(portno, hport);
if (portno == local_port)
{
/* We have looped back, failed. */
return 0;
}
}
return portno;
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: nat_enable
*
* Description:
* Enable NAT function on a network device.
*
* Input Parameters:
* dev - The device on which the outbound packets will be masqueraded.
*
* Returned Value:
* Zero is returned if NAT function is successfully enabled on the device;
* A negated errno value is returned if failed.
*
****************************************************************************/
int nat_enable(FAR struct net_driver_s *dev)
{
net_lock();
if (IFF_IS_NAT(dev->d_flags))
{
nwarn("WARNING: NAT was already enabled for %s!\n", dev->d_ifname);
net_unlock();
return -EEXIST;
}
IFF_SET_NAT(dev->d_flags);
net_unlock();
return OK;
}
/****************************************************************************
* Name: nat_disable
*
* Description:
* Disable NAT function on a network device.
*
* Input Parameters:
* dev - The device on which the NAT function will be disabled.
*
* Returned Value:
* Zero is returned if NAT function is successfully disabled on the device;
* A negated errno value is returned if failed.
*
****************************************************************************/
int nat_disable(FAR struct net_driver_s *dev)
{
net_lock();
if (!IFF_IS_NAT(dev->d_flags))
{
nwarn("WARNING: NAT was not enabled for %s!\n", dev->d_ifname);
net_unlock();
return -ENODEV;
}
/* Clear entries related to dev. */
#ifdef CONFIG_NET_NAT44
ipv4_nat_entry_clear(dev);
#endif
#ifdef CONFIG_NET_NAT66
ipv6_nat_entry_clear(dev);
#endif
IFF_CLR_NAT(dev->d_flags);
net_unlock();
return OK;
}
/****************************************************************************
* Name: nat_port_inuse
*
* Description:
* Check whether a port is currently used by NAT.
*
* Input Parameters:
* domain - The domain of the packet.
* protocol - The L4 protocol of the packet.
* ip - The IP bind with the port (in network byte order).
* port - The port number to check (in network byte order).
*
* Returned Value:
* True if the port is already used by NAT, otherwise false.
*
****************************************************************************/
bool nat_port_inuse(uint8_t domain, uint8_t protocol,
FAR const union ip_addr_u *ip, uint16_t port)
{
#ifdef CONFIG_NET_NAT44
if (domain == PF_INET)
{
return !!ipv4_nat_inbound_entry_find(protocol, ip->ipv4, port,
INADDR_ANY, 0, false);
}
#endif
#ifdef CONFIG_NET_NAT66
if (domain == PF_INET6)
{
return !!ipv6_nat_inbound_entry_find(protocol, ip->ipv6, port,
g_ipv6_unspecaddr, 0, false);
}
#endif
return false;
}
/****************************************************************************
* Name: nat_port_select
*
* 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.
* domain - The domain of the packet.
* protocol - The L4 protocol of the packet.
* external_ip - The external IP bind with the port.
* local_port - The local port of the packet, as reference.
*
* Returned Value:
* External port number on success; 0 on failure
*
****************************************************************************/
uint16_t nat_port_select(FAR struct net_driver_s *dev,
uint8_t domain, uint8_t protocol,
FAR const union ip_addr_u *external_ip,
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(domain, external_ip, local_port);
/* If failed, try select another unused port. */
if (ret < 0)
{
ret = tcp_selectport(domain, external_ip, 0);
}
return ret > 0 ? ret : 0;
#else
return nat_port_select_without_stack(domain, IP_PROTO_TCP,
external_ip, local_port);
#endif
}
#endif
#ifdef CONFIG_NET_UDP
case IP_PROTO_UDP:
{
#ifndef CONFIG_NET_UDP_NO_STACK
union ip_binding_u u;
#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6
if (domain == PF_INET)
#endif
{
u.ipv4.laddr = external_ip->ipv4;
u.ipv4.raddr = INADDR_ANY;
}
#endif
#ifdef CONFIG_NET_IPv6
#ifdef CONFIG_NET_IPv4
else
#endif
{
net_ipv6addr_copy(u.ipv6.laddr, external_ip->ipv6);
net_ipv6addr_copy(u.ipv6.raddr, g_ipv6_unspecaddr);
}
#endif
/* TODO: Try keep origin port as possible. */
return HTONS(udp_select_port(domain, &u));
#else
return nat_port_select_without_stack(domain, IP_PROTO_UDP,
external_ip, 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) ||
nat_port_inuse(domain, IP_PROTO_ICMP, external_ip, id))
{
NET_PORT_NEXT_NH(id, hid);
if (id == local_port)
{
/* We have looped back, failed. */
return 0;
}
}
return id;
#else
return nat_port_select_without_stack(domain, IP_PROTO_ICMP,
external_ip, local_port);
#endif
}
#endif
#ifdef CONFIG_NET_ICMPv6
case IP_PROTO_ICMP6:
{
#ifdef CONFIG_NET_ICMPv6_SOCKET
uint16_t id = local_port;
uint16_t hid = NTOHS(id);
while (icmpv6_active(id) ||
nat_port_inuse(domain, IP_PROTO_ICMP6, external_ip, id))
{
NET_PORT_NEXT_NH(id, hid);
if (id == local_port)
{
/* We have looped back, failed. */
return 0;
}
}
return id;
#else
return nat_port_select_without_stack(domain, IP_PROTO_ICMP6,
external_ip, local_port);
#endif
}
#endif
}
/* Select original port for unsupported protocol. */
return local_port;
}
/****************************************************************************
* Name: nat_expire_time
*
* Description:
* Get the expiration time of a specific protocol.
*
* Input Parameters:
* protocol - The L4 protocol of the packet.
*
* Returned Value:
* The expiration time of the protocol.
*
****************************************************************************/
uint32_t nat_expire_time(uint8_t protocol)
{
/* 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 (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.
*/
return TICK2SEC(clock_systime_ticks()) +
CONFIG_NET_NAT_TCP_EXPIRE_SEC;
#endif
#ifdef CONFIG_NET_UDP
case IP_PROTO_UDP:
return TICK2SEC(clock_systime_ticks()) +
CONFIG_NET_NAT_UDP_EXPIRE_SEC;
#endif
#ifdef CONFIG_NET_ICMP
case IP_PROTO_ICMP:
return TICK2SEC(clock_systime_ticks()) +
CONFIG_NET_NAT_ICMP_EXPIRE_SEC;
#endif
#ifdef CONFIG_NET_ICMPv6
case IP_PROTO_ICMP6:
return TICK2SEC(clock_systime_ticks()) +
CONFIG_NET_NAT_ICMPv6_EXPIRE_SEC;
#endif
default:
nwarn("WARNING: Unsupported protocol %" PRIu8 "\n", protocol);
return 0;
}
}
#endif /* CONFIG_NET_NAT */