Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
authorDavid S. Miller <davem@davemloft.net>
Fri, 22 Dec 2017 16:16:31 +0000 (11:16 -0500)
committerDavid S. Miller <davem@davemloft.net>
Fri, 22 Dec 2017 16:16:31 +0000 (11:16 -0500)
Lots of overlapping changes.  Also on the net-next side
the XDP state management is handled more in the generic
layers so undo the 'net' nfp fix which isn't applicable
in net-next.

Include a necessary change by Jakub Kicinski, with log message:

====================
cls_bpf no longer takes care of offload tracking.  Make sure
netdevsim performs necessary checks.  This fixes a warning
caused by TC trying to remove a filter it has not added.

Signed-off-by: Jakub Kicinski <jakub.kicinski@netronome.com>
Reviewed-by: Quentin Monnet <quentin.monnet@netronome.com>
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
26 files changed:
1  2 
arch/powerpc/net/bpf_jit_comp64.c
arch/s390/net/bpf_jit_comp.c
arch/sparc/net/bpf_jit_comp_64.c
drivers/net/ethernet/mellanox/mlx5/core/en_main.c
drivers/net/ethernet/netronome/nfp/bpf/main.c
drivers/net/ethernet/netronome/nfp/bpf/main.h
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
drivers/net/netdevsim/bpf.c
drivers/net/phy/marvell.c
drivers/net/vxlan.c
drivers/s390/net/qeth_core_main.c
include/linux/bpf_verifier.h
include/net/pkt_cls.h
kernel/bpf/verifier.c
net/core/dev.c
net/ipv4/ip_gre.c
net/ipv6/ip6_gre.c
net/ipv6/ip6_output.c
net/ipv6/ip6_tunnel.c
net/ipv6/route.c
net/openvswitch/flow.c
net/sctp/ulpqueue.c
tools/testing/selftests/bpf/Makefile
tools/testing/selftests/bpf/test_offload.py
tools/testing/selftests/bpf/test_progs.c
tools/testing/selftests/bpf/test_verifier.c

