/**************************************************************************** * net/ipforward/ipv6_forward.c * * Copyright (C) 2017 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * 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. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "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 * COPYRIGHT OWNER OR CONTRIBUTORS 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. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include "netdev/netdev.h" #include "sixlowpan/sixlowpan.h" #include "devif/devif.h" #include "ipforward/ipforward.h" #if defined(CONFIG_NET_IPFORWARD) && defined(CONFIG_NET_IPv6) /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define PACKET_FORWARDED 0 #define PACKET_NOT_FORWARDED 1 /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: ipv6_hdrsize * * Description: * Return the size of the IPv6 header and the following. * * Input Parameters: * ipv6 - A pointer to the IPv6 header in within the IPv6 packet. This * is immeidately followed by the L3 header which may be TCP, UDP, * or ICMPv6. * * 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. * ****************************************************************************/ #if defined(CONFIG_NETDEV_MULTINIC) && defined(CONFIG_DEBUG_NET_WARN) static int ipv6_hdrsize(FAR struct ipv6_hdr_s *ipv6) { /* Size is determined by the following protocol header, */ switch (ipv6->proto) { #ifdef CONFIG_NET_TCP case IP_PROTO_TCP: { FAR struct tcp_hdr_s *tcp = (FAR struct tcp_hdr_s *)((FAR uint8_t *)ipv6 + IPv6_HDRLEN); 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 IPv6_HDRLEN + tcpsize; } break; #endif #ifdef CONFIG_NET_UDP case IP_PROTO_UDP: return IPv6_HDRLEN + UDP_HDRLEN; break; #endif #ifdef CONFIG_NET_ICMPv6 case IP_PROTO_ICMP6: return IPv6_HDRLEN + ICMPv6_HDRLEN; break; #endif default: nwarn("WARNING: Unrecognized proto: %u\n", ipv6->proto); return -EPROTONOSUPPORT; } } #endif /**************************************************************************** * Name: ipv6_decr_ttl * * Description: * Decrement the IPv6 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: * ipv6 - A pointer to the IPv6 header in within the IPv6 packet to be * forwarded. * * Returned Value: * The new TTL value is returned. A value <= 0 means the hop limit has * expired. * ****************************************************************************/ #if defined(CONFIG_NETDEV_MULTINIC) || defined(CONFIG_NET_6LOWPAN) static int ipv6_decr_ttl(FAR struct ipv6_hdr_s *ipv6) { int ttl = (int)ipv6->ttl - 1; if (ttl <= 0) { #ifdef CONFIG_NET_ICMPv6 /* Return an ICMPv6 error packet back to the sender. */ # warning Missing logic #endif /* Return zero which must cause the packet to be dropped */ return 0; } /* Save the updated TTL value */ ipv6->ttl = ttl; /* NOTE: We do not have to recalculate the IPv6 checksum because (1) the * IPv6 header does not include a checksum itself and (2) the TTL is not * included in the sum for the TCP and UDP headers. */ return ttl; } #endif /**************************************************************************** * Name: ipv6_packet_conversion * * Description: * Generic output conversion hook. Only needed for IEEE802.15.4 for now * but this is a point where support for other conversions may be * provided. * * Returned value: * PACKET_FORWARDED - Packet was forwarded * PACKET_NOT_FORWARDED - Packet was not forwarded * < 0 - And error occurred (and packet not fowarded). * ****************************************************************************/ #ifdef CONFIG_NET_6LOWPAN static int ipv6_packet_conversion(FAR struct net_driver_s *dev, FAR struct net_driver_s *fwddev, FAR struct ipv6_hdr_s *ipv6) { int ret = PACKET_NOT_FORWARDED; if (dev->d_len > 0) { #ifdef CONFIG_NET_MULTILINK /* Handle the case where multiple link layer protocols are supported */ if (fwddev->d_lltype != NET_LL_IEEE802154 && fwddev->d_lltype != NET_LL_PKTRADIO) { nwarn("WARNING: Unsupported link layer... Not forwarded\n"); } else #endif #ifdef CONFIG_NET_TCP if (ipv6->proto == IP_PROTO_TCP) { /* Decrement the TTL in the IPv6 header. If it decrements to * zero, then drop the packet. */ ret = ipv6_decr_ttl(ipv6); if (ret < 1) { nwarn("WARNING: Hop limit exceeded... Dropping!\n"); ret = -EMULTIHOP; } else { /* Let 6LoWPAN convert IPv6 TCP output into IEEE802.15.4 * frames. */ sixlowpan_tcp_send(dev, fwddev, ipv6); /* The packet was forwarded */ dev->d_len = 0; return PACKET_FORWARDED; } } else #endif #ifdef CONFIG_NET_UDP if (ipv6->proto == IP_PROTO_UDP) { /* Decrement the TTL in the IPv6 header. If it decrements to * zero, then drop the packet. */ ret = ipv6_decr_ttl(ipv6); if (ret < 1) { nwarn("WARNING: Hop limit exceeded... Dropping!\n"); ret = -EMULTIHOP; } else { /* Let 6LoWPAN convert IPv6 UDP output into IEEE802.15.4 * frames. */ sixlowpan_udp_send(dev, fwddev, ipv6); /* The packet was forwarded */ dev->d_len = 0; return PACKET_FORWARDED; } } else #endif #ifdef CONFIG_NET_ICMPv6 if (ipv6->proto == IP_PROTO_ICMP6) { /* Decrement the TTL in the IPv6 header. If it decrements to * zero, then drop the packet. */ ret = ipv6_decr_ttl(ipv6); if (ret < 1) { nwarn("WARNING: Hop limit exceeded... Dropping!\n"); ret = -EMULTIHOP; } else { /* Let 6LoWPAN convert IPv6 ICMPv6 output into IEEE802.15.4 * frames. */ sixlowpan_icmpv6_send(dev, fwddev, ipv6); /* The packet was forwarded */ dev->d_len = 0; return PACKET_FORWARDED; } } else #endif { /* Otherwise, we cannot forward the packet */ nwarn("WARNING: Dropping. Unsupported 6LoWPAN protocol: %d\n", ipv6->proto); } } /* The packet was not forwarded (or the HOP limit was exceeded) */ ipv6_dropstats(ipv6); return ret; } #else # define ipv6_packet_conversion(dev, fwddev, ipv6) (PACKET_NOT_FORWARDED) #endif /* CONFIG_NET_6LOWPAN */ /**************************************************************************** * Name: ipv6_dev_forward * * Description: * This function is called from ipv6_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 IPv6 packet. * fwdddev - The device on which the packet must be forwarded. * ipv6 - A pointer to the IPv6 header in within the IPv6 packet * * Returned Value: * Zero is returned if the packet was successfully forwarded; A negated * errno value is returned if the packet is not forwardable. In that * latter case, the caller (ipv6_input()) should drop the packet. * ****************************************************************************/ #ifdef CONFIG_NETDEV_MULTINIC static int ipv6_dev_forward(FAR struct net_driver_s *dev, FAR struct net_driver_s *fwddev, FAR struct ipv6_hdr_s *ipv6) { FAR struct forward_s *fwd = NULL; #ifdef CONFIG_DEBUG_NET_WARN int hdrsize; #endif int ret; /* Perform any necessary packet conversions. */ ret = ipv6_packet_conversion(dev, fwddev, ipv6); if (ret < 0) { nwarn("WARNING: ipv6_packet_conversion failed: %d\n", ret); goto errout; } else if (ret == PACKET_NOT_FORWARDED) { /* Verify that the full packet will fit within the forwarding devices * MTU. We provide no support for fragmenting forwarded packets. */ if (NET_LL_HDRLEN(fwddev) + dev->d_len > NET_DEV_MTU(fwddev)) { 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_IPv4 fwd->f_domain = PF_INET6; /* IPv6 address domain */ #endif #ifdef CONFIG_DEBUG_NET_WARN /* Get the size of the IPv6 + L3 header. */ hdrsize = ipv6_hdrsize(ipv6); if (hdrsize < IPv6_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 /* Try to allocate the head of an IOB chain. If this fails, the * packet will be dropped; we are not operating in a context where * waiting for an IOB is a good idea */ fwd->f_iob = iob_tryalloc(false); if (fwd->f_iob == NULL) { nwarn("WARNING: iob_tryalloc() failed\n"); ret = -ENOMEM; goto errout_with_fwd; } /* Copy the L2/L3 headers plus any following payload into an IOB * chain. iob_trycopin() will not wait, but will fail there are no * available IOBs. * * REVISIT: Consider an alternative design that does not require data * copying. This would require a pool of d_buf's that are managed by * the network rather than the network device. */ ret = iob_trycopyin(fwd->f_iob, (FAR const uint8_t *)ipv6, dev->d_len, 0, false); if (ret < 0) { nwarn("WARNING: iob_trycopyin() failed: %d\n", ret); goto errout_with_iobchain; } /* Decrement the TTL in the copy of the IPv6 header (retaining the * original TTL in the sourcee to handle the broadcast case). If the * TTL decrements to zero, then do not forward the packet. */ ret = ipv6_decr_ttl((FAR struct ipv6_hdr_s *)fwd->f_iob->io_data); if (ret < 1) { nwarn("WARNING: Hop limit exceeded... Dropping!\n"); ret = -EMULTIHOP; goto errout_with_iobchain; } /* Then set up to forward the packet according to the protocol. */ ret = ipfwd_forward(fwd); if (ret >= 0) { dev->d_len = 0; return OK; } } errout_with_iobchain: if (fwd != NULL && fwd->f_iob != NULL) { iob_free_chain(fwd->f_iob); } errout_with_fwd: if (fwd != NULL) { ipfwd_free(fwd); } errout: return ret; } #endif /* CONFIG_NETDEV_MULTINIC */ /**************************************************************************** * Name: ipv6_forward_callback * * Description: * This function is a callback from netdev_foreach. It implements the * the broadcase 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 IPv6 packet. * ipv6 - A convenience pointer to the IPv6 header in within the IPv6 * 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. * ****************************************************************************/ #if defined(CONFIG_NET_IPFORWARD_BROADCAST) && \ defined(CONFIG_NETDEV_MULTINIC) int ipv6_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 ipv6_hdr_s *ipv6; int ret; DEBUGASSERT(fwddev != NULL && dev != NULL && dev->d_buf != NULL); /* Check if we are forwarding on the same device that we received the * packet from. */ if (fwddev != dev) { /* Recover the pointer to the IPv6 header in the receiving device's * d_buf. */ ipv6 = (FAR struct ipv6_hdr_s *)&dev->d_buf[NET_LL_HDRLEN(dev)]; /* Send the packet asynchrously on the forwarding device. */ ret = ipv6_dev_forward(dev, fwddev, ipv6); if (ret < 0) { nwarn("WARNING: ipv6_dev_forward failed: %d\n", ret); return ret; } } return OK; } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: ipv6_forward * * Description: * This function is called from ipv6_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 IPv6 * networks served by various network devices. * * Input Parameters: * dev - The device on which the packet was received and which contains * the IPv6 packet. * ipv6 - A convenience pointer to the IPv6 header in within the IPv6 * 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 ipv6_input. * - ipv6 points to the IPv6 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 (ipv6_input()) should drop the packet. * ****************************************************************************/ int ipv6_forward(FAR struct net_driver_s *dev, FAR struct ipv6_hdr_s *ipv6) { FAR struct net_driver_s *fwddev; int ret; /* Search for a device that can forward this packet. This is a trivial * search if there is only a single network device (CONFIG_NETDEV_MULTINIC * not defined). But netdev_findby_ipv6addr() will still assure * routability in that case. */ #ifdef CONFIG_NETDEV_MULTINIC fwddev = netdev_findby_ipv6addr(ipv6->srcipaddr, ipv6->destipaddr); #else fwddev = netdev_findby_ipv6addr(ipv6->destipaddr); #endif if (fwddev == NULL) { nwarn("WARNING: Not routable\n"); return (ssize_t)-ENETUNREACH; } #if defined(CONFIG_NETDEV_MULTINIC) /* 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 = ipv6_dev_forward(dev, fwddev, ipv6); if (ret < 0) { nwarn("WARNING: ipv6_dev_forward failed: %d\n", ret); goto drop; } } else #endif /* CONFIG_NETDEV_MULTINIC */ #if defined(CONFIG_NET_6LOWPAN) /* REVISIT: Currently only suport for 6LoWPAN */ { /* 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. */ /* Perform any necessary packet conversions. If the packet was handled * via a backdoor path (or dropped), then dev->d_len will be zero. If * the packet needs to be forwarded in the normal manner then * dev->d_len will be unchanged. */ ret = ipv6_packet_conversion(dev, dev, ipv6); if (ret < 0) { nwarn("WARNING: ipv6_packet_conversion failed: %d\n", ret); goto drop; } else if (ret == PACKET_NOT_FORWARDED) { #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 IPv6 adress. * This will involve ICMPv6 and Neighbor Discovery. */ /* Correct dev->d_buf by adding back the L1 header length */ #endif /* Nothing other 6LoWPAN forwarding is currently handled and that * case was dealt with in ipv6_packet_conversion(). * * REVISIT: Is tht an issue? Do other use cases make sense? */ nwarn("WARNING: Packet forwarding supported only for 6LoWPAN\n"); ret = -ENOSYS; goto drop; } } #else /* CONFIG_NET_6LOWPAN */ { nwarn("WARNING: Packet forwarding not supported in this configuration\n"); ret = -ENOSYS; goto drop; } #endif /* CONFIG_NET_6LOWPAN */ /* Return success. ipv6_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: ipv6_dropstats(ipv6); dev->d_len = 0; return ret; } /**************************************************************************** * Name: ipv6_forward_broadcast * * Description: * This function is called from ipv6_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 IPv6 packet. * ipv6 - A convenience pointer to the IPv6 header in within the IPv6 * 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 ipv6_input. * - ipv6 points to the IPv6 header with dev->d_buf. * * Returned Value: * None * ****************************************************************************/ #if defined(CONFIG_NET_IPFORWARD_BROADCAST) && \ defined(CONFIG_NETDEV_MULTINIC) void ipv6_forward_broadcast(FAR struct net_driver_s *dev, FAR struct ipv6_hdr_s *ipv6) { /* Don't bother if the TTL would expire */ if (ipv6->ttl > 1) { /* Forward the the broadcast/multicast packet to all devices except, * of course, the device that received the packet. */ (void)netdev_foreach(ipv6_forward_callback, dev); } } #endif #endif /* CONFIG_NET_IPFORWARD && CONFIG_NET_IPv6 */