Files
nuttx/net/ipforward/ipv4_forward.c
T
Shunchao Hu eb4df019af
Build Documentation / build-html (push) Has been cancelled
net/ipforward: Forbid non-forwardable multicast scopes.
RFC 3171 reserves 224.0.0.0/24 for link-local IPv4 multicast
scope, so packets in this range must not be forwarded by routers,
regardless of the TTL value.

IPv6 also defines multicast scopes that must not be forwarded beyond
the local topology. In particular, interface-local and link-local
multicast destinations must not be routed across interfaces.

Add IPv4/IPv6 scope checks so non-forwardable multicast packets are
rejected before entering the multicast forwarding path.

Signed-off-by: Shunchao Hu <ankohuu@gmail.com>
2026-04-18 19:54:59 -03:00

663 lines
19 KiB
C

/****************************************************************************
* net/ipforward/ipv4_forward.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 <string.h>
#include <assert.h>
#include <nuttx/debug.h>
#include <errno.h>
#include <nuttx/mm/iob.h>
#include <nuttx/net/net.h>
#include <nuttx/net/netdev.h>
#include <nuttx/net/netstats.h>
#include "netdev/netdev.h"
#include "utils/utils.h"
#include "sixlowpan/sixlowpan.h"
#include "icmp/icmp.h"
#include "ipfilter/ipfilter.h"
#include "ipforward/ipforward.h"
#include "nat/nat.h"
#include "devif/devif.h"
#if defined(CONFIG_NET_IPFORWARD) && defined(CONFIG_NET_IPv4)
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ipv4_hdrsize
*
* Description:
* Return the size of the IPv4 header and the following.
*
* Input Parameters:
* ipv4 - A pointer to the IPv4 header in within the IPv4 packet. This
* is immediately followed by the L3 header which may be TCP, UDP
* or ICMP.
*
* Returned Value:
* The size of the combined L2 + L3 headers is returned on success. An
* error is returned only if the prototype is not supported.
*
****************************************************************************/
#ifdef CONFIG_DEBUG_NET_WARN
static int ipv4_hdrsize(FAR struct ipv4_hdr_s *ipv4)
{
uint16_t iphdrlen;
/* Get the IP header length (accounting for possible options). */
iphdrlen = (ipv4->vhl & IPv4_HLMASK) << 2;
/* Size is also determined by the following protocol header, */
switch (ipv4->proto)
{
#ifdef CONFIG_NET_TCP
case IP_PROTO_TCP:
{
FAR struct tcp_hdr_s *tcp =
(FAR struct tcp_hdr_s *)((FAR uint8_t *)ipv4 + iphdrlen);
unsigned int tcpsize;
/* The TCP header length is encoded in the top 4 bits of the
* tcpoffset field (in units of 32-bit words).
*/
tcpsize = ((uint16_t)tcp->tcpoffset >> 4) << 2;
return iphdrlen + tcpsize;
}
break;
#endif
#ifdef CONFIG_NET_UDP
case IP_PROTO_UDP:
return iphdrlen + UDP_HDRLEN;
break;
#endif
#ifdef CONFIG_NET_ICMP
case IP_PROTO_ICMP:
return iphdrlen + ICMP_HDRLEN;
break;
#endif
default:
nwarn("WARNING: Unrecognized proto: %u\n", ipv4->proto);
return -EPROTONOSUPPORT;
}
}
#endif
/****************************************************************************
* Name: ipv4_decr_ttl
*
* Description:
* Decrement the IPv4 TTL (time to live value). TTL field is set by the
* sender of the packet and reduced by every router on the route to its
* destination. If the TTL field reaches zero before the datagram arrives
* at its destination, then the datagram is discarded and an ICMP error
* packet (11 - Time Exceeded) is sent back to the sender.
*
* The purpose of the TTL field is to avoid a situation in which an
* undeliverable datagram keeps circulating on an Internet system, and
* such a system eventually becoming swamped by such "immortals".
*
* Input Parameters:
* ipv4 - A pointer to the IPv4 header in within the IPv4 packet to be
* forwarded.
*
* Returned Value:
* The new TTL value is returned. A value <= 0 means the hop limit has
* expired.
*
****************************************************************************/
static int ipv4_decr_ttl(FAR struct ipv4_hdr_s *ipv4)
{
uint16_t iphdrlen;
uint16_t sum;
int ttl;
/* Check time-to-live (TTL) */
ttl = (int)ipv4->ttl - 1;
if (ttl <= 0)
{
/* Return zero which must cause the packet to be dropped */
return 0;
}
/* Save the updated TTL value */
ipv4->ttl = ttl;
/* Get the IP header length (accounting for possible options). */
iphdrlen = (ipv4->vhl & IPv4_HLMASK) << 2;
/* Re-calculate the IPv4 checksum. This checksum is the Internet checksum
* of the 20 bytes of the IPv4 header. This checksum will be different
* because we just modify the IPv4 TTL.
*/
ipv4->ipchksum = 0;
sum = chksum(0, (FAR const uint8_t *)ipv4, iphdrlen);
if (sum == 0)
{
sum = 0xffff;
}
else
{
sum = HTONS(sum);
}
ipv4->ipchksum = ~sum;
return ttl;
}
/****************************************************************************
* Name: ipv4_dev_forward
*
* Description:
* This function is called from ipv4_forward when it is necessary to
* forward a packet from the current device to different device. In this
* case, the forwarding operation must be performed asynchronously when
* the TX poll is received from the forwarding device.
*
* Input Parameters:
* dev - The device on which the packet was received and which
* contains the IPv4 packet.
* fwdddev - The device on which the packet must be forwarded.
* ipv4 - A pointer to the IPv4 header in within the IPv4 packet
*
* Returned Value:
* Zero is returned if the packet was successfully forward; A negated
* errno value is returned if the packet is not forwardable. In that
* latter case, the caller (ipv4_input()) should drop the packet.
*
****************************************************************************/
static int ipv4_dev_forward(FAR struct net_driver_s *dev,
FAR struct net_driver_s *fwddev,
FAR struct ipv4_hdr_s *ipv4)
{
FAR struct forward_s *fwd = NULL;
#ifdef CONFIG_DEBUG_NET_WARN
int hdrsize;
#endif
int ret;
if (IFF_IS_NODST_FORWARD(fwddev->d_flags))
{
nwarn("WARNING: IP forwarding disabled on destination device %s\n",
fwddev->d_ifname);
ret = -EOPNOTSUPP;
goto errout;
}
#ifdef CONFIG_NET_IPFILTER
/* Do filter before forwarding, to make sure we drop silently before
* replying any other errors.
*/
ret = ipv4_filter_fwd(dev, fwddev, ipv4);
if (ret < 0)
{
ninfo("Drop/Reject FORWARD packet due to filter %d\n", ret);
/* Let ipv4_forward reply the reject. */
if (ret == IPFILTER_TARGET_REJECT)
{
ret = -ENETUNREACH;
}
goto errout;
}
#endif
/* Verify that the full packet will fit within the forwarding device's MTU
* if DF is set.
*/
if (NET_LL_HDRLEN(fwddev) + dev->d_len > NETDEV_PKTSIZE(fwddev)
#ifdef CONFIG_NET_IPFRAG
&& (ipv4->ipoffset[0] & (IP_FLAG_DONTFRAG >> 8))
#endif
)
{
nwarn("WARNING: Packet > MTU... Dropping\n");
ret = -EFBIG;
goto errout;
}
/* Get a pre-allocated forwarding structure, This structure will be
* completely zeroed when we receive it.
*/
fwd = ipfwd_alloc();
if (fwd == NULL)
{
nwarn("WARNING: Failed to allocate forwarding structure\n");
ret = -ENOMEM;
goto errout;
}
/* Initialize the easy stuff in the forwarding structure */
fwd->f_dev = fwddev; /* Forwarding device */
#ifdef CONFIG_NET_IPv6
fwd->f_domain = PF_INET; /* IPv4 address domain */
#endif
#ifdef CONFIG_DEBUG_NET_WARN
/* Get the size of the IPv4 + L3 header. */
hdrsize = ipv4_hdrsize(ipv4);
if (hdrsize < IPv4_HDRLEN)
{
nwarn("WARNING: Could not determine L2+L3 header size\n");
ret = -EPROTONOSUPPORT;
goto errout_with_fwd;
}
/* The L2/L3 headers must fit within one, contiguous IOB. */
if (hdrsize > CONFIG_IOB_BUFSIZE)
{
nwarn("WARNING: Header is too big for pre-allocated structure\n");
ret = -E2BIG;
goto errout_with_fwd;
}
#endif
/* Relay the device buffer */
fwd->f_iob = dev->d_iob;
/* Decrement the TTL in the copy of the IPv4 header (retaining the
* original TTL in the source to handle the broadcast case). If the
* TLL decrements to zero, then do not forward the packet.
*/
ret = ipv4_decr_ttl(ipv4);
if (ret < 1)
{
nwarn("WARNING: Hop limit exceeded... Dropping!\n");
ret = -EMULTIHOP;
goto errout_with_fwd;
}
#ifdef CONFIG_NET_NAT44
/* Try NAT outbound, rule matching will be performed in NAT module. */
ret = ipv4_nat_outbound(fwd->f_dev, ipv4, NAT_MANIP_SRC);
if (ret < 0)
{
nwarn("WARNING: Performing NAT44 outbound failed, dropping!\n");
goto errout_with_fwd;
}
#endif
/* Then set up to forward the packet according to the protocol. */
ret = ipfwd_forward(fwd);
if (ret >= 0)
{
netdev_iob_clear(dev);
return OK;
}
errout_with_fwd:
if (fwd != NULL)
{
ipfwd_free(fwd);
}
errout:
return ret;
}
/****************************************************************************
* Name: ipv4_forward_callback
*
* Description:
* This function is a callback from netdev_foreach. It implements the
* the broadcast forwarding action for each network device (other than, of
* course, the device that received the packet).
*
* Input Parameters:
* dev - The device on which the packet was received and which contains
* the IPv4 packet.
* ipv4 - A convenience pointer to the IPv4 header in within the IPv4
* packet
*
* Returned Value:
* Typically returns zero (meaning to continue the enumeration), but will
* return a non-zero to stop the enumeration if an error occurs.
*
****************************************************************************/
#ifdef CONFIG_NET_IPFORWARD_BROADCAST
static int ipv4_forward_callback(FAR struct net_driver_s *fwddev,
FAR void *arg)
{
FAR struct net_driver_s *dev = (FAR struct net_driver_s *)arg;
FAR struct ipv4_hdr_s *ipv4;
FAR struct iob_s *iob;
int ret;
DEBUGASSERT(fwddev != NULL);
/* Only IFF_RUNNING device and non-loopback device need forward packet */
if (!IFF_IS_RUNNING(fwddev->d_flags) ||
fwddev->d_lltype == NET_LL_LOOPBACK)
{
return OK;
}
DEBUGASSERT(dev != NULL && dev->d_buf != NULL);
/* Check if we are forwarding on the same device that we received the
* packet from.
*/
if (fwddev != dev)
{
/* Backup the forward IP packet */
iob = netdev_iob_clone(dev, true);
if (iob == NULL)
{
nerr("ERROR: IOB clone failed when forwarding broadcast.\n");
return -ENOMEM;
}
/* Recover the pointer to the IPv4 header in the receiving device's
* d_buf.
*/
ipv4 = IPv4BUF;
/* Send the packet asynchrously on the forwarding device. */
ret = ipv4_dev_forward(dev, fwddev, ipv4);
if (ret < 0)
{
iob_free_chain(iob);
nwarn("WARNING: ipv4_dev_forward failed: %d\n", ret);
return ret;
}
/* Restore device iob with backup iob */
netdev_iob_replace(dev, iob);
}
return OK;
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: ipv4_forward
*
* Description:
* This function is called from ipv4_input when a packet is received that
* is not destined for us. In this case, the packet may need to be
* forwarded to another device (or sent back out the same device)
* depending configuration, routing table information, and the IPv4
* networks served by various network devices.
*
* Input Parameters:
* dev - The device on which the packet was received and which contains
* the IPv4 packet.
* ipv4 - A convenience pointer to the IPv4 header in within the IPv4
* packet
*
* On input:
* - dev->d_buf holds the received packet.
* - dev->d_len holds the length of the received packet MINUS the
* size of the L1 header. That was subtracted out by ipv4_input.
* - ipv4 points to the IPv4 header with dev->d_buf.
*
* Returned Value:
* Zero is returned if the packet was successfully forward; A negated
* errno value is returned if the packet is not forwardable. In that
* latter case, the caller (ipv4_input()) should drop the packet.
*
****************************************************************************/
int ipv4_forward(FAR struct net_driver_s *dev, FAR struct ipv4_hdr_s *ipv4)
{
in_addr_t destipaddr;
in_addr_t srcipaddr;
FAR struct net_driver_s *fwddev;
int ret;
#if defined(CONFIG_NET_ICMP) && !defined(CONFIG_NET_ICMP_NO_STACK)
int icmp_reply_type;
int icmp_reply_code;
#endif /* CONFIG_NET_ICMP */
if (IFF_IS_NOSRC_FORWARD(dev->d_flags))
{
nwarn("WARNING: IP forwarding disabled on source device %s\n",
dev->d_ifname);
ret = -EOPNOTSUPP;
goto drop;
}
/* Search for a device that can forward this packet. */
destipaddr = net_ip4addr_conv32(ipv4->destipaddr);
srcipaddr = net_ip4addr_conv32(ipv4->srcipaddr);
fwddev = netdev_findby_ripv4addr(srcipaddr, destipaddr);
if (fwddev == NULL)
{
nwarn("WARNING: Not routable\n");
ret = -ENETUNREACH;
goto drop;
}
/* Check if we are forwarding on the same device that we received the
* packet from.
*/
if (fwddev != dev)
{
/* Send the packet asynchrously on the forwarding device. */
ret = ipv4_dev_forward(dev, fwddev, ipv4);
if (ret < 0)
{
nwarn("WARNING: ipv4_dev_forward failed: %d\n", ret);
goto drop;
}
}
else
{
/* Single network device. The use case here is where an endpoint acts
* as a hub in a star configuration. This is typical for a wireless
* star configuration where not all endpoints are accessible from all
* other endpoints, but seems less useful for a wired network.
*/
#ifdef CONFIG_NET_ETHERNET
/* REVISIT: For Ethernet we may have to fix up the Ethernet header:
* - source MAC, the MAC of the current device.
* - dest MAC, the MAC associated with the destination IPv4 address.
* This will involve ICMP.
*/
/* Correct dev->d_buf by adding back the L1 header length */
#endif
nwarn("WARNING: Packet forwarding to same device not supported\n");
ret = -ENOSYS;
goto drop;
}
/* Return success. ipv4_input will return to the network driver with
* dev->d_len set to the packet size and the network driver will perform
* the transfer.
*/
return OK;
drop:
ipv4_dropstats(ipv4);
#if defined(CONFIG_NET_ICMP) && !defined(CONFIG_NET_ICMP_NO_STACK)
/* Reply ICMP to the sender for particular errors. */
switch (ret)
{
case -ENETUNREACH:
icmp_reply_type = ICMP_DEST_UNREACHABLE;
icmp_reply_code = ICMP_NET_UNREACH;
goto reply;
case -EFBIG:
icmp_reply_type = ICMP_DEST_UNREACHABLE;
icmp_reply_code = ICMP_FRAG_NEEDED;
goto reply;
case -EMULTIHOP:
icmp_reply_type = ICMP_TIME_EXCEEDED;
icmp_reply_code = ICMP_EXC_TTL;
goto reply;
case -EOPNOTSUPP:
icmp_reply_type = ICMP_DEST_UNREACHABLE;
icmp_reply_code = ICMP_HOST_UNREACH;
goto reply;
default:
break; /* We don't know how to reply, just go on (to drop). */
}
#endif /* CONFIG_NET_ICMP */
dev->d_len = 0;
return ret;
#if defined(CONFIG_NET_ICMP) && !defined(CONFIG_NET_ICMP_NO_STACK)
reply:
# ifdef CONFIG_NET_NAT44
/* Before we reply ICMP, call NAT outbound to try to translate destination
* address & port back to original status.
*/
ipv4_nat_outbound(dev, ipv4, NAT_MANIP_DST);
# endif /* CONFIG_NET_NAT44 */
icmp_reply(dev, icmp_reply_type, icmp_reply_code);
return OK;
#endif /* CONFIG_NET_ICMP */
}
/****************************************************************************
* Name: ipv4_forward_broadcast
*
* Description:
* This function is called from ipv4_input when a broadcast or multicast
* packet is received. If CONFIG_NET_IPFORWARD_BROADCAST is enabled, this
* function will forward the broadcast packet to other networks through
* other network devices.
*
* Input Parameters:
* dev - The device on which the packet was received and which contains
* the IPv4 packet.
* ipv4 - A convenience pointer to the IPv4 header in within the IPv4
* packet
*
* On input:
* - dev->d_buf holds the received packet.
* - dev->d_len holds the length of the received packet MINUS the
* size of the L1 header. That was subtracted out by ipv4_input.
* - ipv4 points to the IPv4 header with dev->d_buf.
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_NET_IPFORWARD_BROADCAST
void ipv4_forward_broadcast(FAR struct net_driver_s *dev,
FAR struct ipv4_hdr_s *ipv4)
{
/* Check if source device supports IP forwarding capability.
* Broadcast/multicast forwarding is only allowed if the receiving
* device has SRC_FORWARD enabled. This is consistent with the unicast
* forwarding policy enforced in ipv4_forward().
*/
if (IFF_IS_NOSRC_FORWARD(dev->d_flags))
{
nwarn("WARNING: IP broadcast forwarding disabled "
"on source device %s\n", dev->d_ifname);
return;
}
/* Do not forward link-local multicast packets (224.0.0.0/24).
* Per RFC 3171, addresses in 224.0.0.0/24 are reserved for
* link-local scope and MUST NOT be forwarded by any router,
* regardless of TTL.
*/
if ((net_ip4addr_conv32(ipv4->destipaddr) &
HTONL(0xffffff00)) == HTONL(0xe0000000))
{
return;
}
/* Don't bother if the TTL would expire */
if (ipv4->ttl > 1)
{
/* Forward the broadcast/multicast packet to all devices except,
* of course, the device that received the packet.
*/
netdev_foreach(ipv4_forward_callback, dev);
}
}
#endif
#endif /* CONFIG_NET_IPFORWARD && CONFIG_NET_IPv4 */