Simple merge
Simple merge
Simple merge
index 7da8146,0000000..a243fa7
mode 100644,000000..100644
--- /dev/null
@@@ -1,370 -1,0 +1,373 @@@
-       if (nsim_xdp_offload_active(ns))
-               return -EBUSY;
 +/*
 + * Copyright (C) 2017 Netronome Systems, Inc.
 + *
 + * This software is licensed under the GNU General License Version 2,
 + * June 1991 as shown in the file COPYING in the top-level directory of this
 + * source tree.
 + *
 + * THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
 + * WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 + * FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
 + * OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
 + * THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
 + */
 +
 +#include <linux/bpf.h>
 +#include <linux/bpf_verifier.h>
 +#include <linux/debugfs.h>
 +#include <linux/kernel.h>
 +#include <linux/rtnetlink.h>
 +#include <net/pkt_cls.h>
 +
 +#include "netdevsim.h"
 +
 +struct nsim_bpf_bound_prog {
 +      struct netdevsim *ns;
 +      struct bpf_prog *prog;
 +      struct dentry *ddir;
 +      const char *state;
 +      bool is_loaded;
 +      struct list_head l;
 +};
 +
 +static int nsim_debugfs_bpf_string_read(struct seq_file *file, void *data)
 +{
 +      const char **str = file->private;
 +
 +      if (*str)
 +              seq_printf(file, "%s\n", *str);
 +
 +      return 0;
 +}
 +
 +static int nsim_debugfs_bpf_string_open(struct inode *inode, struct file *f)
 +{
 +      return single_open(f, nsim_debugfs_bpf_string_read, inode->i_private);
 +}
 +
 +static const struct file_operations nsim_bpf_string_fops = {
 +      .owner = THIS_MODULE,
 +      .open = nsim_debugfs_bpf_string_open,
 +      .release = single_release,
 +      .read = seq_read,
 +      .llseek = seq_lseek
 +};
 +
 +static int
 +nsim_bpf_verify_insn(struct bpf_verifier_env *env, int insn_idx, int prev_insn)
 +{
 +      struct nsim_bpf_bound_prog *state;
 +
 +      state = env->prog->aux->offload->dev_priv;
 +      if (state->ns->bpf_bind_verifier_delay && !insn_idx)
 +              msleep(state->ns->bpf_bind_verifier_delay);
 +
 +      return 0;
 +}
 +
 +static const struct bpf_ext_analyzer_ops nsim_bpf_analyzer_ops = {
 +      .insn_hook = nsim_bpf_verify_insn,
 +};
 +
 +static bool nsim_xdp_offload_active(struct netdevsim *ns)
 +{
 +      return ns->xdp_prog_mode == XDP_ATTACHED_HW;
 +}
 +
 +static void nsim_prog_set_loaded(struct bpf_prog *prog, bool loaded)
 +{
 +      struct nsim_bpf_bound_prog *state;
 +
 +      if (!prog || !prog->aux->offload)
 +              return;
 +
 +      state = prog->aux->offload->dev_priv;
 +      state->is_loaded = loaded;
 +}
 +
 +static int
 +nsim_bpf_offload(struct netdevsim *ns, struct bpf_prog *prog, bool oldprog)
 +{
 +      nsim_prog_set_loaded(ns->bpf_offloaded, false);
 +
 +      WARN(!!ns->bpf_offloaded != oldprog,
 +           "bad offload state, expected offload %sto be active",
 +           oldprog ? "" : "not ");
 +      ns->bpf_offloaded = prog;
 +      ns->bpf_offloaded_id = prog ? prog->aux->id : 0;
 +      nsim_prog_set_loaded(prog, true);
 +
 +      return 0;
 +}
 +
 +int nsim_bpf_setup_tc_block_cb(enum tc_setup_type type,
 +                             void *type_data, void *cb_priv)
 +{
 +      struct tc_cls_bpf_offload *cls_bpf = type_data;
 +      struct bpf_prog *prog = cls_bpf->prog;
 +      struct netdevsim *ns = cb_priv;
++      struct bpf_prog *oldprog;
 +
 +      if (type != TC_SETUP_CLSBPF ||
 +          !tc_can_offload(ns->netdev) ||
 +          cls_bpf->common.protocol != htons(ETH_P_ALL) ||
 +          cls_bpf->common.chain_index)
 +              return -EOPNOTSUPP;
 +
-       switch (cls_bpf->command) {
-       case TC_CLSBPF_REPLACE:
-               return nsim_bpf_offload(ns, prog, true);
-       case TC_CLSBPF_ADD:
-               return nsim_bpf_offload(ns, prog, false);
-       case TC_CLSBPF_DESTROY:
-               return nsim_bpf_offload(ns, NULL, true);
-       default:
 +      if (!ns->bpf_tc_accept)
 +              return -EOPNOTSUPP;
 +      /* Note: progs without skip_sw will probably not be dev bound */
 +      if (prog && !prog->aux->offload && !ns->bpf_tc_non_bound_accept)
 +              return -EOPNOTSUPP;
 +
++      if (cls_bpf->command != TC_CLSBPF_OFFLOAD)
 +              return -EOPNOTSUPP;
++
++      oldprog = cls_bpf->oldprog;
++
++      /* Don't remove if oldprog doesn't match driver's state */
++      if (ns->bpf_offloaded != oldprog) {
++              oldprog = NULL;
++              if (!cls_bpf->prog)
++                      return 0;
++              if (ns->bpf_offloaded)
++                      return -EBUSY;
 +      }
++
++      return nsim_bpf_offload(ns, cls_bpf->prog, oldprog);
 +}
 +
 +int nsim_bpf_disable_tc(struct netdevsim *ns)
 +{
 +      if (ns->bpf_offloaded && !nsim_xdp_offload_active(ns))
 +              return -EBUSY;
 +      return 0;
 +}
 +
 +static int nsim_xdp_offload_prog(struct netdevsim *ns, struct netdev_bpf *bpf)
 +{
 +      if (!nsim_xdp_offload_active(ns) && !bpf->prog)
 +              return 0;
 +      if (!nsim_xdp_offload_active(ns) && bpf->prog && ns->bpf_offloaded) {
 +              NSIM_EA(bpf->extack, "TC program is already loaded");
 +              return -EBUSY;
 +      }
 +
 +      return nsim_bpf_offload(ns, bpf->prog, nsim_xdp_offload_active(ns));
 +}
 +
 +static int nsim_xdp_set_prog(struct netdevsim *ns, struct netdev_bpf *bpf)
 +{
 +      int err;
 +
 +      if (ns->xdp_prog && (bpf->flags ^ ns->xdp_flags) & XDP_FLAGS_MODES) {
 +              NSIM_EA(bpf->extack, "program loaded with different flags");
 +              return -EBUSY;
 +      }
 +
 +      if (bpf->command == XDP_SETUP_PROG && !ns->bpf_xdpdrv_accept) {
 +              NSIM_EA(bpf->extack, "driver XDP disabled in DebugFS");
 +              return -EOPNOTSUPP;
 +      }
 +      if (bpf->command == XDP_SETUP_PROG_HW && !ns->bpf_xdpoffload_accept) {
 +              NSIM_EA(bpf->extack, "XDP offload disabled in DebugFS");
 +              return -EOPNOTSUPP;
 +      }
 +
 +      if (bpf->command == XDP_SETUP_PROG_HW) {
 +              err = nsim_xdp_offload_prog(ns, bpf);
 +              if (err)
 +                      return err;
 +      }
 +
 +      if (ns->xdp_prog)
 +              bpf_prog_put(ns->xdp_prog);
 +
 +      ns->xdp_prog = bpf->prog;
 +      ns->xdp_flags = bpf->flags;
 +
 +      if (!bpf->prog)
 +              ns->xdp_prog_mode = XDP_ATTACHED_NONE;
 +      else if (bpf->command == XDP_SETUP_PROG)
 +              ns->xdp_prog_mode = XDP_ATTACHED_DRV;
 +      else
 +              ns->xdp_prog_mode = XDP_ATTACHED_HW;
 +
 +      return 0;
 +}
 +
 +static int nsim_bpf_create_prog(struct netdevsim *ns, struct bpf_prog *prog)
 +{
 +      struct nsim_bpf_bound_prog *state;
 +      char name[16];
 +
 +      state = kzalloc(sizeof(*state), GFP_KERNEL);
 +      if (!state)
 +              return -ENOMEM;
 +
 +      state->ns = ns;
 +      state->prog = prog;
 +      state->state = "verify";
 +
 +      /* Program id is not populated yet when we create the state. */
 +      sprintf(name, "%u", ns->prog_id_gen++);
 +      state->ddir = debugfs_create_dir(name, ns->ddir_bpf_bound_progs);
 +      if (IS_ERR_OR_NULL(state->ddir)) {
 +              kfree(state);
 +              return -ENOMEM;
 +      }
 +
 +      debugfs_create_u32("id", 0400, state->ddir, &prog->aux->id);
 +      debugfs_create_file("state", 0400, state->ddir,
 +                          &state->state, &nsim_bpf_string_fops);
 +      debugfs_create_bool("loaded", 0400, state->ddir, &state->is_loaded);
 +
 +      list_add_tail(&state->l, &ns->bpf_bound_progs);
 +
 +      prog->aux->offload->dev_priv = state;
 +
 +      return 0;
 +}
 +
 +static void nsim_bpf_destroy_prog(struct bpf_prog *prog)
 +{
 +      struct nsim_bpf_bound_prog *state;
 +
 +      state = prog->aux->offload->dev_priv;
 +      WARN(state->is_loaded,
 +           "offload state destroyed while program still bound");
 +      debugfs_remove_recursive(state->ddir);
 +      list_del(&state->l);
 +      kfree(state);
 +}
 +
 +static int nsim_setup_prog_checks(struct netdevsim *ns, struct netdev_bpf *bpf)
 +{
 +      if (bpf->prog && bpf->prog->aux->offload) {
 +              NSIM_EA(bpf->extack, "attempt to load offloaded prog to drv");
 +              return -EINVAL;
 +      }
 +      if (ns->netdev->mtu > NSIM_XDP_MAX_MTU) {
 +              NSIM_EA(bpf->extack, "MTU too large w/ XDP enabled");
 +              return -EINVAL;
 +      }
 +      if (nsim_xdp_offload_active(ns)) {
 +              NSIM_EA(bpf->extack, "xdp offload active, can't load drv prog");
 +              return -EBUSY;
 +      }
 +      return 0;
 +}
 +
 +static int
 +nsim_setup_prog_hw_checks(struct netdevsim *ns, struct netdev_bpf *bpf)
 +{
 +      struct nsim_bpf_bound_prog *state;
 +
 +      if (!bpf->prog)
 +              return 0;
 +
 +      if (!bpf->prog->aux->offload) {
 +              NSIM_EA(bpf->extack, "xdpoffload of non-bound program");
 +              return -EINVAL;
 +      }
 +      if (bpf->prog->aux->offload->netdev != ns->netdev) {
 +              NSIM_EA(bpf->extack, "program bound to different dev");
 +              return -EINVAL;
 +      }
 +
 +      state = bpf->prog->aux->offload->dev_priv;
 +      if (WARN_ON(strcmp(state->state, "xlated"))) {
 +              NSIM_EA(bpf->extack, "offloading program in bad state");
 +              return -EINVAL;
 +      }
 +      return 0;
 +}
 +
 +int nsim_bpf(struct net_device *dev, struct netdev_bpf *bpf)
 +{
 +      struct netdevsim *ns = netdev_priv(dev);
 +      struct nsim_bpf_bound_prog *state;
 +      int err;
 +
 +      ASSERT_RTNL();
 +
 +      switch (bpf->command) {
 +      case BPF_OFFLOAD_VERIFIER_PREP:
 +              if (!ns->bpf_bind_accept)
 +                      return -EOPNOTSUPP;
 +
 +              err = nsim_bpf_create_prog(ns, bpf->verifier.prog);
 +              if (err)
 +                      return err;
 +
 +              bpf->verifier.ops = &nsim_bpf_analyzer_ops;
 +              return 0;
 +      case BPF_OFFLOAD_TRANSLATE:
 +              state = bpf->offload.prog->aux->offload->dev_priv;
 +
 +              state->state = "xlated";
 +              return 0;
 +      case BPF_OFFLOAD_DESTROY:
 +              nsim_bpf_destroy_prog(bpf->offload.prog);
 +              return 0;
 +      case XDP_QUERY_PROG:
 +              bpf->prog_attached = ns->xdp_prog_mode;
 +              bpf->prog_id = ns->xdp_prog ? ns->xdp_prog->aux->id : 0;
 +              bpf->prog_flags = ns->xdp_prog ? ns->xdp_flags : 0;
 +              return 0;
 +      case XDP_SETUP_PROG:
 +              err = nsim_setup_prog_checks(ns, bpf);
 +              if (err)
 +                      return err;
 +
 +              return nsim_xdp_set_prog(ns, bpf);
 +      case XDP_SETUP_PROG_HW:
 +              err = nsim_setup_prog_hw_checks(ns, bpf);
 +              if (err)
 +                      return err;
 +
 +              return nsim_xdp_set_prog(ns, bpf);
 +      default:
 +              return -EINVAL;
 +      }
 +}
 +
 +int nsim_bpf_init(struct netdevsim *ns)
 +{
 +      INIT_LIST_HEAD(&ns->bpf_bound_progs);
 +
 +      debugfs_create_u32("bpf_offloaded_id", 0400, ns->ddir,
 +                         &ns->bpf_offloaded_id);
 +
 +      ns->bpf_bind_accept = true;
 +      debugfs_create_bool("bpf_bind_accept", 0600, ns->ddir,
 +                          &ns->bpf_bind_accept);
 +      debugfs_create_u32("bpf_bind_verifier_delay", 0600, ns->ddir,
 +                         &ns->bpf_bind_verifier_delay);
 +      ns->ddir_bpf_bound_progs =
 +              debugfs_create_dir("bpf_bound_progs", ns->ddir);
 +      if (IS_ERR_OR_NULL(ns->ddir_bpf_bound_progs))
 +              return -ENOMEM;
 +
 +      ns->bpf_tc_accept = true;
 +      debugfs_create_bool("bpf_tc_accept", 0600, ns->ddir,
 +                          &ns->bpf_tc_accept);
 +      debugfs_create_bool("bpf_tc_non_bound_accept", 0600, ns->ddir,
 +                          &ns->bpf_tc_non_bound_accept);
 +      ns->bpf_xdpdrv_accept = true;
 +      debugfs_create_bool("bpf_xdpdrv_accept", 0600, ns->ddir,
 +                          &ns->bpf_xdpdrv_accept);
 +      ns->bpf_xdpoffload_accept = true;
 +      debugfs_create_bool("bpf_xdpoffload_accept", 0600, ns->ddir,
 +                          &ns->bpf_xdpoffload_accept);
 +
 +      return 0;
 +}
 +
 +void nsim_bpf_uninit(struct netdevsim *ns)
 +{
 +      WARN_ON(!list_empty(&ns->bpf_bound_progs));
 +      WARN_ON(ns->xdp_prog);
 +      WARN_ON(ns->bpf_offloaded);
 +}
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -1425,54 -1072,29 +1430,77 @@@ static int check_ptr_alignment(struct b
                                           strict);
  }
  
 +static int update_stack_depth(struct bpf_verifier_env *env,
 +                            const struct bpf_func_state *func,
 +                            int off)
 +{
 +      u16 stack = env->subprog_stack_depth[func->subprogno], total = 0;
 +      struct bpf_verifier_state *cur = env->cur_state;
 +      int i;
 +
 +      if (stack >= -off)
 +              return 0;
 +
 +      /* update known max for given subprogram */
 +      env->subprog_stack_depth[func->subprogno] = -off;
 +
 +      /* compute the total for current call chain */
 +      for (i = 0; i <= cur->curframe; i++) {
 +              u32 depth = env->subprog_stack_depth[cur->frame[i]->subprogno];
 +
 +              /* round up to 32-bytes, since this is granularity
 +               * of interpreter stack sizes
 +               */
 +              depth = round_up(depth, 32);
 +              total += depth;
 +      }
 +
 +      if (total > MAX_BPF_STACK) {
 +              verbose(env, "combined stack size of %d calls is %d. Too large\n",
 +                      cur->curframe, total);
 +              return -EACCES;
 +      }
 +      return 0;
 +}
 +
 +static int get_callee_stack_depth(struct bpf_verifier_env *env,
 +                                const struct bpf_insn *insn, int idx)
 +{
 +      int start = idx + insn->imm + 1, subprog;
 +
 +      subprog = find_subprog(env, start);
 +      if (subprog < 0) {
 +              WARN_ONCE(1, "verifier bug. No program starts at insn %d\n",
 +                        start);
 +              return -EFAULT;
 +      }
 +      subprog++;
 +      return env->subprog_stack_depth[subprog];
 +}
 +
