mirror of
https://github.com/apache/nuttx.git
synced 2026-08-07 05:27:17 +00:00
Allow non-local IPv4 fragments to reach the forwarding path before local fragment reassembly. This lets routers forward fragmented datagrams without requiring local reassembly state. Keep NAT44 and IP filter builds on the existing reassembly path because those features can depend on L4 headers. Preserve the original fragment offset and MF flag when fragout must split an already-fragmented packet for a smaller egress MTU. Signed-off-by: shichunma <shichunma@bestechnic.com>
602 lines
17 KiB
C
602 lines
17 KiB
C
/****************************************************************************
|
|
* net/devif/ipv4_input.c
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*
|
|
* Copyright (C) 2007-2009, 2013-2015, 2018-2019 Gregory Nutt. All rights
|
|
* reserved.
|
|
* Author: Gregory Nutt <gnutt@nuttx.org>
|
|
*
|
|
* Adapted for NuttX from logic in uIP which also has a BSD-like license:
|
|
*
|
|
* uIP is an implementation of the TCP/IP protocol stack intended for
|
|
* small 8-bit and 16-bit microcontrollers.
|
|
*
|
|
* uIP provides the necessary protocols for Internet communication,
|
|
* with a very small code footprint and RAM requirements - the uIP
|
|
* code size is on the order of a few kilobytes and RAM usage is on
|
|
* the order of a few hundred bytes.
|
|
*
|
|
* Original author Adam Dunkels <adam@dunkels.com>
|
|
* Copyright () 2001-2003, Adam Dunkels.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. The name of the author may not be used to endorse or promote
|
|
* products derived from this software without specific prior
|
|
* written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
|
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
|
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* uIP is a small implementation of the IP, UDP and TCP protocols (as
|
|
* well as some basic ICMP stuff). The implementation couples the IP,
|
|
* UDP, TCP and the application layers very tightly. To keep the size
|
|
* of the compiled code down, this code frequently uses the goto
|
|
* statement. While it would be possible to break the ipv4_input()
|
|
* function into many smaller functions, this would increase the code
|
|
* size because of the overhead of parameter passing and the fact that
|
|
* the optimizer would not be as efficient.
|
|
*
|
|
* The principle is that we have a small buffer, called the d_buf,
|
|
* in which the device driver puts an incoming packet. The TCP/IP
|
|
* stack parses the headers in the packet, and calls the
|
|
* application. If the remote host has sent data to the application,
|
|
* this data is present in the d_buf and the application read the
|
|
* data from there. It is up to the application to put this data into
|
|
* a byte stream if needed. The application will not be fed with data
|
|
* that is out of sequence.
|
|
*
|
|
* If the application wishes to send data to the peer, it should put
|
|
* its data into the d_buf. The d_appdata pointer points to the
|
|
* first available byte. The TCP/IP stack will calculate the
|
|
* checksums, and fill in the necessary header fields and finally send
|
|
* the packet back to the peer.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
#ifdef CONFIG_NET_IPv4
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <nuttx/debug.h>
|
|
#include <string.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <net/if.h>
|
|
|
|
#include <nuttx/net/netconfig.h>
|
|
#include <nuttx/net/netdev.h>
|
|
#include <nuttx/net/netstats.h>
|
|
#include <nuttx/net/ip.h>
|
|
#include <nuttx/net/ipopt.h>
|
|
|
|
#include "arp/arp.h"
|
|
#include "inet/inet.h"
|
|
#include "tcp/tcp.h"
|
|
#include "udp/udp.h"
|
|
#include "pkt/pkt.h"
|
|
#include "icmp/icmp.h"
|
|
#include "igmp/igmp.h"
|
|
|
|
#include "ipforward/ipforward.h"
|
|
#include "devif/devif.h"
|
|
#include "nat/nat.h"
|
|
#include "ipfilter/ipfilter.h"
|
|
#include "ipfrag/ipfrag.h"
|
|
#include "utils/utils.h"
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: ipv4_check_opt
|
|
*
|
|
* Description:
|
|
* Check the IP options length.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
static int ipv4_check_opt(FAR struct ipv4_hdr_s *ipv4)
|
|
{
|
|
FAR uint8_t *opt = (FAR uint8_t *)(ipv4 + 1);
|
|
int optlen;
|
|
|
|
optlen = ((ipv4->vhl & IPv4_HLMASK) << 2) - IPv4_HDRLEN;
|
|
while (optlen > 0)
|
|
{
|
|
if (opt[0] == IPOPT_END_TYPE)
|
|
{
|
|
break;
|
|
}
|
|
else if (opt[0] == IPOPT_NOOP_TYPE)
|
|
{
|
|
opt++;
|
|
optlen--;
|
|
}
|
|
else if (optlen > 1)
|
|
{
|
|
int len = opt[1];
|
|
if (len > optlen)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
opt += len;
|
|
optlen -= len;
|
|
}
|
|
else
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: ipv4_fragin_or_drop
|
|
*
|
|
* Description:
|
|
* Reassemble an incoming IPv4 fragment for local processing or drop it if
|
|
* fragment reassembly is not available.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int ipv4_fragin_or_drop(FAR struct net_driver_s *dev)
|
|
{
|
|
#ifdef CONFIG_NET_IPFRAG
|
|
if (ipv4_fragin(dev) == OK)
|
|
{
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_NET_STATISTICS
|
|
g_netstats.ipv4.drop++;
|
|
g_netstats.ipv4.fragerr++;
|
|
#endif
|
|
nwarn("WARNING: IP fragment dropped\n");
|
|
|
|
dev->d_len = 0;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ipv4_in
|
|
*
|
|
* Description:
|
|
* Receive an IPv4 packet from the network device. Verify and forward to
|
|
* L3 packet handling logic if the packet is destined for us.
|
|
*
|
|
* This is the iob buffer version of ipv4_input(),
|
|
* this function will support send/receive iob vectors directly between
|
|
* the driver and l3/l4 stack to avoid unnecessary memory copies,
|
|
* especially on hardware that supports Scatter/gather, which can
|
|
* greatly improve performance
|
|
* this function will uses d_iob as packets input which used by some
|
|
* NICs such as celluler net driver.
|
|
*
|
|
* Input Parameters:
|
|
* dev - The device on which the packet was received and which contains
|
|
* the IPv4 packet.
|
|
*
|
|
* Returned Value:
|
|
* OK - The packet was processed (or dropped) and can be discarded.
|
|
* ERROR - Hold the packet and try again later. There is a listening
|
|
* socket but no receive in place to catch the packet yet. The
|
|
* device's d_len will be set to zero in this case as there is
|
|
* no outgoing data.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int ipv4_in(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct ipv4_hdr_s *ipv4 = IPv4BUF;
|
|
in_addr_t destipaddr;
|
|
uint16_t totlen;
|
|
bool isfrag;
|
|
int ret = OK;
|
|
|
|
/* Handle ARP on input then give the IPv4 packet to the network layer */
|
|
|
|
arp_ipin(dev);
|
|
|
|
/* This is where the input processing starts. */
|
|
|
|
#ifdef CONFIG_NET_STATISTICS
|
|
g_netstats.ipv4.recv++;
|
|
#endif
|
|
|
|
/* Start of IP input header processing code.
|
|
*
|
|
* Check validity of the IP header.
|
|
* REVISIT: Does not account for varying IP header length due to the
|
|
* presences of IPv4 options. The header length is encoded as a number
|
|
* 32-bit words in the HL nibble of the VHL.
|
|
*/
|
|
|
|
if ((ipv4->vhl & IP_VERSION_MASK) != 0x40 ||
|
|
(ipv4->vhl & IPv4_HLMASK) < 5)
|
|
{
|
|
/* IP version and header length. */
|
|
|
|
#ifdef CONFIG_NET_STATISTICS
|
|
g_netstats.ipv4.drop++;
|
|
g_netstats.ipv4.vhlerr++;
|
|
#endif
|
|
nwarn("WARNING: Invalid IP version or header length: %02x\n",
|
|
ipv4->vhl);
|
|
goto drop;
|
|
}
|
|
|
|
/* Get the size of the packet minus the size of link layer header */
|
|
|
|
dev->d_len -= NET_LL_HDRLEN(dev);
|
|
|
|
if (((ipv4->vhl & IPv4_HLMASK) << 2) > dev->d_len)
|
|
{
|
|
nwarn("WARNING: Packet shorter than IPv4 header\n");
|
|
goto drop;
|
|
}
|
|
|
|
/* Make sure that all packet processing logic knows that there is an IPv4
|
|
* packet in the device buffer.
|
|
*/
|
|
|
|
IFF_SET_IPv4(dev->d_flags);
|
|
|
|
/* Check the size of the packet. If the size reported to us in d_len is
|
|
* smaller the size reported in the IP header, we assume that the packet
|
|
* has been corrupted in transit. If the size of d_len is larger than the
|
|
* size reported in the IP packet header, the packet has been padded and
|
|
* we set d_len to the correct value.
|
|
*/
|
|
|
|
totlen = (ipv4->len[0] << 8) + ipv4->len[1];
|
|
if (totlen < dev->d_len)
|
|
{
|
|
iob_update_pktlen(dev->d_iob, totlen, false);
|
|
dev->d_len = totlen;
|
|
}
|
|
else if (totlen > dev->d_len)
|
|
{
|
|
#ifdef CONFIG_NET_STATISTICS
|
|
g_netstats.ipv4.drop++;
|
|
#endif
|
|
nwarn("WARNING: IP packet shorter than length in IP header\n");
|
|
goto drop;
|
|
}
|
|
|
|
/* Check the fragment flag. */
|
|
|
|
isfrag = (ipv4->ipoffset[0] & 0x3f) != 0 || ipv4->ipoffset[1] != 0;
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (ipv4_check_opt(ipv4) != OK)
|
|
{
|
|
#ifdef CONFIG_NET_STATISTICS
|
|
g_netstats.ipv4.drop++;
|
|
#endif
|
|
nwarn("WARNING: IP options error\n");
|
|
goto drop;
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_NET_NAT44) || defined(CONFIG_NET_IPFILTER)
|
|
if (isfrag)
|
|
{
|
|
return ipv4_fragin_or_drop(dev);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_NET_NAT44
|
|
/* Try NAT inbound, rule matching will be performed in NAT module. */
|
|
|
|
ipv4_nat_inbound(dev, ipv4);
|
|
#endif
|
|
|
|
/* Get the destination IP address in a friendlier form */
|
|
|
|
destipaddr = net_ip4addr_conv32(ipv4->destipaddr);
|
|
|
|
/* Keep the common local-unicast case on the fast path, then classify
|
|
* non-local packets as broadcast, multicast, or forwardable unicast.
|
|
*/
|
|
|
|
if (net_ipv4addr_cmp(destipaddr, dev->d_ipaddr))
|
|
{
|
|
#ifdef CONFIG_NET_ICMP
|
|
if (net_ipv4addr_cmp(dev->d_ipaddr, INADDR_ANY))
|
|
{
|
|
nwarn("WARNING: No IP address assigned\n");
|
|
goto drop;
|
|
}
|
|
#endif
|
|
}
|
|
else if (net_ipv4addr_cmp(destipaddr, INADDR_BROADCAST) ||
|
|
(net_ipv4addr_maskcmp(destipaddr, dev->d_ipaddr,
|
|
dev->d_netmask) &&
|
|
net_ipv4addr_broadcast(destipaddr, dev->d_netmask)))
|
|
{
|
|
#ifdef CONFIG_NET_IPFORWARD_BROADCAST
|
|
ipv4_forward_broadcast(dev, ipv4);
|
|
#endif
|
|
|
|
if (isfrag)
|
|
{
|
|
return ipv4_fragin_or_drop(dev);
|
|
}
|
|
|
|
#if defined(CONFIG_NET_BROADCAST) && defined(NET_UDP_HAVE_STACK)
|
|
if (ipv4->proto == IP_PROTO_UDP)
|
|
{
|
|
ret = udp_ipv4_input(dev);
|
|
goto done;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_NET_STATISTICS
|
|
g_netstats.ipv4.drop++;
|
|
#endif
|
|
goto drop;
|
|
}
|
|
else if (IN_MULTICAST(NTOHL(destipaddr)))
|
|
{
|
|
#ifdef CONFIG_NET_IGMP
|
|
if (igmp_grpfind(dev, &destipaddr) != NULL)
|
|
{
|
|
#ifdef CONFIG_NET_IPFORWARD_BROADCAST
|
|
ipv4_forward_broadcast(dev, ipv4);
|
|
#endif
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
#ifdef CONFIG_NET_STATISTICS
|
|
g_netstats.ipv4.drop++;
|
|
#endif
|
|
goto drop;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef CONFIG_NET_IPFORWARD
|
|
/* Try to forward the unicast packet. */
|
|
|
|
if (ipv4_forward(dev, ipv4) >= 0)
|
|
{
|
|
/* The packet was forwarded. Return success; d_len will
|
|
* be set appropriately by the forwarding logic: Cleared
|
|
* if the packet is forward via another device or non-zero
|
|
* if it will be forwarded by the same device that it was
|
|
* received on.
|
|
*/
|
|
|
|
goto done;
|
|
}
|
|
else
|
|
#endif
|
|
#if defined(NET_UDP_HAVE_STACK) && defined(CONFIG_NET_BINDTODEVICE)
|
|
/* If the protocol specific socket option NET_BINDTODEVICE
|
|
* is selected, then we must forward all UDP packets to the bound
|
|
* socket.
|
|
*/
|
|
|
|
if (ipv4->proto != IP_PROTO_UDP)
|
|
#endif
|
|
{
|
|
/* Not destined for us and not forwardable... Drop the
|
|
* packet.
|
|
*/
|
|
|
|
ninfo("WARNING: Not destined for us; not forwardable... "
|
|
"Dropping!\n");
|
|
|
|
#ifdef CONFIG_NET_STATISTICS
|
|
g_netstats.ipv4.drop++;
|
|
#endif
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
/* Forwarding has already had a chance to consume non-local unicast
|
|
* fragments. Any remaining fragments are for local input and must be
|
|
* reassembled before the transport layer sees them.
|
|
*/
|
|
|
|
if (isfrag)
|
|
{
|
|
return ipv4_fragin_or_drop(dev);
|
|
}
|
|
|
|
#ifdef CONFIG_NET_IPV4_CHECKSUMS
|
|
if (((dev->d_features & NETDEV_RX_CSUM) == 0)
|
|
&& (ipv4_chksum(IPv4BUF) != 0xffff))
|
|
{
|
|
/* Compute and check the IP header checksum. */
|
|
|
|
#ifdef CONFIG_NET_STATISTICS
|
|
g_netstats.ipv4.drop++;
|
|
g_netstats.ipv4.chkerr++;
|
|
#endif
|
|
nwarn("WARNING: Bad IP checksum\n");
|
|
goto drop;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_NET_IPFILTER
|
|
if (ipv4_filter_in(dev) != IPFILTER_TARGET_ACCEPT)
|
|
{
|
|
ninfo("Drop/Reject INPUT packet due to filter.\n");
|
|
goto done;
|
|
}
|
|
#endif
|
|
|
|
/* Now process the incoming packet according to the protocol. */
|
|
|
|
switch (ipv4->proto)
|
|
{
|
|
#ifdef NET_TCP_HAVE_STACK
|
|
case IP_PROTO_TCP: /* TCP input */
|
|
tcp_ipv4_input(dev);
|
|
break;
|
|
#endif
|
|
|
|
#ifdef NET_UDP_HAVE_STACK
|
|
case IP_PROTO_UDP: /* UDP input */
|
|
udp_ipv4_input(dev);
|
|
break;
|
|
#endif
|
|
|
|
#ifdef NET_ICMP_HAVE_STACK
|
|
/* Check for ICMP input */
|
|
|
|
case IP_PROTO_ICMP: /* ICMP input */
|
|
icmp_input(dev);
|
|
break;
|
|
#endif
|
|
|
|
#ifdef CONFIG_NET_IGMP
|
|
/* Check for IGMP input */
|
|
|
|
case IP_PROTO_IGMP: /* IGMP input */
|
|
igmp_input(dev);
|
|
break;
|
|
#endif
|
|
|
|
default: /* Unrecognized/unsupported protocol */
|
|
#ifdef CONFIG_NET_STATISTICS
|
|
g_netstats.ipv4.drop++;
|
|
g_netstats.ipv4.protoerr++;
|
|
#endif
|
|
|
|
nwarn("WARNING: Unrecognized IP protocol\n");
|
|
#if defined(CONFIG_NET_ICMP) && !defined(CONFIG_NET_ICMP_NO_STACK)
|
|
icmp_reply(dev, ICMP_DEST_UNREACHABLE, ICMP_PROT_UNREACH);
|
|
goto done;
|
|
#else
|
|
goto drop;
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_NET_IPFILTER
|
|
ipfilter_out(dev);
|
|
#endif
|
|
|
|
#if defined(CONFIG_NET_IPFORWARD) || defined(CONFIG_NET_IPFILTER) || \
|
|
(defined(CONFIG_NET_BROADCAST) && defined(NET_UDP_HAVE_STACK)) || \
|
|
defined(CONFIG_NET_ICMP) && !defined(CONFIG_NET_ICMP_NO_STACK)
|
|
done:
|
|
#endif
|
|
|
|
#ifdef CONFIG_NET_IPFRAG
|
|
ip_fragout(dev);
|
|
#endif
|
|
|
|
devif_out(dev);
|
|
|
|
/* Return and let the caller do any pending transmission. */
|
|
|
|
return ret;
|
|
|
|
/* Drop the packet. NOTE that OK is returned meaning that the
|
|
* packet has been processed (although processed unsuccessfully).
|
|
*/
|
|
|
|
drop:
|
|
dev->d_len = 0;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: ipv4_input
|
|
*
|
|
* Description:
|
|
* Receive an IPv4 packet from the network device. Verify and forward to
|
|
* L3 packet handling logic if the packet is destined for us.
|
|
*
|
|
* Input Parameters:
|
|
* dev - The device on which the packet was received and which contains
|
|
* the IPv4 packet.
|
|
*
|
|
* Returned Value:
|
|
* OK - The packet was processed (or dropped) and can be discarded.
|
|
* ERROR - Hold the packet and try again later. There is a listening
|
|
* socket but no receive in place to catch the packet yet. The
|
|
* device's d_len will be set to zero in this case as there is
|
|
* no outgoing data.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int ipv4_input(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR uint8_t *buf;
|
|
int ret;
|
|
|
|
netdev_lock(dev);
|
|
|
|
/* Store reception timestamp if enabled and not provided by hardware. */
|
|
|
|
#if defined(CONFIG_NET_TIMESTAMP) && !defined(CONFIG_ARCH_HAVE_NETDEV_TIMESTAMP)
|
|
clock_gettime(CLOCK_REALTIME, &dev->d_rxtime);
|
|
#endif
|
|
|
|
if (dev->d_iob != NULL)
|
|
{
|
|
buf = dev->d_buf;
|
|
|
|
/* Set the device buffer to l2 */
|
|
|
|
dev->d_buf = NETLLBUF;
|
|
ret = ipv4_in(dev);
|
|
|
|
dev->d_buf = buf;
|
|
|
|
netdev_unlock(dev);
|
|
return ret;
|
|
}
|
|
|
|
ret = netdev_input(dev, ipv4_in, true);
|
|
netdev_unlock(dev);
|
|
return ret;
|
|
}
|
|
|
|
#endif /* CONFIG_NET_IPv4 */
|