net: select NAT external port by tcp_selectport for TCP

Signed-off-by: Zhe Weng <wengzhe@xiaomi.com>
This commit is contained in:
Zhe Weng 2022-10-28 16:08:36 +08:00 committed by Xiang Xiao
parent 8239ddeef4
commit f498102512
5 changed files with 225 additions and 122 deletions

View file

@ -131,7 +131,7 @@ static int ipv4_nat_outbound_tcp(FAR struct net_driver_s *dev,
FAR struct tcp_hdr_s *tcp =
(FAR struct tcp_hdr_s *)((FAR uint8_t *)ipv4 + iphdrlen);
FAR struct ipv4_nat_entry *entry = ipv4_nat_outbound_entry_find(
IP_PROTO_TCP, net_ip4addr_conv32(ipv4->srcipaddr), tcp->srcport);
dev, IP_PROTO_TCP, net_ip4addr_conv32(ipv4->srcipaddr), tcp->srcport);
if (!entry)
{
/* Outbound entry creation failed, should have corresponding entry. */

View file

@ -31,6 +31,7 @@
#include <nuttx/queue.h>
#include "nat/nat.h"
#include "tcp/tcp.h"
#if defined(CONFIG_NET_NAT) && defined(CONFIG_NET_IPv4)
@ -44,6 +45,46 @@ static dq_queue_t g_entries;
* Private Functions
****************************************************************************/
/****************************************************************************
* 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))
{
if (++hport >= 32000) /* TODO: Why we limit to 32000 in net stack? */
{
hport = 4096;
}
portno = HTONS(hport);
}
return portno;
}
#endif
/****************************************************************************
* Name: ipv4_nat_select_port
*
@ -51,6 +92,7 @@ static dq_queue_t g_entries;
* 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.
*
@ -59,13 +101,51 @@ static dq_queue_t g_entries;
*
****************************************************************************/
static uint16_t ipv4_nat_select_port(uint8_t protocol, uint16_t local_port)
static uint16_t ipv4_nat_select_port(FAR struct net_driver_s *dev,
uint8_t protocol,
uint16_t local_port)
{
/* TODO: Implement this, need to consider local ports and nat ports.
* TODO: Shall we let the chosen port same as local_port if possible?
*/
switch (protocol)
{
#ifdef CONFIG_NET_TCP
case IP_PROTO_TCP:
{
#ifndef CONFIG_NET_TCP_NO_STACK
/* Try to select local_port first. */
# warning Missing logic
int ret = tcp_selectport(PF_INET,
(FAR const union ip_addr_u *)&dev->d_draddr,
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_draddr, 0);
}
return ret > 0 ? ret : 0;
#else
return ipv4_nat_select_port_without_stack(IP_PROTO_TCP,
dev->d_draddr,
local_port);
#endif
}
#endif
#ifdef CONFIG_NET_UDP
# warning Missing logic
#endif
#ifdef CONFIG_NET_ICMP
# warning Missing logic
#endif
}
/* TODO: Currently select original port for unsupported protocol, maybe
* return zero to indicate failure.
*/
return local_port;
}
@ -237,6 +317,7 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, uint16_t external_port,
* 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.
@ -247,8 +328,8 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, uint16_t external_port,
****************************************************************************/
FAR struct ipv4_nat_entry *
ipv4_nat_outbound_entry_find(uint8_t protocol, in_addr_t local_ip,
uint16_t local_port)
ipv4_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
in_addr_t local_ip, uint16_t local_port)
{
FAR sq_entry_t *p;
FAR sq_entry_t *tmp;
@ -283,7 +364,7 @@ ipv4_nat_outbound_entry_find(uint8_t protocol, in_addr_t local_ip,
"proto=%d, local=%x:%d, try creating one.\n",
protocol, local_ip, local_port);
uint16_t external_port = ipv4_nat_select_port(protocol, 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");

View file

@ -204,6 +204,7 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, uint16_t external_port,
* 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.
@ -214,8 +215,8 @@ ipv4_nat_inbound_entry_find(uint8_t protocol, uint16_t external_port,
****************************************************************************/
FAR struct ipv4_nat_entry *
ipv4_nat_outbound_entry_find(uint8_t protocol, in_addr_t local_ip,
uint16_t local_port);
ipv4_nat_outbound_entry_find(FAR struct net_driver_s *dev, uint8_t protocol,
in_addr_t local_ip, uint16_t local_port);
#endif /* CONFIG_NET_NAT && CONFIG_NET_IPv4 */
#endif /* __NET_NAT_NAT_H */

View file

@ -552,6 +552,27 @@ int tcp_remote_ipv6_device(FAR struct tcp_conn_s *conn);
FAR struct tcp_conn_s *tcp_alloc_accept(FAR struct net_driver_s *dev,
FAR struct tcp_hdr_s *tcp);
/****************************************************************************
* Name: tcp_selectport
*
* Description:
* If the port number is zero; select an unused port for the connection.
* If the port number is non-zero, verify that no other connection has
* been created with this port number.
*
* Returned Value:
* Selected or verified port number in network order on success, a negated
* errno on failure.
*
* Assumptions:
* Interrupts are disabled
*
****************************************************************************/
int tcp_selectport(uint8_t domain,
FAR const union ip_addr_u *ipaddr,
uint16_t portno);
/****************************************************************************
* Name: tcp_bind
*

View file

@ -165,117 +165,6 @@ static FAR struct tcp_conn_s *
return NULL;
}
/****************************************************************************
* Name: tcp_selectport
*
* Description:
* If the port number is zero; select an unused port for the connection.
* If the port number is non-zero, verify that no other connection has
* been created with this port number.
*
* Input Parameters:
* portno -- the selected port number in network order. Zero means no port
* selected.
*
* Returned Value:
* Selected or verified port number in network order on success, a negated
* errno on failure:
*
* EADDRINUSE
* The given address is already in use.
* EADDRNOTAVAIL
* Cannot assign requested address (unlikely)
*
* Assumptions:
* Interrupts are disabled
*
****************************************************************************/
static int tcp_selectport(uint8_t domain,
FAR const union ip_addr_u *ipaddr,
uint16_t portno)
{
static uint16_t g_last_tcp_port;
ssize_t ret;
/* Generate port base dynamically */
if (g_last_tcp_port == 0)
{
ret = getrandom(&g_last_tcp_port, sizeof(uint16_t), 0);
if (ret < 0)
{
ret = getrandom(&g_last_tcp_port, sizeof(uint16_t), GRND_RANDOM);
}
if (ret != sizeof(uint16_t))
{
g_last_tcp_port = clock_systime_ticks() % 32000;
}
else
{
g_last_tcp_port = g_last_tcp_port % 32000;
}
if (g_last_tcp_port < 4096)
{
g_last_tcp_port += 4096;
}
}
if (portno == 0)
{
/* No local port assigned. Loop until we find a valid listen port
* number that is not being used by any other connection. NOTE the
* following loop is assumed to terminate but could not if all
* 32000-4096+1 ports are in used (unlikely).
*/
do
{
/* Guess that the next available port number will be the one after
* the last port number assigned. Make sure that the port number
* is within range.
*/
if (++g_last_tcp_port >= 32000)
{
g_last_tcp_port = 4096;
}
portno = HTONS(g_last_tcp_port);
}
while (tcp_listener(domain, ipaddr, portno)
#if defined(CONFIG_NET_NAT) && defined(CONFIG_NET_IPv4)
|| (domain == PF_INET &&
ipv4_nat_port_inuse(IP_PROTO_TCP, ipaddr->ipv4, portno))
#endif
);
}
else
{
/* A port number has been supplied. Verify that no other TCP/IP
* connection is using this local port.
*/
if (tcp_listener(domain, ipaddr, portno)
#if defined(CONFIG_NET_NAT) && defined(CONFIG_NET_IPv4)
|| (domain == PF_INET &&
ipv4_nat_port_inuse(IP_PROTO_TCP, ipaddr->ipv4, portno))
#endif
)
{
/* It is in use... return EADDRINUSE */
return -EADDRINUSE;
}
}
/* Return the selected or verified port number (host byte order) */
return portno;
}
/****************************************************************************
* Name: tcp_ipv4_active
*
@ -586,6 +475,117 @@ FAR struct tcp_conn_s *tcp_alloc_conn(void)
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: tcp_selectport
*
* Description:
* If the port number is zero; select an unused port for the connection.
* If the port number is non-zero, verify that no other connection has
* been created with this port number.
*
* Input Parameters:
* portno -- the selected port number in network order. Zero means no port
* selected.
*
* Returned Value:
* Selected or verified port number in network order on success, a negated
* errno on failure:
*
* EADDRINUSE
* The given address is already in use.
* EADDRNOTAVAIL
* Cannot assign requested address (unlikely)
*
* Assumptions:
* Interrupts are disabled
*
****************************************************************************/
int tcp_selectport(uint8_t domain,
FAR const union ip_addr_u *ipaddr,
uint16_t portno)
{
static uint16_t g_last_tcp_port;
ssize_t ret;
/* Generate port base dynamically */
if (g_last_tcp_port == 0)
{
ret = getrandom(&g_last_tcp_port, sizeof(uint16_t), 0);
if (ret < 0)
{
ret = getrandom(&g_last_tcp_port, sizeof(uint16_t), GRND_RANDOM);
}
if (ret != sizeof(uint16_t))
{
g_last_tcp_port = clock_systime_ticks() % 32000;
}
else
{
g_last_tcp_port = g_last_tcp_port % 32000;
}
if (g_last_tcp_port < 4096)
{
g_last_tcp_port += 4096;
}
}
if (portno == 0)
{
/* No local port assigned. Loop until we find a valid listen port
* number that is not being used by any other connection. NOTE the
* following loop is assumed to terminate but could not if all
* 32000-4096+1 ports are in used (unlikely).
*/
do
{
/* Guess that the next available port number will be the one after
* the last port number assigned. Make sure that the port number
* is within range.
*/
if (++g_last_tcp_port >= 32000)
{
g_last_tcp_port = 4096;
}
portno = HTONS(g_last_tcp_port);
}
while (tcp_listener(domain, ipaddr, portno)
#if defined(CONFIG_NET_NAT) && defined(CONFIG_NET_IPv4)
|| (domain == PF_INET &&
ipv4_nat_port_inuse(IP_PROTO_TCP, ipaddr->ipv4, portno))
#endif
);
}
else
{
/* A port number has been supplied. Verify that no other TCP/IP
* connection is using this local port.
*/
if (tcp_listener(domain, ipaddr, portno)
#if defined(CONFIG_NET_NAT) && defined(CONFIG_NET_IPv4)
|| (domain == PF_INET &&
ipv4_nat_port_inuse(IP_PROTO_TCP, ipaddr->ipv4, portno))
#endif
)
{
/* It is in use... return EADDRINUSE */
return -EADDRINUSE;
}
}
/* Return the selected or verified port number (host byte order) */
return portno;
}
/****************************************************************************
* Name: tcp_initialize
*