+ /* truncate register to smaller size (in bytes)
+  * must be called with size < BPF_REG_SIZE
+  */
+ static void coerce_reg_to_size(struct bpf_reg_state *reg, int size)
+ {
+       u64 mask;
+       /* clear high bits in bit representation */
+       reg->var_off = tnum_cast(reg->var_off, size);
+       /* fix arithmetic bounds */
+       mask = ((u64)1 << (size * 8)) - 1;
+       if ((reg->umin_value & ~mask) == (reg->umax_value & ~mask)) {
+               reg->umin_value &= mask;
+               reg->umax_value &= mask;
+       } else {
+               reg->umin_value = 0;
+               reg->umax_value = mask;
+       }
+       reg->smin_value = reg->umin_value;
+       reg->smax_value = reg->umax_value;
+ }
  /* check whether memory at (regno + off) is accessible for t = (read | write)
   * if t==write, value_regno is a register which value is stored into memory
   * if t==read, value_regno is a register which will receive the value from memory
@@@ -1678,14 -1302,15 +1704,15 @@@ static int check_stack_boundary(struct 
        }
  
        /* Only allow fixed-offset stack reads */
 -      if (!tnum_is_const(regs[regno].var_off)) {
 +      if (!tnum_is_const(reg->var_off)) {
                char tn_buf[48];
  
 -              tnum_strn(tn_buf, sizeof(tn_buf), regs[regno].var_off);
 +              tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
                verbose(env, "invalid variable stack read R%d var_off=%s\n",
                        regno, tn_buf);
+               return -EACCES;
        }
 -      off = regs[regno].off + regs[regno].var_off.value;
 +      off = reg->off + reg->var_off.value;
        if (off >= 0 || off < -MAX_BPF_STACK || off + access_size > 0 ||
            access_size < 0 || (access_size == 0 && !zero_size_allowed)) {
                verbose(env, "invalid stack type R%d off=%d access_size=%d\n",
@@@ -2758,12 -2294,9 +2828,11 @@@ static int adjust_scalar_min_max_vals(s
  static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
                                   struct bpf_insn *insn)
  {
 -      struct bpf_reg_state *regs = cur_regs(env), *dst_reg, *src_reg;
 +      struct bpf_verifier_state *vstate = env->cur_state;
 +      struct bpf_func_state *state = vstate->frame[vstate->curframe];
 +      struct bpf_reg_state *regs = state->regs, *dst_reg, *src_reg;
        struct bpf_reg_state *ptr_reg = NULL, off_reg = {0};
        u8 opcode = BPF_OP(insn->code);
-       int rc;
  
        dst_reg = &regs[insn->dst_reg];
        src_reg = NULL;
diff --cc net/core/dev.c
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -4326,6 -4321,15 +4321,14 @@@ static int inet6_rtm_getroute(struct sk
                goto errout;
        }
  
 -      if (fibmatch && rt->dst.from) {
 -              struct rt6_info *ort = container_of(rt->dst.from,
 -                                                  struct rt6_info, dst);
++      if (fibmatch && rt->from) {
++              struct rt6_info *ort = rt->from;
+               dst_hold(&ort->dst);
+               ip6_rt_put(rt);
+               rt = ort;
+       }
        skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
        if (!skb) {
                ip6_rt_put(rt);
Simple merge
Simple merge
Simple merge
index 3914f7a,0000000..c940505
mode 100755,000000..100755
--- /dev/null
@@@ -1,681 -1,0 +1,681 @@@
-     sim.cls_bpf_add_filter(obj, da=True, skip_sw=True)
-     # The above will trigger a splat until TC cls_bpf drivers are fixed
 +#!/usr/bin/python3
 +
 +# Copyright (C) 2017 Netronome Systems, Inc.
 +#
 +# This software is licensed under the GNU General License Version 2,
 +# June 1991 as shown in the file COPYING in the top-level directory of this
 +# source tree.
 +#
 +# THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
 +# WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 +# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 +# FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
 +# OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
 +# THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
 +
 +from datetime import datetime
 +import argparse
 +import json
 +import os
 +import pprint
 +import subprocess
 +import time
 +
 +logfile = None
 +log_level = 1
 +bpf_test_dir = os.path.dirname(os.path.realpath(__file__))
 +pp = pprint.PrettyPrinter()
 +devs = [] # devices we created for clean up
 +files = [] # files to be removed
 +
 +def log_get_sec(level=0):
 +    return "*" * (log_level + level)
 +
 +def log_level_inc(add=1):
 +    global log_level
 +    log_level += add
 +
 +def log_level_dec(sub=1):
 +    global log_level
 +    log_level -= sub
 +
 +def log_level_set(level):
 +    global log_level
 +    log_level = level
 +
 +def log(header, data, level=None):
 +    """
 +    Output to an optional log.
 +    """
 +    if logfile is None:
 +        return
 +    if level is not None:
 +        log_level_set(level)
 +
 +    if not isinstance(data, str):
 +        data = pp.pformat(data)
 +
 +    if len(header):
 +        logfile.write("\n" + log_get_sec() + " ")
 +        logfile.write(header)
 +    if len(header) and len(data.strip()):
 +        logfile.write("\n")
 +    logfile.write(data)
 +
 +def skip(cond, msg):
 +    if not cond:
 +        return
 +    print("SKIP: " + msg)
 +    log("SKIP: " + msg, "", level=1)
 +    os.sys.exit(0)
 +
 +def fail(cond, msg):
 +    if not cond:
 +        return
 +    print("FAIL: " + msg)
 +    log("FAIL: " + msg, "", level=1)
 +    os.sys.exit(1)
 +
 +def start_test(msg):
 +    log(msg, "", level=1)
 +    log_level_inc()
 +    print(msg)
 +
 +def cmd(cmd, shell=True, include_stderr=False, background=False, fail=True):
 +    """
 +    Run a command in subprocess and return tuple of (retval, stdout);
 +    optionally return stderr as well as third value.
 +    """
 +    proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE,
 +                            stderr=subprocess.PIPE)
 +    if background:
 +        msg = "%s START: %s" % (log_get_sec(1),
 +                                datetime.now().strftime("%H:%M:%S.%f"))
 +        log("BKG " + proc.args, msg)
 +        return proc
 +
 +    return cmd_result(proc, include_stderr=include_stderr, fail=fail)
 +
 +def cmd_result(proc, include_stderr=False, fail=False):
 +    stdout, stderr = proc.communicate()
 +    stdout = stdout.decode("utf-8")
 +    stderr = stderr.decode("utf-8")
 +    proc.stdout.close()
 +    proc.stderr.close()
 +
 +    stderr = "\n" + stderr
 +    if stderr[-1] == "\n":
 +        stderr = stderr[:-1]
 +
 +    sec = log_get_sec(1)
 +    log("CMD " + proc.args,
 +        "RETCODE: %d\n%s STDOUT:\n%s%s STDERR:%s\n%s END: %s" %
 +        (proc.returncode, sec, stdout, sec, stderr,
 +         sec, datetime.now().strftime("%H:%M:%S.%f")))
 +
 +    if proc.returncode != 0 and fail:
 +        if len(stderr) > 0 and stderr[-1] == "\n":
 +            stderr = stderr[:-1]
 +        raise Exception("Command failed: %s\n%s" % (proc.args, stderr))
 +
 +    if include_stderr:
 +        return proc.returncode, stdout, stderr
 +    else:
 +        return proc.returncode, stdout
 +
 +def rm(f):
 +    cmd("rm -f %s" % (f))
 +    if f in files:
 +        files.remove(f)
 +
 +def tool(name, args, flags, JSON=True, fail=True):
 +    params = ""
 +    if JSON:
 +        params += "%s " % (flags["json"])
 +
 +    ret, out = cmd(name + " " + params + args, fail=fail)
 +    if JSON and len(out.strip()) != 0:
 +        return ret, json.loads(out)
 +    else:
 +        return ret, out
 +
 +def bpftool(args, JSON=True, fail=True):
 +    return tool("bpftool", args, {"json":"-p"}, JSON=JSON, fail=fail)
 +
 +def bpftool_prog_list(expected=None):
 +    _, progs = bpftool("prog show", JSON=True, fail=True)
 +    if expected is not None:
 +        if len(progs) != expected:
 +            fail(True, "%d BPF programs loaded, expected %d" %
 +                 (len(progs), expected))
 +    return progs
 +
 +def bpftool_prog_list_wait(expected=0, n_retry=20):
 +    for i in range(n_retry):
 +        nprogs = len(bpftool_prog_list())
 +        if nprogs == expected:
 +            return
 +        time.sleep(0.05)
 +    raise Exception("Time out waiting for program counts to stabilize want %d, have %d" % (expected, nprogs))
 +
 +def ip(args, force=False, JSON=True, fail=True):
 +    if force:
 +        args = "-force " + args
 +    return tool("ip", args, {"json":"-j"}, JSON=JSON, fail=fail)
 +
 +def tc(args, JSON=True, fail=True):
 +    return tool("tc", args, {"json":"-p"}, JSON=JSON, fail=fail)
 +
 +def ethtool(dev, opt, args, fail=True):
 +    return cmd("ethtool %s %s %s" % (opt, dev["ifname"], args), fail=fail)
 +
 +def bpf_obj(name, sec=".text", path=bpf_test_dir,):
 +    return "obj %s sec %s" % (os.path.join(path, name), sec)
 +
 +def bpf_pinned(name):
 +    return "pinned %s" % (name)
 +
 +def bpf_bytecode(bytecode):
 +    return "bytecode \"%s\"" % (bytecode)
 +
 +class DebugfsDir:
 +    """
 +    Class for accessing DebugFS directories as a dictionary.
 +    """
 +
 +    def __init__(self, path):
 +        self.path = path
 +        self._dict = self._debugfs_dir_read(path)
 +
 +    def __len__(self):
 +        return len(self._dict.keys())
 +
 +    def __getitem__(self, key):
 +        if type(key) is int:
 +            key = list(self._dict.keys())[key]
 +        return self._dict[key]
 +
 +    def __setitem__(self, key, value):
 +        log("DebugFS set %s = %s" % (key, value), "")
 +        log_level_inc()
 +
 +        cmd("echo '%s' > %s/%s" % (value, self.path, key))
 +        log_level_dec()
 +
 +        _, out = cmd('cat %s/%s' % (self.path, key))
 +        self._dict[key] = out.strip()
 +
 +    def _debugfs_dir_read(self, path):
 +        dfs = {}
 +
 +        log("DebugFS state for %s" % (path), "")
 +        log_level_inc(add=2)
 +
 +        _, out = cmd('ls ' + path)
 +        for f in out.split():
 +            p = os.path.join(path, f)
 +            if os.path.isfile(p):
 +                _, out = cmd('cat %s/%s' % (path, f))
 +                dfs[f] = out.strip()
 +            elif os.path.isdir(p):
 +                dfs[f] = DebugfsDir(p)
 +            else:
 +                raise Exception("%s is neither file nor directory" % (p))
 +
 +        log_level_dec()
 +        log("DebugFS state", dfs)
 +        log_level_dec()
 +
 +        return dfs
 +
 +class NetdevSim:
 +    """
 +    Class for netdevsim netdevice and its attributes.
 +    """
 +
 +    def __init__(self):
 +        self.dev = self._netdevsim_create()
 +        devs.append(self)
 +
 +        self.dfs_dir = '/sys/kernel/debug/netdevsim/%s' % (self.dev['ifname'])
 +        self.dfs_refresh()
 +
 +    def __getitem__(self, key):
 +        return self.dev[key]
 +
 +    def _netdevsim_create(self):
 +        _, old  = ip("link show")
 +        ip("link add sim%d type netdevsim")
 +        _, new  = ip("link show")
 +
 +        for dev in new:
 +            f = filter(lambda x: x["ifname"] == dev["ifname"], old)
 +            if len(list(f)) == 0:
 +                return dev
 +
 +        raise Exception("failed to create netdevsim device")
 +
 +    def remove(self):
 +        devs.remove(self)
 +        ip("link del dev %s" % (self.dev["ifname"]))
 +
 +    def dfs_refresh(self):
 +        self.dfs = DebugfsDir(self.dfs_dir)
 +        return self.dfs
 +
 +    def dfs_num_bound_progs(self):
 +        path = os.path.join(self.dfs_dir, "bpf_bound_progs")
 +        _, progs = cmd('ls %s' % (path))
 +        return len(progs.split())
 +
 +    def dfs_get_bound_progs(self, expected):
 +        progs = DebugfsDir(os.path.join(self.dfs_dir, "bpf_bound_progs"))
 +        if expected is not None:
 +            if len(progs) != expected:
 +                fail(True, "%d BPF programs bound, expected %d" %
 +                     (len(progs), expected))
 +        return progs
 +
 +    def wait_for_flush(self, bound=0, total=0, n_retry=20):
 +        for i in range(n_retry):
 +            nbound = self.dfs_num_bound_progs()
 +            nprogs = len(bpftool_prog_list())
 +            if nbound == bound and nprogs == total:
 +                return
 +            time.sleep(0.05)
 +        raise Exception("Time out waiting for program counts to stabilize want %d/%d, have %d bound, %d loaded" % (bound, total, nbound, nprogs))
 +
 +    def set_mtu(self, mtu, fail=True):
 +        return ip("link set dev %s mtu %d" % (self.dev["ifname"], mtu),
 +                  fail=fail)
 +
 +    def set_xdp(self, bpf, mode, force=False, fail=True):
 +        return ip("link set dev %s xdp%s %s" % (self.dev["ifname"], mode, bpf),
 +                  force=force, fail=fail)
 +
 +    def unset_xdp(self, mode, force=False, fail=True):
 +        return ip("link set dev %s xdp%s off" % (self.dev["ifname"], mode),
 +                  force=force, fail=fail)
 +
 +    def ip_link_show(self, xdp):
 +        _, link = ip("link show dev %s" % (self['ifname']))
 +        if len(link) > 1:
 +            raise Exception("Multiple objects on ip link show")
 +        if len(link) < 1:
 +            return {}
 +        fail(xdp != "xdp" in link,
 +             "XDP program not reporting in iplink (reported %s, expected %s)" %
 +             ("xdp" in link, xdp))
 +        return link[0]
 +
 +    def tc_add_ingress(self):
 +        tc("qdisc add dev %s ingress" % (self['ifname']))
 +
 +    def tc_del_ingress(self):
 +        tc("qdisc del dev %s ingress" % (self['ifname']))
 +
 +    def tc_flush_filters(self, bound=0, total=0):
 +        self.tc_del_ingress()
 +        self.tc_add_ingress()
 +        self.wait_for_flush(bound=bound, total=total)
 +
 +    def tc_show_ingress(self, expected=None):
 +        # No JSON support, oh well...
 +        flags = ["skip_sw", "skip_hw", "in_hw"]
 +        named = ["protocol", "pref", "chain", "handle", "id", "tag"]
 +
 +        args = "-s filter show dev %s ingress" % (self['ifname'])
 +        _, out = tc(args, JSON=False)
 +
 +        filters = []
 +        lines = out.split('\n')
 +        for line in lines:
 +            words = line.split()
 +            if "handle" not in words:
 +                continue
 +            fltr = {}
 +            for flag in flags:
 +                fltr[flag] = flag in words
 +            for name in named:
 +                try:
 +                    idx = words.index(name)
 +                    fltr[name] = words[idx + 1]
 +                except ValueError:
 +                    pass
 +            filters.append(fltr)
 +
 +        if expected is not None:
 +            fail(len(filters) != expected,
 +                 "%d ingress filters loaded, expected %d" %
 +                 (len(filters), expected))
 +        return filters
 +
 +    def cls_bpf_add_filter(self, bpf, da=False, skip_sw=False, skip_hw=False,
 +                           fail=True):
 +        params = ""
 +        if da:
 +            params += " da"
 +        if skip_sw:
 +            params += " skip_sw"
 +        if skip_hw:
 +            params += " skip_hw"
 +        return tc("filter add dev %s ingress bpf %s %s" %
 +                  (self['ifname'], bpf, params), fail=fail)
 +
 +    def set_ethtool_tc_offloads(self, enable, fail=True):
 +        args = "hw-tc-offload %s" % ("on" if enable else "off")
 +        return ethtool(self, "-K", args, fail=fail)
 +
 +################################################################################
 +def clean_up():
 +    for dev in devs:
 +        dev.remove()
 +    for f in files:
 +        cmd("rm -f %s" % (f))
 +
 +def pin_prog(file_name, idx=0):
 +    progs = bpftool_prog_list(expected=(idx + 1))
 +    prog = progs[idx]
 +    bpftool("prog pin id %d %s" % (prog["id"], file_name))
 +    files.append(file_name)
 +
 +    return file_name, bpf_pinned(file_name)
 +
 +# Parse command line
 +parser = argparse.ArgumentParser()
 +parser.add_argument("--log", help="output verbose log to given file")
 +args = parser.parse_args()
 +if args.log:
 +    logfile = open(args.log, 'w+')
 +    logfile.write("# -*-Org-*-")
 +
 +log("Prepare...", "", level=1)
 +log_level_inc()
 +
 +# Check permissions
 +skip(os.getuid() != 0, "test must be run as root")
 +
 +# Check tools
 +ret, progs = bpftool("prog", fail=False)
 +skip(ret != 0, "bpftool not installed")
 +# Check no BPF programs are loaded
 +skip(len(progs) != 0, "BPF programs already loaded on the system")
 +
 +# Check netdevsim
 +ret, out = cmd("modprobe netdevsim", fail=False)
 +skip(ret != 0, "netdevsim module could not be loaded")
 +
 +# Check debugfs
 +_, out = cmd("mount")
 +if out.find("/sys/kernel/debug type debugfs") == -1:
 +    cmd("mount -t debugfs none /sys/kernel/debug")
 +
 +# Check samples are compiled
 +samples = ["sample_ret0.o"]
 +for s in samples:
 +    ret, out = cmd("ls %s/%s" % (bpf_test_dir, s), fail=False)
 +    skip(ret != 0, "sample %s/%s not found, please compile it" %
 +         (bpf_test_dir, s))
 +
 +try:
 +    obj = bpf_obj("sample_ret0.o")
 +    bytecode = bpf_bytecode("1,6 0 0 4294967295,")
 +
 +    start_test("Test destruction of generic XDP...")
 +    sim = NetdevSim()
 +    sim.set_xdp(obj, "generic")
 +    sim.remove()
 +    bpftool_prog_list_wait(expected=0)
 +
 +    sim = NetdevSim()
 +    sim.tc_add_ingress()
 +
 +    start_test("Test TC non-offloaded...")
 +    ret, _ = sim.cls_bpf_add_filter(obj, skip_hw=True, fail=False)
 +    fail(ret != 0, "Software TC filter did not load")
 +
 +    start_test("Test TC non-offloaded isn't getting bound...")
 +    ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
 +    fail(ret != 0, "Software TC filter did not load")
 +    sim.dfs_get_bound_progs(expected=0)
 +
 +    sim.tc_flush_filters()
 +
 +    start_test("Test TC offloads are off by default...")
 +    ret, _ = sim.cls_bpf_add_filter(obj, skip_sw=True, fail=False)
 +    fail(ret == 0, "TC filter loaded without enabling TC offloads")
 +    sim.wait_for_flush()
 +
 +    sim.set_ethtool_tc_offloads(True)
 +    sim.dfs["bpf_tc_non_bound_accept"] = "Y"
 +
 +    start_test("Test TC offload by default...")
 +    ret, _ = sim.cls_bpf_add_filter(obj, fail=False)
 +    fail(ret != 0, "Software TC filter did not load")
 +    sim.dfs_get_bound_progs(expected=0)
 +    ingress = sim.tc_show_ingress(expected=1)
 +    fltr = ingress[0]
 +    fail(not fltr["in_hw"], "Filter not offloaded by default")
 +
 +    sim.tc_flush_filters()
 +
 +    start_test("Test TC cBPF bytcode tries offload by default...")
 +    ret, _ = sim.cls_bpf_add_filter(bytecode, fail=False)
 +    fail(ret != 0, "Software TC filter did not load")
 +    sim.dfs_get_bound_progs(expected=0)
 +    ingress = sim.tc_show_ingress(expected=1)
 +    fltr = ingress[0]
 +    fail(not fltr["in_hw"], "Bytecode not offloaded by default")
 +
 +    sim.tc_flush_filters()
 +    sim.dfs["bpf_tc_non_bound_accept"] = "N"
 +
 +    start_test("Test TC cBPF unbound bytecode doesn't offload...")
 +    ret, _ = sim.cls_bpf_add_filter(bytecode, skip_sw=True, fail=False)
 +    fail(ret == 0, "TC bytecode loaded for offload")
 +    sim.wait_for_flush()
 +
 +    start_test("Test TC offloads work...")
 +    ret, _ = sim.cls_bpf_add_filter(obj, skip_sw=True, fail=False)
 +    fail(ret != 0, "TC filter did not load with TC offloads enabled")
 +
 +    start_test("Test TC offload basics...")
 +    dfs = sim.dfs_get_bound_progs(expected=1)
 +    progs = bpftool_prog_list(expected=1)
 +    ingress = sim.tc_show_ingress(expected=1)
 +
 +    dprog = dfs[0]
 +    prog = progs[0]
 +    fltr = ingress[0]
 +    fail(fltr["skip_hw"], "TC does reports 'skip_hw' on offloaded filter")
 +    fail(not fltr["in_hw"], "TC does not report 'in_hw' for offloaded filter")
 +    fail(not fltr["skip_sw"], "TC does not report 'skip_sw' back")
 +
 +    start_test("Test TC offload is device-bound...")
 +    fail(str(prog["id"]) != fltr["id"], "Program IDs don't match")
 +    fail(prog["tag"] != fltr["tag"], "Program tags don't match")
 +    fail(fltr["id"] != dprog["id"], "Program IDs don't match")
 +    fail(dprog["state"] != "xlated", "Offloaded program state not translated")
 +    fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
 +
 +    start_test("Test disabling TC offloads is rejected while filters installed...")
 +    ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
 +    fail(ret == 0, "Driver should refuse to disable TC offloads with filters installed...")
 +
 +    start_test("Test qdisc removal frees things...")
 +    sim.tc_flush_filters()
 +    sim.tc_show_ingress(expected=0)
 +
 +    start_test("Test disabling TC offloads is OK without filters...")
 +    ret, _ = sim.set_ethtool_tc_offloads(False, fail=False)
 +    fail(ret != 0,
 +         "Driver refused to disable TC offloads without filters installed...")
 +
 +    sim.set_ethtool_tc_offloads(True)
 +
 +    start_test("Test destroying device gets rid of TC filters...")
 +    sim.cls_bpf_add_filter(obj, skip_sw=True)
 +    sim.remove()
 +    bpftool_prog_list_wait(expected=0)
 +
 +    sim = NetdevSim()
 +    sim.set_ethtool_tc_offloads(True)
 +
 +    start_test("Test destroying device gets rid of XDP...")
 +    sim.set_xdp(obj, "offload")
 +    sim.remove()
 +    bpftool_prog_list_wait(expected=0)
 +
 +    sim = NetdevSim()
 +    sim.set_ethtool_tc_offloads(True)
 +
 +    start_test("Test XDP prog reporting...")
 +    sim.set_xdp(obj, "drv")
 +    ipl = sim.ip_link_show(xdp=True)
 +    progs = bpftool_prog_list(expected=1)
 +    fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
 +         "Loaded program has wrong ID")
 +
 +    start_test("Test XDP prog replace without force...")
 +    ret, _ = sim.set_xdp(obj, "drv", fail=False)
 +    fail(ret == 0, "Replaced XDP program without -force")
 +    sim.wait_for_flush(total=1)
 +
 +    start_test("Test XDP prog replace with force...")
 +    ret, _ = sim.set_xdp(obj, "drv", force=True, fail=False)
 +    fail(ret != 0, "Could not replace XDP program with -force")
 +    bpftool_prog_list_wait(expected=1)
 +    ipl = sim.ip_link_show(xdp=True)
 +    progs = bpftool_prog_list(expected=1)
 +    fail(ipl["xdp"]["prog"]["id"] != progs[0]["id"],
 +         "Loaded program has wrong ID")
 +
 +    start_test("Test XDP prog replace with bad flags...")
 +    ret, _ = sim.set_xdp(obj, "offload", force=True, fail=False)
 +    fail(ret == 0, "Replaced XDP program with a program in different mode")
 +    ret, _ = sim.set_xdp(obj, "", force=True, fail=False)
 +    fail(ret == 0, "Replaced XDP program with a program in different mode")
 +
 +    start_test("Test XDP prog remove with bad flags...")
 +    ret, _ = sim.unset_xdp("offload", force=True, fail=False)
 +    fail(ret == 0, "Removed program with a bad mode mode")
 +    ret, _ = sim.unset_xdp("", force=True, fail=False)
 +    fail(ret == 0, "Removed program with a bad mode mode")
 +
 +    start_test("Test MTU restrictions...")
 +    ret, _ = sim.set_mtu(9000, fail=False)
 +    fail(ret == 0,
 +         "Driver should refuse to increase MTU to 9000 with XDP loaded...")
 +    sim.unset_xdp("drv")
 +    bpftool_prog_list_wait(expected=0)
 +    sim.set_mtu(9000)
 +    ret, _ = sim.set_xdp(obj, "drv", fail=False)
 +    fail(ret == 0, "Driver should refuse to load program with MTU of 9000...")
 +    sim.set_mtu(1500)
 +
 +    sim.wait_for_flush()
 +    start_test("Test XDP offload...")
 +    sim.set_xdp(obj, "offload")
 +    ipl = sim.ip_link_show(xdp=True)
 +    link_xdp = ipl["xdp"]["prog"]
 +    progs = bpftool_prog_list(expected=1)
 +    prog = progs[0]
 +    fail(link_xdp["id"] != prog["id"], "Loaded program has wrong ID")
 +
 +    start_test("Test XDP offload is device bound...")
 +    dfs = sim.dfs_get_bound_progs(expected=1)
 +    dprog = dfs[0]
 +
 +    fail(prog["id"] != link_xdp["id"], "Program IDs don't match")
 +    fail(prog["tag"] != link_xdp["tag"], "Program tags don't match")
 +    fail(str(link_xdp["id"]) != dprog["id"], "Program IDs don't match")
 +    fail(dprog["state"] != "xlated", "Offloaded program state not translated")
 +    fail(dprog["loaded"] != "Y", "Offloaded program is not loaded")
 +
 +    start_test("Test removing XDP program many times...")
 +    sim.unset_xdp("offload")
 +    sim.unset_xdp("offload")
 +    sim.unset_xdp("drv")
 +    sim.unset_xdp("drv")
 +    sim.unset_xdp("")
 +    sim.unset_xdp("")
 +    bpftool_prog_list_wait(expected=0)
 +
 +    start_test("Test attempt to use a program for a wrong device...")
 +    sim2 = NetdevSim()
 +    sim2.set_xdp(obj, "offload")
 +    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
 +
 +    ret, _ = sim.set_xdp(pinned, "offload", fail=False)
 +    fail(ret == 0, "Pinned program loaded for a different device accepted")
 +    sim2.remove()
 +    ret, _ = sim.set_xdp(pinned, "offload", fail=False)
 +    fail(ret == 0, "Pinned program loaded for a removed device accepted")
 +    rm(pin_file)
 +    bpftool_prog_list_wait(expected=0)
 +
 +    start_test("Test mixing of TC and XDP...")
 +    sim.tc_add_ingress()
 +    sim.set_xdp(obj, "offload")
 +    ret, _ = sim.cls_bpf_add_filter(obj, skip_sw=True, fail=False)
 +    fail(ret == 0, "Loading TC when XDP active should fail")
 +    sim.unset_xdp("offload")
 +    sim.wait_for_flush()
 +
 +    sim.cls_bpf_add_filter(obj, skip_sw=True)
 +    ret, _ = sim.set_xdp(obj, "offload", fail=False)
 +    fail(ret == 0, "Loading XDP when TC active should fail")
 +
 +    start_test("Test binding TC from pinned...")
 +    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp")
 +    sim.tc_flush_filters(bound=1, total=1)
 +    sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True)
 +    sim.tc_flush_filters(bound=1, total=1)
 +
 +    start_test("Test binding XDP from pinned...")
 +    sim.set_xdp(obj, "offload")
 +    pin_file, pinned = pin_prog("/sys/fs/bpf/tmp2", idx=1)
 +
 +    sim.set_xdp(pinned, "offload", force=True)
 +    sim.unset_xdp("offload")
 +    sim.set_xdp(pinned, "offload", force=True)
 +    sim.unset_xdp("offload")
 +
 +    start_test("Test offload of wrong type fails...")
 +    ret, _ = sim.cls_bpf_add_filter(pinned, da=True, skip_sw=True, fail=False)
 +    fail(ret == 0, "Managed to attach XDP program to TC")
 +
 +    start_test("Test asking for TC offload of two filters...")
 +    sim.cls_bpf_add_filter(obj, da=True, skip_sw=True)
++    ret, _ = sim.cls_bpf_add_filter(obj, da=True, skip_sw=True, fail=False)
++    fail(ret == 0, "Managed to offload two TC filters at the same time")
 +
 +    sim.tc_flush_filters(bound=2, total=2)
 +
 +    start_test("Test if netdev removal waits for translation...")
 +    delay_msec = 500
 +    sim.dfs["bpf_bind_verifier_delay"] = delay_msec
 +    start = time.time()
 +    cmd_line = "tc filter add dev %s ingress bpf %s da skip_sw" % \
 +               (sim['ifname'], obj)
 +    tc_proc = cmd(cmd_line, background=True, fail=False)
 +    # Wait for the verifier to start
 +    while sim.dfs_num_bound_progs() <= 2:
 +        pass
 +    sim.remove()
 +    end = time.time()
 +    ret, _ = cmd_result(tc_proc, fail=False)
 +    time_diff = end - start
 +    log("Time", "start:\t%s\nend:\t%s\ndiff:\t%s" % (start, end, time_diff))
 +
 +    fail(ret == 0, "Managed to load TC filter on a unregistering device")
 +    delay_sec = delay_msec * 0.001
 +    fail(time_diff < delay_sec, "Removal process took %s, expected %s" %
 +         (time_diff, delay_sec))
 +
 +    print("%s: OK" % (os.path.basename(__file__)))
 +
 +finally:
 +    log("Clean up...", "", level=1)
 +    log_level_inc()
 +    clean_up()