net/ipv6: Introduce accept_unsolicited_na knob to implement router-side changes for...
authorArun Ajith S <aajith@arista.com>
Fri, 15 Apr 2022 08:34:02 +0000 (08:34 +0000)
committerDavid S. Miller <davem@davemloft.net>
Sun, 17 Apr 2022 12:23:49 +0000 (13:23 +0100)
Add a new neighbour cache entry in STALE state for routers on receiving
an unsolicited (gratuitous) neighbour advertisement with
target link-layer-address option specified.
This is similar to the arp_accept configuration for IPv4.
A new sysctl endpoint is created to turn on this behaviour:
/proc/sys/net/ipv6/conf/interface/accept_unsolicited_na.

Signed-off-by: Arun Ajith S <aajith@arista.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
Documentation/networking/ip-sysctl.rst
include/linux/ipv6.h
include/uapi/linux/ipv6.h
net/ipv6/addrconf.c
net/ipv6/ndisc.c
tools/testing/selftests/net/Makefile
tools/testing/selftests/net/ndisc_unsolicited_na_test.sh [new file with mode: 0755]

index b0024aa7b0514f7174ebf7512e2c7da256b494d1..433f2e4a5feddeda3ea870b6f0d08dde77eb027b 100644 (file)
@@ -2467,6 +2467,33 @@ drop_unsolicited_na - BOOLEAN
 
        By default this is turned off.
 
+accept_unsolicited_na - BOOLEAN
+       Add a new neighbour cache entry in STALE state for routers on receiving an
+       unsolicited neighbour advertisement with target link-layer address option
+       specified. This is as per router-side behavior documented in RFC9131.
+       This has lower precedence than drop_unsolicited_na.
+
+        ====   ======  ======  ==============================================
+        drop   accept  fwding                   behaviour
+        ----   ------  ------  ----------------------------------------------
+           1        X       X  Drop NA packet and don't pass up the stack
+           0        0       X  Pass NA packet up the stack, don't update NC
+           0        1       0  Pass NA packet up the stack, don't update NC
+           0        1       1  Pass NA packet up the stack, and add a STALE
+                               NC entry
+        ====   ======  ======  ==============================================
+
+       This will optimize the return path for the initial off-link communication
+       that is initiated by a directly connected host, by ensuring that
+       the first-hop router which turns on this setting doesn't have to
+       buffer the initial return packets to do neighbour-solicitation.
+       The prerequisite is that the host is configured to send
+       unsolicited neighbour advertisements on interface bringup.
+       This setting should be used in conjunction with the ndisc_notify setting
+       on the host to satisfy this prerequisite.
+
+       By default this is turned off.
+
 enhanced_dad - BOOLEAN
        Include a nonce option in the IPv6 neighbor solicitation messages used for
        duplicate address detection per RFC7527. A received DAD NS will only signal
index 16870f86c74d3d1f5dfb7edac1e7db85f1ef6755..918bfea4ef5f495e16bda92e047e5179a9b023f5 100644 (file)
@@ -61,6 +61,7 @@ struct ipv6_devconf {
        __s32           suppress_frag_ndisc;
        __s32           accept_ra_mtu;
        __s32           drop_unsolicited_na;
+       __s32           accept_unsolicited_na;
        struct ipv6_stable_secret {
                bool initialized;
                struct in6_addr secret;
index d4178dace0bfbb4b4004553542b3e47886f812ea..549ddeaf788b543dac2d8f0df1ed04bf45d8a6a2 100644 (file)
@@ -194,6 +194,7 @@ enum {
        DEVCONF_IOAM6_ID,
        DEVCONF_IOAM6_ID_WIDE,
        DEVCONF_NDISC_EVICT_NOCARRIER,
+       DEVCONF_ACCEPT_UNSOLICITED_NA,
        DEVCONF_MAX
 };
 
index 1afc4c024981a6103fba350b4109694ef8b33e47..6473dc84b71d8c203651656eaace9e40457d2d9d 100644 (file)
@@ -5587,6 +5587,7 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf,
        array[DEVCONF_IOAM6_ID] = cnf->ioam6_id;
        array[DEVCONF_IOAM6_ID_WIDE] = cnf->ioam6_id_wide;
        array[DEVCONF_NDISC_EVICT_NOCARRIER] = cnf->ndisc_evict_nocarrier;
+       array[DEVCONF_ACCEPT_UNSOLICITED_NA] = cnf->accept_unsolicited_na;
 }
 
 static inline size_t inet6_ifla6_size(void)
@@ -7037,6 +7038,15 @@ static const struct ctl_table addrconf_sysctl[] = {
                .extra1         = (void *)SYSCTL_ZERO,
                .extra2         = (void *)SYSCTL_ONE,
        },
+       {
+               .procname       = "accept_unsolicited_na",
+               .data           = &ipv6_devconf.accept_unsolicited_na,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec,
+               .extra1         = (void *)SYSCTL_ZERO,
+               .extra2         = (void *)SYSCTL_ONE,
+       },
        {
                /* sentinel */
        }
index fcb288b0ae131834f061b11be99ab62171d80fee..254addad0dd31cb9af234e99725203fa5457d187 100644 (file)
@@ -979,6 +979,7 @@ static void ndisc_recv_na(struct sk_buff *skb)
        struct inet6_dev *idev = __in6_dev_get(dev);
        struct inet6_ifaddr *ifp;
        struct neighbour *neigh;
+       bool create_neigh;
 
        if (skb->len < sizeof(struct nd_msg)) {
                ND_PRINTK(2, warn, "NA: packet too short\n");
@@ -999,6 +1000,7 @@ static void ndisc_recv_na(struct sk_buff *skb)
        /* For some 802.11 wireless deployments (and possibly other networks),
         * there will be a NA proxy and unsolicitd packets are attacks
         * and thus should not be accepted.
+        * drop_unsolicited_na takes precedence over accept_unsolicited_na
         */
        if (!msg->icmph.icmp6_solicited && idev &&
            idev->cnf.drop_unsolicited_na)
@@ -1039,7 +1041,23 @@ static void ndisc_recv_na(struct sk_buff *skb)
                in6_ifa_put(ifp);
                return;
        }
-       neigh = neigh_lookup(&nd_tbl, &msg->target, dev);
+       /* RFC 9131 updates original Neighbour Discovery RFC 4861.
+        * An unsolicited NA can now create a neighbour cache entry
+        * on routers if it has Target LL Address option.
+        *
+        * drop   accept  fwding                   behaviour
+        * ----   ------  ------  ----------------------------------------------
+        *    1        X       X  Drop NA packet and don't pass up the stack
+        *    0        0       X  Pass NA packet up the stack, don't update NC
+        *    0        1       0  Pass NA packet up the stack, don't update NC
+        *    0        1       1  Pass NA packet up the stack, and add a STALE
+        *                          NC entry
+        * Note that we don't do a (daddr == all-routers-mcast) check.
+        */
+       create_neigh = !msg->icmph.icmp6_solicited && lladdr &&
+                      idev && idev->cnf.forwarding &&
+                      idev->cnf.accept_unsolicited_na;
+       neigh = __neigh_lookup(&nd_tbl, &msg->target, dev, create_neigh);
 
        if (neigh) {
                u8 old_flags = neigh->flags;
index 3fe2515aa616e915af630847c57207de1656c540..af7f6e6ff182455d1631f0088f455e96bc5937de 100644 (file)
@@ -36,6 +36,7 @@ TEST_PROGS += srv6_end_dt4_l3vpn_test.sh
 TEST_PROGS += srv6_end_dt6_l3vpn_test.sh
 TEST_PROGS += vrf_strict_mode_test.sh
 TEST_PROGS += arp_ndisc_evict_nocarrier.sh
+TEST_PROGS += ndisc_unsolicited_na_test.sh
 TEST_PROGS_EXTENDED := in_netns.sh setup_loopback.sh setup_veth.sh
 TEST_PROGS_EXTENDED += toeplitz_client.sh toeplitz.sh
 TEST_GEN_FILES =  socket nettest
diff --git a/tools/testing/selftests/net/ndisc_unsolicited_na_test.sh b/tools/testing/selftests/net/ndisc_unsolicited_na_test.sh
new file mode 100755 (executable)
index 0000000..f508657
--- /dev/null
@@ -0,0 +1,255 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+# This test is for the accept_unsolicited_na feature to
+# enable RFC9131 behaviour. The following is the test-matrix.
+# drop   accept  fwding                   behaviour
+# ----   ------  ------  ----------------------------------------------
+#    1        X       X  Drop NA packet and don't pass up the stack
+#    0        0       X  Pass NA packet up the stack, don't update NC
+#    0        1       0  Pass NA packet up the stack, don't update NC
+#    0        1       1  Pass NA packet up the stack, and add a STALE
+#                           NC entry
+
+ret=0
+# Kselftest framework requirement - SKIP code is 4.
+ksft_skip=4
+
+PAUSE_ON_FAIL=no
+PAUSE=no
+
+HOST_NS="ns-host"
+ROUTER_NS="ns-router"
+
+HOST_INTF="veth-host"
+ROUTER_INTF="veth-router"
+
+ROUTER_ADDR="2000:20::1"
+HOST_ADDR="2000:20::2"
+SUBNET_WIDTH=64
+ROUTER_ADDR_WITH_MASK="${ROUTER_ADDR}/${SUBNET_WIDTH}"
+HOST_ADDR_WITH_MASK="${HOST_ADDR}/${SUBNET_WIDTH}"
+
+IP_HOST="ip -6 -netns ${HOST_NS}"
+IP_HOST_EXEC="ip netns exec ${HOST_NS}"
+IP_ROUTER="ip -6 -netns ${ROUTER_NS}"
+IP_ROUTER_EXEC="ip netns exec ${ROUTER_NS}"
+
+tcpdump_stdout=
+tcpdump_stderr=
+
+log_test()
+{
+       local rc=$1
+       local expected=$2
+       local msg="$3"
+
+       if [ ${rc} -eq ${expected} ]; then
+               printf "    TEST: %-60s  [ OK ]\n" "${msg}"
+               nsuccess=$((nsuccess+1))
+       else
+               ret=1
+               nfail=$((nfail+1))
+               printf "    TEST: %-60s  [FAIL]\n" "${msg}"
+               if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
+               echo
+                       echo "hit enter to continue, 'q' to quit"
+                       read a
+                       [ "$a" = "q" ] && exit 1
+               fi
+       fi
+
+       if [ "${PAUSE}" = "yes" ]; then
+               echo
+               echo "hit enter to continue, 'q' to quit"
+               read a
+               [ "$a" = "q" ] && exit 1
+       fi
+}
+
+setup()
+{
+       set -e
+
+       local drop_unsolicited_na=$1
+       local accept_unsolicited_na=$2
+       local forwarding=$3
+
+       # Setup two namespaces and a veth tunnel across them.
+       # On end of the tunnel is a router and the other end is a host.
+       ip netns add ${HOST_NS}
+       ip netns add ${ROUTER_NS}
+       ${IP_ROUTER} link add ${ROUTER_INTF} type veth \
+                peer name ${HOST_INTF} netns ${HOST_NS}
+
+       # Enable IPv6 on both router and host, and configure static addresses.
+       # The router here is the DUT
+       # Setup router configuration as specified by the arguments.
+       # forwarding=0 case is to check that a non-router
+       # doesn't add neighbour entries.
+        ROUTER_CONF=net.ipv6.conf.${ROUTER_INTF}
+       ${IP_ROUTER_EXEC} sysctl -qw \
+                ${ROUTER_CONF}.forwarding=${forwarding}
+       ${IP_ROUTER_EXEC} sysctl -qw \
+                ${ROUTER_CONF}.drop_unsolicited_na=${drop_unsolicited_na}
+       ${IP_ROUTER_EXEC} sysctl -qw \
+                ${ROUTER_CONF}.accept_unsolicited_na=${accept_unsolicited_na}
+       ${IP_ROUTER_EXEC} sysctl -qw ${ROUTER_CONF}.disable_ipv6=0
+       ${IP_ROUTER} addr add ${ROUTER_ADDR_WITH_MASK} dev ${ROUTER_INTF}
+
+       # Turn on ndisc_notify on host interface so that
+       # the host sends unsolicited NAs.
+       HOST_CONF=net.ipv6.conf.${HOST_INTF}
+       ${IP_HOST_EXEC} sysctl -qw ${HOST_CONF}.ndisc_notify=1
+       ${IP_HOST_EXEC} sysctl -qw ${HOST_CONF}.disable_ipv6=0
+       ${IP_HOST} addr add ${HOST_ADDR_WITH_MASK} dev ${HOST_INTF}
+
+       set +e
+}
+
+start_tcpdump() {
+       set -e
+       tcpdump_stdout=`mktemp`
+       tcpdump_stderr=`mktemp`
+       ${IP_ROUTER_EXEC} timeout 15s \
+                tcpdump --immediate-mode -tpni ${ROUTER_INTF} -c 1 \
+                "icmp6 && icmp6[0] == 136 && src ${HOST_ADDR}" \
+                > ${tcpdump_stdout} 2> /dev/null
+       set +e
+}
+
+cleanup_tcpdump()
+{
+       set -e
+       [[ ! -z  ${tcpdump_stdout} ]] && rm -f ${tcpdump_stdout}
+       [[ ! -z  ${tcpdump_stderr} ]] && rm -f ${tcpdump_stderr}
+       tcpdump_stdout=
+       tcpdump_stderr=
+       set +e
+}
+
+cleanup()
+{
+       cleanup_tcpdump
+       ip netns del ${HOST_NS}
+       ip netns del ${ROUTER_NS}
+}
+
+link_up() {
+       set -e
+       ${IP_ROUTER} link set dev ${ROUTER_INTF} up
+       ${IP_HOST} link set dev ${HOST_INTF} up
+       set +e
+}
+
+verify_ndisc() {
+       local drop_unsolicited_na=$1
+       local accept_unsolicited_na=$2
+       local forwarding=$3
+
+       neigh_show_output=$(${IP_ROUTER} neigh show \
+                to ${HOST_ADDR} dev ${ROUTER_INTF} nud stale)
+       if [ ${drop_unsolicited_na} -eq 0 ] && \
+                       [ ${accept_unsolicited_na} -eq 1 ] && \
+                       [ ${forwarding} -eq 1 ]; then
+               # Neighbour entry expected to be present for 011 case
+               [[ ${neigh_show_output} ]]
+       else
+               # Neighbour entry expected to be absent for all other cases
+               [[ -z ${neigh_show_output} ]]
+       fi
+}
+
+test_unsolicited_na_common()
+{
+       # Setup the test bed, but keep links down
+       setup $1 $2 $3
+
+       # Bring the link up, wait for the NA,
+       # and add a delay to ensure neighbour processing is done.
+       link_up
+       start_tcpdump
+
+       # Verify the neighbour table
+       verify_ndisc $1 $2 $3
+
+}
+
+test_unsolicited_na_combination() {
+       test_unsolicited_na_common $1 $2 $3
+       test_msg=("test_unsolicited_na: "
+               "drop_unsolicited_na=$1 "
+               "accept_unsolicited_na=$2 "
+               "forwarding=$3")
+       log_test $? 0 "${test_msg[*]}"
+       cleanup
+}
+
+test_unsolicited_na_combinations() {
+       # Args: drop_unsolicited_na accept_unsolicited_na forwarding
+
+       # Expect entry
+       test_unsolicited_na_combination 0 1 1
+
+       # Expect no entry
+       test_unsolicited_na_combination 0 0 0
+       test_unsolicited_na_combination 0 0 1
+       test_unsolicited_na_combination 0 1 0
+       test_unsolicited_na_combination 1 0 0
+       test_unsolicited_na_combination 1 0 1
+       test_unsolicited_na_combination 1 1 0
+       test_unsolicited_na_combination 1 1 1
+}
+
+###############################################################################
+# usage
+
+usage()
+{
+       cat <<EOF
+usage: ${0##*/} OPTS
+        -p          Pause on fail
+        -P          Pause after each test before cleanup
+EOF
+}
+
+###############################################################################
+# main
+
+while getopts :pPh o
+do
+       case $o in
+               p) PAUSE_ON_FAIL=yes;;
+               P) PAUSE=yes;;
+               h) usage; exit 0;;
+               *) usage; exit 1;;
+       esac
+done
+
+# make sure we don't pause twice
+[ "${PAUSE}" = "yes" ] && PAUSE_ON_FAIL=no
+
+if [ "$(id -u)" -ne 0 ];then
+       echo "SKIP: Need root privileges"
+       exit $ksft_skip;
+fi
+
+if [ ! -x "$(command -v ip)" ]; then
+       echo "SKIP: Could not run test without ip tool"
+       exit $ksft_skip
+fi
+
+if [ ! -x "$(command -v tcpdump)" ]; then
+       echo "SKIP: Could not run test without tcpdump tool"
+       exit $ksft_skip
+fi
+
+# start clean
+cleanup &> /dev/null
+
+test_unsolicited_na_combinations
+
+printf "\nTests passed: %3d\n" ${nsuccess}
+printf "Tests failed: %3d\n"   ${nfail}
+
+exit $ret