Skip to content

Enabling IPv6-forwarding on VLAN-based interface triggers deadloop & hang #77402

@hakehuang

Description

@hakehuang

Background

In order to measure the network IP forwarding performance, we enabled IPv6 forwarding by setting the CONFIG_NET_ROUTING=y. And, reuse & customize the sample/net/vlan for testing because the on-hand HW board only has 1 Ethernet port. The customization is only adding 1 static route in vlan/src/main.c pointing to a gateway located in VLAN.200 virtual interface. The traffics will be injected from VLAN.100 with a DIP matched to the static route, and then should be forwarded to VLAN.200 (same physical port). Thus, we can use a Spirent TestCenter and use 1 port to Tx/Rx packets for measurement.

Steps

  1. Enable CONFIG_NET_ROUTING

in file subsys/net/ip/Kconfig: (enabled NET_ROUTING by adding 1 line below)


 config NET_ROUTING
        bool
        depends on NET_ROUTE
+       default y if NET_ROUTE
        help
          Allow IPv6 routing between different network interfaces and

  1. Add 1 static route in sample/net/vlan/src/main.c

#include "ipv6.h"
#include "nbr.h"
#include "route.h"

/* Generic address that we are using to generate some more addresses */
static struct in6_addr netaddr = { { { 0x20, 0x01, 0x0d, 0xb8, 0x01, 0x01, 0, 0,
                                            0, 0, 0, 0, 0, 0, 0, 0 } } };

static struct in6_addr gw_addr = { { { 0x20, 0x01, 0x0d, 0xb8, 0x02, 0, 0, 0,
                                            0, 0, 0, 0, 0, 0, 0, 2 } } };

static char mac_addr[6] = {0x70, 0xb5, 0xe8, 0x65, 0x33, 0x05};

static int init_route(struct net_if *iface)
{
        struct net_route_entry *route_entry;
        struct net_nbr *nbr;
        struct net_linkaddr ll_addr = {0};

        ll_addr.len = 6U;
        ll_addr.type = NET_LINK_ETHERNET;
        ll_addr.addr = &mac_addr[0];

        nbr = net_ipv6_nbr_add(iface,
                               &gw_addr,
                               &ll_addr,
                               true,
                               NET_IPV6_NBR_STATE_REACHABLE);

        if (!nbr) {
                LOG_ERR("failed to add nbr.");
                return -1;
        }

        route_entry = net_route_add(iface,
                                    &netaddr, 64,
                                    &gw_addr,
                                    NET_IPV6_ND_INFINITE_LIFETIME,
                                    NET_ROUTE_PREFERENCE_LOW);

        if (!route_entry) {
                LOG_ERR("failed to add route.");
                //net_neighbor_remove(nbr);
                return -1;
        }

        LOG_INF("init route done.");

        return 0;
}

