From patchwork Thu Feb 21 11:56:54 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Toke_H=C3=B8iland-J=C3=B8rgensen?= X-Patchwork-Id: 1046099 X-Patchwork-Delegate: bpf@iogearbox.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 444tHd0XLQz9s5c for ; Thu, 21 Feb 2019 22:57:01 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726952AbfBUL47 (ORCPT ); Thu, 21 Feb 2019 06:56:59 -0500 Received: from mail-ed1-f66.google.com ([209.85.208.66]:42738 "EHLO mail-ed1-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725820AbfBUL46 (ORCPT ); Thu, 21 Feb 2019 06:56:58 -0500 Received: by mail-ed1-f66.google.com with SMTP id j89so13481502edb.9 for ; Thu, 21 Feb 2019 03:56:57 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:subject:from:to:cc:date:message-id:user-agent :mime-version:content-transfer-encoding; bh=AGyV/MnjBfsyK17fvSrmvSdmXIHoYLosQD6HYaqcDD0=; b=Vq01zxv8WZzhLQGOCQViOo4sYqLQ9t1pKOJV0RTm6rnLv/cgEGvrP9WrFPYnYclFLD W6fqXyMtuN0Vv+2kaG8VmTf5GAKXuMILcSq++2qFpDdD1lDmmU60a8wYAOl7x6L5iIle SMfRx162zqIKjAEWZuTpLMv4DFgw1RIacz8x8Y3SIOzCJcdgz2wWFmx/Wpd2TrnJL58z Kwi7RETDE09UvaoFIpeRvKHjiHQPC7s2Pcp4P3aas7DfAugZurd21cqCrluZsOo3TzPq ZYiWtPYv5WGrAVyBxO5AkdxKpXpRvNSceBHiKU4TTvltoT8IDxBwQGFGDHvZcKziZxi7 lI1g== X-Gm-Message-State: AHQUAuYkklQq8mBripEqdKDGnf/BYOtWMfxmpzoOth+udYyCk8/M+cxA 5rV33ksbNDgcTxe1LpxNmoqFaw== X-Google-Smtp-Source: AHgI3IaFStF43t19NZtInIJzoNLSLg+Voq/nS0eqFlclgAxAExNGhw+e/fLX+syVpOKnxWvIElNSqQ== X-Received: by 2002:a50:9008:: with SMTP id b8mr31814506eda.199.1550750216148; Thu, 21 Feb 2019 03:56:56 -0800 (PST) Received: from alrua-x1.borgediget.toke.dk (borgediget.toke.dk. [85.204.121.218]) by smtp.gmail.com with ESMTPSA id a9sm6209660edl.63.2019.02.21.03.56.54 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Thu, 21 Feb 2019 03:56:55 -0800 (PST) Received: by alrua-x1.borgediget.toke.dk (Postfix, from userid 1000) id 138BC1803B8; Thu, 21 Feb 2019 12:56:54 +0100 (CET) Subject: [PATCH net-next 1/2] xdp: Always use a devmap for XDP_REDIRECT to a device From: Toke =?utf-8?q?H=C3=B8iland-J=C3=B8rgensen?= To: David Miller Cc: netdev@vger.kernel.org, Jesper Dangaard Brouer , Daniel Borkmann , Alexei Starovoitov Date: Thu, 21 Feb 2019 12:56:54 +0100 Message-ID: <155075021399.13610.12521373406832889226.stgit@alrua-x1> User-Agent: StGit/unknown-version MIME-Version: 1.0 Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org An XDP program can redirect packets between interfaces using either the xdp_redirect() helper or the xdp_redirect_map() helper. Apart from the flexibility of updating maps from userspace, the redirect_map() helper also uses the map structure to batch packets, which results in a significant (around 50%) performance boost. However, the xdp_redirect() API is simpler if one just wants to redirect to another interface, which means people tend to use this interface and then wonder why they getter worse performance than expected. This patch seeks to close this performance difference between the two APIs. It achieves this by changing xdp_redirect() to use a hidden devmap for looking up destination interfaces, thus gaining the batching benefit with no visible difference from the user API point of view. A hidden per-namespace map is allocated when an XDP program that uses the non-map xdp_redirect() helper is first loaded. This map is populated with all available interfaces in its namespace, and kept up to date as interfaces come and go. Once allocated, the map is kept around until the namespace is removed. The hidden map uses the ifindex as map key, which means they are limited to ifindexes smaller than the map size of 64. A later patch introduces a new map type to lift this restriction. Performance numbers: Before patch: xdp_redirect: 5426035 pkt/s xdp_redirect_map: 8412754 pkt/s After patch: xdp_redirect: 8314702 pkt/s xdp_redirect_map: 8411854 pkt/s This corresponds to a 53% increase in xdp_redirect performance, or a reduction in per-packet processing time by 64 nanoseconds. Signed-off-by: Toke Høiland-Jørgensen --- include/linux/bpf.h | 12 +++ include/net/net_namespace.h | 2 - include/net/netns/xdp.h | 5 + kernel/bpf/devmap.c | 159 +++++++++++++++++++++++++++++++++++-------- kernel/bpf/verifier.c | 6 ++ net/core/filter.c | 58 +--------------- 6 files changed, 157 insertions(+), 85 deletions(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index bd169a7bcc93..4f8f179df9fd 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -606,6 +606,8 @@ struct xdp_buff; struct sk_buff; struct bpf_dtab_netdev *__dev_map_lookup_elem(struct bpf_map *map, u32 key); +struct bpf_map *__dev_map_get_default_map(struct net_device *dev); +int dev_map_alloc_default_map(void); void __dev_map_insert_ctx(struct bpf_map *map, u32 index); void __dev_map_flush(struct bpf_map *map); int dev_map_enqueue(struct bpf_dtab_netdev *dst, struct xdp_buff *xdp, @@ -687,6 +689,16 @@ static inline struct net_device *__dev_map_lookup_elem(struct bpf_map *map, return NULL; } +static inline struct bpf_map *__dev_map_get_default_map(struct net_device *dev) +{ + return NULL; +} + +static inline int dev_map_alloc_default_map(void) +{ + return 0; +} + static inline void __dev_map_insert_ctx(struct bpf_map *map, u32 index) { } diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index a68ced28d8f4..6706ecc25d8f 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -162,7 +162,7 @@ struct net { #if IS_ENABLED(CONFIG_CAN) struct netns_can can; #endif -#ifdef CONFIG_XDP_SOCKETS +#ifdef CONFIG_BPF_SYSCALL struct netns_xdp xdp; #endif struct sock *diag_nlsk; diff --git a/include/net/netns/xdp.h b/include/net/netns/xdp.h index e5734261ba0a..4e2d6394b45d 100644 --- a/include/net/netns/xdp.h +++ b/include/net/netns/xdp.h @@ -5,9 +5,14 @@ #include #include +struct bpf_dtab; + struct netns_xdp { +#ifdef CONFIG_XDP_SOCKETS struct mutex lock; struct hlist_head list; +#endif + struct bpf_dtab *default_map; }; #endif /* __NETNS_XDP_H__ */ diff --git a/kernel/bpf/devmap.c b/kernel/bpf/devmap.c index 191b79948424..425077664ac6 100644 --- a/kernel/bpf/devmap.c +++ b/kernel/bpf/devmap.c @@ -56,6 +56,7 @@ (BPF_F_NUMA_NODE | BPF_F_RDONLY | BPF_F_WRONLY) #define DEV_MAP_BULK_SIZE 16 +#define DEV_MAP_DEFAULT_SIZE 64 struct xdp_bulk_queue { struct xdp_frame *q[DEV_MAP_BULK_SIZE]; struct net_device *dev_rx; @@ -85,23 +86,11 @@ static u64 dev_map_bitmap_size(const union bpf_attr *attr) return BITS_TO_LONGS((u64) attr->max_entries) * sizeof(unsigned long); } -static struct bpf_map *dev_map_alloc(union bpf_attr *attr) +static int dev_map_init_map(struct bpf_dtab *dtab, union bpf_attr *attr, + bool check_memlock) { - struct bpf_dtab *dtab; - int err = -EINVAL; u64 cost; - - if (!capable(CAP_NET_ADMIN)) - return ERR_PTR(-EPERM); - - /* check sanity of attributes */ - if (attr->max_entries == 0 || attr->key_size != 4 || - attr->value_size != 4 || attr->map_flags & ~DEV_CREATE_FLAG_MASK) - return ERR_PTR(-EINVAL); - - dtab = kzalloc(sizeof(*dtab), GFP_USER); - if (!dtab) - return ERR_PTR(-ENOMEM); + int err; bpf_map_init_from_attr(&dtab->map, attr); @@ -109,39 +98,63 @@ static struct bpf_map *dev_map_alloc(union bpf_attr *attr) cost = (u64) dtab->map.max_entries * sizeof(struct bpf_dtab_netdev *); cost += dev_map_bitmap_size(attr) * num_possible_cpus(); if (cost >= U32_MAX - PAGE_SIZE) - goto free_dtab; + return -EINVAL; dtab->map.pages = round_up(cost, PAGE_SIZE) >> PAGE_SHIFT; - /* if map size is larger than memlock limit, reject it early */ - err = bpf_map_precharge_memlock(dtab->map.pages); - if (err) - goto free_dtab; - - err = -ENOMEM; + if (check_memlock) { + /* if map size is larger than memlock limit, reject it early */ + err = bpf_map_precharge_memlock(dtab->map.pages); + if (err) + return -EINVAL; + } /* A per cpu bitfield with a bit per possible net device */ dtab->flush_needed = __alloc_percpu_gfp(dev_map_bitmap_size(attr), __alignof__(unsigned long), GFP_KERNEL | __GFP_NOWARN); if (!dtab->flush_needed) - goto free_dtab; + return -ENOMEM; dtab->netdev_map = bpf_map_area_alloc(dtab->map.max_entries * sizeof(struct bpf_dtab_netdev *), dtab->map.numa_node); - if (!dtab->netdev_map) - goto free_dtab; + if (!dtab->netdev_map) { + free_percpu(dtab->flush_needed); + return -ENOMEM; + } spin_lock(&dev_map_lock); list_add_tail_rcu(&dtab->list, &dev_map_list); spin_unlock(&dev_map_lock); + return 0; +} + +static struct bpf_map *dev_map_alloc(union bpf_attr *attr) +{ + struct bpf_dtab *dtab; + int err = -EINVAL; + + if (!capable(CAP_NET_ADMIN)) + return ERR_PTR(-EPERM); + + /* check sanity of attributes */ + if (attr->max_entries == 0 || attr->key_size != 4 || + attr->value_size != 4 || attr->map_flags & ~DEV_CREATE_FLAG_MASK) + return ERR_PTR(-EINVAL); + + dtab = kzalloc(sizeof(*dtab), GFP_USER); + if (!dtab) + return ERR_PTR(-ENOMEM); + + err = dev_map_init_map(dtab, attr, true); + if (err) { + kfree(dtab); + return ERR_PTR(err); + } + return &dtab->map; -free_dtab: - free_percpu(dtab->flush_needed); - kfree(dtab); - return ERR_PTR(err); } static void dev_map_free(struct bpf_map *map) @@ -311,6 +324,17 @@ struct bpf_dtab_netdev *__dev_map_lookup_elem(struct bpf_map *map, u32 key) return obj; } +/* This is only being called from xdp_do_redirect() if the xdp_redirect helper + * is used; the default map is allocated on XDP program load if the helper is + * used, so will always be available at this point. + */ +struct bpf_map *__dev_map_get_default_map(struct net_device *dev) +{ + struct net *net = dev_net(dev); + + return &net->xdp.default_map->map; +} + /* Runs under RCU-read-side, plus in softirq under NAPI protection. * Thus, safe percpu variable access. */ @@ -496,10 +520,19 @@ static int dev_map_notification(struct notifier_block *notifier, ulong event, void *ptr) { struct net_device *netdev = netdev_notifier_info_to_dev(ptr); + struct net *net = dev_net(netdev); + u32 idx = netdev->ifindex; struct bpf_dtab *dtab; int i; switch (event) { + case NETDEV_REGISTER: + rcu_read_lock(); + dtab = READ_ONCE(net->xdp.default_map); + if (dtab) + dev_map_update_elem(&dtab->map, &idx, &idx, 0); + rcu_read_unlock(); + break; case NETDEV_UNREGISTER: /* This rcu_read_lock/unlock pair is needed because * dev_map_list is an RCU list AND to ensure a delete @@ -528,16 +561,84 @@ static int dev_map_notification(struct notifier_block *notifier, return NOTIFY_OK; } +static void __net_exit dev_map_net_exit(struct net *net) +{ + struct bpf_dtab *dtab; + + dtab = xchg(&net->xdp.default_map, NULL); + if (dtab) + dev_map_free(&dtab->map); +} + static struct notifier_block dev_map_notifier = { .notifier_call = dev_map_notification, }; +static struct pernet_operations dev_map_net_ops = { + .exit = dev_map_net_exit, +}; + +int dev_map_alloc_default_map(void) +{ + struct net *net = current->nsproxy->net_ns; + struct bpf_dtab *dtab, *old_dtab; + struct net_device *netdev; + union bpf_attr attr = {}; + u32 idx; + int err; + + if (READ_ONCE(net->xdp.default_map)) + return 0; + + dtab = kzalloc(sizeof(*net->xdp.default_map), GFP_USER); + if (!dtab) + return -ENOMEM; + + attr.max_entries = DEV_MAP_DEFAULT_SIZE; + attr.map_type = BPF_MAP_TYPE_DEVMAP; + attr.value_size = 4; + attr.key_size = 4; + + err = dev_map_init_map(dtab, &attr, false); + if (err) { + kfree(dtab); + return err; + } + + for_each_netdev(net, netdev) { + if (netdev->ifindex < DEV_MAP_DEFAULT_SIZE) { + idx = netdev->ifindex; + err = dev_map_update_elem(&dtab->map, &idx, &idx, 0); + if (err) { + dev_map_free(&dtab->map); + return err; + } + } + } + + old_dtab = xchg(&net->xdp.default_map, dtab); + if (old_dtab) + dev_map_free(&old_dtab->map); + + return 0; +} + static int __init dev_map_init(void) { + int err; + /* Assure tracepoint shadow struct _bpf_dtab_netdev is in sync */ BUILD_BUG_ON(offsetof(struct bpf_dtab_netdev, dev) != offsetof(struct _bpf_dtab_netdev, dev)); + register_netdevice_notifier(&dev_map_notifier); + + err = register_pernet_subsys(&dev_map_net_ops); + if (err) { + unregister_netdevice_notifier(&dev_map_notifier); + return err; + } + return 0; } diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index b63bc77af2d1..629661db36ee 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -7527,6 +7527,12 @@ static int fixup_bpf_calls(struct bpf_verifier_env *env) prog->dst_needed = 1; if (insn->imm == BPF_FUNC_get_prandom_u32) bpf_user_rnd_init_once(); + if (insn->imm == BPF_FUNC_redirect) { + int err = dev_map_alloc_default_map(); + + if (err) + return err; + } if (insn->imm == BPF_FUNC_override_return) prog->kprobe_override = 1; if (insn->imm == BPF_FUNC_tail_call) { diff --git a/net/core/filter.c b/net/core/filter.c index b5a002d7b263..c709b1468bb6 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -3326,58 +3326,6 @@ static const struct bpf_func_proto bpf_xdp_adjust_meta_proto = { .arg2_type = ARG_ANYTHING, }; -static int __bpf_tx_xdp(struct net_device *dev, - struct bpf_map *map, - struct xdp_buff *xdp, - u32 index) -{ - struct xdp_frame *xdpf; - int err, sent; - - if (!dev->netdev_ops->ndo_xdp_xmit) { - return -EOPNOTSUPP; - } - - err = xdp_ok_fwd_dev(dev, xdp->data_end - xdp->data); - if (unlikely(err)) - return err; - - xdpf = convert_to_xdp_frame(xdp); - if (unlikely(!xdpf)) - return -EOVERFLOW; - - sent = dev->netdev_ops->ndo_xdp_xmit(dev, 1, &xdpf, XDP_XMIT_FLUSH); - if (sent <= 0) - return sent; - return 0; -} - -static noinline int -xdp_do_redirect_slow(struct net_device *dev, struct xdp_buff *xdp, - struct bpf_prog *xdp_prog, struct bpf_redirect_info *ri) -{ - struct net_device *fwd; - u32 index = ri->ifindex; - int err; - - fwd = dev_get_by_index_rcu(dev_net(dev), index); - ri->ifindex = 0; - if (unlikely(!fwd)) { - err = -EINVAL; - goto err; - } - - err = __bpf_tx_xdp(fwd, NULL, xdp, 0); - if (unlikely(err)) - goto err; - - _trace_xdp_redirect(dev, xdp_prog, index); - return 0; -err: - _trace_xdp_redirect_err(dev, xdp_prog, index, err); - return err; -} - static int __bpf_tx_xdp_map(struct net_device *dev_rx, void *fwd, struct bpf_map *map, struct xdp_buff *xdp, @@ -3508,10 +3456,10 @@ int xdp_do_redirect(struct net_device *dev, struct xdp_buff *xdp, struct bpf_redirect_info *ri = this_cpu_ptr(&bpf_redirect_info); struct bpf_map *map = READ_ONCE(ri->map); - if (likely(map)) - return xdp_do_redirect_map(dev, xdp, xdp_prog, map, ri); + if (unlikely(!map)) + map = __dev_map_get_default_map(dev); - return xdp_do_redirect_slow(dev, xdp, xdp_prog, ri); + return xdp_do_redirect_map(dev, xdp, xdp_prog, map, ri); } EXPORT_SYMBOL_GPL(xdp_do_redirect); From patchwork Thu Feb 21 11:56:54 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Toke_H=C3=B8iland-J=C3=B8rgensen?= X-Patchwork-Id: 1046100 X-Patchwork-Delegate: bpf@iogearbox.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 444tHg34kVz9s21 for ; Thu, 21 Feb 2019 22:57:03 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727457AbfBUL5C (ORCPT ); Thu, 21 Feb 2019 06:57:02 -0500 Received: from mail-ed1-f68.google.com ([209.85.208.68]:35941 "EHLO mail-ed1-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725774AbfBUL5A (ORCPT ); Thu, 21 Feb 2019 06:57:00 -0500 Received: by mail-ed1-f68.google.com with SMTP id g9so12360217eds.3 for ; Thu, 21 Feb 2019 03:56:57 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:subject:from:to:cc:date:message-id:in-reply-to :references:user-agent:mime-version:content-transfer-encoding; bh=d59GyUxXhCi2dTubUWa36JuQwE7qmL8GNT/etfAiszc=; b=bB2EWHjIaPjr/VgM35UF58ajv4iHWQKVHlxLsqXoPrIrqNwZHt+QgNqyRk3pmqpyGg RIyvV0eT7mpvPylJu2stOVn3UkbthuywBike5Nbqq1BFbWP1zhmhczTKIMYH4e+rVzP5 MfDVyh6RsBm7at7RXmFdmbWUk8YmLuaLRFRzXxvNkCXLMLrAGV2U/vUaJu9pzRmpfJfn H7ZASBbM3lFX3rfzcXAlddfp3xuBQpe1792CmNUeYvhQEZSX55BpE2T1K2/Qtoz27jcI MGmdB/uq3Bf9grisgPXZUt7w6KyV/gqxiyvXblF6LpuXgL3sP+eaIvgVAX5egQOE7L5O ISHg== X-Gm-Message-State: AHQUAubQVysZe++T6RjO4P5/yWL4+sYoPFgSAhXyIkkbQ/OJ4c07C1qD jkS6+AjvGGyPYTlBYQ90IFmp4w== X-Google-Smtp-Source: AHgI3IatZog0x8ZwuXm64tVJxBt3V5Zp7Vq9AnBpmVUnigovs06kxE+60pAwJ0UHazhRm5BPqfMf2g== X-Received: by 2002:a17:906:6043:: with SMTP id p3mr26747226ejj.72.1550750216688; Thu, 21 Feb 2019 03:56:56 -0800 (PST) Received: from alrua-x1.borgediget.toke.dk (alrua-x1.vpn.toke.dk. [2a00:7660:6da:10::2]) by smtp.gmail.com with ESMTPSA id k26sm4891814ejv.63.2019.02.21.03.56.54 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Thu, 21 Feb 2019 03:56:55 -0800 (PST) Received: by alrua-x1.borgediget.toke.dk (Postfix, from userid 1000) id 20B0A183AF9; Thu, 21 Feb 2019 12:56:54 +0100 (CET) Subject: [PATCH net-next 2/2] xdp: Add devmap_idx map type for looking up devices by ifindex From: Toke =?utf-8?q?H=C3=B8iland-J=C3=B8rgensen?= To: David Miller Cc: netdev@vger.kernel.org, Jesper Dangaard Brouer , Daniel Borkmann , Alexei Starovoitov Date: Thu, 21 Feb 2019 12:56:54 +0100 Message-ID: <155075021407.13610.6656977312753058829.stgit@alrua-x1> In-Reply-To: <155075021399.13610.12521373406832889226.stgit@alrua-x1> References: <155075021399.13610.12521373406832889226.stgit@alrua-x1> User-Agent: StGit/unknown-version MIME-Version: 1.0 Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org A common pattern when using xdp_redirect_map() is to create a device map where the lookup key is simply ifindex. Because device maps are arrays, this leaves holes in the map, and the map has to be sized to fit the largest ifindex, regardless of how many devices actually are actually needed in the map. This patch adds a second type of device map where the key is interpreted as an ifindex and looked up using a hashmap, instead of being used as an array index. This leads to maps being densely packed, so they can be smaller. The default maps used by xdp_redirect() are changed to use the new map type, which means that xdp_redirect() is no longer limited to ifindex < 64, but instead to 64 total simultaneous interfaces per network namespace. This also provides an easy way to compare the performance of devmap and devmap_idx: xdp_redirect_map (devmap): 8394560 pkt/s xdp_redirect (devmap_idx): 8179480 pkt/s Difference: 215080 pkt/s or 3.1 nanoseconds per packet. Signed-off-by: Toke Høiland-Jørgensen --- include/linux/bpf.h | 12 + include/linux/bpf_types.h | 1 include/trace/events/xdp.h | 3 include/uapi/linux/bpf.h | 1 kernel/bpf/devmap.c | 304 +++++++++++++++++++++++++++---- kernel/bpf/verifier.c | 2 net/core/filter.c | 11 + tools/bpf/bpftool/map.c | 1 tools/include/uapi/linux/bpf.h | 1 tools/lib/bpf/libbpf_probes.c | 1 tools/testing/selftests/bpf/test_maps.c | 16 ++ 11 files changed, 310 insertions(+), 43 deletions(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 4f8f179df9fd..e7308ff59071 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -606,9 +606,10 @@ struct xdp_buff; struct sk_buff; struct bpf_dtab_netdev *__dev_map_lookup_elem(struct bpf_map *map, u32 key); +struct bpf_dtab_netdev *__dev_map_idx_lookup_elem(struct bpf_map *map, u32 key); struct bpf_map *__dev_map_get_default_map(struct net_device *dev); int dev_map_alloc_default_map(void); -void __dev_map_insert_ctx(struct bpf_map *map, u32 index); +void __dev_map_insert_ctx(struct bpf_map *map, struct bpf_dtab_netdev *dst); void __dev_map_flush(struct bpf_map *map); int dev_map_enqueue(struct bpf_dtab_netdev *dst, struct xdp_buff *xdp, struct net_device *dev_rx); @@ -689,6 +690,12 @@ static inline struct net_device *__dev_map_lookup_elem(struct bpf_map *map, return NULL; } +static inline struct net_device *__dev_map_idx_lookup_elem(struct bpf_map *map, + u32 key) +{ + return NULL; +} + static inline struct bpf_map *__dev_map_get_default_map(struct net_device *dev) { return NULL; @@ -699,7 +706,8 @@ static inline int dev_map_alloc_default_map(void) return 0; } -static inline void __dev_map_insert_ctx(struct bpf_map *map, u32 index) +static inline void __dev_map_insert_ctx(struct bpf_map *map, + struct net_device *dst) { } diff --git a/include/linux/bpf_types.h b/include/linux/bpf_types.h index 08bf2f1fe553..374c013ca243 100644 --- a/include/linux/bpf_types.h +++ b/include/linux/bpf_types.h @@ -59,6 +59,7 @@ BPF_MAP_TYPE(BPF_MAP_TYPE_ARRAY_OF_MAPS, array_of_maps_map_ops) BPF_MAP_TYPE(BPF_MAP_TYPE_HASH_OF_MAPS, htab_of_maps_map_ops) #ifdef CONFIG_NET BPF_MAP_TYPE(BPF_MAP_TYPE_DEVMAP, dev_map_ops) +BPF_MAP_TYPE(BPF_MAP_TYPE_DEVMAP_IDX, dev_map_idx_ops) #if defined(CONFIG_BPF_STREAM_PARSER) BPF_MAP_TYPE(BPF_MAP_TYPE_SOCKMAP, sock_map_ops) BPF_MAP_TYPE(BPF_MAP_TYPE_SOCKHASH, sock_hash_ops) diff --git a/include/trace/events/xdp.h b/include/trace/events/xdp.h index e95cb86b65cf..fcf006d49f67 100644 --- a/include/trace/events/xdp.h +++ b/include/trace/events/xdp.h @@ -147,7 +147,8 @@ struct _bpf_dtab_netdev { #define devmap_ifindex(fwd, map) \ (!fwd ? 0 : \ - ((map->map_type == BPF_MAP_TYPE_DEVMAP) ? \ + ((map->map_type == BPF_MAP_TYPE_DEVMAP || \ + map->map_type == BPF_MAP_TYPE_DEVMAP_IDX) ? \ ((struct _bpf_dtab_netdev *)fwd)->dev->ifindex : 0)) #define _trace_xdp_redirect_map(dev, xdp, fwd, map, idx) \ diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 1777fa0c61e4..a09243a38c2d 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -132,6 +132,7 @@ enum bpf_map_type { BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE, BPF_MAP_TYPE_QUEUE, BPF_MAP_TYPE_STACK, + BPF_MAP_TYPE_DEVMAP_IDX, }; /* Note that tracing related programs such as diff --git a/kernel/bpf/devmap.c b/kernel/bpf/devmap.c index 425077664ac6..e9f7b0ad7ff3 100644 --- a/kernel/bpf/devmap.c +++ b/kernel/bpf/devmap.c @@ -46,6 +46,12 @@ * notifier hook walks the map we know that new dev references can not be * added by the user because core infrastructure ensures dev_get_by_index() * calls will fail at this point. + * + * The devmap_idx type is a map type which interprets keys as ifindexes and + * indexes these using a hashmap. This allows maps that use ifindex as key to be + * densely packed instead of having holes in the lookup array for unused + * ifindexes. The setup and packet enqueue/send code is shared between the two + * types of devmap; only the lookup and insertion is different. */ #include #include @@ -65,6 +71,8 @@ struct xdp_bulk_queue { struct bpf_dtab_netdev { struct net_device *dev; /* must be first member, due to tracepoint */ + unsigned int ifindex; + struct hlist_node index_hlist; struct bpf_dtab *dtab; unsigned int bit; struct xdp_bulk_queue __percpu *bulkq; @@ -76,11 +84,29 @@ struct bpf_dtab { struct bpf_dtab_netdev **netdev_map; unsigned long __percpu *flush_needed; struct list_head list; + + /* these are only used for DEVMAP_IDX type maps */ + unsigned long *bits_used; + struct hlist_head *dev_index_head; + spinlock_t index_lock; }; static DEFINE_SPINLOCK(dev_map_lock); static LIST_HEAD(dev_map_list); +static struct hlist_head *dev_map_create_hash(void) +{ + int i; + struct hlist_head *hash; + + hash = kmalloc_array(NETDEV_HASHENTRIES, sizeof(*hash), GFP_KERNEL); + if (hash != NULL) + for (i = 0; i < NETDEV_HASHENTRIES; i++) + INIT_HLIST_HEAD(&hash[i]); + + return hash; +} + static u64 dev_map_bitmap_size(const union bpf_attr *attr) { return BITS_TO_LONGS((u64) attr->max_entries) * sizeof(unsigned long); @@ -97,6 +123,11 @@ static int dev_map_init_map(struct bpf_dtab *dtab, union bpf_attr *attr, /* make sure page count doesn't overflow */ cost = (u64) dtab->map.max_entries * sizeof(struct bpf_dtab_netdev *); cost += dev_map_bitmap_size(attr) * num_possible_cpus(); + + if (attr->map_type == BPF_MAP_TYPE_DEVMAP_IDX) + cost += dev_map_bitmap_size(attr) + + sizeof(struct hlist_head) * NETDEV_HASHENTRIES; + if (cost >= U32_MAX - PAGE_SIZE) return -EINVAL; @@ -119,9 +150,20 @@ static int dev_map_init_map(struct bpf_dtab *dtab, union bpf_attr *attr, dtab->netdev_map = bpf_map_area_alloc(dtab->map.max_entries * sizeof(struct bpf_dtab_netdev *), dtab->map.numa_node); - if (!dtab->netdev_map) { - free_percpu(dtab->flush_needed); - return -ENOMEM; + if (!dtab->netdev_map) + goto err_map; + + if (attr->map_type == BPF_MAP_TYPE_DEVMAP_IDX) { + dtab->bits_used = kzalloc(dev_map_bitmap_size(attr), + GFP_KERNEL); + if (!dtab->bits_used) + goto err_bitmap; + + dtab->dev_index_head = dev_map_create_hash(); + if (!dtab->dev_index_head) + goto err_idx; + + spin_lock_init(&dtab->index_lock); } spin_lock(&dev_map_lock); @@ -129,6 +171,13 @@ static int dev_map_init_map(struct bpf_dtab *dtab, union bpf_attr *attr, spin_unlock(&dev_map_lock); return 0; + err_idx: + kfree(dtab->bits_used); + err_bitmap: + bpf_map_area_free(dtab->netdev_map); + err_map: + free_percpu(dtab->flush_needed); + return -ENOMEM; } static struct bpf_map *dev_map_alloc(union bpf_attr *attr) @@ -200,6 +249,10 @@ static void dev_map_free(struct bpf_map *map) kfree(dev); } + if (map->map_type == BPF_MAP_TYPE_DEVMAP_IDX) { + kfree(dtab->dev_index_head); + kfree(dtab->bits_used); + } free_percpu(dtab->flush_needed); bpf_map_area_free(dtab->netdev_map); kfree(dtab); @@ -222,12 +275,76 @@ static int dev_map_get_next_key(struct bpf_map *map, void *key, void *next_key) return 0; } -void __dev_map_insert_ctx(struct bpf_map *map, u32 bit) +static inline struct hlist_head *dev_map_index_hash(struct bpf_dtab *dtab, + int ifindex) +{ + return &dtab->dev_index_head[ifindex & (NETDEV_HASHENTRIES - 1)]; +} + +struct bpf_dtab_netdev *__dev_map_idx_lookup_elem(struct bpf_map *map, u32 key) +{ + struct bpf_dtab *dtab = container_of(map, struct bpf_dtab, map); + struct hlist_head *head = dev_map_index_hash(dtab, key); + struct bpf_dtab_netdev *dev; + + hlist_for_each_entry_rcu(dev, head, index_hlist) + if (dev->ifindex == key) + return dev; + + return NULL; +} + +static int dev_map_idx_get_next_key(struct bpf_map *map, void *key, + void *next_key) +{ + struct bpf_dtab *dtab = container_of(map, struct bpf_dtab, map); + u32 ifindex, *next = next_key; + struct bpf_dtab_netdev *dev, *next_dev; + struct hlist_head *head; + int i = 0; + + if (!key) + goto find_first; + + ifindex = *(u32 *)key; + + dev = __dev_map_idx_lookup_elem(map, ifindex); + if (!dev) + goto find_first; + + next_dev = hlist_entry_safe(rcu_dereference_raw(hlist_next_rcu(&dev->index_hlist)), + struct bpf_dtab_netdev, index_hlist); + + if (next_dev) { + *next = next_dev->ifindex; + return 0; + } + + i = ifindex & (NETDEV_HASHENTRIES - 1); + i++; + + find_first: + for (; i < NETDEV_HASHENTRIES; i++) { + head = dev_map_index_hash(dtab, i); + + next_dev = hlist_entry_safe(rcu_dereference_raw(hlist_first_rcu(head)), + struct bpf_dtab_netdev, + index_hlist); + if (next_dev) { + *next = next_dev->ifindex; + return 0; + } + } + + return -ENOENT; +} + +void __dev_map_insert_ctx(struct bpf_map *map, struct bpf_dtab_netdev *dst) { struct bpf_dtab *dtab = container_of(map, struct bpf_dtab, map); unsigned long *bitmap = this_cpu_ptr(dtab->flush_needed); - __set_bit(bit, bitmap); + __set_bit(dst->bit, bitmap); } static int bq_xmit_all(struct bpf_dtab_netdev *obj, @@ -396,9 +513,16 @@ int dev_map_generic_redirect(struct bpf_dtab_netdev *dst, struct sk_buff *skb, static void *dev_map_lookup_elem(struct bpf_map *map, void *key) { struct bpf_dtab_netdev *obj = __dev_map_lookup_elem(map, *(u32 *)key); - struct net_device *dev = obj ? obj->dev : NULL; - return dev ? &dev->ifindex : NULL; + return obj ? &obj->ifindex : NULL; +} + +static void *dev_map_idx_lookup_elem(struct bpf_map *map, void *key) +{ + struct bpf_dtab_netdev *obj = __dev_map_idx_lookup_elem(map, + *(u32 *)key); + + return obj ? &obj->ifindex : NULL; } static void dev_map_flush_old(struct bpf_dtab_netdev *dev) @@ -453,12 +577,81 @@ static int dev_map_delete_elem(struct bpf_map *map, void *key) return 0; } -static int dev_map_update_elem(struct bpf_map *map, void *key, void *value, - u64 map_flags) +static int dev_map_idx_delete_elem(struct bpf_map *map, void *key) { struct bpf_dtab *dtab = container_of(map, struct bpf_dtab, map); + struct bpf_dtab_netdev *old_dev; + int k = *(u32 *)key; + + old_dev = __dev_map_idx_lookup_elem(map, k); + if (!old_dev) + return 0; + + spin_lock(&dtab->index_lock); + hlist_del_rcu(&old_dev->index_hlist); + spin_unlock(&dtab->index_lock); + + xchg(&dtab->netdev_map[old_dev->bit], NULL); + clear_bit_unlock(old_dev->bit, dtab->bits_used); + call_rcu(&old_dev->rcu, __dev_map_entry_free); + return 0; +} + + +static bool __dev_map_find_bit(struct bpf_dtab *dtab, unsigned int *bit) +{ + unsigned int b = 0; + + retry: + b = find_next_zero_bit(dtab->bits_used, dtab->map.max_entries, b); + + if (b >= dtab->map.max_entries) + return false; + + if (test_and_set_bit_lock(b, dtab->bits_used)) + goto retry; + + *bit = b; + return true; +} + +static struct bpf_dtab_netdev *__dev_map_alloc_node(struct bpf_dtab *dtab, + u32 ifindex, + unsigned int bit) +{ struct net *net = current->nsproxy->net_ns; gfp_t gfp = GFP_ATOMIC | __GFP_NOWARN; + struct bpf_dtab_netdev *dev; + + dev = kmalloc_node(sizeof(*dev), gfp, dtab->map.numa_node); + if (!dev) + return ERR_PTR(-ENOMEM); + + dev->bulkq = __alloc_percpu_gfp(sizeof(*dev->bulkq), + sizeof(void *), gfp); + if (!dev->bulkq) { + kfree(dev); + return ERR_PTR(-ENOMEM); + } + + dev->dev = dev_get_by_index(net, ifindex); + if (!dev->dev) { + free_percpu(dev->bulkq); + kfree(dev); + return ERR_PTR(-EINVAL); + } + + dev->ifindex = dev->dev->ifindex; + dev->bit = bit; + dev->dtab = dtab; + + return dev; +} + +static int dev_map_update_elem(struct bpf_map *map, void *key, void *value, + u64 map_flags) +{ + struct bpf_dtab *dtab = container_of(map, struct bpf_dtab, map); struct bpf_dtab_netdev *dev, *old_dev; u32 i = *(u32 *)key; u32 ifindex = *(u32 *)value; @@ -473,26 +666,9 @@ static int dev_map_update_elem(struct bpf_map *map, void *key, void *value, if (!ifindex) { dev = NULL; } else { - dev = kmalloc_node(sizeof(*dev), gfp, map->numa_node); - if (!dev) - return -ENOMEM; - - dev->bulkq = __alloc_percpu_gfp(sizeof(*dev->bulkq), - sizeof(void *), gfp); - if (!dev->bulkq) { - kfree(dev); - return -ENOMEM; - } - - dev->dev = dev_get_by_index(net, ifindex); - if (!dev->dev) { - free_percpu(dev->bulkq); - kfree(dev); - return -EINVAL; - } - - dev->bit = i; - dev->dtab = dtab; + dev = __dev_map_alloc_node(dtab, ifindex, i); + if (IS_ERR(dev)) + return PTR_ERR(dev); } /* Use call_rcu() here to ensure rcu critical sections have completed @@ -506,6 +682,52 @@ static int dev_map_update_elem(struct bpf_map *map, void *key, void *value, return 0; } +static int dev_map_idx_update_elem(struct bpf_map *map, void *key, void *value, + u64 map_flags) +{ + struct bpf_dtab *dtab = container_of(map, struct bpf_dtab, map); + struct bpf_dtab_netdev *dev, *old_dev; + u32 idx = *(u32 *)key; + u32 val = *(u32 *)value; + u32 bit; + + if (unlikely(map_flags > BPF_EXIST)) + return -EINVAL; + if (unlikely(map_flags == BPF_NOEXIST)) + return -EEXIST; + + old_dev = __dev_map_idx_lookup_elem(map, idx); + if (!val) { + if (!old_dev) + return 0; + + xchg(&dtab->netdev_map[old_dev->bit], NULL); + spin_lock(&dtab->index_lock); + hlist_del_rcu(&old_dev->index_hlist); + spin_unlock(&dtab->index_lock); + + clear_bit_unlock(old_dev->bit, dtab->bits_used); + call_rcu(&old_dev->rcu, __dev_map_entry_free); + } else { + if (idx != val) + return -EINVAL; + if (old_dev) + return 0; + if (!__dev_map_find_bit(dtab, &bit)) + return -E2BIG; + dev = __dev_map_alloc_node(dtab, idx, bit); + if (IS_ERR(dev)) + return PTR_ERR(dev); + + xchg(&dtab->netdev_map[bit], dev); + spin_lock(&dtab->index_lock); + hlist_add_head_rcu(&dev->index_hlist, + dev_map_index_hash(dtab, dev->ifindex)); + spin_unlock(&dtab->index_lock); + } + return 0; +} + const struct bpf_map_ops dev_map_ops = { .map_alloc = dev_map_alloc, .map_free = dev_map_free, @@ -516,6 +738,16 @@ const struct bpf_map_ops dev_map_ops = { .map_check_btf = map_check_no_btf, }; +const struct bpf_map_ops dev_map_idx_ops = { + .map_alloc = dev_map_alloc, + .map_free = dev_map_free, + .map_get_next_key = dev_map_idx_get_next_key, + .map_lookup_elem = dev_map_idx_lookup_elem, + .map_update_elem = dev_map_idx_update_elem, + .map_delete_elem = dev_map_idx_delete_elem, + .map_check_btf = map_check_no_btf, +}; + static int dev_map_notification(struct notifier_block *notifier, ulong event, void *ptr) { @@ -595,7 +827,7 @@ int dev_map_alloc_default_map(void) return -ENOMEM; attr.max_entries = DEV_MAP_DEFAULT_SIZE; - attr.map_type = BPF_MAP_TYPE_DEVMAP; + attr.map_type = BPF_MAP_TYPE_DEVMAP_IDX; attr.value_size = 4; attr.key_size = 4; @@ -606,13 +838,11 @@ int dev_map_alloc_default_map(void) } for_each_netdev(net, netdev) { - if (netdev->ifindex < DEV_MAP_DEFAULT_SIZE) { - idx = netdev->ifindex; - err = dev_map_update_elem(&dtab->map, &idx, &idx, 0); - if (err) { - dev_map_free(&dtab->map); - return err; - } + idx = netdev->ifindex; + err = dev_map_update_elem(&dtab->map, &idx, &idx, 0); + if (err) { + dev_map_free(&dtab->map); + return err; } } diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 629661db36ee..d6b9a7f3eb0e 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -2528,6 +2528,7 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env, * for now. */ case BPF_MAP_TYPE_DEVMAP: + case BPF_MAP_TYPE_DEVMAP_IDX: if (func_id != BPF_FUNC_redirect_map) goto error; break; @@ -2600,6 +2601,7 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env, break; case BPF_FUNC_redirect_map: if (map->map_type != BPF_MAP_TYPE_DEVMAP && + map->map_type != BPF_MAP_TYPE_DEVMAP_IDX && map->map_type != BPF_MAP_TYPE_CPUMAP && map->map_type != BPF_MAP_TYPE_XSKMAP) goto error; diff --git a/net/core/filter.c b/net/core/filter.c index c709b1468bb6..305c9bbf1753 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -3334,13 +3334,14 @@ static int __bpf_tx_xdp_map(struct net_device *dev_rx, void *fwd, int err; switch (map->map_type) { - case BPF_MAP_TYPE_DEVMAP: { + case BPF_MAP_TYPE_DEVMAP: + case BPF_MAP_TYPE_DEVMAP_IDX: { struct bpf_dtab_netdev *dst = fwd; err = dev_map_enqueue(dst, xdp, dev_rx); if (unlikely(err)) return err; - __dev_map_insert_ctx(map, index); + __dev_map_insert_ctx(map, dst); break; } case BPF_MAP_TYPE_CPUMAP: { @@ -3373,6 +3374,7 @@ void xdp_do_flush_map(void) if (map) { switch (map->map_type) { case BPF_MAP_TYPE_DEVMAP: + case BPF_MAP_TYPE_DEVMAP_IDX: __dev_map_flush(map); break; case BPF_MAP_TYPE_CPUMAP: @@ -3393,6 +3395,8 @@ static inline void *__xdp_map_lookup_elem(struct bpf_map *map, u32 index) switch (map->map_type) { case BPF_MAP_TYPE_DEVMAP: return __dev_map_lookup_elem(map, index); + case BPF_MAP_TYPE_DEVMAP_IDX: + return __dev_map_idx_lookup_elem(map, index); case BPF_MAP_TYPE_CPUMAP: return __cpu_map_lookup_elem(map, index); case BPF_MAP_TYPE_XSKMAP: @@ -3483,7 +3487,8 @@ static int xdp_do_generic_redirect_map(struct net_device *dev, goto err; } - if (map->map_type == BPF_MAP_TYPE_DEVMAP) { + if (map->map_type == BPF_MAP_TYPE_DEVMAP || + map->map_type == BPF_MAP_TYPE_DEVMAP_IDX) { struct bpf_dtab_netdev *dst = fwd; err = dev_map_generic_redirect(dst, skb, xdp_prog); diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c index e0c650d91784..0864ce33df94 100644 --- a/tools/bpf/bpftool/map.c +++ b/tools/bpf/bpftool/map.c @@ -37,6 +37,7 @@ const char * const map_type_name[] = { [BPF_MAP_TYPE_ARRAY_OF_MAPS] = "array_of_maps", [BPF_MAP_TYPE_HASH_OF_MAPS] = "hash_of_maps", [BPF_MAP_TYPE_DEVMAP] = "devmap", + [BPF_MAP_TYPE_DEVMAP_IDX] = "devmap_idx", [BPF_MAP_TYPE_SOCKMAP] = "sockmap", [BPF_MAP_TYPE_CPUMAP] = "cpumap", [BPF_MAP_TYPE_XSKMAP] = "xskmap", diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 1777fa0c61e4..a09243a38c2d 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -132,6 +132,7 @@ enum bpf_map_type { BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE, BPF_MAP_TYPE_QUEUE, BPF_MAP_TYPE_STACK, + BPF_MAP_TYPE_DEVMAP_IDX, }; /* Note that tracing related programs such as diff --git a/tools/lib/bpf/libbpf_probes.c b/tools/lib/bpf/libbpf_probes.c index 8c3a1c04dcb2..b87b760a1355 100644 --- a/tools/lib/bpf/libbpf_probes.c +++ b/tools/lib/bpf/libbpf_probes.c @@ -172,6 +172,7 @@ bool bpf_probe_map_type(enum bpf_map_type map_type, __u32 ifindex) case BPF_MAP_TYPE_ARRAY_OF_MAPS: case BPF_MAP_TYPE_HASH_OF_MAPS: case BPF_MAP_TYPE_DEVMAP: + case BPF_MAP_TYPE_DEVMAP_IDX: case BPF_MAP_TYPE_SOCKMAP: case BPF_MAP_TYPE_CPUMAP: case BPF_MAP_TYPE_XSKMAP: diff --git a/tools/testing/selftests/bpf/test_maps.c b/tools/testing/selftests/bpf/test_maps.c index 3c627771f965..63681e4647f9 100644 --- a/tools/testing/selftests/bpf/test_maps.c +++ b/tools/testing/selftests/bpf/test_maps.c @@ -519,6 +519,21 @@ static void test_devmap(unsigned int task, void *data) close(fd); } +static void test_devmap_idx(unsigned int task, void *data) +{ + int fd; + __u32 key, value; + + fd = bpf_create_map(BPF_MAP_TYPE_DEVMAP_IDX, sizeof(key), sizeof(value), + 2, 0); + if (fd < 0) { + printf("Failed to create devmap_idx '%s'!\n", strerror(errno)); + exit(1); + } + + close(fd); +} + static void test_queuemap(unsigned int task, void *data) { const int MAP_SIZE = 32; @@ -1686,6 +1701,7 @@ static void run_all_tests(void) test_arraymap_percpu_many_keys(); test_devmap(0, NULL); + test_devmap_idx(0, NULL); test_sockmap(0, NULL); test_map_large();