Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 7 Jun 2018 01:39:49 +0000 (18:39 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 7 Jun 2018 01:39:49 +0000 (18:39 -0700)
Pull networking updates from David Miller:

 1) Add Maglev hashing scheduler to IPVS, from Inju Song.

 2) Lots of new TC subsystem tests from Roman Mashak.

 3) Add TCP zero copy receive and fix delayed acks and autotuning with
    SO_RCVLOWAT, from Eric Dumazet.

 4) Add XDP_REDIRECT support to mlx5 driver, from Jesper Dangaard
    Brouer.

 5) Add ttl inherit support to vxlan, from Hangbin Liu.

 6) Properly separate ipv6 routes into their logically independant
    components. fib6_info for the routing table, and fib6_nh for sets of
    nexthops, which thus can be shared. From David Ahern.

 7) Add bpf_xdp_adjust_tail helper, which can be used to generate ICMP
    messages from XDP programs. From Nikita V. Shirokov.

 8) Lots of long overdue cleanups to the r8169 driver, from Heiner
    Kallweit.

 9) Add BTF ("BPF Type Format"), from Martin KaFai Lau.

10) Add traffic condition monitoring to iwlwifi, from Luca Coelho.

11) Plumb extack down into fib_rules, from Roopa Prabhu.

12) Add Flower classifier offload support to igb, from Vinicius Costa
    Gomes.

13) Add UDP GSO support, from Willem de Bruijn.

14) Add documentation for eBPF helpers, from Quentin Monnet.

15) Add TLS tx offload to mlx5, from Ilya Lesokhin.

16) Allow applications to be given the number of bytes available to read
    on a socket via a control message returned from recvmsg(), from
    Soheil Hassas Yeganeh.

17) Add x86_32 eBPF JIT compiler, from Wang YanQing.

18) Add AF_XDP sockets, with zerocopy support infrastructure as well.
    From Björn Töpel.

19) Remove indirect load support from all of the BPF JITs and handle
    these operations in the verifier by translating them into native BPF
    instead. From Daniel Borkmann.

20) Add GRO support to ipv6 gre tunnels, from Eran Ben Elisha.

21) Allow XDP programs to do lookups in the main kernel routing tables
    for forwarding. From David Ahern.

22) Allow drivers to store hardware state into an ELF section of kernel
    dump vmcore files, and use it in cxgb4. From Rahul Lakkireddy.

23) Various RACK and loss detection improvements in TCP, from Yuchung
    Cheng.

24) Add TCP SACK compression, from Eric Dumazet.

25) Add User Mode Helper support and basic bpfilter infrastructure, from
    Alexei Starovoitov.

26) Support ports and protocol values in RTM_GETROUTE, from Roopa
    Prabhu.

27) Support bulking in ->ndo_xdp_xmit() API, from Jesper Dangaard
    Brouer.

28) Add lots of forwarding selftests, from Petr Machata.

29) Add generic network device failover driver, from Sridhar Samudrala.

* ra.kernel.org:/pub/scm/linux/kernel/git/davem/net-next: (1959 commits)
  strparser: Add __strp_unpause and use it in ktls.
  rxrpc: Fix terminal retransmission connection ID to include the channel
  net: hns3: Optimize PF CMDQ interrupt switching process
  net: hns3: Fix for VF mailbox receiving unknown message
  net: hns3: Fix for VF mailbox cannot receiving PF response
  bnx2x: use the right constant
  Revert "net: sched: cls: Fix offloading when ingress dev is vxlan"
  net: dsa: b53: Fix for brcm tag issue in Cygnus SoC
  enic: fix UDP rss bits
  netdev-FAQ: clarify DaveM's position for stable backports
  rtnetlink: validate attributes in do_setlink()
  mlxsw: Add extack messages for port_{un, }split failures
  netdevsim: Add extack error message for devlink reload
  devlink: Add extack to reload and port_{un, }split operations
  net: metrics: add proper netlink validation
  ipmr: fix error path when ipmr_new_table fails
  ip6mr: only set ip6mr_table from setsockopt when ip6mr_new_table succeeds
  net: hns3: remove unused hclgevf_cfg_func_mta_filter
  netfilter: provide udp*_lib_lookup for nf_tproxy
  qed*: Utilize FW 8.37.2.0
  ...

58 files changed:
1  2 
MAINTAINERS
Makefile
arch/s390/net/Makefile
arch/x86/Kconfig
drivers/net/ethernet/mellanox/mlx5/core/fs_core.c
drivers/net/ethernet/sfc/efx.c
drivers/net/wireless/ath/ath10k/core.c
drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c
drivers/net/wireless/mediatek/mt76/agg-rx.c
drivers/s390/net/qeth_core_main.c
include/linux/mmc/sdio_ids.h
include/linux/net.h
include/linux/perf_event.h
include/linux/skbuff.h
include/linux/trace_events.h
include/net/ip6_fib.h
include/net/ip_vs.h
include/net/sctp/sctp.h
include/net/sock.h
include/net/tcp.h
include/net/udp.h
init/Kconfig
kernel/events/core.c
net/bridge/netfilter/ebtables.c
net/core/dev.c
net/core/neighbour.c
net/core/sock.c
net/ipv4/af_inet.c
net/ipv4/fib_trie.c
net/ipv4/ipconfig.c
net/ipv4/ipmr.c
net/ipv4/proc.c
net/ipv4/route.c
net/ipv4/tcp.c
net/ipv4/tcp_ipv4.c
net/ipv4/udp.c
net/ipv6/addrconf.c
net/ipv6/af_inet6.c
net/ipv6/anycast.c
net/ipv6/ip6_fib.c
net/ipv6/ip6mr.c
net/ipv6/route.c
net/ipv6/tcp_ipv6.c
net/ipv6/udp.c
net/l2tp/l2tp_ppp.c
net/netfilter/ipvs/ip_vs_app.c
net/netfilter/ipvs/ip_vs_ctl.c
net/netfilter/nfnetlink_log.c
net/netfilter/nfnetlink_queue.c
net/packet/af_packet.c
net/rxrpc/ar-internal.h
net/sctp/socket.c
net/tipc/socket.c
net/xdp/xsk.c
security/selinux/hooks.c
tools/testing/selftests/net/fib_tests.sh
tools/testing/selftests/net/pmtu.sh
tools/testing/selftests/net/rtnetlink.sh

diff --cc MAINTAINERS
index f446443a556e29a38ab09b94cc13f0d6ae128951,884657f0be115e473e7f34d45da7629b951d9cdb..448957cab8490229e9d963ba1f1e668499271dc3
@@@ -5448,11 -5413,14 +5448,19 @@@ S:   Maintaine
  F:    Documentation/hwmon/f71805f
  F:    drivers/hwmon/f71805f.c
  
 +FADDR2LINE
 +M:    Josh Poimboeuf <jpoimboe@redhat.com>
 +S:    Maintained
 +F:    scripts/faddr2line
 +
+ FAILOVER MODULE
+ M:    Sridhar Samudrala <sridhar.samudrala@intel.com>
+ L:    netdev@vger.kernel.org
+ S:    Supported
+ F:    net/core/failover.c
+ F:    include/net/failover.h
+ F:    Documentation/networking/failover.rst
  FANOTIFY
  M:    Jan Kara <jack@suse.cz>
  R:    Amir Goldstein <amir73il@gmail.com>
diff --cc Makefile
index bbc979aeea29c88b31b38ce162266fa0ae9dac79,62c110084fae74c25eb68d312ba57fd4e3f3a883..019a5a020606f431acb025439b311006714df0f1
+++ b/Makefile
@@@ -509,6 -510,11 +509,11 @@@ ifeq ($(shell $(CONFIG_SHELL) $(srctree
    KBUILD_AFLAGS += -DCC_HAVE_ASM_GOTO
  endif
  
 -ifeq ($(call shell-cached,$(CONFIG_SHELL) $(srctree)/scripts/cc-can-link.sh $(CC)), y)
++ifeq ($(shell $(CONFIG_SHELL) $(srctree)/scripts/cc-can-link.sh $(CC)), y)
+   CC_CAN_LINK := y
+   export CC_CAN_LINK
+ endif
  ifeq ($(config-targets),1)
  # ===========================================================================
  # *config targets only - make sure prerequisites are updated, and descend
index e2b85ffdbb0c0b353e1f3b024311813fb05fb9c2,d4663b4bf509894e62c3b02c69726ee5717c2dd4..8cab6deb0403df6639431f3b64922c4794099d2e
@@@ -2,5 -2,4 +2,5 @@@
  #
  # Arch-specific network modules
  #
- obj-$(CONFIG_BPF_JIT) += bpf_jit.o bpf_jit_comp.o
+ obj-$(CONFIG_BPF_JIT) += bpf_jit_comp.o
 +obj-$(CONFIG_HAVE_PNETID) += pnet.o
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
diff --cc init/Kconfig
Simple merge
Simple merge
Simple merge
diff --cc net/core/dev.c
Simple merge
Simple merge
diff --cc net/core/sock.c
Simple merge
Simple merge
Simple merge
index bbcbcc113d197a93c35db46f76d4107e067c253e,86c9f755de3ddddc93b1bf9ee1b6532cf6d6a7fd..88212615bf4ce75f5e840297b1be70f4e4e0b83b
@@@ -1282,6 -1317,74 +1317,62 @@@ static int pnp_seq_show(struct seq_fil
                           &ic_servaddr);
        return 0;
  }
 -static int pnp_seq_open(struct inode *indoe, struct file *file)
 -{
 -      return single_open(file, pnp_seq_show, NULL);
 -}
 -
 -static const struct file_operations pnp_seq_fops = {
 -      .open           = pnp_seq_open,
 -      .read           = seq_read,
 -      .llseek         = seq_lseek,
 -      .release        = single_release,
 -};
 -
+ /* Create the /proc/net/ipconfig directory */
+ static int __init ipconfig_proc_net_init(void)
+ {
+       ipconfig_dir = proc_net_mkdir(&init_net, "ipconfig", init_net.proc_net);
+       if (!ipconfig_dir)
+               return -ENOMEM;
+       return 0;
+ }
+ /* Create a new file under /proc/net/ipconfig */
+ static int ipconfig_proc_net_create(const char *name,
+                                   const struct file_operations *fops)
+ {
+       char *pname;
+       struct proc_dir_entry *p;
+       if (!ipconfig_dir)
+               return -ENOMEM;
+       pname = kasprintf(GFP_KERNEL, "%s%s", "ipconfig/", name);
+       if (!pname)
+               return -ENOMEM;
+       p = proc_create(pname, 0444, init_net.proc_net, fops);
+       kfree(pname);
+       if (!p)
+               return -ENOMEM;
+       return 0;
+ }
+ /* Write NTP server IP addresses to /proc/net/ipconfig/ntp_servers */
+ static int ntp_servers_seq_show(struct seq_file *seq, void *v)
+ {
+       int i;
+       for (i = 0; i < CONF_NTP_SERVERS_MAX; i++) {
+               if (ic_ntp_servers[i] != NONE)
+                       seq_printf(seq, "%pI4\n", &ic_ntp_servers[i]);
+       }
+       return 0;
+ }
+ static int ntp_servers_seq_open(struct inode *inode, struct file *file)
+ {
+       return single_open(file, ntp_servers_seq_show, NULL);
+ }
+ static const struct file_operations ntp_servers_seq_fops = {
+       .open           = ntp_servers_seq_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+ };
  #endif /* CONFIG_PROC_FS */
  
  /*
@@@ -1356,8 -1459,20 +1447,20 @@@ static int __init ip_auto_config(void
        int err;
        unsigned int i;
  
+       /* Initialise all name servers and NTP servers to NONE (but only if the
+        * "ip=" or "nfsaddrs=" kernel command line parameters weren't decoded,
+        * otherwise we'll overwrite the IP addresses specified there)
+        */
+       if (ic_set_manually == 0) {
+               ic_nameservers_predef();
+               ic_ntp_servers_predef();
+       }
  #ifdef CONFIG_PROC_FS
 -      proc_create("pnp", 0444, init_net.proc_net, &pnp_seq_fops);
 +      proc_create_single("pnp", 0444, init_net.proc_net, pnp_seq_show);
+       if (ipconfig_proc_net_init() == 0)
+               ipconfig_proc_net_create("ntp_servers", &ntp_servers_seq_fops);
  #endif /* CONFIG_PROC_FS */
  
        if (!ic_enable)
diff --cc net/ipv4/ipmr.c
Simple merge
diff --cc net/ipv4/proc.c
Simple merge
Simple merge
diff --cc net/ipv4/tcp.c
Simple merge
Simple merge
diff --cc net/ipv4/udp.c
Simple merge
Simple merge
Simple merge
Simple merge
index 01372dd74e385dd38332593dcdb60dce0d65f278,f9132a6de917449e871a9d0b0c07b7bf91145d1a..7aa4c41a3bd912e99490b46544b2fa6512e8a1fc
@@@ -2209,15 -2229,25 +2229,16 @@@ void fib6_gc_cleanup(void
  }
  
  #ifdef CONFIG_PROC_FS
 -
 -struct ipv6_route_iter {
 -      struct seq_net_private p;
 -      struct fib6_walker w;
 -      loff_t skip;
 -      struct fib6_table *tbl;
 -      int sernum;
 -};
 -
  static int ipv6_route_seq_show(struct seq_file *seq, void *v)
  {
-       struct rt6_info *rt = v;
+       struct fib6_info *rt = v;
        struct ipv6_route_iter *iter = seq->private;
+       const struct net_device *dev;
  
-       seq_printf(seq, "%pi6 %02x ", &rt->rt6i_dst.addr, rt->rt6i_dst.plen);
+       seq_printf(seq, "%pi6 %02x ", &rt->fib6_dst.addr, rt->fib6_dst.plen);
  
  #ifdef CONFIG_IPV6_SUBTREES
-       seq_printf(seq, "%pi6 %02x ", &rt->rt6i_src.addr, rt->rt6i_src.plen);
+       seq_printf(seq, "%pi6 %02x ", &rt->fib6_src.addr, rt->fib6_src.plen);
  #else
        seq_puts(seq, "00000000000000000000000000000000 00 ");
  #endif
Simple merge
Simple merge
Simple merge
diff --cc net/ipv6/udp.c
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
diff --cc net/xdp/xsk.c
index 0000000000000000000000000000000000000000,ddca4bf1cfc89f9daa20cc36c1e1098900e9997e..c6ed2454f7ce55e86bd8a8ea03773df6cb6f1414
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,789 +1,788 @@@
 -static unsigned int xsk_poll(struct file *file, struct socket *sock,
 -                           struct poll_table_struct *wait)
+ // SPDX-License-Identifier: GPL-2.0
+ /* XDP sockets
+  *
+  * AF_XDP sockets allows a channel between XDP programs and userspace
+  * applications.
+  * Copyright(c) 2018 Intel Corporation.
+  *
+  * Author(s): Björn Töpel <bjorn.topel@intel.com>
+  *          Magnus Karlsson <magnus.karlsson@intel.com>
+  */
+ #define pr_fmt(fmt) "AF_XDP: %s: " fmt, __func__
+ #include <linux/if_xdp.h>
+ #include <linux/init.h>
+ #include <linux/sched/mm.h>
+ #include <linux/sched/signal.h>
+ #include <linux/sched/task.h>
+ #include <linux/socket.h>
+ #include <linux/file.h>
+ #include <linux/uaccess.h>
+ #include <linux/net.h>
+ #include <linux/netdevice.h>
+ #include <linux/rculist.h>
+ #include <net/xdp_sock.h>
+ #include <net/xdp.h>
+ #include "xsk_queue.h"
+ #include "xdp_umem.h"
+ #define TX_BATCH_SIZE 16
+ static struct xdp_sock *xdp_sk(struct sock *sk)
+ {
+       return (struct xdp_sock *)sk;
+ }
+ bool xsk_is_setup_for_bpf_map(struct xdp_sock *xs)
+ {
+       return READ_ONCE(xs->rx) &&  READ_ONCE(xs->umem) &&
+               READ_ONCE(xs->umem->fq);
+ }
+ u64 *xsk_umem_peek_addr(struct xdp_umem *umem, u64 *addr)
+ {
+       return xskq_peek_addr(umem->fq, addr);
+ }
+ EXPORT_SYMBOL(xsk_umem_peek_addr);
+ void xsk_umem_discard_addr(struct xdp_umem *umem)
+ {
+       xskq_discard_addr(umem->fq);
+ }
+ EXPORT_SYMBOL(xsk_umem_discard_addr);
+ static int __xsk_rcv(struct xdp_sock *xs, struct xdp_buff *xdp, u32 len)
+ {
+       void *buffer;
+       u64 addr;
+       int err;
+       if (!xskq_peek_addr(xs->umem->fq, &addr) ||
+           len > xs->umem->chunk_size_nohr) {
+               xs->rx_dropped++;
+               return -ENOSPC;
+       }
+       addr += xs->umem->headroom;
+       buffer = xdp_umem_get_data(xs->umem, addr);
+       memcpy(buffer, xdp->data, len);
+       err = xskq_produce_batch_desc(xs->rx, addr, len);
+       if (!err) {
+               xskq_discard_addr(xs->umem->fq);
+               xdp_return_buff(xdp);
+               return 0;
+       }
+       xs->rx_dropped++;
+       return err;
+ }
+ static int __xsk_rcv_zc(struct xdp_sock *xs, struct xdp_buff *xdp, u32 len)
+ {
+       int err = xskq_produce_batch_desc(xs->rx, (u64)xdp->handle, len);
+       if (err) {
+               xdp_return_buff(xdp);
+               xs->rx_dropped++;
+       }
+       return err;
+ }
+ int xsk_rcv(struct xdp_sock *xs, struct xdp_buff *xdp)
+ {
+       u32 len;
+       if (xs->dev != xdp->rxq->dev || xs->queue_id != xdp->rxq->queue_index)
+               return -EINVAL;
+       len = xdp->data_end - xdp->data;
+       return (xdp->rxq->mem.type == MEM_TYPE_ZERO_COPY) ?
+               __xsk_rcv_zc(xs, xdp, len) : __xsk_rcv(xs, xdp, len);
+ }
+ void xsk_flush(struct xdp_sock *xs)
+ {
+       xskq_produce_flush_desc(xs->rx);
+       xs->sk.sk_data_ready(&xs->sk);
+ }
+ int xsk_generic_rcv(struct xdp_sock *xs, struct xdp_buff *xdp)
+ {
+       u32 len = xdp->data_end - xdp->data;
+       void *buffer;
+       u64 addr;
+       int err;
+       if (!xskq_peek_addr(xs->umem->fq, &addr) ||
+           len > xs->umem->chunk_size_nohr) {
+               xs->rx_dropped++;
+               return -ENOSPC;
+       }
+       addr += xs->umem->headroom;
+       buffer = xdp_umem_get_data(xs->umem, addr);
+       memcpy(buffer, xdp->data, len);
+       err = xskq_produce_batch_desc(xs->rx, addr, len);
+       if (!err) {
+               xskq_discard_addr(xs->umem->fq);
+               xsk_flush(xs);
+               return 0;
+       }
+       xs->rx_dropped++;
+       return err;
+ }
+ void xsk_umem_complete_tx(struct xdp_umem *umem, u32 nb_entries)
+ {
+       xskq_produce_flush_addr_n(umem->cq, nb_entries);
+ }
+ EXPORT_SYMBOL(xsk_umem_complete_tx);
+ void xsk_umem_consume_tx_done(struct xdp_umem *umem)
+ {
+       struct xdp_sock *xs;
+       rcu_read_lock();
+       list_for_each_entry_rcu(xs, &umem->xsk_list, list) {
+               xs->sk.sk_write_space(&xs->sk);
+       }
+       rcu_read_unlock();
+ }
+ EXPORT_SYMBOL(xsk_umem_consume_tx_done);
+ bool xsk_umem_consume_tx(struct xdp_umem *umem, dma_addr_t *dma, u32 *len)
+ {
+       struct xdp_desc desc;
+       struct xdp_sock *xs;
+       rcu_read_lock();
+       list_for_each_entry_rcu(xs, &umem->xsk_list, list) {
+               if (!xskq_peek_desc(xs->tx, &desc))
+                       continue;
+               if (xskq_produce_addr_lazy(umem->cq, desc.addr))
+                       goto out;
+               *dma = xdp_umem_get_dma(umem, desc.addr);
+               *len = desc.len;
+               xskq_discard_desc(xs->tx);
+               rcu_read_unlock();
+               return true;
+       }
+ out:
+       rcu_read_unlock();
+       return false;
+ }
+ EXPORT_SYMBOL(xsk_umem_consume_tx);
+ static int xsk_zc_xmit(struct sock *sk)
+ {
+       struct xdp_sock *xs = xdp_sk(sk);
+       struct net_device *dev = xs->dev;
+       return dev->netdev_ops->ndo_xsk_async_xmit(dev, xs->queue_id);
+ }
+ static void xsk_destruct_skb(struct sk_buff *skb)
+ {
+       u64 addr = (u64)(long)skb_shinfo(skb)->destructor_arg;
+       struct xdp_sock *xs = xdp_sk(skb->sk);
+       WARN_ON_ONCE(xskq_produce_addr(xs->umem->cq, addr));
+       sock_wfree(skb);
+ }
+ static int xsk_generic_xmit(struct sock *sk, struct msghdr *m,
+                           size_t total_len)
+ {
+       u32 max_batch = TX_BATCH_SIZE;
+       struct xdp_sock *xs = xdp_sk(sk);
+       bool sent_frame = false;
+       struct xdp_desc desc;
+       struct sk_buff *skb;
+       int err = 0;
+       if (unlikely(!xs->tx))
+               return -ENOBUFS;
+       mutex_lock(&xs->mutex);
+       while (xskq_peek_desc(xs->tx, &desc)) {
+               char *buffer;
+               u64 addr;
+               u32 len;
+               if (max_batch-- == 0) {
+                       err = -EAGAIN;
+                       goto out;
+               }
+               if (xskq_reserve_addr(xs->umem->cq)) {
+                       err = -EAGAIN;
+                       goto out;
+               }
+               len = desc.len;
+               if (unlikely(len > xs->dev->mtu)) {
+                       err = -EMSGSIZE;
+                       goto out;
+               }
+               if (xs->queue_id >= xs->dev->real_num_tx_queues) {
+                       err = -ENXIO;
+                       goto out;
+               }
+               skb = sock_alloc_send_skb(sk, len, 1, &err);
+               if (unlikely(!skb)) {
+                       err = -EAGAIN;
+                       goto out;
+               }
+               skb_put(skb, len);
+               addr = desc.addr;
+               buffer = xdp_umem_get_data(xs->umem, addr);
+               err = skb_store_bits(skb, 0, buffer, len);
+               if (unlikely(err)) {
+                       kfree_skb(skb);
+                       goto out;
+               }
+               skb->dev = xs->dev;
+               skb->priority = sk->sk_priority;
+               skb->mark = sk->sk_mark;
+               skb_shinfo(skb)->destructor_arg = (void *)(long)addr;
+               skb->destructor = xsk_destruct_skb;
+               err = dev_direct_xmit(skb, xs->queue_id);
+               /* Ignore NET_XMIT_CN as packet might have been sent */
+               if (err == NET_XMIT_DROP || err == NETDEV_TX_BUSY) {
+                       err = -EAGAIN;
+                       /* SKB consumed by dev_direct_xmit() */
+                       goto out;
+               }
+               sent_frame = true;
+               xskq_discard_desc(xs->tx);
+       }
+ out:
+       if (sent_frame)
+               sk->sk_write_space(sk);
+       mutex_unlock(&xs->mutex);
+       return err;
+ }
+ static int xsk_sendmsg(struct socket *sock, struct msghdr *m, size_t total_len)
+ {
+       bool need_wait = !(m->msg_flags & MSG_DONTWAIT);
+       struct sock *sk = sock->sk;
+       struct xdp_sock *xs = xdp_sk(sk);
+       if (unlikely(!xs->dev))
+               return -ENXIO;
+       if (unlikely(!(xs->dev->flags & IFF_UP)))
+               return -ENETDOWN;
+       if (need_wait)
+               return -EOPNOTSUPP;
+       return (xs->zc) ? xsk_zc_xmit(sk) : xsk_generic_xmit(sk, m, total_len);
+ }
 -      unsigned int mask = datagram_poll(file, sock, wait);
++static __poll_t xsk_poll_mask(struct socket *sock, __poll_t events)
+ {
 -      .poll           = xsk_poll,
++      __poll_t mask = datagram_poll_mask(sock, events);
+       struct sock *sk = sock->sk;
+       struct xdp_sock *xs = xdp_sk(sk);
+       if (xs->rx && !xskq_empty_desc(xs->rx))
+               mask |= POLLIN | POLLRDNORM;
+       if (xs->tx && !xskq_full_desc(xs->tx))
+               mask |= POLLOUT | POLLWRNORM;
+       return mask;
+ }
+ static int xsk_init_queue(u32 entries, struct xsk_queue **queue,
+                         bool umem_queue)
+ {
+       struct xsk_queue *q;
+       if (entries == 0 || *queue || !is_power_of_2(entries))
+               return -EINVAL;
+       q = xskq_create(entries, umem_queue);
+       if (!q)
+               return -ENOMEM;
+       /* Make sure queue is ready before it can be seen by others */
+       smp_wmb();
+       *queue = q;
+       return 0;
+ }
+ static int xsk_release(struct socket *sock)
+ {
+       struct sock *sk = sock->sk;
+       struct xdp_sock *xs = xdp_sk(sk);
+       struct net *net;
+       if (!sk)
+               return 0;
+       net = sock_net(sk);
+       local_bh_disable();
+       sock_prot_inuse_add(net, sk->sk_prot, -1);
+       local_bh_enable();
+       if (xs->dev) {
+               /* Wait for driver to stop using the xdp socket. */
+               synchronize_net();
+               dev_put(xs->dev);
+               xs->dev = NULL;
+       }
+       sock_orphan(sk);
+       sock->sk = NULL;
+       sk_refcnt_debug_release(sk);
+       sock_put(sk);
+       return 0;
+ }
+ static struct socket *xsk_lookup_xsk_from_fd(int fd)
+ {
+       struct socket *sock;
+       int err;
+       sock = sockfd_lookup(fd, &err);
+       if (!sock)
+               return ERR_PTR(-ENOTSOCK);
+       if (sock->sk->sk_family != PF_XDP) {
+               sockfd_put(sock);
+               return ERR_PTR(-ENOPROTOOPT);
+       }
+       return sock;
+ }
+ static int xsk_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
+ {
+       struct sockaddr_xdp *sxdp = (struct sockaddr_xdp *)addr;
+       struct sock *sk = sock->sk;
+       struct xdp_sock *xs = xdp_sk(sk);
+       struct net_device *dev;
+       u32 flags, qid;
+       int err = 0;
+       if (addr_len < sizeof(struct sockaddr_xdp))
+               return -EINVAL;
+       if (sxdp->sxdp_family != AF_XDP)
+               return -EINVAL;
+       mutex_lock(&xs->mutex);
+       if (xs->dev) {
+               err = -EBUSY;
+               goto out_release;
+       }
+       dev = dev_get_by_index(sock_net(sk), sxdp->sxdp_ifindex);
+       if (!dev) {
+               err = -ENODEV;
+               goto out_release;
+       }
+       if (!xs->rx && !xs->tx) {
+               err = -EINVAL;
+               goto out_unlock;
+       }
+       qid = sxdp->sxdp_queue_id;
+       if ((xs->rx && qid >= dev->real_num_rx_queues) ||
+           (xs->tx && qid >= dev->real_num_tx_queues)) {
+               err = -EINVAL;
+               goto out_unlock;
+       }
+       flags = sxdp->sxdp_flags;
+       if (flags & XDP_SHARED_UMEM) {
+               struct xdp_sock *umem_xs;
+               struct socket *sock;
+               if ((flags & XDP_COPY) || (flags & XDP_ZEROCOPY)) {
+                       /* Cannot specify flags for shared sockets. */
+                       err = -EINVAL;
+                       goto out_unlock;
+               }
+               if (xs->umem) {
+                       /* We have already our own. */
+                       err = -EINVAL;
+                       goto out_unlock;
+               }
+               sock = xsk_lookup_xsk_from_fd(sxdp->sxdp_shared_umem_fd);
+               if (IS_ERR(sock)) {
+                       err = PTR_ERR(sock);
+                       goto out_unlock;
+               }
+               umem_xs = xdp_sk(sock->sk);
+               if (!umem_xs->umem) {
+                       /* No umem to inherit. */
+                       err = -EBADF;
+                       sockfd_put(sock);
+                       goto out_unlock;
+               } else if (umem_xs->dev != dev || umem_xs->queue_id != qid) {
+                       err = -EINVAL;
+                       sockfd_put(sock);
+                       goto out_unlock;
+               }
+               xdp_get_umem(umem_xs->umem);
+               xs->umem = umem_xs->umem;
+               sockfd_put(sock);
+       } else if (!xs->umem || !xdp_umem_validate_queues(xs->umem)) {
+               err = -EINVAL;
+               goto out_unlock;
+       } else {
+               /* This xsk has its own umem. */
+               xskq_set_umem(xs->umem->fq, &xs->umem->props);
+               xskq_set_umem(xs->umem->cq, &xs->umem->props);
+               err = xdp_umem_assign_dev(xs->umem, dev, qid, flags);
+               if (err)
+                       goto out_unlock;
+       }
+       xs->dev = dev;
+       xs->zc = xs->umem->zc;
+       xs->queue_id = qid;
+       xskq_set_umem(xs->rx, &xs->umem->props);
+       xskq_set_umem(xs->tx, &xs->umem->props);
+       xdp_add_sk_umem(xs->umem, xs);
+ out_unlock:
+       if (err)
+               dev_put(dev);
+ out_release:
+       mutex_unlock(&xs->mutex);
+       return err;
+ }
+ static int xsk_setsockopt(struct socket *sock, int level, int optname,
+                         char __user *optval, unsigned int optlen)
+ {
+       struct sock *sk = sock->sk;
+       struct xdp_sock *xs = xdp_sk(sk);
+       int err;
+       if (level != SOL_XDP)
+               return -ENOPROTOOPT;
+       switch (optname) {
+       case XDP_RX_RING:
+       case XDP_TX_RING:
+       {
+               struct xsk_queue **q;
+               int entries;
+               if (optlen < sizeof(entries))
+                       return -EINVAL;
+               if (copy_from_user(&entries, optval, sizeof(entries)))
+                       return -EFAULT;
+               mutex_lock(&xs->mutex);
+               q = (optname == XDP_TX_RING) ? &xs->tx : &xs->rx;
+               err = xsk_init_queue(entries, q, false);
+               mutex_unlock(&xs->mutex);
+               return err;
+       }
+       case XDP_UMEM_REG:
+       {
+               struct xdp_umem_reg mr;
+               struct xdp_umem *umem;
+               if (copy_from_user(&mr, optval, sizeof(mr)))
+                       return -EFAULT;
+               mutex_lock(&xs->mutex);
+               if (xs->umem) {
+                       mutex_unlock(&xs->mutex);
+                       return -EBUSY;
+               }
+               umem = xdp_umem_create(&mr);
+               if (IS_ERR(umem)) {
+                       mutex_unlock(&xs->mutex);
+                       return PTR_ERR(umem);
+               }
+               /* Make sure umem is ready before it can be seen by others */
+               smp_wmb();
+               xs->umem = umem;
+               mutex_unlock(&xs->mutex);
+               return 0;
+       }
+       case XDP_UMEM_FILL_RING:
+       case XDP_UMEM_COMPLETION_RING:
+       {
+               struct xsk_queue **q;
+               int entries;
+               if (copy_from_user(&entries, optval, sizeof(entries)))
+                       return -EFAULT;
+               mutex_lock(&xs->mutex);
+               if (!xs->umem) {
+                       mutex_unlock(&xs->mutex);
+                       return -EINVAL;
+               }
+               q = (optname == XDP_UMEM_FILL_RING) ? &xs->umem->fq :
+                       &xs->umem->cq;
+               err = xsk_init_queue(entries, q, true);
+               mutex_unlock(&xs->mutex);
+               return err;
+       }
+       default:
+               break;
+       }
+       return -ENOPROTOOPT;
+ }
+ static int xsk_getsockopt(struct socket *sock, int level, int optname,
+                         char __user *optval, int __user *optlen)
+ {
+       struct sock *sk = sock->sk;
+       struct xdp_sock *xs = xdp_sk(sk);
+       int len;
+       if (level != SOL_XDP)
+               return -ENOPROTOOPT;
+       if (get_user(len, optlen))
+               return -EFAULT;
+       if (len < 0)
+               return -EINVAL;
+       switch (optname) {
+       case XDP_STATISTICS:
+       {
+               struct xdp_statistics stats;
+               if (len < sizeof(stats))
+                       return -EINVAL;
+               mutex_lock(&xs->mutex);
+               stats.rx_dropped = xs->rx_dropped;
+               stats.rx_invalid_descs = xskq_nb_invalid_descs(xs->rx);
+               stats.tx_invalid_descs = xskq_nb_invalid_descs(xs->tx);
+               mutex_unlock(&xs->mutex);
+               if (copy_to_user(optval, &stats, sizeof(stats)))
+                       return -EFAULT;
+               if (put_user(sizeof(stats), optlen))
+                       return -EFAULT;
+               return 0;
+       }
+       case XDP_MMAP_OFFSETS:
+       {
+               struct xdp_mmap_offsets off;
+               if (len < sizeof(off))
+                       return -EINVAL;
+               off.rx.producer = offsetof(struct xdp_rxtx_ring, ptrs.producer);
+               off.rx.consumer = offsetof(struct xdp_rxtx_ring, ptrs.consumer);
+               off.rx.desc     = offsetof(struct xdp_rxtx_ring, desc);
+               off.tx.producer = offsetof(struct xdp_rxtx_ring, ptrs.producer);
+               off.tx.consumer = offsetof(struct xdp_rxtx_ring, ptrs.consumer);
+               off.tx.desc     = offsetof(struct xdp_rxtx_ring, desc);
+               off.fr.producer = offsetof(struct xdp_umem_ring, ptrs.producer);
+               off.fr.consumer = offsetof(struct xdp_umem_ring, ptrs.consumer);
+               off.fr.desc     = offsetof(struct xdp_umem_ring, desc);
+               off.cr.producer = offsetof(struct xdp_umem_ring, ptrs.producer);
+               off.cr.consumer = offsetof(struct xdp_umem_ring, ptrs.consumer);
+               off.cr.desc     = offsetof(struct xdp_umem_ring, desc);
+               len = sizeof(off);
+               if (copy_to_user(optval, &off, len))
+                       return -EFAULT;
+               if (put_user(len, optlen))
+                       return -EFAULT;
+               return 0;
+       }
+       default:
+               break;
+       }
+       return -EOPNOTSUPP;
+ }
+ static int xsk_mmap(struct file *file, struct socket *sock,
+                   struct vm_area_struct *vma)
+ {
+       unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+       unsigned long size = vma->vm_end - vma->vm_start;
+       struct xdp_sock *xs = xdp_sk(sock->sk);
+       struct xsk_queue *q = NULL;
+       struct xdp_umem *umem;
+       unsigned long pfn;
+       struct page *qpg;
+       if (offset == XDP_PGOFF_RX_RING) {
+               q = READ_ONCE(xs->rx);
+       } else if (offset == XDP_PGOFF_TX_RING) {
+               q = READ_ONCE(xs->tx);
+       } else {
+               umem = READ_ONCE(xs->umem);
+               if (!umem)
+                       return -EINVAL;
+               if (offset == XDP_UMEM_PGOFF_FILL_RING)
+                       q = READ_ONCE(umem->fq);
+               else if (offset == XDP_UMEM_PGOFF_COMPLETION_RING)
+                       q = READ_ONCE(umem->cq);
+       }
+       if (!q)
+               return -EINVAL;
+       qpg = virt_to_head_page(q->ring);
+       if (size > (PAGE_SIZE << compound_order(qpg)))
+               return -EINVAL;
+       pfn = virt_to_phys(q->ring) >> PAGE_SHIFT;
+       return remap_pfn_range(vma, vma->vm_start, pfn,
+                              size, vma->vm_page_prot);
+ }
+ static struct proto xsk_proto = {
+       .name =         "XDP",
+       .owner =        THIS_MODULE,
+       .obj_size =     sizeof(struct xdp_sock),
+ };
+ static const struct proto_ops xsk_proto_ops = {
+       .family         = PF_XDP,
+       .owner          = THIS_MODULE,
+       .release        = xsk_release,
+       .bind           = xsk_bind,
+       .connect        = sock_no_connect,
+       .socketpair     = sock_no_socketpair,
+       .accept         = sock_no_accept,
+       .getname        = sock_no_getname,
++      .poll_mask      = xsk_poll_mask,
+       .ioctl          = sock_no_ioctl,
+       .listen         = sock_no_listen,
+       .shutdown       = sock_no_shutdown,
+       .setsockopt     = xsk_setsockopt,
+       .getsockopt     = xsk_getsockopt,
+       .sendmsg        = xsk_sendmsg,
+       .recvmsg        = sock_no_recvmsg,
+       .mmap           = xsk_mmap,
+       .sendpage       = sock_no_sendpage,
+ };
+ static void xsk_destruct(struct sock *sk)
+ {
+       struct xdp_sock *xs = xdp_sk(sk);
+       if (!sock_flag(sk, SOCK_DEAD))
+               return;
+       xskq_destroy(xs->rx);
+       xskq_destroy(xs->tx);
+       xdp_del_sk_umem(xs->umem, xs);
+       xdp_put_umem(xs->umem);
+       sk_refcnt_debug_dec(sk);
+ }
+ static int xsk_create(struct net *net, struct socket *sock, int protocol,
+                     int kern)
+ {
+       struct sock *sk;
+       struct xdp_sock *xs;
+       if (!ns_capable(net->user_ns, CAP_NET_RAW))
+               return -EPERM;
+       if (sock->type != SOCK_RAW)
+               return -ESOCKTNOSUPPORT;
+       if (protocol)
+               return -EPROTONOSUPPORT;
+       sock->state = SS_UNCONNECTED;
+       sk = sk_alloc(net, PF_XDP, GFP_KERNEL, &xsk_proto, kern);
+       if (!sk)
+               return -ENOBUFS;
+       sock->ops = &xsk_proto_ops;
+       sock_init_data(sock, sk);
+       sk->sk_family = PF_XDP;
+       sk->sk_destruct = xsk_destruct;
+       sk_refcnt_debug_inc(sk);
+       xs = xdp_sk(sk);
+       mutex_init(&xs->mutex);
+       local_bh_disable();
+       sock_prot_inuse_add(net, &xsk_proto, 1);
+       local_bh_enable();
+       return 0;
+ }
+ static const struct net_proto_family xsk_family_ops = {
+       .family = PF_XDP,
+       .create = xsk_create,
+       .owner  = THIS_MODULE,
+ };
+ static int __init xsk_init(void)
+ {
+       int err;
+       err = proto_register(&xsk_proto, 0 /* no slab */);
+       if (err)
+               goto out;
+       err = sock_register(&xsk_family_ops);
+       if (err)
+               goto out_proto;
+       return 0;
+ out_proto:
+       proto_unregister(&xsk_proto);
+ out:
+       return err;
+ }
+ fs_initcall(xsk_init);
Simple merge
index 5baac82b9287a3f8246e008b5fb1e81f7190286f,9780c5a2d73f7a99091f09e944e45a801ef04ab0..78245d60d8bc3fd6f1671fc63a0ec670b2045769
mode 100755,100644..100644
@@@ -5,11 -5,12 +5,14 @@@
  # different events.
  
  ret=0
 +# Kselftest framework requirement - SKIP code is 4.
 +ksft_skip=4
  
- VERBOSE=${VERBOSE:=0}
- PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
+ # all tests in this script. Can be overridden with -t option
+ TESTS="unregister down carrier nexthop ipv6_rt ipv4_rt ipv6_addr_metric ipv4_addr_metric"
+ VERBOSE=0
+ PAUSE_ON_FAIL=no
+ PAUSE=no
  IP="ip -netns testns"
  
  log_test()
@@@ -565,23 -575,828 +577,828 @@@ fib_nexthop_test(
  }
  
  ################################################################################
- #
+ # Tests on route add and replace
+ run_cmd()
+ {
+       local cmd="$1"
+       local out
+       local stderr="2>/dev/null"
+       if [ "$VERBOSE" = "1" ]; then
+               printf "    COMMAND: $cmd\n"
+               stderr=
+       fi
+       out=$(eval $cmd $stderr)
+       rc=$?
+       if [ "$VERBOSE" = "1" -a -n "$out" ]; then
+               echo "    $out"
+       fi
+       [ "$VERBOSE" = "1" ] && echo
+       return $rc
+ }
+ # add route for a prefix, flushing any existing routes first
+ # expected to be the first step of a test
+ add_route6()
+ {
+       local pfx="$1"
+       local nh="$2"
+       local out
+       if [ "$VERBOSE" = "1" ]; then
+               echo
+               echo "    ##################################################"
+               echo
+       fi
+       run_cmd "$IP -6 ro flush ${pfx}"
+       [ $? -ne 0 ] && exit 1
+       out=$($IP -6 ro ls match ${pfx})
+       if [ -n "$out" ]; then
+               echo "Failed to flush routes for prefix used for tests."
+               exit 1
+       fi
+       run_cmd "$IP -6 ro add ${pfx} ${nh}"
+       if [ $? -ne 0 ]; then
+               echo "Failed to add initial route for test."
+               exit 1
+       fi
+ }
+ # add initial route - used in replace route tests
+ add_initial_route6()
+ {
+       add_route6 "2001:db8:104::/64" "$1"
+ }
+ check_route6()
+ {
+       local pfx="2001:db8:104::/64"
+       local expected="$1"
+       local out
+       local rc=0
+       out=$($IP -6 ro ls match ${pfx} | sed -e 's/ pref medium//')
+       [ "${out}" = "${expected}" ] && return 0
+       if [ -z "${out}" ]; then
+               if [ "$VERBOSE" = "1" ]; then
+                       printf "\nNo route entry found\n"
+                       printf "Expected:\n"
+                       printf "    ${expected}\n"
+               fi
+               return 1
+       fi
+       # tricky way to convert output to 1-line without ip's
+       # messy '\'; this drops all extra white space
+       out=$(echo ${out})
+       if [ "${out}" != "${expected}" ]; then
+               rc=1
+               if [ "${VERBOSE}" = "1" ]; then
+                       printf "    Unexpected route entry. Have:\n"
+                       printf "        ${out}\n"
+                       printf "    Expected:\n"
+                       printf "        ${expected}\n\n"
+               fi
+       fi
+       return $rc
+ }
+ route_cleanup()
+ {
+       $IP li del red 2>/dev/null
+       $IP li del dummy1 2>/dev/null
+       $IP li del veth1 2>/dev/null
+       $IP li del veth3 2>/dev/null
+       cleanup &> /dev/null
+ }
+ route_setup()
+ {
+       route_cleanup
+       setup
+       [ "${VERBOSE}" = "1" ] && set -x
+       set -e
+       $IP li add red up type vrf table 101
+       $IP li add veth1 type veth peer name veth2
+       $IP li add veth3 type veth peer name veth4
+       $IP li set veth1 up
+       $IP li set veth3 up
+       $IP li set veth2 vrf red up
+       $IP li set veth4 vrf red up
+       $IP li add dummy1 type dummy
+       $IP li set dummy1 vrf red up
+       $IP -6 addr add 2001:db8:101::1/64 dev veth1
+       $IP -6 addr add 2001:db8:101::2/64 dev veth2
+       $IP -6 addr add 2001:db8:103::1/64 dev veth3
+       $IP -6 addr add 2001:db8:103::2/64 dev veth4
+       $IP -6 addr add 2001:db8:104::1/64 dev dummy1
+       $IP addr add 172.16.101.1/24 dev veth1
+       $IP addr add 172.16.101.2/24 dev veth2
+       $IP addr add 172.16.103.1/24 dev veth3
+       $IP addr add 172.16.103.2/24 dev veth4
+       $IP addr add 172.16.104.1/24 dev dummy1
+       set +ex
+ }
+ # assumption is that basic add of a single path route works
+ # otherwise just adding an address on an interface is broken
+ ipv6_rt_add()
+ {
+       local rc
+       echo
+       echo "IPv6 route add / append tests"
+       # route add same prefix - fails with EEXISTS b/c ip adds NLM_F_EXCL
+       add_route6 "2001:db8:104::/64" "via 2001:db8:101::2"
+       run_cmd "$IP -6 ro add 2001:db8:104::/64 via 2001:db8:103::2"
+       log_test $? 2 "Attempt to add duplicate route - gw"
+       # route add same prefix - fails with EEXISTS b/c ip adds NLM_F_EXCL
+       add_route6 "2001:db8:104::/64" "via 2001:db8:101::2"
+       run_cmd "$IP -6 ro add 2001:db8:104::/64 dev veth3"
+       log_test $? 2 "Attempt to add duplicate route - dev only"
+       # route add same prefix - fails with EEXISTS b/c ip adds NLM_F_EXCL
+       add_route6 "2001:db8:104::/64" "via 2001:db8:101::2"
+       run_cmd "$IP -6 ro add unreachable 2001:db8:104::/64"
+       log_test $? 2 "Attempt to add duplicate route - reject route"
+       # iproute2 prepend only sets NLM_F_CREATE
+       # - adds a new route; does NOT convert existing route to ECMP
+       add_route6 "2001:db8:104::/64" "via 2001:db8:101::2"
+       run_cmd "$IP -6 ro prepend 2001:db8:104::/64 via 2001:db8:103::2"
+       check_route6 "2001:db8:104::/64 via 2001:db8:101::2 dev veth1 metric 1024 2001:db8:104::/64 via 2001:db8:103::2 dev veth3 metric 1024"
+       log_test $? 0 "Add new route for existing prefix (w/o NLM_F_EXCL)"
+       # route append with same prefix adds a new route
+       # - iproute2 sets NLM_F_CREATE | NLM_F_APPEND
+       add_route6 "2001:db8:104::/64" "via 2001:db8:101::2"
+       run_cmd "$IP -6 ro append 2001:db8:104::/64 via 2001:db8:103::2"
+       check_route6 "2001:db8:104::/64 metric 1024 nexthop via 2001:db8:101::2 dev veth1 weight 1 nexthop via 2001:db8:103::2 dev veth3 weight 1"
+       log_test $? 0 "Append nexthop to existing route - gw"
+       add_route6 "2001:db8:104::/64" "via 2001:db8:101::2"
+       run_cmd "$IP -6 ro append 2001:db8:104::/64 dev veth3"
+       check_route6 "2001:db8:104::/64 metric 1024 nexthop via 2001:db8:101::2 dev veth1 weight 1 nexthop dev veth3 weight 1"
+       log_test $? 0 "Append nexthop to existing route - dev only"
+       # multipath route can not have a nexthop that is a reject route
+       add_route6 "2001:db8:104::/64" "via 2001:db8:101::2"
+       run_cmd "$IP -6 ro append unreachable 2001:db8:104::/64"
+       log_test $? 2 "Append nexthop to existing route - reject route"
+       # reject route can not be converted to multipath route
+       run_cmd "$IP -6 ro flush 2001:db8:104::/64"
+       run_cmd "$IP -6 ro add unreachable 2001:db8:104::/64"
+       run_cmd "$IP -6 ro append 2001:db8:104::/64 via 2001:db8:103::2"
+       log_test $? 2 "Append nexthop to existing reject route - gw"
+       run_cmd "$IP -6 ro flush 2001:db8:104::/64"
+       run_cmd "$IP -6 ro add unreachable 2001:db8:104::/64"
+       run_cmd "$IP -6 ro append 2001:db8:104::/64 dev veth3"
+       log_test $? 2 "Append nexthop to existing reject route - dev only"
+       # insert mpath directly
+       add_route6 "2001:db8:104::/64" "nexthop via 2001:db8:101::2 nexthop via 2001:db8:103::2"
+       check_route6  "2001:db8:104::/64 metric 1024 nexthop via 2001:db8:101::2 dev veth1 weight 1 nexthop via 2001:db8:103::2 dev veth3 weight 1"
+       log_test $? 0 "Add multipath route"
+       add_route6 "2001:db8:104::/64" "nexthop via 2001:db8:101::2 nexthop via 2001:db8:103::2"
+       run_cmd "$IP -6 ro add 2001:db8:104::/64 nexthop via 2001:db8:101::2 nexthop via 2001:db8:103::2"
+       log_test $? 2 "Attempt to add duplicate multipath route"
+       # insert of a second route without append but different metric
+       add_route6 "2001:db8:104::/64" "via 2001:db8:101::2"
+       run_cmd "$IP -6 ro add 2001:db8:104::/64 via 2001:db8:103::2 metric 512"
+       rc=$?
+       if [ $rc -eq 0 ]; then
+               run_cmd "$IP -6 ro add 2001:db8:104::/64 via 2001:db8:103::3 metric 256"
+               rc=$?
+       fi
+       log_test $rc 0 "Route add with different metrics"
+       run_cmd "$IP -6 ro del 2001:db8:104::/64 metric 512"
+       rc=$?
+       if [ $rc -eq 0 ]; then
+               check_route6 "2001:db8:104::/64 via 2001:db8:103::3 dev veth3 metric 256 2001:db8:104::/64 via 2001:db8:101::2 dev veth1 metric 1024"
+               rc=$?
+       fi
+       log_test $rc 0 "Route delete with metric"
+ }
  
fib_test()
ipv6_rt_replace_single()
  {
-       if [ -n "$TEST" ]; then
-               eval $TEST
+       # single path with single path
+       #
+       add_initial_route6 "via 2001:db8:101::2"
+       run_cmd "$IP -6 ro replace 2001:db8:104::/64 via 2001:db8:103::2"
+       check_route6 "2001:db8:104::/64 via 2001:db8:103::2 dev veth3 metric 1024"
+       log_test $? 0 "Single path with single path"
+       # single path with multipath
+       #
+       add_initial_route6 "nexthop via 2001:db8:101::2"
+       run_cmd "$IP -6 ro replace 2001:db8:104::/64 nexthop via 2001:db8:101::3 nexthop via 2001:db8:103::2"
+       check_route6 "2001:db8:104::/64 metric 1024 nexthop via 2001:db8:101::3 dev veth1 weight 1 nexthop via 2001:db8:103::2 dev veth3 weight 1"
+       log_test $? 0 "Single path with multipath"
+       # single path with reject
+       #
+       add_initial_route6 "nexthop via 2001:db8:101::2"
+       run_cmd "$IP -6 ro replace unreachable 2001:db8:104::/64"
+       check_route6 "unreachable 2001:db8:104::/64 dev lo metric 1024"
+       log_test $? 0 "Single path with reject route"
+       # single path with single path using MULTIPATH attribute
+       #
+       add_initial_route6 "via 2001:db8:101::2"
+       run_cmd "$IP -6 ro replace 2001:db8:104::/64 nexthop via 2001:db8:103::2"
+       check_route6 "2001:db8:104::/64 via 2001:db8:103::2 dev veth3 metric 1024"
+       log_test $? 0 "Single path with single path via multipath attribute"
+       # route replace fails - invalid nexthop
+       add_initial_route6 "via 2001:db8:101::2"
+       run_cmd "$IP -6 ro replace 2001:db8:104::/64 via 2001:db8:104::2"
+       if [ $? -eq 0 ]; then
+               # previous command is expected to fail so if it returns 0
+               # that means the test failed.
+               log_test 0 1 "Invalid nexthop"
        else
-               fib_unreg_test
-               fib_down_test
-               fib_carrier_test
-               fib_nexthop_test
+               check_route6 "2001:db8:104::/64 via 2001:db8:101::2 dev veth1 metric 1024"
+               log_test $? 0 "Invalid nexthop"
        fi
+       # replace non-existent route
+       # - note use of change versus replace since ip adds NLM_F_CREATE
+       #   for replace
+       add_initial_route6 "via 2001:db8:101::2"
+       run_cmd "$IP -6 ro change 2001:db8:105::/64 via 2001:db8:101::2"
+       log_test $? 2 "Single path - replace of non-existent route"
+ }
+ ipv6_rt_replace_mpath()
+ {
+       # multipath with multipath
+       add_initial_route6 "nexthop via 2001:db8:101::2 nexthop via 2001:db8:103::2"
+       run_cmd "$IP -6 ro replace 2001:db8:104::/64 nexthop via 2001:db8:101::3 nexthop via 2001:db8:103::3"
+       check_route6  "2001:db8:104::/64 metric 1024 nexthop via 2001:db8:101::3 dev veth1 weight 1 nexthop via 2001:db8:103::3 dev veth3 weight 1"
+       log_test $? 0 "Multipath with multipath"
+       # multipath with single
+       add_initial_route6 "nexthop via 2001:db8:101::2 nexthop via 2001:db8:103::2"
+       run_cmd "$IP -6 ro replace 2001:db8:104::/64 via 2001:db8:101::3"
+       check_route6  "2001:db8:104::/64 via 2001:db8:101::3 dev veth1 metric 1024"
+       log_test $? 0 "Multipath with single path"
+       # multipath with single
+       add_initial_route6 "nexthop via 2001:db8:101::2 nexthop via 2001:db8:103::2"
+       run_cmd "$IP -6 ro replace 2001:db8:104::/64 nexthop via 2001:db8:101::3"
+       check_route6 "2001:db8:104::/64 via 2001:db8:101::3 dev veth1 metric 1024"
+       log_test $? 0 "Multipath with single path via multipath attribute"
+       # multipath with reject
+       add_initial_route6 "nexthop via 2001:db8:101::2 nexthop via 2001:db8:103::2"
+       run_cmd "$IP -6 ro replace unreachable 2001:db8:104::/64"
+       check_route6 "unreachable 2001:db8:104::/64 dev lo metric 1024"
+       log_test $? 0 "Multipath with reject route"
+       # route replace fails - invalid nexthop 1
+       add_initial_route6 "nexthop via 2001:db8:101::2 nexthop via 2001:db8:103::2"
+       run_cmd "$IP -6 ro replace 2001:db8:104::/64 nexthop via 2001:db8:111::3 nexthop via 2001:db8:103::3"
+       check_route6  "2001:db8:104::/64 metric 1024 nexthop via 2001:db8:101::2 dev veth1 weight 1 nexthop via 2001:db8:103::2 dev veth3 weight 1"
+       log_test $? 0 "Multipath - invalid first nexthop"
+       # route replace fails - invalid nexthop 2
+       add_initial_route6 "nexthop via 2001:db8:101::2 nexthop via 2001:db8:103::2"
+       run_cmd "$IP -6 ro replace 2001:db8:104::/64 nexthop via 2001:db8:101::3 nexthop via 2001:db8:113::3"
+       check_route6  "2001:db8:104::/64 metric 1024 nexthop via 2001:db8:101::2 dev veth1 weight 1 nexthop via 2001:db8:103::2 dev veth3 weight 1"
+       log_test $? 0 "Multipath - invalid second nexthop"
+       # multipath non-existent route
+       add_initial_route6 "nexthop via 2001:db8:101::2 nexthop via 2001:db8:103::2"
+       run_cmd "$IP -6 ro change 2001:db8:105::/64 nexthop via 2001:db8:101::3 nexthop via 2001:db8:103::3"
+       log_test $? 2 "Multipath - replace of non-existent route"
+ }
+ ipv6_rt_replace()
+ {
+       echo
+       echo "IPv6 route replace tests"
+       ipv6_rt_replace_single
+       ipv6_rt_replace_mpath
+ }
+ ipv6_route_test()
+ {
+       route_setup
+       ipv6_rt_add
+       ipv6_rt_replace
+       route_cleanup
  }
  
+ ip_addr_metric_check()
+ {
+       ip addr help 2>&1 | grep -q metric
+       if [ $? -ne 0 ]; then
+               echo "iproute2 command does not support metric for addresses. Skipping test"
+               return 1
+       fi
+       return 0
+ }
+ ipv6_addr_metric_test()
+ {
+       local rc
+       echo
+       echo "IPv6 prefix route tests"
+       ip_addr_metric_check || return 1
+       setup
+       set -e
+       $IP li add dummy1 type dummy
+       $IP li add dummy2 type dummy
+       $IP li set dummy1 up
+       $IP li set dummy2 up
+       # default entry is metric 256
+       run_cmd "$IP -6 addr add dev dummy1 2001:db8:104::1/64"
+       run_cmd "$IP -6 addr add dev dummy2 2001:db8:104::2/64"
+       set +e
+       check_route6 "2001:db8:104::/64 dev dummy1 proto kernel metric 256 2001:db8:104::/64 dev dummy2 proto kernel metric 256"
+       log_test $? 0 "Default metric"
+       set -e
+       run_cmd "$IP -6 addr flush dev dummy1"
+       run_cmd "$IP -6 addr add dev dummy1 2001:db8:104::1/64 metric 257"
+       set +e
+       check_route6 "2001:db8:104::/64 dev dummy2 proto kernel metric 256 2001:db8:104::/64 dev dummy1 proto kernel metric 257"
+       log_test $? 0 "User specified metric on first device"
+       set -e
+       run_cmd "$IP -6 addr flush dev dummy2"
+       run_cmd "$IP -6 addr add dev dummy2 2001:db8:104::2/64 metric 258"
+       set +e
+       check_route6 "2001:db8:104::/64 dev dummy1 proto kernel metric 257 2001:db8:104::/64 dev dummy2 proto kernel metric 258"
+       log_test $? 0 "User specified metric on second device"
+       run_cmd "$IP -6 addr del dev dummy1 2001:db8:104::1/64 metric 257"
+       rc=$?
+       if [ $rc -eq 0 ]; then
+               check_route6 "2001:db8:104::/64 dev dummy2 proto kernel metric 258"
+               rc=$?
+       fi
+       log_test $rc 0 "Delete of address on first device"
+       run_cmd "$IP -6 addr change dev dummy2 2001:db8:104::2/64 metric 259"
+       rc=$?
+       if [ $rc -eq 0 ]; then
+               check_route6 "2001:db8:104::/64 dev dummy2 proto kernel metric 259"
+               rc=$?
+       fi
+       log_test $rc 0 "Modify metric of address"
+       # verify prefix route removed on down
+       run_cmd "ip netns exec testns sysctl -qw net.ipv6.conf.all.keep_addr_on_down=1"
+       run_cmd "$IP li set dev dummy2 down"
+       rc=$?
+       if [ $rc -eq 0 ]; then
+               check_route6 ""
+               rc=$?
+       fi
+       log_test $rc 0 "Prefix route removed on link down"
+       # verify prefix route re-inserted with assigned metric
+       run_cmd "$IP li set dev dummy2 up"
+       rc=$?
+       if [ $rc -eq 0 ]; then
+               check_route6 "2001:db8:104::/64 dev dummy2 proto kernel metric 259"
+               rc=$?
+       fi
+       log_test $rc 0 "Prefix route with metric on link up"
+       $IP li del dummy1
+       $IP li del dummy2
+       cleanup
+ }
+ # add route for a prefix, flushing any existing routes first
+ # expected to be the first step of a test
+ add_route()
+ {
+       local pfx="$1"
+       local nh="$2"
+       local out
+       if [ "$VERBOSE" = "1" ]; then
+               echo
+               echo "    ##################################################"
+               echo
+       fi
+       run_cmd "$IP ro flush ${pfx}"
+       [ $? -ne 0 ] && exit 1
+       out=$($IP ro ls match ${pfx})
+       if [ -n "$out" ]; then
+               echo "Failed to flush routes for prefix used for tests."
+               exit 1
+       fi
+       run_cmd "$IP ro add ${pfx} ${nh}"
+       if [ $? -ne 0 ]; then
+               echo "Failed to add initial route for test."
+               exit 1
+       fi
+ }
+ # add initial route - used in replace route tests
+ add_initial_route()
+ {
+       add_route "172.16.104.0/24" "$1"
+ }
+ check_route()
+ {
+       local pfx="172.16.104.0/24"
+       local expected="$1"
+       local out
+       local rc=0
+       out=$($IP ro ls match ${pfx})
+       [ "${out}" = "${expected}" ] && return 0
+       if [ -z "${out}" ]; then
+               if [ "$VERBOSE" = "1" ]; then
+                       printf "\nNo route entry found\n"
+                       printf "Expected:\n"
+                       printf "    ${expected}\n"
+               fi
+               return 1
+       fi
+       # tricky way to convert output to 1-line without ip's
+       # messy '\'; this drops all extra white space
+       out=$(echo ${out})
+       if [ "${out}" != "${expected}" ]; then
+               rc=1
+               if [ "${VERBOSE}" = "1" ]; then
+                       printf "    Unexpected route entry. Have:\n"
+                       printf "        ${out}\n"
+                       printf "    Expected:\n"
+                       printf "        ${expected}\n\n"
+               fi
+       fi
+       return $rc
+ }
+ # assumption is that basic add of a single path route works
+ # otherwise just adding an address on an interface is broken
+ ipv4_rt_add()
+ {
+       local rc
+       echo
+       echo "IPv4 route add / append tests"
+       # route add same prefix - fails with EEXISTS b/c ip adds NLM_F_EXCL
+       add_route "172.16.104.0/24" "via 172.16.101.2"
+       run_cmd "$IP ro add 172.16.104.0/24 via 172.16.103.2"
+       log_test $? 2 "Attempt to add duplicate route - gw"
+       # route add same prefix - fails with EEXISTS b/c ip adds NLM_F_EXCL
+       add_route "172.16.104.0/24" "via 172.16.101.2"
+       run_cmd "$IP ro add 172.16.104.0/24 dev veth3"
+       log_test $? 2 "Attempt to add duplicate route - dev only"
+       # route add same prefix - fails with EEXISTS b/c ip adds NLM_F_EXCL
+       add_route "172.16.104.0/24" "via 172.16.101.2"
+       run_cmd "$IP ro add unreachable 172.16.104.0/24"
+       log_test $? 2 "Attempt to add duplicate route - reject route"
+       # iproute2 prepend only sets NLM_F_CREATE
+       # - adds a new route; does NOT convert existing route to ECMP
+       add_route "172.16.104.0/24" "via 172.16.101.2"
+       run_cmd "$IP ro prepend 172.16.104.0/24 via 172.16.103.2"
+       check_route "172.16.104.0/24 via 172.16.103.2 dev veth3 172.16.104.0/24 via 172.16.101.2 dev veth1"
+       log_test $? 0 "Add new nexthop for existing prefix"
+       # route append with same prefix adds a new route
+       # - iproute2 sets NLM_F_CREATE | NLM_F_APPEND
+       add_route "172.16.104.0/24" "via 172.16.101.2"
+       run_cmd "$IP ro append 172.16.104.0/24 via 172.16.103.2"
+       check_route "172.16.104.0/24 via 172.16.101.2 dev veth1 172.16.104.0/24 via 172.16.103.2 dev veth3"
+       log_test $? 0 "Append nexthop to existing route - gw"
+       add_route "172.16.104.0/24" "via 172.16.101.2"
+       run_cmd "$IP ro append 172.16.104.0/24 dev veth3"
+       check_route "172.16.104.0/24 via 172.16.101.2 dev veth1 172.16.104.0/24 dev veth3 scope link"
+       log_test $? 0 "Append nexthop to existing route - dev only"
+       add_route "172.16.104.0/24" "via 172.16.101.2"
+       run_cmd "$IP ro append unreachable 172.16.104.0/24"
+       check_route "172.16.104.0/24 via 172.16.101.2 dev veth1 unreachable 172.16.104.0/24"
+       log_test $? 0 "Append nexthop to existing route - reject route"
+       run_cmd "$IP ro flush 172.16.104.0/24"
+       run_cmd "$IP ro add unreachable 172.16.104.0/24"
+       run_cmd "$IP ro append 172.16.104.0/24 via 172.16.103.2"
+       check_route "unreachable 172.16.104.0/24 172.16.104.0/24 via 172.16.103.2 dev veth3"
+       log_test $? 0 "Append nexthop to existing reject route - gw"
+       run_cmd "$IP ro flush 172.16.104.0/24"
+       run_cmd "$IP ro add unreachable 172.16.104.0/24"
+       run_cmd "$IP ro append 172.16.104.0/24 dev veth3"
+       check_route "unreachable 172.16.104.0/24 172.16.104.0/24 dev veth3 scope link"
+       log_test $? 0 "Append nexthop to existing reject route - dev only"
+       # insert mpath directly
+       add_route "172.16.104.0/24" "nexthop via 172.16.101.2 nexthop via 172.16.103.2"
+       check_route  "172.16.104.0/24 nexthop via 172.16.101.2 dev veth1 weight 1 nexthop via 172.16.103.2 dev veth3 weight 1"
+       log_test $? 0 "add multipath route"
+       add_route "172.16.104.0/24" "nexthop via 172.16.101.2 nexthop via 172.16.103.2"
+       run_cmd "$IP ro add 172.16.104.0/24 nexthop via 172.16.101.2 nexthop via 172.16.103.2"
+       log_test $? 2 "Attempt to add duplicate multipath route"
+       # insert of a second route without append but different metric
+       add_route "172.16.104.0/24" "via 172.16.101.2"
+       run_cmd "$IP ro add 172.16.104.0/24 via 172.16.103.2 metric 512"
+       rc=$?
+       if [ $rc -eq 0 ]; then
+               run_cmd "$IP ro add 172.16.104.0/24 via 172.16.103.3 metric 256"
+               rc=$?
+       fi
+       log_test $rc 0 "Route add with different metrics"
+       run_cmd "$IP ro del 172.16.104.0/24 metric 512"
+       rc=$?
+       if [ $rc -eq 0 ]; then
+               check_route "172.16.104.0/24 via 172.16.101.2 dev veth1 172.16.104.0/24 via 172.16.103.3 dev veth3 metric 256"
+               rc=$?
+       fi
+       log_test $rc 0 "Route delete with metric"
+ }
+ ipv4_rt_replace_single()
+ {
+       # single path with single path
+       #
+       add_initial_route "via 172.16.101.2"
+       run_cmd "$IP ro replace 172.16.104.0/24 via 172.16.103.2"
+       check_route "172.16.104.0/24 via 172.16.103.2 dev veth3"
+       log_test $? 0 "Single path with single path"
+       # single path with multipath
+       #
+       add_initial_route "nexthop via 172.16.101.2"
+       run_cmd "$IP ro replace 172.16.104.0/24 nexthop via 172.16.101.3 nexthop via 172.16.103.2"
+       check_route "172.16.104.0/24 nexthop via 172.16.101.3 dev veth1 weight 1 nexthop via 172.16.103.2 dev veth3 weight 1"
+       log_test $? 0 "Single path with multipath"
+       # single path with reject
+       #
+       add_initial_route "nexthop via 172.16.101.2"
+       run_cmd "$IP ro replace unreachable 172.16.104.0/24"
+       check_route "unreachable 172.16.104.0/24"
+       log_test $? 0 "Single path with reject route"
+       # single path with single path using MULTIPATH attribute
+       #
+       add_initial_route "via 172.16.101.2"
+       run_cmd "$IP ro replace 172.16.104.0/24 nexthop via 172.16.103.2"
+       check_route "172.16.104.0/24 via 172.16.103.2 dev veth3"
+       log_test $? 0 "Single path with single path via multipath attribute"
+       # route replace fails - invalid nexthop
+       add_initial_route "via 172.16.101.2"
+       run_cmd "$IP ro replace 172.16.104.0/24 via 2001:db8:104::2"
+       if [ $? -eq 0 ]; then
+               # previous command is expected to fail so if it returns 0
+               # that means the test failed.
+               log_test 0 1 "Invalid nexthop"
+       else
+               check_route "172.16.104.0/24 via 172.16.101.2 dev veth1"
+               log_test $? 0 "Invalid nexthop"
+       fi
+       # replace non-existent route
+       # - note use of change versus replace since ip adds NLM_F_CREATE
+       #   for replace
+       add_initial_route "via 172.16.101.2"
+       run_cmd "$IP ro change 172.16.105.0/24 via 172.16.101.2"
+       log_test $? 2 "Single path - replace of non-existent route"
+ }
+ ipv4_rt_replace_mpath()
+ {
+       # multipath with multipath
+       add_initial_route "nexthop via 172.16.101.2 nexthop via 172.16.103.2"
+       run_cmd "$IP ro replace 172.16.104.0/24 nexthop via 172.16.101.3 nexthop via 172.16.103.3"
+       check_route  "172.16.104.0/24 nexthop via 172.16.101.3 dev veth1 weight 1 nexthop via 172.16.103.3 dev veth3 weight 1"
+       log_test $? 0 "Multipath with multipath"
+       # multipath with single
+       add_initial_route "nexthop via 172.16.101.2 nexthop via 172.16.103.2"
+       run_cmd "$IP ro replace 172.16.104.0/24 via 172.16.101.3"
+       check_route  "172.16.104.0/24 via 172.16.101.3 dev veth1"
+       log_test $? 0 "Multipath with single path"
+       # multipath with single
+       add_initial_route "nexthop via 172.16.101.2 nexthop via 172.16.103.2"
+       run_cmd "$IP ro replace 172.16.104.0/24 nexthop via 172.16.101.3"
+       check_route "172.16.104.0/24 via 172.16.101.3 dev veth1"
+       log_test $? 0 "Multipath with single path via multipath attribute"
+       # multipath with reject
+       add_initial_route "nexthop via 172.16.101.2 nexthop via 172.16.103.2"
+       run_cmd "$IP ro replace unreachable 172.16.104.0/24"
+       check_route "unreachable 172.16.104.0/24"
+       log_test $? 0 "Multipath with reject route"
+       # route replace fails - invalid nexthop 1
+       add_initial_route "nexthop via 172.16.101.2 nexthop via 172.16.103.2"
+       run_cmd "$IP ro replace 172.16.104.0/24 nexthop via 172.16.111.3 nexthop via 172.16.103.3"
+       check_route  "172.16.104.0/24 nexthop via 172.16.101.2 dev veth1 weight 1 nexthop via 172.16.103.2 dev veth3 weight 1"
+       log_test $? 0 "Multipath - invalid first nexthop"
+       # route replace fails - invalid nexthop 2
+       add_initial_route "nexthop via 172.16.101.2 nexthop via 172.16.103.2"
+       run_cmd "$IP ro replace 172.16.104.0/24 nexthop via 172.16.101.3 nexthop via 172.16.113.3"
+       check_route  "172.16.104.0/24 nexthop via 172.16.101.2 dev veth1 weight 1 nexthop via 172.16.103.2 dev veth3 weight 1"
+       log_test $? 0 "Multipath - invalid second nexthop"
+       # multipath non-existent route
+       add_initial_route "nexthop via 172.16.101.2 nexthop via 172.16.103.2"
+       run_cmd "$IP ro change 172.16.105.0/24 nexthop via 172.16.101.3 nexthop via 172.16.103.3"
+       log_test $? 2 "Multipath - replace of non-existent route"
+ }
+ ipv4_rt_replace()
+ {
+       echo
+       echo "IPv4 route replace tests"
+       ipv4_rt_replace_single
+       ipv4_rt_replace_mpath
+ }
+ ipv4_route_test()
+ {
+       route_setup
+       ipv4_rt_add
+       ipv4_rt_replace
+       route_cleanup
+ }
+ ipv4_addr_metric_test()
+ {
+       local rc
+       echo
+       echo "IPv4 prefix route tests"
+       ip_addr_metric_check || return 1
+       setup
+       set -e
+       $IP li add dummy1 type dummy
+       $IP li add dummy2 type dummy
+       $IP li set dummy1 up
+       $IP li set dummy2 up
+       # default entry is metric 256
+       run_cmd "$IP addr add dev dummy1 172.16.104.1/24"
+       run_cmd "$IP addr add dev dummy2 172.16.104.2/24"
+       set +e
+       check_route "172.16.104.0/24 dev dummy1 proto kernel scope link src 172.16.104.1 172.16.104.0/24 dev dummy2 proto kernel scope link src 172.16.104.2"
+       log_test $? 0 "Default metric"
+       set -e
+       run_cmd "$IP addr flush dev dummy1"
+       run_cmd "$IP addr add dev dummy1 172.16.104.1/24 metric 257"
+       set +e
+       check_route "172.16.104.0/24 dev dummy2 proto kernel scope link src 172.16.104.2 172.16.104.0/24 dev dummy1 proto kernel scope link src 172.16.104.1 metric 257"
+       log_test $? 0 "User specified metric on first device"
+       set -e
+       run_cmd "$IP addr flush dev dummy2"
+       run_cmd "$IP addr add dev dummy2 172.16.104.2/24 metric 258"
+       set +e
+       check_route "172.16.104.0/24 dev dummy1 proto kernel scope link src 172.16.104.1 metric 257 172.16.104.0/24 dev dummy2 proto kernel scope link src 172.16.104.2 metric 258"
+       log_test $? 0 "User specified metric on second device"
+       run_cmd "$IP addr del dev dummy1 172.16.104.1/24 metric 257"
+       rc=$?
+       if [ $rc -eq 0 ]; then
+               check_route "172.16.104.0/24 dev dummy2 proto kernel scope link src 172.16.104.2 metric 258"
+               rc=$?
+       fi
+       log_test $rc 0 "Delete of address on first device"
+       run_cmd "$IP addr change dev dummy2 172.16.104.2/24 metric 259"
+       rc=$?
+       if [ $rc -eq 0 ]; then
+               check_route "172.16.104.0/24 dev dummy2 proto kernel scope link src 172.16.104.2 metric 259"
+               rc=$?
+       fi
+       log_test $rc 0 "Modify metric of address"
+       # verify prefix route removed on down
+       run_cmd "$IP li set dev dummy2 down"
+       rc=$?
+       if [ $rc -eq 0 ]; then
+               check_route ""
+               rc=$?
+       fi
+       log_test $rc 0 "Prefix route removed on link down"
+       # verify prefix route re-inserted with assigned metric
+       run_cmd "$IP li set dev dummy2 up"
+       rc=$?
+       if [ $rc -eq 0 ]; then
+               check_route "172.16.104.0/24 dev dummy2 proto kernel scope link src 172.16.104.2 metric 259"
+               rc=$?
+       fi
+       log_test $rc 0 "Prefix route with metric on link up"
+       $IP li del dummy1
+       $IP li del dummy2
+       cleanup
+ }
+ ################################################################################
+ # usage
+ usage()
+ {
+       cat <<EOF
+ usage: ${0##*/} OPTS
+         -t <test>   Test(s) to run (default: all)
+                     (options: $TESTS)
+         -p          Pause on fail
+         -P          Pause after each test before cleanup
+         -v          verbose mode (show commands and output)
+ EOF
+ }
+ ################################################################################
+ # main
+ while getopts :t:pPhv o
+ do
+       case $o in
+               t) TESTS=$OPTARG;;
+               p) PAUSE_ON_FAIL=yes;;
+               P) PAUSE=yes;;
+               v) VERBOSE=$(($VERBOSE + 1));;
+               h) usage; exit 0;;
+               *) usage; exit 1;;
+       esac
+ done
+ PEER_CMD="ip netns exec ${PEER_NS}"
+ # 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 0
 +      exit $ksft_skip;
  fi
  
  if [ ! -x "$(command -v ip)" ]; then
Simple merge