static int init_app(void)
{
        struct net_if *iface;
        struct ud ud;
        struct in6_addr addr6;
        int ret;

        iface = net_if_get_first_by_type(&NET_L2_GET_NAME(ETHERNET));
        if (!iface) {

… … … …
        /* Bring up the VLAN interface automatically */
        net_if_up(ud.first);
        net_if_up(ud.second);

        ret = init_route(ud.second);

        return ret;
}
------------------------------------------------------------------------------- 
  1. Add Target_Include to sample/net/vlan/CMakeLists.txt in order to build the new main.c

# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(vlan)

target_include_directories(app PRIVATE ${ZEPHYR_BASE}/subsys/net/ip)
target_sources(app PRIVATE src/main.c)

include(${ZEPHYR_BASE}/samples/net/common/common.cmake)

Now we can build and flash to run the new VLAN sample with IPv6 forwarding enabled from VLAN.100 to VLAN.200.
Here is the console check about the newly added static route.


uart:~$ net route 

IPv6 routes for interface 1 (0x80000a40) (Ethernet)
=================================================

IPv6 routes for interface 2 (0x80000b50) (Virtual)
================================================

IPv6 routes for interface 3 (0x80000c60) (Virtual)
================================================
IPv6 prefix : 2001:db8:101::/64
        neighbor : 0x8000020c   addr : 70:B5:E8:65:33:05        lifetime : infinite
-------------------------------------------------------------------------------------

[Issue]
Ping from Linux PC VLAN.100 to RT1060 board VLAN.100 OK, so is VLAN.200.
Ping from Linux PC VLAN.100 to one external IP-addr (e.g. 2001:db8:101::1), which led to RT1060 VLAN.100 port as the gateway, and then RT1060 looked up the route-table and found a match with that static route, then was forwarded to RT1060 VLAN.200 port. Before being sent out the physical port, RT1060 hung.
After code checking & debugging, we found in ‘net_ipv6_prepare_for_send()’ that, it always looks up the ‘nbr’ (gateway) and replaces pkt->iface with the ‘nbr->iface’, unconditionally.


        if (!iface) {
                /* This means that the dst was not onlink, so try to
                 * figure out the interface using nexthop instead.
                 */
                if (net_if_ipv6_addr_onlink(&iface, nexthop)) {
                        net_pkt_set_iface(pkt, iface);
                } else {
                        /* nexthop might be the nbr list, e.g. a link-local
                         * address of a connected peer.
                         */
                        nbr = net_ipv6_nbr_lookup(NULL, nexthop);
                        if (nbr) {
                                iface = nbr->iface;
                                net_pkt_set_iface(pkt, iface);
                        } else {
                                iface = net_pkt_iface(pkt);
                        }
                }

                /* If the above check returns null, we try to send
                 * the packet and hope for the best.
                 */
        }

try_send:
… …

While in ‘net_if_queue_tx()’, it calls ‘net_if_tx()’ with the 1st parameter of ‘net_pkt_iface(pkt)’. This value is always the virtual iface (VLAN-iface) because of ‘net_ipv6_prepare_for_send()’ operations above.

-------------------------------------------------------------------------------------
void net_if_queue_tx(struct net_if *iface, struct net_pkt *pkt)
{
        if (!net_pkt_filter_send_ok(pkt)) {
                /* silently drop the packet */
                net_pkt_unref(pkt);
                return;
        }

        uint8_t prio = net_pkt_priority(pkt);
        uint8_t tc = net_tx_priority2tc(prio);

        net_stats_update_tc_sent_pkt(iface, tc);
        net_stats_update_tc_sent_bytes(iface, tc, net_pkt_get_len(pkt));
        net_stats_update_tc_sent_priority(iface, tc, prio);

        /* For highest priority packet, skip the TX queue and push directly to
         * the driver. Also if there are no TX queue/thread, push the packet
         * directly to the driver.
         */
        if ((IS_ENABLED(CONFIG_NET_TC_SKIP_FOR_HIGH_PRIO) &&
             prio >= NET_PRIORITY_CA) || NET_TC_TX_COUNT == 0) {
                net_pkt_set_tx_stats_tick(pkt, k_cycle_get_32());

                net_if_tx(net_pkt_iface(pkt), pkt);
//              net_if_tx(iface, pkt);
                return;
        }
------------------------------------------------------------------------------------------

For VLAN interfaces, the data-send will loop 1st the virtual iface (VLAN-iface) and 2nd the physical iface with which VLAN attached. And the 2nd time data-send will actually send out the packets. However, ‘net_ipv6_prepare_for_send()’ code always changes the pkt->iface back to the ‘nbr->iface’ which is the virtual iface. So, the packets will be looped infinitely between the 1st time and the 2nd time, and system will hang.

[Suggested Fixes]
Two possible solutions:
a) Simply change the ‘net_if_tx()’ 1st parameter from ‘net_pkt_iface(pkt)’ to ‘iface’,
b) Give a precondition for calling ‘net_ipv6_prepare_for_send()’ – if no lladdr_dst.


        /* If the ll dst address is not set check if it is present in the nbr
         * cache.
         */
        if (IS_ENABLED(CONFIG_NET_IPV6) && net_pkt_family(pkt) == AF_INET6) {
                if (!net_pkt_lladdr_dst(pkt)->addr)
                        verdict = net_ipv6_prepare_for_send(pkt);
        }

We’ve tried both can work well.

Metadata

Metadata

Labels

area: NetworkingbugThe issue is a bug, or the PR is fixing a bugpriority: mediumMedium impact/importance bug

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions