Merge branch 'Add bpf programmable net device'
Daniel Borkmann says: ==================== This work adds a BPF programmable device which can operate in L3 or L2 mode where the BPF program is part of the xmit routine. It's program management is done via bpf_mprog and it comes with BPF link support. For details see patch 1 and following. Thanks! v3 -> v4: - Moved netkit_release_all() into ndo_uninit (Stan) - Two small commit msg corrections (Toke) - Added Acked/Reviewed-by v2 -> v3: - Remove setting dev->min_mtu to ETH_MIN_MTU (Andrew) - Do not populate ethtool info->version (Andrew) - Populate netdev private data before register_netdevice (Andrew) - Use strscpy for ifname template (Jakub) - Use GFP_KERNEL_ACCOUNT for link kzalloc (Jakub) - Carry and dump link attach type for bpftool (Toke) v1 -> v2: - Rename from meta (Toke, Andrii, Alexei) - Reuse skb_scrub_packet (Stan) - Remove IFF_META and use netdev_ops (Toke) - Add comment to multicast handler (Toke) - Remove silly version info (Toke) - Fix attach_type_name (Quentin) - Rework libbpf link attach api to be similar as tcx (Andrii) - Move flags last for bpf_netkit_opts (Andrii) - Rebased to bpf_mprog query api changes - Folded link support patch into main one ==================== Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
This commit is contained in:
commit
22360fad58
@ -3795,6 +3795,15 @@ L: bpf@vger.kernel.org
|
||||
S: Odd Fixes
|
||||
K: (?:\b|_)bpf(?:\b|_)
|
||||
|
||||
BPF [NETKIT] (BPF-programmable network device)
|
||||
M: Daniel Borkmann <daniel@iogearbox.net>
|
||||
M: Nikolay Aleksandrov <razor@blackwall.org>
|
||||
L: bpf@vger.kernel.org
|
||||
L: netdev@vger.kernel.org
|
||||
S: Supported
|
||||
F: drivers/net/netkit.c
|
||||
F: include/net/netkit.h
|
||||
|
||||
BPF [NETWORKING] (struct_ops, reuseport)
|
||||
M: Martin KaFai Lau <martin.lau@linux.dev>
|
||||
L: bpf@vger.kernel.org
|
||||
|
@ -448,6 +448,15 @@ config NLMON
|
||||
diagnostics, etc. This is mostly intended for developers or support
|
||||
to debug netlink issues. If unsure, say N.
|
||||
|
||||
config NETKIT
|
||||
bool "BPF-programmable network device"
|
||||
depends on BPF_SYSCALL
|
||||
help
|
||||
The netkit device is a virtual networking device where BPF programs
|
||||
can be attached to the device(s) transmission routine in order to
|
||||
implement the driver's internal logic. The device can be configured
|
||||
to operate in L3 or L2 mode. If unsure, say N.
|
||||
|
||||
config NET_VRF
|
||||
tristate "Virtual Routing and Forwarding (Lite)"
|
||||
depends on IP_MULTIPLE_TABLES
|
||||
|
@ -22,6 +22,7 @@ obj-$(CONFIG_MDIO) += mdio.o
|
||||
obj-$(CONFIG_NET) += loopback.o
|
||||
obj-$(CONFIG_NETDEV_LEGACY_INIT) += Space.o
|
||||
obj-$(CONFIG_NETCONSOLE) += netconsole.o
|
||||
obj-$(CONFIG_NETKIT) += netkit.o
|
||||
obj-y += phy/
|
||||
obj-y += pse-pd/
|
||||
obj-y += mdio/
|
||||
|
940
drivers/net/netkit.c
Normal file
940
drivers/net/netkit.c
Normal file
@ -0,0 +1,940 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2023 Isovalent */
|
||||
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/ethtool.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/netfilter_netdev.h>
|
||||
#include <linux/bpf_mprog.h>
|
||||
|
||||
#include <net/netkit.h>
|
||||
#include <net/dst.h>
|
||||
#include <net/tcx.h>
|
||||
|
||||
#define DRV_NAME "netkit"
|
||||
|
||||
struct netkit {
|
||||
/* Needed in fast-path */
|
||||
struct net_device __rcu *peer;
|
||||
struct bpf_mprog_entry __rcu *active;
|
||||
enum netkit_action policy;
|
||||
struct bpf_mprog_bundle bundle;
|
||||
|
||||
/* Needed in slow-path */
|
||||
enum netkit_mode mode;
|
||||
bool primary;
|
||||
u32 headroom;
|
||||
};
|
||||
|
||||
struct netkit_link {
|
||||
struct bpf_link link;
|
||||
struct net_device *dev;
|
||||
u32 location;
|
||||
};
|
||||
|
||||
static __always_inline int
|
||||
netkit_run(const struct bpf_mprog_entry *entry, struct sk_buff *skb,
|
||||
enum netkit_action ret)
|
||||
{
|
||||
const struct bpf_mprog_fp *fp;
|
||||
const struct bpf_prog *prog;
|
||||
|
||||
bpf_mprog_foreach_prog(entry, fp, prog) {
|
||||
bpf_compute_data_pointers(skb);
|
||||
ret = bpf_prog_run(prog, skb);
|
||||
if (ret != NETKIT_NEXT)
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void netkit_prep_forward(struct sk_buff *skb, bool xnet)
|
||||
{
|
||||
skb_scrub_packet(skb, xnet);
|
||||
skb->priority = 0;
|
||||
nf_skip_egress(skb, true);
|
||||
}
|
||||
|
||||
static struct netkit *netkit_priv(const struct net_device *dev)
|
||||
{
|
||||
return netdev_priv(dev);
|
||||
}
|
||||
|
||||
static netdev_tx_t netkit_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct netkit *nk = netkit_priv(dev);
|
||||
enum netkit_action ret = READ_ONCE(nk->policy);
|
||||
netdev_tx_t ret_dev = NET_XMIT_SUCCESS;
|
||||
const struct bpf_mprog_entry *entry;
|
||||
struct net_device *peer;
|
||||
|
||||
rcu_read_lock();
|
||||
peer = rcu_dereference(nk->peer);
|
||||
if (unlikely(!peer || !(peer->flags & IFF_UP) ||
|
||||
!pskb_may_pull(skb, ETH_HLEN) ||
|
||||
skb_orphan_frags(skb, GFP_ATOMIC)))
|
||||
goto drop;
|
||||
netkit_prep_forward(skb, !net_eq(dev_net(dev), dev_net(peer)));
|
||||
skb->dev = peer;
|
||||
entry = rcu_dereference(nk->active);
|
||||
if (entry)
|
||||
ret = netkit_run(entry, skb, ret);
|
||||
switch (ret) {
|
||||
case NETKIT_NEXT:
|
||||
case NETKIT_PASS:
|
||||
skb->protocol = eth_type_trans(skb, skb->dev);
|
||||
skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN);
|
||||
__netif_rx(skb);
|
||||
break;
|
||||
case NETKIT_REDIRECT:
|
||||
skb_do_redirect(skb);
|
||||
break;
|
||||
case NETKIT_DROP:
|
||||
default:
|
||||
drop:
|
||||
kfree_skb(skb);
|
||||
dev_core_stats_tx_dropped_inc(dev);
|
||||
ret_dev = NET_XMIT_DROP;
|
||||
break;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
return ret_dev;
|
||||
}
|
||||
|
||||
static int netkit_open(struct net_device *dev)
|
||||
{
|
||||
struct netkit *nk = netkit_priv(dev);
|
||||
struct net_device *peer = rtnl_dereference(nk->peer);
|
||||
|
||||
if (!peer)
|
||||
return -ENOTCONN;
|
||||
if (peer->flags & IFF_UP) {
|
||||
netif_carrier_on(dev);
|
||||
netif_carrier_on(peer);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int netkit_close(struct net_device *dev)
|
||||
{
|
||||
struct netkit *nk = netkit_priv(dev);
|
||||
struct net_device *peer = rtnl_dereference(nk->peer);
|
||||
|
||||
netif_carrier_off(dev);
|
||||
if (peer)
|
||||
netif_carrier_off(peer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int netkit_get_iflink(const struct net_device *dev)
|
||||
{
|
||||
struct netkit *nk = netkit_priv(dev);
|
||||
struct net_device *peer;
|
||||
int iflink = 0;
|
||||
|
||||
rcu_read_lock();
|
||||
peer = rcu_dereference(nk->peer);
|
||||
if (peer)
|
||||
iflink = peer->ifindex;
|
||||
rcu_read_unlock();
|
||||
return iflink;
|
||||
}
|
||||
|
||||
static void netkit_set_multicast(struct net_device *dev)
|
||||
{
|
||||
/* Nothing to do, we receive whatever gets pushed to us! */
|
||||
}
|
||||
|
||||
static void netkit_set_headroom(struct net_device *dev, int headroom)
|
||||
{
|
||||
struct netkit *nk = netkit_priv(dev), *nk2;
|
||||
struct net_device *peer;
|
||||
|
||||
if (headroom < 0)
|
||||
headroom = NET_SKB_PAD;
|
||||
|
||||
rcu_read_lock();
|
||||
peer = rcu_dereference(nk->peer);
|
||||
if (unlikely(!peer))
|
||||
goto out;
|
||||
|
||||
nk2 = netkit_priv(peer);
|
||||
nk->headroom = headroom;
|
||||
headroom = max(nk->headroom, nk2->headroom);
|
||||
|
||||
peer->needed_headroom = headroom;
|
||||
dev->needed_headroom = headroom;
|
||||
out:
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
static struct net_device *netkit_peer_dev(struct net_device *dev)
|
||||
{
|
||||
return rcu_dereference(netkit_priv(dev)->peer);
|
||||
}
|
||||
|
||||
static void netkit_uninit(struct net_device *dev);
|
||||
|
||||
static const struct net_device_ops netkit_netdev_ops = {
|
||||
.ndo_open = netkit_open,
|
||||
.ndo_stop = netkit_close,
|
||||
.ndo_start_xmit = netkit_xmit,
|
||||
.ndo_set_rx_mode = netkit_set_multicast,
|
||||
.ndo_set_rx_headroom = netkit_set_headroom,
|
||||
.ndo_get_iflink = netkit_get_iflink,
|
||||
.ndo_get_peer_dev = netkit_peer_dev,
|
||||
.ndo_uninit = netkit_uninit,
|
||||
.ndo_features_check = passthru_features_check,
|
||||
};
|
||||
|
||||
static void netkit_get_drvinfo(struct net_device *dev,
|
||||
struct ethtool_drvinfo *info)
|
||||
{
|
||||
strscpy(info->driver, DRV_NAME, sizeof(info->driver));
|
||||
}
|
||||
|
||||
static const struct ethtool_ops netkit_ethtool_ops = {
|
||||
.get_drvinfo = netkit_get_drvinfo,
|
||||
};
|
||||
|
||||
static void netkit_setup(struct net_device *dev)
|
||||
{
|
||||
static const netdev_features_t netkit_features_hw_vlan =
|
||||
NETIF_F_HW_VLAN_CTAG_TX |
|
||||
NETIF_F_HW_VLAN_CTAG_RX |
|
||||
NETIF_F_HW_VLAN_STAG_TX |
|
||||
NETIF_F_HW_VLAN_STAG_RX;
|
||||
static const netdev_features_t netkit_features =
|
||||
netkit_features_hw_vlan |
|
||||
NETIF_F_SG |
|
||||
NETIF_F_FRAGLIST |
|
||||
NETIF_F_HW_CSUM |
|
||||
NETIF_F_RXCSUM |
|
||||
NETIF_F_SCTP_CRC |
|
||||
NETIF_F_HIGHDMA |
|
||||
NETIF_F_GSO_SOFTWARE |
|
||||
NETIF_F_GSO_ENCAP_ALL;
|
||||
|
||||
ether_setup(dev);
|
||||
dev->max_mtu = ETH_MAX_MTU;
|
||||
|
||||
dev->flags |= IFF_NOARP;
|
||||
dev->priv_flags &= ~IFF_TX_SKB_SHARING;
|
||||
dev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
|
||||
dev->priv_flags |= IFF_PHONY_HEADROOM;
|
||||
dev->priv_flags |= IFF_NO_QUEUE;
|
||||
|
||||
dev->ethtool_ops = &netkit_ethtool_ops;
|
||||
dev->netdev_ops = &netkit_netdev_ops;
|
||||
|
||||
dev->features |= netkit_features | NETIF_F_LLTX;
|
||||
dev->hw_features = netkit_features;
|
||||
dev->hw_enc_features = netkit_features;
|
||||
dev->mpls_features = NETIF_F_HW_CSUM | NETIF_F_GSO_SOFTWARE;
|
||||
dev->vlan_features = dev->features & ~netkit_features_hw_vlan;
|
||||
|
||||
dev->needs_free_netdev = true;
|
||||
|
||||
netif_set_tso_max_size(dev, GSO_MAX_SIZE);
|
||||
}
|
||||
|
||||
static struct net *netkit_get_link_net(const struct net_device *dev)
|
||||
{
|
||||
struct netkit *nk = netkit_priv(dev);
|
||||
struct net_device *peer = rtnl_dereference(nk->peer);
|
||||
|
||||
return peer ? dev_net(peer) : dev_net(dev);
|
||||
}
|
||||
|
||||
static int netkit_check_policy(int policy, struct nlattr *tb,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
switch (policy) {
|
||||
case NETKIT_PASS:
|
||||
case NETKIT_DROP:
|
||||
return 0;
|
||||
default:
|
||||
NL_SET_ERR_MSG_ATTR(extack, tb,
|
||||
"Provided default xmit policy not supported");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int netkit_check_mode(int mode, struct nlattr *tb,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
switch (mode) {
|
||||
case NETKIT_L2:
|
||||
case NETKIT_L3:
|
||||
return 0;
|
||||
default:
|
||||
NL_SET_ERR_MSG_ATTR(extack, tb,
|
||||
"Provided device mode can only be L2 or L3");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int netkit_validate(struct nlattr *tb[], struct nlattr *data[],
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct nlattr *attr = tb[IFLA_ADDRESS];
|
||||
|
||||
if (!attr)
|
||||
return 0;
|
||||
NL_SET_ERR_MSG_ATTR(extack, attr,
|
||||
"Setting Ethernet address is not supported");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static struct rtnl_link_ops netkit_link_ops;
|
||||
|
||||
static int netkit_new_link(struct net *src_net, struct net_device *dev,
|
||||
struct nlattr *tb[], struct nlattr *data[],
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct nlattr *peer_tb[IFLA_MAX + 1], **tbp = tb, *attr;
|
||||
enum netkit_action default_prim = NETKIT_PASS;
|
||||
enum netkit_action default_peer = NETKIT_PASS;
|
||||
enum netkit_mode mode = NETKIT_L3;
|
||||
unsigned char ifname_assign_type;
|
||||
struct ifinfomsg *ifmp = NULL;
|
||||
struct net_device *peer;
|
||||
char ifname[IFNAMSIZ];
|
||||
struct netkit *nk;
|
||||
struct net *net;
|
||||
int err;
|
||||
|
||||
if (data) {
|
||||
if (data[IFLA_NETKIT_MODE]) {
|
||||
attr = data[IFLA_NETKIT_MODE];
|
||||
mode = nla_get_u32(attr);
|
||||
err = netkit_check_mode(mode, attr, extack);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
if (data[IFLA_NETKIT_PEER_INFO]) {
|
||||
attr = data[IFLA_NETKIT_PEER_INFO];
|
||||
ifmp = nla_data(attr);
|
||||
err = rtnl_nla_parse_ifinfomsg(peer_tb, attr, extack);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = netkit_validate(peer_tb, NULL, extack);
|
||||
if (err < 0)
|
||||
return err;
|
||||
tbp = peer_tb;
|
||||
}
|
||||
if (data[IFLA_NETKIT_POLICY]) {
|
||||
attr = data[IFLA_NETKIT_POLICY];
|
||||
default_prim = nla_get_u32(attr);
|
||||
err = netkit_check_policy(default_prim, attr, extack);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
if (data[IFLA_NETKIT_PEER_POLICY]) {
|
||||
attr = data[IFLA_NETKIT_PEER_POLICY];
|
||||
default_peer = nla_get_u32(attr);
|
||||
err = netkit_check_policy(default_peer, attr, extack);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (ifmp && tbp[IFLA_IFNAME]) {
|
||||
nla_strscpy(ifname, tbp[IFLA_IFNAME], IFNAMSIZ);
|
||||
ifname_assign_type = NET_NAME_USER;
|
||||
} else {
|
||||
strscpy(ifname, "nk%d", IFNAMSIZ);
|
||||
ifname_assign_type = NET_NAME_ENUM;
|
||||
}
|
||||
|
||||
net = rtnl_link_get_net(src_net, tbp);
|
||||
if (IS_ERR(net))
|
||||
return PTR_ERR(net);
|
||||
|
||||
peer = rtnl_create_link(net, ifname, ifname_assign_type,
|
||||
&netkit_link_ops, tbp, extack);
|
||||
if (IS_ERR(peer)) {
|
||||
put_net(net);
|
||||
return PTR_ERR(peer);
|
||||
}
|
||||
|
||||
netif_inherit_tso_max(peer, dev);
|
||||
|
||||
if (mode == NETKIT_L2)
|
||||
eth_hw_addr_random(peer);
|
||||
if (ifmp && dev->ifindex)
|
||||
peer->ifindex = ifmp->ifi_index;
|
||||
|
||||
nk = netkit_priv(peer);
|
||||
nk->primary = false;
|
||||
nk->policy = default_peer;
|
||||
nk->mode = mode;
|
||||
bpf_mprog_bundle_init(&nk->bundle);
|
||||
RCU_INIT_POINTER(nk->active, NULL);
|
||||
RCU_INIT_POINTER(nk->peer, NULL);
|
||||
|
||||
err = register_netdevice(peer);
|
||||
put_net(net);
|
||||
if (err < 0)
|
||||
goto err_register_peer;
|
||||
netif_carrier_off(peer);
|
||||
if (mode == NETKIT_L2)
|
||||
dev_change_flags(peer, peer->flags & ~IFF_NOARP, NULL);
|
||||
|
||||
err = rtnl_configure_link(peer, NULL, 0, NULL);
|
||||
if (err < 0)
|
||||
goto err_configure_peer;
|
||||
|
||||
if (mode == NETKIT_L2)
|
||||
eth_hw_addr_random(dev);
|
||||
if (tb[IFLA_IFNAME])
|
||||
nla_strscpy(dev->name, tb[IFLA_IFNAME], IFNAMSIZ);
|
||||
else
|
||||
strscpy(dev->name, "nk%d", IFNAMSIZ);
|
||||
|
||||
nk = netkit_priv(dev);
|
||||
nk->primary = true;
|
||||
nk->policy = default_prim;
|
||||
nk->mode = mode;
|
||||
bpf_mprog_bundle_init(&nk->bundle);
|
||||
RCU_INIT_POINTER(nk->active, NULL);
|
||||
RCU_INIT_POINTER(nk->peer, NULL);
|
||||
|
||||
err = register_netdevice(dev);
|
||||
if (err < 0)
|
||||
goto err_configure_peer;
|
||||
netif_carrier_off(dev);
|
||||
if (mode == NETKIT_L2)
|
||||
dev_change_flags(dev, dev->flags & ~IFF_NOARP, NULL);
|
||||
|
||||
rcu_assign_pointer(netkit_priv(dev)->peer, peer);
|
||||
rcu_assign_pointer(netkit_priv(peer)->peer, dev);
|
||||
return 0;
|
||||
err_configure_peer:
|
||||
unregister_netdevice(peer);
|
||||
return err;
|
||||
err_register_peer:
|
||||
free_netdev(peer);
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct bpf_mprog_entry *netkit_entry_fetch(struct net_device *dev,
|
||||
bool bundle_fallback)
|
||||
{
|
||||
struct netkit *nk = netkit_priv(dev);
|
||||
struct bpf_mprog_entry *entry;
|
||||
|
||||
ASSERT_RTNL();
|
||||
entry = rcu_dereference_rtnl(nk->active);
|
||||
if (entry)
|
||||
return entry;
|
||||
if (bundle_fallback)
|
||||
return &nk->bundle.a;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void netkit_entry_update(struct net_device *dev,
|
||||
struct bpf_mprog_entry *entry)
|
||||
{
|
||||
struct netkit *nk = netkit_priv(dev);
|
||||
|
||||
ASSERT_RTNL();
|
||||
rcu_assign_pointer(nk->active, entry);
|
||||
}
|
||||
|
||||
static void netkit_entry_sync(void)
|
||||
{
|
||||
synchronize_rcu();
|
||||
}
|
||||
|
||||
static struct net_device *netkit_dev_fetch(struct net *net, u32 ifindex, u32 which)
|
||||
{
|
||||
struct net_device *dev;
|
||||
struct netkit *nk;
|
||||
|
||||
ASSERT_RTNL();
|
||||
|
||||
switch (which) {
|
||||
case BPF_NETKIT_PRIMARY:
|
||||
case BPF_NETKIT_PEER:
|
||||
break;
|
||||
default:
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
dev = __dev_get_by_index(net, ifindex);
|
||||
if (!dev)
|
||||
return ERR_PTR(-ENODEV);
|
||||
if (dev->netdev_ops != &netkit_netdev_ops)
|
||||
return ERR_PTR(-ENXIO);
|
||||
|
||||
nk = netkit_priv(dev);
|
||||
if (!nk->primary)
|
||||
return ERR_PTR(-EACCES);
|
||||
if (which == BPF_NETKIT_PEER) {
|
||||
dev = rcu_dereference_rtnl(nk->peer);
|
||||
if (!dev)
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
return dev;
|
||||
}
|
||||
|
||||
int netkit_prog_attach(const union bpf_attr *attr, struct bpf_prog *prog)
|
||||
{
|
||||
struct bpf_mprog_entry *entry, *entry_new;
|
||||
struct bpf_prog *replace_prog = NULL;
|
||||
struct net_device *dev;
|
||||
int ret;
|
||||
|
||||
rtnl_lock();
|
||||
dev = netkit_dev_fetch(current->nsproxy->net_ns, attr->target_ifindex,
|
||||
attr->attach_type);
|
||||
if (IS_ERR(dev)) {
|
||||
ret = PTR_ERR(dev);
|
||||
goto out;
|
||||
}
|
||||
entry = netkit_entry_fetch(dev, true);
|
||||
if (attr->attach_flags & BPF_F_REPLACE) {
|
||||
replace_prog = bpf_prog_get_type(attr->replace_bpf_fd,
|
||||
prog->type);
|
||||
if (IS_ERR(replace_prog)) {
|
||||
ret = PTR_ERR(replace_prog);
|
||||
replace_prog = NULL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
ret = bpf_mprog_attach(entry, &entry_new, prog, NULL, replace_prog,
|
||||
attr->attach_flags, attr->relative_fd,
|
||||
attr->expected_revision);
|
||||
if (!ret) {
|
||||
if (entry != entry_new) {
|
||||
netkit_entry_update(dev, entry_new);
|
||||
netkit_entry_sync();
|
||||
}
|
||||
bpf_mprog_commit(entry);
|
||||
}
|
||||
out:
|
||||
if (replace_prog)
|
||||
bpf_prog_put(replace_prog);
|
||||
rtnl_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
int netkit_prog_detach(const union bpf_attr *attr, struct bpf_prog *prog)
|
||||
{
|
||||
struct bpf_mprog_entry *entry, *entry_new;
|
||||
struct net_device *dev;
|
||||
int ret;
|
||||
|
||||
rtnl_lock();
|
||||
dev = netkit_dev_fetch(current->nsproxy->net_ns, attr->target_ifindex,
|
||||
attr->attach_type);
|
||||
if (IS_ERR(dev)) {
|
||||
ret = PTR_ERR(dev);
|
||||
goto out;
|
||||
}
|
||||
entry = netkit_entry_fetch(dev, false);
|
||||
if (!entry) {
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
ret = bpf_mprog_detach(entry, &entry_new, prog, NULL, attr->attach_flags,
|
||||
attr->relative_fd, attr->expected_revision);
|
||||
if (!ret) {
|
||||
if (!bpf_mprog_total(entry_new))
|
||||
entry_new = NULL;
|
||||
netkit_entry_update(dev, entry_new);
|
||||
netkit_entry_sync();
|
||||
bpf_mprog_commit(entry);
|
||||
}
|
||||
out:
|
||||
rtnl_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
int netkit_prog_query(const union bpf_attr *attr, union bpf_attr __user *uattr)
|
||||
{
|
||||
struct net_device *dev;
|
||||
int ret;
|
||||
|
||||
rtnl_lock();
|
||||
dev = netkit_dev_fetch(current->nsproxy->net_ns,
|
||||
attr->query.target_ifindex,
|
||||
attr->query.attach_type);
|
||||
if (IS_ERR(dev)) {
|
||||
ret = PTR_ERR(dev);
|
||||
goto out;
|
||||
}
|
||||
ret = bpf_mprog_query(attr, uattr, netkit_entry_fetch(dev, false));
|
||||
out:
|
||||
rtnl_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct netkit_link *netkit_link(const struct bpf_link *link)
|
||||
{
|
||||
return container_of(link, struct netkit_link, link);
|
||||
}
|
||||
|
||||
static int netkit_link_prog_attach(struct bpf_link *link, u32 flags,
|
||||
u32 id_or_fd, u64 revision)
|
||||
{
|
||||
struct netkit_link *nkl = netkit_link(link);
|
||||
struct bpf_mprog_entry *entry, *entry_new;
|
||||
struct net_device *dev = nkl->dev;
|
||||
int ret;
|
||||
|
||||
ASSERT_RTNL();
|
||||
entry = netkit_entry_fetch(dev, true);
|
||||
ret = bpf_mprog_attach(entry, &entry_new, link->prog, link, NULL, flags,
|
||||
id_or_fd, revision);
|
||||
if (!ret) {
|
||||
if (entry != entry_new) {
|
||||
netkit_entry_update(dev, entry_new);
|
||||
netkit_entry_sync();
|
||||
}
|
||||
bpf_mprog_commit(entry);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void netkit_link_release(struct bpf_link *link)
|
||||
{
|
||||
struct netkit_link *nkl = netkit_link(link);
|
||||
struct bpf_mprog_entry *entry, *entry_new;
|
||||
struct net_device *dev;
|
||||
int ret = 0;
|
||||
|
||||
rtnl_lock();
|
||||
dev = nkl->dev;
|
||||
if (!dev)
|
||||
goto out;
|
||||
entry = netkit_entry_fetch(dev, false);
|
||||
if (!entry) {
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0);
|
||||
if (!ret) {
|
||||
if (!bpf_mprog_total(entry_new))
|
||||
entry_new = NULL;
|
||||
netkit_entry_update(dev, entry_new);
|
||||
netkit_entry_sync();
|
||||
bpf_mprog_commit(entry);
|
||||
nkl->dev = NULL;
|
||||
}
|
||||
out:
|
||||
WARN_ON_ONCE(ret);
|
||||
rtnl_unlock();
|
||||
}
|
||||
|
||||
static int netkit_link_update(struct bpf_link *link, struct bpf_prog *nprog,
|
||||
struct bpf_prog *oprog)
|
||||
{
|
||||
struct netkit_link *nkl = netkit_link(link);
|
||||
struct bpf_mprog_entry *entry, *entry_new;
|
||||
struct net_device *dev;
|
||||
int ret = 0;
|
||||
|
||||
rtnl_lock();
|
||||
dev = nkl->dev;
|
||||
if (!dev) {
|
||||
ret = -ENOLINK;
|
||||
goto out;
|
||||
}
|
||||
if (oprog && link->prog != oprog) {
|
||||
ret = -EPERM;
|
||||
goto out;
|
||||
}
|
||||
oprog = link->prog;
|
||||
if (oprog == nprog) {
|
||||
bpf_prog_put(nprog);
|
||||
goto out;
|
||||
}
|
||||
entry = netkit_entry_fetch(dev, false);
|
||||
if (!entry) {
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
|
||||
BPF_F_REPLACE | BPF_F_ID,
|
||||
link->prog->aux->id, 0);
|
||||
if (!ret) {
|
||||
WARN_ON_ONCE(entry != entry_new);
|
||||
oprog = xchg(&link->prog, nprog);
|
||||
bpf_prog_put(oprog);
|
||||
bpf_mprog_commit(entry);
|
||||
}
|
||||
out:
|
||||
rtnl_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void netkit_link_dealloc(struct bpf_link *link)
|
||||
{
|
||||
kfree(netkit_link(link));
|
||||
}
|
||||
|
||||
static void netkit_link_fdinfo(const struct bpf_link *link, struct seq_file *seq)
|
||||
{
|
||||
const struct netkit_link *nkl = netkit_link(link);
|
||||
u32 ifindex = 0;
|
||||
|
||||
rtnl_lock();
|
||||
if (nkl->dev)
|
||||
ifindex = nkl->dev->ifindex;
|
||||
rtnl_unlock();
|
||||
|
||||
seq_printf(seq, "ifindex:\t%u\n", ifindex);
|
||||
seq_printf(seq, "attach_type:\t%u (%s)\n",
|
||||
nkl->location,
|
||||
nkl->location == BPF_NETKIT_PRIMARY ? "primary" : "peer");
|
||||
}
|
||||
|
||||
static int netkit_link_fill_info(const struct bpf_link *link,
|
||||
struct bpf_link_info *info)
|
||||
{
|
||||
const struct netkit_link *nkl = netkit_link(link);
|
||||
u32 ifindex = 0;
|
||||
|
||||
rtnl_lock();
|
||||
if (nkl->dev)
|
||||
ifindex = nkl->dev->ifindex;
|
||||
rtnl_unlock();
|
||||
|
||||
info->netkit.ifindex = ifindex;
|
||||
info->netkit.attach_type = nkl->location;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int netkit_link_detach(struct bpf_link *link)
|
||||
{
|
||||
netkit_link_release(link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct bpf_link_ops netkit_link_lops = {
|
||||
.release = netkit_link_release,
|
||||
.detach = netkit_link_detach,
|
||||
.dealloc = netkit_link_dealloc,
|
||||
.update_prog = netkit_link_update,
|
||||
.show_fdinfo = netkit_link_fdinfo,
|
||||
.fill_link_info = netkit_link_fill_info,
|
||||
};
|
||||
|
||||
static int netkit_link_init(struct netkit_link *nkl,
|
||||
struct bpf_link_primer *link_primer,
|
||||
const union bpf_attr *attr,
|
||||
struct net_device *dev,
|
||||
struct bpf_prog *prog)
|
||||
{
|
||||
bpf_link_init(&nkl->link, BPF_LINK_TYPE_NETKIT,
|
||||
&netkit_link_lops, prog);
|
||||
nkl->location = attr->link_create.attach_type;
|
||||
nkl->dev = dev;
|
||||
return bpf_link_prime(&nkl->link, link_primer);
|
||||
}
|
||||
|
||||
int netkit_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
|
||||
{
|
||||
struct bpf_link_primer link_primer;
|
||||
struct netkit_link *nkl;
|
||||
struct net_device *dev;
|
||||
int ret;
|
||||
|
||||
rtnl_lock();
|
||||
dev = netkit_dev_fetch(current->nsproxy->net_ns,
|
||||
attr->link_create.target_ifindex,
|
||||
attr->link_create.attach_type);
|
||||
if (IS_ERR(dev)) {
|
||||
ret = PTR_ERR(dev);
|
||||
goto out;
|
||||
}
|
||||
nkl = kzalloc(sizeof(*nkl), GFP_KERNEL_ACCOUNT);
|
||||
if (!nkl) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
ret = netkit_link_init(nkl, &link_primer, attr, dev, prog);
|
||||
if (ret) {
|
||||
kfree(nkl);
|
||||
goto out;
|
||||
}
|
||||
ret = netkit_link_prog_attach(&nkl->link,
|
||||
attr->link_create.flags,
|
||||
attr->link_create.netkit.relative_fd,
|
||||
attr->link_create.netkit.expected_revision);
|
||||
if (ret) {
|
||||
nkl->dev = NULL;
|
||||
bpf_link_cleanup(&link_primer);
|
||||
goto out;
|
||||
}
|
||||
ret = bpf_link_settle(&link_primer);
|
||||
out:
|
||||
rtnl_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void netkit_release_all(struct net_device *dev)
|
||||
{
|
||||
struct bpf_mprog_entry *entry;
|
||||
struct bpf_tuple tuple = {};
|
||||
struct bpf_mprog_fp *fp;
|
||||
struct bpf_mprog_cp *cp;
|
||||
|
||||
entry = netkit_entry_fetch(dev, false);
|
||||
if (!entry)
|
||||
return;
|
||||
netkit_entry_update(dev, NULL);
|
||||
netkit_entry_sync();
|
||||
bpf_mprog_foreach_tuple(entry, fp, cp, tuple) {
|
||||
if (tuple.link)
|
||||
netkit_link(tuple.link)->dev = NULL;
|
||||
else
|
||||
bpf_prog_put(tuple.prog);
|
||||
}
|
||||
}
|
||||
|
||||
static void netkit_uninit(struct net_device *dev)
|
||||
{
|
||||
netkit_release_all(dev);
|
||||
}
|
||||
|
||||
static void netkit_del_link(struct net_device *dev, struct list_head *head)
|
||||
{
|
||||
struct netkit *nk = netkit_priv(dev);
|
||||
struct net_device *peer = rtnl_dereference(nk->peer);
|
||||
|
||||
RCU_INIT_POINTER(nk->peer, NULL);
|
||||
unregister_netdevice_queue(dev, head);
|
||||
if (peer) {
|
||||
nk = netkit_priv(peer);
|
||||
RCU_INIT_POINTER(nk->peer, NULL);
|
||||
unregister_netdevice_queue(peer, head);
|
||||
}
|
||||
}
|
||||
|
||||
static int netkit_change_link(struct net_device *dev, struct nlattr *tb[],
|
||||
struct nlattr *data[],
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct netkit *nk = netkit_priv(dev);
|
||||
struct net_device *peer = rtnl_dereference(nk->peer);
|
||||
enum netkit_action policy;
|
||||
struct nlattr *attr;
|
||||
int err;
|
||||
|
||||
if (!nk->primary) {
|
||||
NL_SET_ERR_MSG(extack,
|
||||
"netkit link settings can be changed only through the primary device");
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
if (data[IFLA_NETKIT_MODE]) {
|
||||
NL_SET_ERR_MSG_ATTR(extack, data[IFLA_NETKIT_MODE],
|
||||
"netkit link operating mode cannot be changed after device creation");
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
if (data[IFLA_NETKIT_POLICY]) {
|
||||
attr = data[IFLA_NETKIT_POLICY];
|
||||
policy = nla_get_u32(attr);
|
||||
err = netkit_check_policy(policy, attr, extack);
|
||||
if (err)
|
||||
return err;
|
||||
WRITE_ONCE(nk->policy, policy);
|
||||
}
|
||||
|
||||
if (data[IFLA_NETKIT_PEER_POLICY]) {
|
||||
err = -EOPNOTSUPP;
|
||||
attr = data[IFLA_NETKIT_PEER_POLICY];
|
||||
policy = nla_get_u32(attr);
|
||||
if (peer)
|
||||
err = netkit_check_policy(policy, attr, extack);
|
||||
if (err)
|
||||
return err;
|
||||
nk = netkit_priv(peer);
|
||||
WRITE_ONCE(nk->policy, policy);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t netkit_get_size(const struct net_device *dev)
|
||||
{
|
||||
return nla_total_size(sizeof(u32)) + /* IFLA_NETKIT_POLICY */
|
||||
nla_total_size(sizeof(u32)) + /* IFLA_NETKIT_PEER_POLICY */
|
||||
nla_total_size(sizeof(u8)) + /* IFLA_NETKIT_PRIMARY */
|
||||
nla_total_size(sizeof(u32)) + /* IFLA_NETKIT_MODE */
|
||||
0;
|
||||
}
|
||||
|
||||
static int netkit_fill_info(struct sk_buff *skb, const struct net_device *dev)
|
||||
{
|
||||
struct netkit *nk = netkit_priv(dev);
|
||||
struct net_device *peer = rtnl_dereference(nk->peer);
|
||||
|
||||
if (nla_put_u8(skb, IFLA_NETKIT_PRIMARY, nk->primary))
|
||||
return -EMSGSIZE;
|
||||
if (nla_put_u32(skb, IFLA_NETKIT_POLICY, nk->policy))
|
||||
return -EMSGSIZE;
|
||||
if (nla_put_u32(skb, IFLA_NETKIT_MODE, nk->mode))
|
||||
return -EMSGSIZE;
|
||||
|
||||
if (peer) {
|
||||
nk = netkit_priv(peer);
|
||||
if (nla_put_u32(skb, IFLA_NETKIT_PEER_POLICY, nk->policy))
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct nla_policy netkit_policy[IFLA_NETKIT_MAX + 1] = {
|
||||
[IFLA_NETKIT_PEER_INFO] = { .len = sizeof(struct ifinfomsg) },
|
||||
[IFLA_NETKIT_POLICY] = { .type = NLA_U32 },
|
||||
[IFLA_NETKIT_MODE] = { .type = NLA_U32 },
|
||||
[IFLA_NETKIT_PEER_POLICY] = { .type = NLA_U32 },
|
||||
[IFLA_NETKIT_PRIMARY] = { .type = NLA_REJECT,
|
||||
.reject_message = "Primary attribute is read-only" },
|
||||
};
|
||||
|
||||
static struct rtnl_link_ops netkit_link_ops = {
|
||||
.kind = DRV_NAME,
|
||||
.priv_size = sizeof(struct netkit),
|
||||
.setup = netkit_setup,
|
||||
.newlink = netkit_new_link,
|
||||
.dellink = netkit_del_link,
|
||||
.changelink = netkit_change_link,
|
||||
.get_link_net = netkit_get_link_net,
|
||||
.get_size = netkit_get_size,
|
||||
.fill_info = netkit_fill_info,
|
||||
.policy = netkit_policy,
|
||||
.validate = netkit_validate,
|
||||
.maxtype = IFLA_NETKIT_MAX,
|
||||
};
|
||||
|
||||
static __init int netkit_init(void)
|
||||
{
|
||||
BUILD_BUG_ON((int)NETKIT_NEXT != (int)TCX_NEXT ||
|
||||
(int)NETKIT_PASS != (int)TCX_PASS ||
|
||||
(int)NETKIT_DROP != (int)TCX_DROP ||
|
||||
(int)NETKIT_REDIRECT != (int)TCX_REDIRECT);
|
||||
|
||||
return rtnl_link_register(&netkit_link_ops);
|
||||
}
|
||||
|
||||
static __exit void netkit_exit(void)
|
||||
{
|
||||
rtnl_link_unregister(&netkit_link_ops);
|
||||
}
|
||||
|
||||
module_init(netkit_init);
|
||||
module_exit(netkit_exit);
|
||||
|
||||
MODULE_DESCRIPTION("BPF-programmable network device");
|
||||
MODULE_AUTHOR("Daniel Borkmann <daniel@iogearbox.net>");
|
||||
MODULE_AUTHOR("Nikolay Aleksandrov <razor@blackwall.org>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS_RTNL_LINK(DRV_NAME);
|
38
include/net/netkit.h
Normal file
38
include/net/netkit.h
Normal file
@ -0,0 +1,38 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* Copyright (c) 2023 Isovalent */
|
||||
#ifndef __NET_NETKIT_H
|
||||
#define __NET_NETKIT_H
|
||||
|
||||
#include <linux/bpf.h>
|
||||
|
||||
#ifdef CONFIG_NETKIT
|
||||
int netkit_prog_attach(const union bpf_attr *attr, struct bpf_prog *prog);
|
||||
int netkit_link_attach(const union bpf_attr *attr, struct bpf_prog *prog);
|
||||
int netkit_prog_detach(const union bpf_attr *attr, struct bpf_prog *prog);
|
||||
int netkit_prog_query(const union bpf_attr *attr, union bpf_attr __user *uattr);
|
||||
#else
|
||||
static inline int netkit_prog_attach(const union bpf_attr *attr,
|
||||
struct bpf_prog *prog)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static inline int netkit_link_attach(const union bpf_attr *attr,
|
||||
struct bpf_prog *prog)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static inline int netkit_prog_detach(const union bpf_attr *attr,
|
||||
struct bpf_prog *prog)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static inline int netkit_prog_query(const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif /* CONFIG_NETKIT */
|
||||
#endif /* __NET_NETKIT_H */
|
@ -1052,6 +1052,8 @@ enum bpf_attach_type {
|
||||
BPF_CGROUP_UNIX_RECVMSG,
|
||||
BPF_CGROUP_UNIX_GETPEERNAME,
|
||||
BPF_CGROUP_UNIX_GETSOCKNAME,
|
||||
BPF_NETKIT_PRIMARY,
|
||||
BPF_NETKIT_PEER,
|
||||
__MAX_BPF_ATTACH_TYPE
|
||||
};
|
||||
|
||||
@ -1071,6 +1073,7 @@ enum bpf_link_type {
|
||||
BPF_LINK_TYPE_NETFILTER = 10,
|
||||
BPF_LINK_TYPE_TCX = 11,
|
||||
BPF_LINK_TYPE_UPROBE_MULTI = 12,
|
||||
BPF_LINK_TYPE_NETKIT = 13,
|
||||
MAX_BPF_LINK_TYPE,
|
||||
};
|
||||
|
||||
@ -1656,6 +1659,13 @@ union bpf_attr {
|
||||
__u32 flags;
|
||||
__u32 pid;
|
||||
} uprobe_multi;
|
||||
struct {
|
||||
union {
|
||||
__u32 relative_fd;
|
||||
__u32 relative_id;
|
||||
};
|
||||
__u64 expected_revision;
|
||||
} netkit;
|
||||
};
|
||||
} link_create;
|
||||
|
||||
@ -6576,6 +6586,10 @@ struct bpf_link_info {
|
||||
__u32 ifindex;
|
||||
__u32 attach_type;
|
||||
} tcx;
|
||||
struct {
|
||||
__u32 ifindex;
|
||||
__u32 attach_type;
|
||||
} netkit;
|
||||
};
|
||||
} __attribute__((aligned(8)));
|
||||
|
||||
|
@ -756,6 +756,30 @@ struct tunnel_msg {
|
||||
__u32 ifindex;
|
||||
};
|
||||
|
||||
/* netkit section */
|
||||
enum netkit_action {
|
||||
NETKIT_NEXT = -1,
|
||||
NETKIT_PASS = 0,
|
||||
NETKIT_DROP = 2,
|
||||
NETKIT_REDIRECT = 7,
|
||||
};
|
||||
|
||||
enum netkit_mode {
|
||||
NETKIT_L2,
|
||||
NETKIT_L3,
|
||||
};
|
||||
|
||||
enum {
|
||||
IFLA_NETKIT_UNSPEC,
|
||||
IFLA_NETKIT_PEER_INFO,
|
||||
IFLA_NETKIT_PRIMARY,
|
||||
IFLA_NETKIT_POLICY,
|
||||
IFLA_NETKIT_PEER_POLICY,
|
||||
IFLA_NETKIT_MODE,
|
||||
__IFLA_NETKIT_MAX,
|
||||
};
|
||||
#define IFLA_NETKIT_MAX (__IFLA_NETKIT_MAX - 1)
|
||||
|
||||
/* VXLAN section */
|
||||
|
||||
/* include statistics in the dump */
|
||||
|
@ -35,8 +35,9 @@
|
||||
#include <linux/rcupdate_trace.h>
|
||||
#include <linux/memcontrol.h>
|
||||
#include <linux/trace_events.h>
|
||||
#include <net/netfilter/nf_bpf_link.h>
|
||||
|
||||
#include <net/netfilter/nf_bpf_link.h>
|
||||
#include <net/netkit.h>
|
||||
#include <net/tcx.h>
|
||||
|
||||
#define IS_FD_ARRAY(map) ((map)->map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY || \
|
||||
@ -3730,6 +3731,8 @@ attach_type_to_prog_type(enum bpf_attach_type attach_type)
|
||||
return BPF_PROG_TYPE_LSM;
|
||||
case BPF_TCX_INGRESS:
|
||||
case BPF_TCX_EGRESS:
|
||||
case BPF_NETKIT_PRIMARY:
|
||||
case BPF_NETKIT_PEER:
|
||||
return BPF_PROG_TYPE_SCHED_CLS;
|
||||
default:
|
||||
return BPF_PROG_TYPE_UNSPEC;
|
||||
@ -3781,7 +3784,9 @@ static int bpf_prog_attach_check_attach_type(const struct bpf_prog *prog,
|
||||
return 0;
|
||||
case BPF_PROG_TYPE_SCHED_CLS:
|
||||
if (attach_type != BPF_TCX_INGRESS &&
|
||||
attach_type != BPF_TCX_EGRESS)
|
||||
attach_type != BPF_TCX_EGRESS &&
|
||||
attach_type != BPF_NETKIT_PRIMARY &&
|
||||
attach_type != BPF_NETKIT_PEER)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
default:
|
||||
@ -3864,7 +3869,11 @@ static int bpf_prog_attach(const union bpf_attr *attr)
|
||||
ret = cgroup_bpf_prog_attach(attr, ptype, prog);
|
||||
break;
|
||||
case BPF_PROG_TYPE_SCHED_CLS:
|
||||
ret = tcx_prog_attach(attr, prog);
|
||||
if (attr->attach_type == BPF_TCX_INGRESS ||
|
||||
attr->attach_type == BPF_TCX_EGRESS)
|
||||
ret = tcx_prog_attach(attr, prog);
|
||||
else
|
||||
ret = netkit_prog_attach(attr, prog);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
@ -3925,7 +3934,11 @@ static int bpf_prog_detach(const union bpf_attr *attr)
|
||||
ret = cgroup_bpf_prog_detach(attr, ptype);
|
||||
break;
|
||||
case BPF_PROG_TYPE_SCHED_CLS:
|
||||
ret = tcx_prog_detach(attr, prog);
|
||||
if (attr->attach_type == BPF_TCX_INGRESS ||
|
||||
attr->attach_type == BPF_TCX_EGRESS)
|
||||
ret = tcx_prog_detach(attr, prog);
|
||||
else
|
||||
ret = netkit_prog_detach(attr, prog);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
@ -3992,6 +4005,9 @@ static int bpf_prog_query(const union bpf_attr *attr,
|
||||
case BPF_TCX_INGRESS:
|
||||
case BPF_TCX_EGRESS:
|
||||
return tcx_prog_query(attr, uattr);
|
||||
case BPF_NETKIT_PRIMARY:
|
||||
case BPF_NETKIT_PEER:
|
||||
return netkit_prog_query(attr, uattr);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
@ -4973,7 +4989,11 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr)
|
||||
ret = bpf_xdp_link_attach(attr, prog);
|
||||
break;
|
||||
case BPF_PROG_TYPE_SCHED_CLS:
|
||||
ret = tcx_link_attach(attr, prog);
|
||||
if (attr->link_create.attach_type == BPF_TCX_INGRESS ||
|
||||
attr->link_create.attach_type == BPF_TCX_EGRESS)
|
||||
ret = tcx_link_attach(attr, prog);
|
||||
else
|
||||
ret = netkit_link_attach(attr, prog);
|
||||
break;
|
||||
case BPF_PROG_TYPE_NETFILTER:
|
||||
ret = bpf_nf_link_attach(attr, prog);
|
||||
|
@ -37,7 +37,7 @@ DESCRIPTION
|
||||
**bpftool net { show | list }** [ **dev** *NAME* ]
|
||||
List bpf program attachments in the kernel networking subsystem.
|
||||
|
||||
Currently, device driver xdp attachments, tcx and old-style tc
|
||||
Currently, device driver xdp attachments, tcx, netkit and old-style tc
|
||||
classifier/action attachments, flow_dissector as well as netfilter
|
||||
attachments are implemented, i.e., for
|
||||
program types **BPF_PROG_TYPE_XDP**, **BPF_PROG_TYPE_SCHED_CLS**,
|
||||
@ -52,11 +52,11 @@ DESCRIPTION
|
||||
bpf programs, users should consult other tools, e.g., iproute2.
|
||||
|
||||
The current output will start with all xdp program attachments, followed by
|
||||
all tcx, then tc class/qdisc bpf program attachments, then flow_dissector
|
||||
and finally netfilter programs. Both xdp programs and tcx/tc programs are
|
||||
all tcx, netkit, then tc class/qdisc bpf program attachments, then flow_dissector
|
||||
and finally netfilter programs. Both xdp programs and tcx/netkit/tc programs are
|
||||
ordered based on ifindex number. If multiple bpf programs attached
|
||||
to the same networking device through **tc**, the order will be first
|
||||
all bpf programs attached to tcx, then tc classes, then all bpf programs
|
||||
all bpf programs attached to tcx, netkit, then tc classes, then all bpf programs
|
||||
attached to non clsact qdiscs, and finally all bpf programs attached
|
||||
to root and clsact qdisc.
|
||||
|
||||
|
@ -451,6 +451,10 @@ static int show_link_close_json(int fd, struct bpf_link_info *info)
|
||||
show_link_ifindex_json(info->tcx.ifindex, json_wtr);
|
||||
show_link_attach_type_json(info->tcx.attach_type, json_wtr);
|
||||
break;
|
||||
case BPF_LINK_TYPE_NETKIT:
|
||||
show_link_ifindex_json(info->netkit.ifindex, json_wtr);
|
||||
show_link_attach_type_json(info->netkit.attach_type, json_wtr);
|
||||
break;
|
||||
case BPF_LINK_TYPE_XDP:
|
||||
show_link_ifindex_json(info->xdp.ifindex, json_wtr);
|
||||
break;
|
||||
@ -791,6 +795,11 @@ static int show_link_close_plain(int fd, struct bpf_link_info *info)
|
||||
show_link_ifindex_plain(info->tcx.ifindex);
|
||||
show_link_attach_type_plain(info->tcx.attach_type);
|
||||
break;
|
||||
case BPF_LINK_TYPE_NETKIT:
|
||||
printf("\n\t");
|
||||
show_link_ifindex_plain(info->netkit.ifindex);
|
||||
show_link_attach_type_plain(info->netkit.attach_type);
|
||||
break;
|
||||
case BPF_LINK_TYPE_XDP:
|
||||
printf("\n\t");
|
||||
show_link_ifindex_plain(info->xdp.ifindex);
|
||||
|
@ -79,6 +79,8 @@ static const char * const attach_type_strings[] = {
|
||||
static const char * const attach_loc_strings[] = {
|
||||
[BPF_TCX_INGRESS] = "tcx/ingress",
|
||||
[BPF_TCX_EGRESS] = "tcx/egress",
|
||||
[BPF_NETKIT_PRIMARY] = "netkit/primary",
|
||||
[BPF_NETKIT_PEER] = "netkit/peer",
|
||||
};
|
||||
|
||||
const size_t net_attach_type_size = ARRAY_SIZE(attach_type_strings);
|
||||
@ -506,6 +508,9 @@ static void show_dev_tc_bpf(struct ip_devname_ifindex *dev)
|
||||
{
|
||||
__show_dev_tc_bpf(dev, BPF_TCX_INGRESS);
|
||||
__show_dev_tc_bpf(dev, BPF_TCX_EGRESS);
|
||||
|
||||
__show_dev_tc_bpf(dev, BPF_NETKIT_PRIMARY);
|
||||
__show_dev_tc_bpf(dev, BPF_NETKIT_PEER);
|
||||
}
|
||||
|
||||
static int show_dev_tc_bpf_classic(int sock, unsigned int nl_pid,
|
||||
@ -926,7 +931,7 @@ static int do_help(int argc, char **argv)
|
||||
" ATTACH_TYPE := { xdp | xdpgeneric | xdpdrv | xdpoffload }\n"
|
||||
" " HELP_SPEC_OPTIONS " }\n"
|
||||
"\n"
|
||||
"Note: Only xdp, tcx, tc, flow_dissector and netfilter attachments\n"
|
||||
"Note: Only xdp, tcx, tc, netkit, flow_dissector and netfilter attachments\n"
|
||||
" are currently supported.\n"
|
||||
" For progs attached to cgroups, use \"bpftool cgroup\"\n"
|
||||
" to dump program attachments. For program types\n"
|
||||
|
@ -1052,6 +1052,8 @@ enum bpf_attach_type {
|
||||
BPF_CGROUP_UNIX_RECVMSG,
|
||||
BPF_CGROUP_UNIX_GETPEERNAME,
|
||||
BPF_CGROUP_UNIX_GETSOCKNAME,
|
||||
BPF_NETKIT_PRIMARY,
|
||||
BPF_NETKIT_PEER,
|
||||
__MAX_BPF_ATTACH_TYPE
|
||||
};
|
||||
|
||||
@ -1071,6 +1073,7 @@ enum bpf_link_type {
|
||||
BPF_LINK_TYPE_NETFILTER = 10,
|
||||
BPF_LINK_TYPE_TCX = 11,
|
||||
BPF_LINK_TYPE_UPROBE_MULTI = 12,
|
||||
BPF_LINK_TYPE_NETKIT = 13,
|
||||
MAX_BPF_LINK_TYPE,
|
||||
};
|
||||
|
||||
@ -1656,6 +1659,13 @@ union bpf_attr {
|
||||
__u32 flags;
|
||||
__u32 pid;
|
||||
} uprobe_multi;
|
||||
struct {
|
||||
union {
|
||||
__u32 relative_fd;
|
||||
__u32 relative_id;
|
||||
};
|
||||
__u64 expected_revision;
|
||||
} netkit;
|
||||
};
|
||||
} link_create;
|
||||
|
||||
@ -6576,6 +6586,10 @@ struct bpf_link_info {
|
||||
__u32 ifindex;
|
||||
__u32 attach_type;
|
||||
} tcx;
|
||||
struct {
|
||||
__u32 ifindex;
|
||||
__u32 attach_type;
|
||||
} netkit;
|
||||
};
|
||||
} __attribute__((aligned(8)));
|
||||
|
||||
|
@ -211,6 +211,9 @@ struct rtnl_link_stats {
|
||||
* @rx_nohandler: Number of packets received on the interface
|
||||
* but dropped by the networking stack because the device is
|
||||
* not designated to receive packets (e.g. backup link in a bond).
|
||||
*
|
||||
* @rx_otherhost_dropped: Number of packets dropped due to mismatch
|
||||
* in destination MAC address.
|
||||
*/
|
||||
struct rtnl_link_stats64 {
|
||||
__u64 rx_packets;
|
||||
@ -243,6 +246,23 @@ struct rtnl_link_stats64 {
|
||||
__u64 rx_compressed;
|
||||
__u64 tx_compressed;
|
||||
__u64 rx_nohandler;
|
||||
|
||||
__u64 rx_otherhost_dropped;
|
||||
};
|
||||
|
||||
/* Subset of link stats useful for in-HW collection. Meaning of the fields is as
|
||||
* for struct rtnl_link_stats64.
|
||||
*/
|
||||
struct rtnl_hw_stats64 {
|
||||
__u64 rx_packets;
|
||||
__u64 tx_packets;
|
||||
__u64 rx_bytes;
|
||||
__u64 tx_bytes;
|
||||
__u64 rx_errors;
|
||||
__u64 tx_errors;
|
||||
__u64 rx_dropped;
|
||||
__u64 tx_dropped;
|
||||
__u64 multicast;
|
||||
};
|
||||
|
||||
/* The struct should be in sync with struct ifmap */
|
||||
@ -350,7 +370,13 @@ enum {
|
||||
IFLA_GRO_MAX_SIZE,
|
||||
IFLA_TSO_MAX_SIZE,
|
||||
IFLA_TSO_MAX_SEGS,
|
||||
IFLA_ALLMULTI, /* Allmulti count: > 0 means acts ALLMULTI */
|
||||
|
||||
IFLA_DEVLINK_PORT,
|
||||
|
||||
IFLA_GSO_IPV4_MAX_SIZE,
|
||||
IFLA_GRO_IPV4_MAX_SIZE,
|
||||
IFLA_DPLL_PIN,
|
||||
__IFLA_MAX
|
||||
};
|
||||
|
||||
@ -539,6 +565,12 @@ enum {
|
||||
IFLA_BRPORT_MRP_IN_OPEN,
|
||||
IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT,
|
||||
IFLA_BRPORT_MCAST_EHT_HOSTS_CNT,
|
||||
IFLA_BRPORT_LOCKED,
|
||||
IFLA_BRPORT_MAB,
|
||||
IFLA_BRPORT_MCAST_N_GROUPS,
|
||||
IFLA_BRPORT_MCAST_MAX_GROUPS,
|
||||
IFLA_BRPORT_NEIGH_VLAN_SUPPRESS,
|
||||
IFLA_BRPORT_BACKUP_NHID,
|
||||
__IFLA_BRPORT_MAX
|
||||
};
|
||||
#define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1)
|
||||
@ -716,7 +748,79 @@ enum ipvlan_mode {
|
||||
#define IPVLAN_F_PRIVATE 0x01
|
||||
#define IPVLAN_F_VEPA 0x02
|
||||
|
||||
/* Tunnel RTM header */
|
||||
struct tunnel_msg {
|
||||
__u8 family;
|
||||
__u8 flags;
|
||||
__u16 reserved2;
|
||||
__u32 ifindex;
|
||||
};
|
||||
|
||||
/* netkit section */
|
||||
enum netkit_action {
|
||||
NETKIT_NEXT = -1,
|
||||
NETKIT_PASS = 0,
|
||||
NETKIT_DROP = 2,
|
||||
NETKIT_REDIRECT = 7,
|
||||
};
|
||||
|
||||
enum netkit_mode {
|
||||
NETKIT_L2,
|
||||
NETKIT_L3,
|
||||
};
|
||||
|
||||
enum {
|
||||
IFLA_NETKIT_UNSPEC,
|
||||
IFLA_NETKIT_PEER_INFO,
|
||||
IFLA_NETKIT_PRIMARY,
|
||||
IFLA_NETKIT_POLICY,
|
||||
IFLA_NETKIT_PEER_POLICY,
|
||||
IFLA_NETKIT_MODE,
|
||||
__IFLA_NETKIT_MAX,
|
||||
};
|
||||
#define IFLA_NETKIT_MAX (__IFLA_NETKIT_MAX - 1)
|
||||
|
||||
/* VXLAN section */
|
||||
|
||||
/* include statistics in the dump */
|
||||
#define TUNNEL_MSG_FLAG_STATS 0x01
|
||||
|
||||
#define TUNNEL_MSG_VALID_USER_FLAGS TUNNEL_MSG_FLAG_STATS
|
||||
|
||||
/* Embedded inside VXLAN_VNIFILTER_ENTRY_STATS */
|
||||
enum {
|
||||
VNIFILTER_ENTRY_STATS_UNSPEC,
|
||||
VNIFILTER_ENTRY_STATS_RX_BYTES,
|
||||
VNIFILTER_ENTRY_STATS_RX_PKTS,
|
||||
VNIFILTER_ENTRY_STATS_RX_DROPS,
|
||||
VNIFILTER_ENTRY_STATS_RX_ERRORS,
|
||||
VNIFILTER_ENTRY_STATS_TX_BYTES,
|
||||
VNIFILTER_ENTRY_STATS_TX_PKTS,
|
||||
VNIFILTER_ENTRY_STATS_TX_DROPS,
|
||||
VNIFILTER_ENTRY_STATS_TX_ERRORS,
|
||||
VNIFILTER_ENTRY_STATS_PAD,
|
||||
__VNIFILTER_ENTRY_STATS_MAX
|
||||
};
|
||||
#define VNIFILTER_ENTRY_STATS_MAX (__VNIFILTER_ENTRY_STATS_MAX - 1)
|
||||
|
||||
enum {
|
||||
VXLAN_VNIFILTER_ENTRY_UNSPEC,
|
||||
VXLAN_VNIFILTER_ENTRY_START,
|
||||
VXLAN_VNIFILTER_ENTRY_END,
|
||||
VXLAN_VNIFILTER_ENTRY_GROUP,
|
||||
VXLAN_VNIFILTER_ENTRY_GROUP6,
|
||||
VXLAN_VNIFILTER_ENTRY_STATS,
|
||||
__VXLAN_VNIFILTER_ENTRY_MAX
|
||||
};
|
||||
#define VXLAN_VNIFILTER_ENTRY_MAX (__VXLAN_VNIFILTER_ENTRY_MAX - 1)
|
||||
|
||||
enum {
|
||||
VXLAN_VNIFILTER_UNSPEC,
|
||||
VXLAN_VNIFILTER_ENTRY,
|
||||
__VXLAN_VNIFILTER_MAX
|
||||
};
|
||||
#define VXLAN_VNIFILTER_MAX (__VXLAN_VNIFILTER_MAX - 1)
|
||||
|
||||
enum {
|
||||
IFLA_VXLAN_UNSPEC,
|
||||
IFLA_VXLAN_ID,
|
||||
@ -748,6 +852,8 @@ enum {
|
||||
IFLA_VXLAN_GPE,
|
||||
IFLA_VXLAN_TTL_INHERIT,
|
||||
IFLA_VXLAN_DF,
|
||||
IFLA_VXLAN_VNIFILTER, /* only applicable with COLLECT_METADATA mode */
|
||||
IFLA_VXLAN_LOCALBYPASS,
|
||||
__IFLA_VXLAN_MAX
|
||||
};
|
||||
#define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1)
|
||||
@ -781,6 +887,7 @@ enum {
|
||||
IFLA_GENEVE_LABEL,
|
||||
IFLA_GENEVE_TTL_INHERIT,
|
||||
IFLA_GENEVE_DF,
|
||||
IFLA_GENEVE_INNER_PROTO_INHERIT,
|
||||
__IFLA_GENEVE_MAX
|
||||
};
|
||||
#define IFLA_GENEVE_MAX (__IFLA_GENEVE_MAX - 1)
|
||||
@ -826,6 +933,8 @@ enum {
|
||||
IFLA_GTP_FD1,
|
||||
IFLA_GTP_PDP_HASHSIZE,
|
||||
IFLA_GTP_ROLE,
|
||||
IFLA_GTP_CREATE_SOCKETS,
|
||||
IFLA_GTP_RESTART_COUNT,
|
||||
__IFLA_GTP_MAX,
|
||||
};
|
||||
#define IFLA_GTP_MAX (__IFLA_GTP_MAX - 1)
|
||||
@ -1162,6 +1271,17 @@ enum {
|
||||
|
||||
#define IFLA_STATS_FILTER_BIT(ATTR) (1 << (ATTR - 1))
|
||||
|
||||
enum {
|
||||
IFLA_STATS_GETSET_UNSPEC,
|
||||
IFLA_STATS_GET_FILTERS, /* Nest of IFLA_STATS_LINK_xxx, each a u32 with
|
||||
* a filter mask for the corresponding group.
|
||||
*/
|
||||
IFLA_STATS_SET_OFFLOAD_XSTATS_L3_STATS, /* 0 or 1 as u8 */
|
||||
__IFLA_STATS_GETSET_MAX,
|
||||
};
|
||||
|
||||
#define IFLA_STATS_GETSET_MAX (__IFLA_STATS_GETSET_MAX - 1)
|
||||
|
||||
/* These are embedded into IFLA_STATS_LINK_XSTATS:
|
||||
* [IFLA_STATS_LINK_XSTATS]
|
||||
* -> [LINK_XSTATS_TYPE_xxx]
|
||||
@ -1179,10 +1299,21 @@ enum {
|
||||
enum {
|
||||
IFLA_OFFLOAD_XSTATS_UNSPEC,
|
||||
IFLA_OFFLOAD_XSTATS_CPU_HIT, /* struct rtnl_link_stats64 */
|
||||
IFLA_OFFLOAD_XSTATS_HW_S_INFO, /* HW stats info. A nest */
|
||||
IFLA_OFFLOAD_XSTATS_L3_STATS, /* struct rtnl_hw_stats64 */
|
||||
__IFLA_OFFLOAD_XSTATS_MAX
|
||||
};
|
||||
#define IFLA_OFFLOAD_XSTATS_MAX (__IFLA_OFFLOAD_XSTATS_MAX - 1)
|
||||
|
||||
enum {
|
||||
IFLA_OFFLOAD_XSTATS_HW_S_INFO_UNSPEC,
|
||||
IFLA_OFFLOAD_XSTATS_HW_S_INFO_REQUEST, /* u8 */
|
||||
IFLA_OFFLOAD_XSTATS_HW_S_INFO_USED, /* u8 */
|
||||
__IFLA_OFFLOAD_XSTATS_HW_S_INFO_MAX,
|
||||
};
|
||||
#define IFLA_OFFLOAD_XSTATS_HW_S_INFO_MAX \
|
||||
(__IFLA_OFFLOAD_XSTATS_HW_S_INFO_MAX - 1)
|
||||
|
||||
/* XDP section */
|
||||
|
||||
#define XDP_FLAGS_UPDATE_IF_NOEXIST (1U << 0)
|
||||
@ -1281,4 +1412,14 @@ enum {
|
||||
|
||||
#define IFLA_MCTP_MAX (__IFLA_MCTP_MAX - 1)
|
||||
|
||||
/* DSA section */
|
||||
|
||||
enum {
|
||||
IFLA_DSA_UNSPEC,
|
||||
IFLA_DSA_MASTER,
|
||||
__IFLA_DSA_MAX,
|
||||
};
|
||||
|
||||
#define IFLA_DSA_MAX (__IFLA_DSA_MAX - 1)
|
||||
|
||||
#endif /* _UAPI_LINUX_IF_LINK_H */
|
||||
|
@ -810,6 +810,22 @@ int bpf_link_create(int prog_fd, int target_fd,
|
||||
if (!OPTS_ZEROED(opts, tcx))
|
||||
return libbpf_err(-EINVAL);
|
||||
break;
|
||||
case BPF_NETKIT_PRIMARY:
|
||||
case BPF_NETKIT_PEER:
|
||||
relative_fd = OPTS_GET(opts, netkit.relative_fd, 0);
|
||||
relative_id = OPTS_GET(opts, netkit.relative_id, 0);
|
||||
if (relative_fd && relative_id)
|
||||
return libbpf_err(-EINVAL);
|
||||
if (relative_id) {
|
||||
attr.link_create.netkit.relative_id = relative_id;
|
||||
attr.link_create.flags |= BPF_F_ID;
|
||||
} else {
|
||||
attr.link_create.netkit.relative_fd = relative_fd;
|
||||
}
|
||||
attr.link_create.netkit.expected_revision = OPTS_GET(opts, netkit.expected_revision, 0);
|
||||
if (!OPTS_ZEROED(opts, netkit))
|
||||
return libbpf_err(-EINVAL);
|
||||
break;
|
||||
default:
|
||||
if (!OPTS_ZEROED(opts, flags))
|
||||
return libbpf_err(-EINVAL);
|
||||
|
@ -415,6 +415,11 @@ struct bpf_link_create_opts {
|
||||
__u32 relative_id;
|
||||
__u64 expected_revision;
|
||||
} tcx;
|
||||
struct {
|
||||
__u32 relative_fd;
|
||||
__u32 relative_id;
|
||||
__u64 expected_revision;
|
||||
} netkit;
|
||||
};
|
||||
size_t :0;
|
||||
};
|
||||
|
@ -126,6 +126,8 @@ static const char * const attach_type_name[] = {
|
||||
[BPF_TCX_INGRESS] = "tcx_ingress",
|
||||
[BPF_TCX_EGRESS] = "tcx_egress",
|
||||
[BPF_TRACE_UPROBE_MULTI] = "trace_uprobe_multi",
|
||||
[BPF_NETKIT_PRIMARY] = "netkit_primary",
|
||||
[BPF_NETKIT_PEER] = "netkit_peer",
|
||||
};
|
||||
|
||||
static const char * const link_type_name[] = {
|
||||
@ -142,6 +144,7 @@ static const char * const link_type_name[] = {
|
||||
[BPF_LINK_TYPE_NETFILTER] = "netfilter",
|
||||
[BPF_LINK_TYPE_TCX] = "tcx",
|
||||
[BPF_LINK_TYPE_UPROBE_MULTI] = "uprobe_multi",
|
||||
[BPF_LINK_TYPE_NETKIT] = "netkit",
|
||||
};
|
||||
|
||||
static const char * const map_type_name[] = {
|
||||
@ -8915,6 +8918,8 @@ static const struct bpf_sec_def section_defs[] = {
|
||||
SEC_DEF("tc", SCHED_CLS, 0, SEC_NONE), /* deprecated / legacy, use tcx */
|
||||
SEC_DEF("classifier", SCHED_CLS, 0, SEC_NONE), /* deprecated / legacy, use tcx */
|
||||
SEC_DEF("action", SCHED_ACT, 0, SEC_NONE), /* deprecated / legacy, use tcx */
|
||||
SEC_DEF("netkit/primary", SCHED_CLS, BPF_NETKIT_PRIMARY, SEC_NONE),
|
||||
SEC_DEF("netkit/peer", SCHED_CLS, BPF_NETKIT_PEER, SEC_NONE),
|
||||
SEC_DEF("tracepoint+", TRACEPOINT, 0, SEC_NONE, attach_tp),
|
||||
SEC_DEF("tp+", TRACEPOINT, 0, SEC_NONE, attach_tp),
|
||||
SEC_DEF("raw_tracepoint+", RAW_TRACEPOINT, 0, SEC_NONE, attach_raw_tp),
|
||||
@ -12126,6 +12131,40 @@ bpf_program__attach_tcx(const struct bpf_program *prog, int ifindex,
|
||||
return bpf_program_attach_fd(prog, ifindex, "tcx", &link_create_opts);
|
||||
}
|
||||
|
||||
struct bpf_link *
|
||||
bpf_program__attach_netkit(const struct bpf_program *prog, int ifindex,
|
||||
const struct bpf_netkit_opts *opts)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_link_create_opts, link_create_opts);
|
||||
__u32 relative_id;
|
||||
int relative_fd;
|
||||
|
||||
if (!OPTS_VALID(opts, bpf_netkit_opts))
|
||||
return libbpf_err_ptr(-EINVAL);
|
||||
|
||||
relative_id = OPTS_GET(opts, relative_id, 0);
|
||||
relative_fd = OPTS_GET(opts, relative_fd, 0);
|
||||
|
||||
/* validate we don't have unexpected combinations of non-zero fields */
|
||||
if (!ifindex) {
|
||||
pr_warn("prog '%s': target netdevice ifindex cannot be zero\n",
|
||||
prog->name);
|
||||
return libbpf_err_ptr(-EINVAL);
|
||||
}
|
||||
if (relative_fd && relative_id) {
|
||||
pr_warn("prog '%s': relative_fd and relative_id cannot be set at the same time\n",
|
||||
prog->name);
|
||||
return libbpf_err_ptr(-EINVAL);
|
||||
}
|
||||
|
||||
link_create_opts.netkit.expected_revision = OPTS_GET(opts, expected_revision, 0);
|
||||
link_create_opts.netkit.relative_fd = relative_fd;
|
||||
link_create_opts.netkit.relative_id = relative_id;
|
||||
link_create_opts.flags = OPTS_GET(opts, flags, 0);
|
||||
|
||||
return bpf_program_attach_fd(prog, ifindex, "netkit", &link_create_opts);
|
||||
}
|
||||
|
||||
struct bpf_link *bpf_program__attach_freplace(const struct bpf_program *prog,
|
||||
int target_fd,
|
||||
const char *attach_func_name)
|
||||
|
@ -800,6 +800,21 @@ LIBBPF_API struct bpf_link *
|
||||
bpf_program__attach_tcx(const struct bpf_program *prog, int ifindex,
|
||||
const struct bpf_tcx_opts *opts);
|
||||
|
||||
struct bpf_netkit_opts {
|
||||
/* size of this struct, for forward/backward compatibility */
|
||||
size_t sz;
|
||||
__u32 flags;
|
||||
__u32 relative_fd;
|
||||
__u32 relative_id;
|
||||
__u64 expected_revision;
|
||||
size_t :0;
|
||||
};
|
||||
#define bpf_netkit_opts__last_field expected_revision
|
||||
|
||||
LIBBPF_API struct bpf_link *
|
||||
bpf_program__attach_netkit(const struct bpf_program *prog, int ifindex,
|
||||
const struct bpf_netkit_opts *opts);
|
||||
|
||||
struct bpf_map;
|
||||
|
||||
LIBBPF_API struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map);
|
||||
|
@ -398,6 +398,7 @@ LIBBPF_1.3.0 {
|
||||
bpf_object__unpin;
|
||||
bpf_prog_detach_opts;
|
||||
bpf_program__attach_netfilter;
|
||||
bpf_program__attach_netkit;
|
||||
bpf_program__attach_tcx;
|
||||
bpf_program__attach_uprobe_multi;
|
||||
ring__avail_data_size;
|
||||
|
@ -585,11 +585,20 @@ endef
|
||||
# Define test_progs test runner.
|
||||
TRUNNER_TESTS_DIR := prog_tests
|
||||
TRUNNER_BPF_PROGS_DIR := progs
|
||||
TRUNNER_EXTRA_SOURCES := test_progs.c cgroup_helpers.c trace_helpers.c \
|
||||
network_helpers.c testing_helpers.c \
|
||||
btf_helpers.c flow_dissector_load.h \
|
||||
cap_helpers.c test_loader.c xsk.c disasm.c \
|
||||
json_writer.c unpriv_helpers.c \
|
||||
TRUNNER_EXTRA_SOURCES := test_progs.c \
|
||||
cgroup_helpers.c \
|
||||
trace_helpers.c \
|
||||
network_helpers.c \
|
||||
testing_helpers.c \
|
||||
btf_helpers.c \
|
||||
cap_helpers.c \
|
||||
unpriv_helpers.c \
|
||||
netlink_helpers.c \
|
||||
test_loader.c \
|
||||
xsk.c \
|
||||
disasm.c \
|
||||
json_writer.c \
|
||||
flow_dissector_load.h \
|
||||
ip_check_defrag_frags.h
|
||||
TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read $(OUTPUT)/bpf_testmod.ko \
|
||||
$(OUTPUT)/liburandom_read.so \
|
||||
|
@ -71,6 +71,7 @@ CONFIG_NETFILTER_SYNPROXY=y
|
||||
CONFIG_NETFILTER_XT_CONNMARK=y
|
||||
CONFIG_NETFILTER_XT_MATCH_STATE=y
|
||||
CONFIG_NETFILTER_XT_TARGET_CT=y
|
||||
CONFIG_NETKIT=y
|
||||
CONFIG_NF_CONNTRACK=y
|
||||
CONFIG_NF_CONNTRACK_MARK=y
|
||||
CONFIG_NF_DEFRAG_IPV4=y
|
||||
|
358
tools/testing/selftests/bpf/netlink_helpers.c
Normal file
358
tools/testing/selftests/bpf/netlink_helpers.c
Normal file
@ -0,0 +1,358 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/* Taken & modified from iproute2's libnetlink.c
|
||||
* Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "netlink_helpers.h"
|
||||
|
||||
static int rcvbuf = 1024 * 1024;
|
||||
|
||||
void rtnl_close(struct rtnl_handle *rth)
|
||||
{
|
||||
if (rth->fd >= 0) {
|
||||
close(rth->fd);
|
||||
rth->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions,
|
||||
int protocol)
|
||||
{
|
||||
socklen_t addr_len;
|
||||
int sndbuf = 32768;
|
||||
int one = 1;
|
||||
|
||||
memset(rth, 0, sizeof(*rth));
|
||||
rth->proto = protocol;
|
||||
rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
|
||||
if (rth->fd < 0) {
|
||||
perror("Cannot open netlink socket");
|
||||
return -1;
|
||||
}
|
||||
if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF,
|
||||
&sndbuf, sizeof(sndbuf)) < 0) {
|
||||
perror("SO_SNDBUF");
|
||||
goto err;
|
||||
}
|
||||
if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF,
|
||||
&rcvbuf, sizeof(rcvbuf)) < 0) {
|
||||
perror("SO_RCVBUF");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Older kernels may no support extended ACK reporting */
|
||||
setsockopt(rth->fd, SOL_NETLINK, NETLINK_EXT_ACK,
|
||||
&one, sizeof(one));
|
||||
|
||||
memset(&rth->local, 0, sizeof(rth->local));
|
||||
rth->local.nl_family = AF_NETLINK;
|
||||
rth->local.nl_groups = subscriptions;
|
||||
|
||||
if (bind(rth->fd, (struct sockaddr *)&rth->local,
|
||||
sizeof(rth->local)) < 0) {
|
||||
perror("Cannot bind netlink socket");
|
||||
goto err;
|
||||
}
|
||||
addr_len = sizeof(rth->local);
|
||||
if (getsockname(rth->fd, (struct sockaddr *)&rth->local,
|
||||
&addr_len) < 0) {
|
||||
perror("Cannot getsockname");
|
||||
goto err;
|
||||
}
|
||||
if (addr_len != sizeof(rth->local)) {
|
||||
fprintf(stderr, "Wrong address length %d\n", addr_len);
|
||||
goto err;
|
||||
}
|
||||
if (rth->local.nl_family != AF_NETLINK) {
|
||||
fprintf(stderr, "Wrong address family %d\n",
|
||||
rth->local.nl_family);
|
||||
goto err;
|
||||
}
|
||||
rth->seq = time(NULL);
|
||||
return 0;
|
||||
err:
|
||||
rtnl_close(rth);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rtnl_open(struct rtnl_handle *rth, unsigned int subscriptions)
|
||||
{
|
||||
return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);
|
||||
}
|
||||
|
||||
static int __rtnl_recvmsg(int fd, struct msghdr *msg, int flags)
|
||||
{
|
||||
int len;
|
||||
|
||||
do {
|
||||
len = recvmsg(fd, msg, flags);
|
||||
} while (len < 0 && (errno == EINTR || errno == EAGAIN));
|
||||
if (len < 0) {
|
||||
fprintf(stderr, "netlink receive error %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
return -errno;
|
||||
}
|
||||
if (len == 0) {
|
||||
fprintf(stderr, "EOF on netlink\n");
|
||||
return -ENODATA;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int rtnl_recvmsg(int fd, struct msghdr *msg, char **answer)
|
||||
{
|
||||
struct iovec *iov = msg->msg_iov;
|
||||
char *buf;
|
||||
int len;
|
||||
|
||||
iov->iov_base = NULL;
|
||||
iov->iov_len = 0;
|
||||
|
||||
len = __rtnl_recvmsg(fd, msg, MSG_PEEK | MSG_TRUNC);
|
||||
if (len < 0)
|
||||
return len;
|
||||
if (len < 32768)
|
||||
len = 32768;
|
||||
buf = malloc(len);
|
||||
if (!buf) {
|
||||
fprintf(stderr, "malloc error: not enough buffer\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
iov->iov_base = buf;
|
||||
iov->iov_len = len;
|
||||
len = __rtnl_recvmsg(fd, msg, 0);
|
||||
if (len < 0) {
|
||||
free(buf);
|
||||
return len;
|
||||
}
|
||||
if (answer)
|
||||
*answer = buf;
|
||||
else
|
||||
free(buf);
|
||||
return len;
|
||||
}
|
||||
|
||||
static void rtnl_talk_error(struct nlmsghdr *h, struct nlmsgerr *err,
|
||||
nl_ext_ack_fn_t errfn)
|
||||
{
|
||||
fprintf(stderr, "RTNETLINK answers: %s\n",
|
||||
strerror(-err->error));
|
||||
}
|
||||
|
||||
static int __rtnl_talk_iov(struct rtnl_handle *rtnl, struct iovec *iov,
|
||||
size_t iovlen, struct nlmsghdr **answer,
|
||||
bool show_rtnl_err, nl_ext_ack_fn_t errfn)
|
||||
{
|
||||
struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
|
||||
struct iovec riov;
|
||||
struct msghdr msg = {
|
||||
.msg_name = &nladdr,
|
||||
.msg_namelen = sizeof(nladdr),
|
||||
.msg_iov = iov,
|
||||
.msg_iovlen = iovlen,
|
||||
};
|
||||
unsigned int seq = 0;
|
||||
struct nlmsghdr *h;
|
||||
int i, status;
|
||||
char *buf;
|
||||
|
||||
for (i = 0; i < iovlen; i++) {
|
||||
h = iov[i].iov_base;
|
||||
h->nlmsg_seq = seq = ++rtnl->seq;
|
||||
if (answer == NULL)
|
||||
h->nlmsg_flags |= NLM_F_ACK;
|
||||
}
|
||||
status = sendmsg(rtnl->fd, &msg, 0);
|
||||
if (status < 0) {
|
||||
perror("Cannot talk to rtnetlink");
|
||||
return -1;
|
||||
}
|
||||
/* change msg to use the response iov */
|
||||
msg.msg_iov = &riov;
|
||||
msg.msg_iovlen = 1;
|
||||
i = 0;
|
||||
while (1) {
|
||||
next:
|
||||
status = rtnl_recvmsg(rtnl->fd, &msg, &buf);
|
||||
++i;
|
||||
if (status < 0)
|
||||
return status;
|
||||
if (msg.msg_namelen != sizeof(nladdr)) {
|
||||
fprintf(stderr,
|
||||
"Sender address length == %d!\n",
|
||||
msg.msg_namelen);
|
||||
exit(1);
|
||||
}
|
||||
for (h = (struct nlmsghdr *)buf; status >= sizeof(*h); ) {
|
||||
int len = h->nlmsg_len;
|
||||
int l = len - sizeof(*h);
|
||||
|
||||
if (l < 0 || len > status) {
|
||||
if (msg.msg_flags & MSG_TRUNC) {
|
||||
fprintf(stderr, "Truncated message!\n");
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr,
|
||||
"Malformed message: len=%d!\n",
|
||||
len);
|
||||
exit(1);
|
||||
}
|
||||
if (nladdr.nl_pid != 0 ||
|
||||
h->nlmsg_pid != rtnl->local.nl_pid ||
|
||||
h->nlmsg_seq > seq || h->nlmsg_seq < seq - iovlen) {
|
||||
/* Don't forget to skip that message. */
|
||||
status -= NLMSG_ALIGN(len);
|
||||
h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
|
||||
continue;
|
||||
}
|
||||
if (h->nlmsg_type == NLMSG_ERROR) {
|
||||
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h);
|
||||
int error = err->error;
|
||||
|
||||
if (l < sizeof(struct nlmsgerr)) {
|
||||
fprintf(stderr, "ERROR truncated\n");
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
if (error) {
|
||||
errno = -error;
|
||||
if (rtnl->proto != NETLINK_SOCK_DIAG &&
|
||||
show_rtnl_err)
|
||||
rtnl_talk_error(h, err, errfn);
|
||||
}
|
||||
if (i < iovlen) {
|
||||
free(buf);
|
||||
goto next;
|
||||
}
|
||||
if (error) {
|
||||
free(buf);
|
||||
return -i;
|
||||
}
|
||||
if (answer)
|
||||
*answer = (struct nlmsghdr *)buf;
|
||||
else
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
if (answer) {
|
||||
*answer = (struct nlmsghdr *)buf;
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "Unexpected reply!\n");
|
||||
status -= NLMSG_ALIGN(len);
|
||||
h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
|
||||
}
|
||||
free(buf);
|
||||
if (msg.msg_flags & MSG_TRUNC) {
|
||||
fprintf(stderr, "Message truncated!\n");
|
||||
continue;
|
||||
}
|
||||
if (status) {
|
||||
fprintf(stderr, "Remnant of size %d!\n", status);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int __rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
|
||||
struct nlmsghdr **answer, bool show_rtnl_err,
|
||||
nl_ext_ack_fn_t errfn)
|
||||
{
|
||||
struct iovec iov = {
|
||||
.iov_base = n,
|
||||
.iov_len = n->nlmsg_len,
|
||||
};
|
||||
|
||||
return __rtnl_talk_iov(rtnl, &iov, 1, answer, show_rtnl_err, errfn);
|
||||
}
|
||||
|
||||
int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
|
||||
struct nlmsghdr **answer)
|
||||
{
|
||||
return __rtnl_talk(rtnl, n, answer, true, NULL);
|
||||
}
|
||||
|
||||
int addattr(struct nlmsghdr *n, int maxlen, int type)
|
||||
{
|
||||
return addattr_l(n, maxlen, type, NULL, 0);
|
||||
}
|
||||
|
||||
int addattr8(struct nlmsghdr *n, int maxlen, int type, __u8 data)
|
||||
{
|
||||
return addattr_l(n, maxlen, type, &data, sizeof(__u8));
|
||||
}
|
||||
|
||||
int addattr16(struct nlmsghdr *n, int maxlen, int type, __u16 data)
|
||||
{
|
||||
return addattr_l(n, maxlen, type, &data, sizeof(__u16));
|
||||
}
|
||||
|
||||
int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data)
|
||||
{
|
||||
return addattr_l(n, maxlen, type, &data, sizeof(__u32));
|
||||
}
|
||||
|
||||
int addattr64(struct nlmsghdr *n, int maxlen, int type, __u64 data)
|
||||
{
|
||||
return addattr_l(n, maxlen, type, &data, sizeof(__u64));
|
||||
}
|
||||
|
||||
int addattrstrz(struct nlmsghdr *n, int maxlen, int type, const char *str)
|
||||
{
|
||||
return addattr_l(n, maxlen, type, str, strlen(str)+1);
|
||||
}
|
||||
|
||||
int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data,
|
||||
int alen)
|
||||
{
|
||||
int len = RTA_LENGTH(alen);
|
||||
struct rtattr *rta;
|
||||
|
||||
if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) {
|
||||
fprintf(stderr, "%s: Message exceeded bound of %d\n",
|
||||
__func__, maxlen);
|
||||
return -1;
|
||||
}
|
||||
rta = NLMSG_TAIL(n);
|
||||
rta->rta_type = type;
|
||||
rta->rta_len = len;
|
||||
if (alen)
|
||||
memcpy(RTA_DATA(rta), data, alen);
|
||||
n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len)
|
||||
{
|
||||
if (NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len) > maxlen) {
|
||||
fprintf(stderr, "%s: Message exceeded bound of %d\n",
|
||||
__func__, maxlen);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(NLMSG_TAIL(n), data, len);
|
||||
memset((void *) NLMSG_TAIL(n) + len, 0, NLMSG_ALIGN(len) - len);
|
||||
n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type)
|
||||
{
|
||||
struct rtattr *nest = NLMSG_TAIL(n);
|
||||
|
||||
addattr_l(n, maxlen, type, NULL, 0);
|
||||
return nest;
|
||||
}
|
||||
|
||||
int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest)
|
||||
{
|
||||
nest->rta_len = (void *)NLMSG_TAIL(n) - (void *)nest;
|
||||
return n->nlmsg_len;
|
||||
}
|
46
tools/testing/selftests/bpf/netlink_helpers.h
Normal file
46
tools/testing/selftests/bpf/netlink_helpers.h
Normal file
@ -0,0 +1,46 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#ifndef NETLINK_HELPERS_H
|
||||
#define NETLINK_HELPERS_H
|
||||
|
||||
#include <string.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
|
||||
struct rtnl_handle {
|
||||
int fd;
|
||||
struct sockaddr_nl local;
|
||||
struct sockaddr_nl peer;
|
||||
__u32 seq;
|
||||
__u32 dump;
|
||||
int proto;
|
||||
FILE *dump_fp;
|
||||
#define RTNL_HANDLE_F_LISTEN_ALL_NSID 0x01
|
||||
#define RTNL_HANDLE_F_SUPPRESS_NLERR 0x02
|
||||
#define RTNL_HANDLE_F_STRICT_CHK 0x04
|
||||
int flags;
|
||||
};
|
||||
|
||||
#define NLMSG_TAIL(nmsg) \
|
||||
((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
|
||||
|
||||
typedef int (*nl_ext_ack_fn_t)(const char *errmsg, uint32_t off,
|
||||
const struct nlmsghdr *inner_nlh);
|
||||
|
||||
int rtnl_open(struct rtnl_handle *rth, unsigned int subscriptions)
|
||||
__attribute__((warn_unused_result));
|
||||
void rtnl_close(struct rtnl_handle *rth);
|
||||
int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
|
||||
struct nlmsghdr **answer)
|
||||
__attribute__((warn_unused_result));
|
||||
|
||||
int addattr(struct nlmsghdr *n, int maxlen, int type);
|
||||
int addattr8(struct nlmsghdr *n, int maxlen, int type, __u8 data);
|
||||
int addattr16(struct nlmsghdr *n, int maxlen, int type, __u16 data);
|
||||
int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data);
|
||||
int addattr64(struct nlmsghdr *n, int maxlen, int type, __u64 data);
|
||||
int addattrstrz(struct nlmsghdr *n, int maxlen, int type, const char *data);
|
||||
int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen);
|
||||
int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len);
|
||||
struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type);
|
||||
int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest);
|
||||
#endif /* NETLINK_HELPERS_H */
|
@ -4,6 +4,10 @@
|
||||
#define TC_HELPERS
|
||||
#include <test_progs.h>
|
||||
|
||||
#ifndef loopback
|
||||
# define loopback 1
|
||||
#endif
|
||||
|
||||
static inline __u32 id_from_prog_fd(int fd)
|
||||
{
|
||||
struct bpf_prog_info prog_info = {};
|
||||
|
687
tools/testing/selftests/bpf/prog_tests/tc_netkit.c
Normal file
687
tools/testing/selftests/bpf/prog_tests/tc_netkit.c
Normal file
@ -0,0 +1,687 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2023 Isovalent */
|
||||
#include <uapi/linux/if_link.h>
|
||||
#include <net/if.h>
|
||||
#include <test_progs.h>
|
||||
|
||||
#define netkit_peer "nk0"
|
||||
#define netkit_name "nk1"
|
||||
|
||||
#define ping_addr_neigh 0x0a000002 /* 10.0.0.2 */
|
||||
#define ping_addr_noneigh 0x0a000003 /* 10.0.0.3 */
|
||||
|
||||
#include "test_tc_link.skel.h"
|
||||
#include "netlink_helpers.h"
|
||||
#include "tc_helpers.h"
|
||||
|
||||
#define ICMP_ECHO 8
|
||||
|
||||
struct icmphdr {
|
||||
__u8 type;
|
||||
__u8 code;
|
||||
__sum16 checksum;
|
||||
struct {
|
||||
__be16 id;
|
||||
__be16 sequence;
|
||||
} echo;
|
||||
};
|
||||
|
||||
struct iplink_req {
|
||||
struct nlmsghdr n;
|
||||
struct ifinfomsg i;
|
||||
char buf[1024];
|
||||
};
|
||||
|
||||
static int create_netkit(int mode, int policy, int peer_policy, int *ifindex,
|
||||
bool same_netns)
|
||||
{
|
||||
struct rtnl_handle rth = { .fd = -1 };
|
||||
struct iplink_req req = {};
|
||||
struct rtattr *linkinfo, *data;
|
||||
const char *type = "netkit";
|
||||
int err;
|
||||
|
||||
err = rtnl_open(&rth, 0);
|
||||
if (!ASSERT_OK(err, "open_rtnetlink"))
|
||||
return err;
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
|
||||
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
|
||||
req.n.nlmsg_type = RTM_NEWLINK;
|
||||
req.i.ifi_family = AF_UNSPEC;
|
||||
|
||||
addattr_l(&req.n, sizeof(req), IFLA_IFNAME, netkit_name,
|
||||
strlen(netkit_name));
|
||||
linkinfo = addattr_nest(&req.n, sizeof(req), IFLA_LINKINFO);
|
||||
addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, type, strlen(type));
|
||||
data = addattr_nest(&req.n, sizeof(req), IFLA_INFO_DATA);
|
||||
addattr32(&req.n, sizeof(req), IFLA_NETKIT_POLICY, policy);
|
||||
addattr32(&req.n, sizeof(req), IFLA_NETKIT_PEER_POLICY, peer_policy);
|
||||
addattr32(&req.n, sizeof(req), IFLA_NETKIT_MODE, mode);
|
||||
addattr_nest_end(&req.n, data);
|
||||
addattr_nest_end(&req.n, linkinfo);
|
||||
|
||||
err = rtnl_talk(&rth, &req.n, NULL);
|
||||
ASSERT_OK(err, "talk_rtnetlink");
|
||||
rtnl_close(&rth);
|
||||
*ifindex = if_nametoindex(netkit_name);
|
||||
|
||||
ASSERT_GT(*ifindex, 0, "retrieve_ifindex");
|
||||
ASSERT_OK(system("ip netns add foo"), "create netns");
|
||||
ASSERT_OK(system("ip link set dev " netkit_name " up"),
|
||||
"up primary");
|
||||
ASSERT_OK(system("ip addr add dev " netkit_name " 10.0.0.1/24"),
|
||||
"addr primary");
|
||||
if (same_netns) {
|
||||
ASSERT_OK(system("ip link set dev " netkit_peer " up"),
|
||||
"up peer");
|
||||
ASSERT_OK(system("ip addr add dev " netkit_peer " 10.0.0.2/24"),
|
||||
"addr peer");
|
||||
} else {
|
||||
ASSERT_OK(system("ip link set " netkit_peer " netns foo"),
|
||||
"move peer");
|
||||
ASSERT_OK(system("ip netns exec foo ip link set dev "
|
||||
netkit_peer " up"), "up peer");
|
||||
ASSERT_OK(system("ip netns exec foo ip addr add dev "
|
||||
netkit_peer " 10.0.0.2/24"), "addr peer");
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static void destroy_netkit(void)
|
||||
{
|
||||
ASSERT_OK(system("ip link del dev " netkit_name), "del primary");
|
||||
ASSERT_OK(system("ip netns del foo"), "delete netns");
|
||||
ASSERT_EQ(if_nametoindex(netkit_name), 0, netkit_name "_ifindex");
|
||||
}
|
||||
|
||||
static int __send_icmp(__u32 dest)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
struct icmphdr icmp;
|
||||
int sock, ret;
|
||||
|
||||
ret = write_sysctl("/proc/sys/net/ipv4/ping_group_range", "0 0");
|
||||
if (!ASSERT_OK(ret, "write_sysctl(net.ipv4.ping_group_range)"))
|
||||
return ret;
|
||||
|
||||
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
|
||||
if (!ASSERT_GE(sock, 0, "icmp_socket"))
|
||||
return -errno;
|
||||
|
||||
ret = setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE,
|
||||
netkit_name, strlen(netkit_name) + 1);
|
||||
if (!ASSERT_OK(ret, "setsockopt(SO_BINDTODEVICE)"))
|
||||
goto out;
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(dest);
|
||||
|
||||
memset(&icmp, 0, sizeof(icmp));
|
||||
icmp.type = ICMP_ECHO;
|
||||
icmp.echo.id = 1234;
|
||||
icmp.echo.sequence = 1;
|
||||
|
||||
ret = sendto(sock, &icmp, sizeof(icmp), 0,
|
||||
(struct sockaddr *)&addr, sizeof(addr));
|
||||
if (!ASSERT_GE(ret, 0, "icmp_sendto"))
|
||||
ret = -errno;
|
||||
else
|
||||
ret = 0;
|
||||
out:
|
||||
close(sock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int send_icmp(void)
|
||||
{
|
||||
return __send_icmp(ping_addr_neigh);
|
||||
}
|
||||
|
||||
void serial_test_tc_netkit_basic(void)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_prog_query_opts, optq);
|
||||
LIBBPF_OPTS(bpf_netkit_opts, optl);
|
||||
__u32 prog_ids[2], link_ids[2];
|
||||
__u32 pid1, pid2, lid1, lid2;
|
||||
struct test_tc_link *skel;
|
||||
struct bpf_link *link;
|
||||
int err, ifindex;
|
||||
|
||||
err = create_netkit(NETKIT_L2, NETKIT_PASS, NETKIT_PASS,
|
||||
&ifindex, false);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
skel = test_tc_link__open();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_open"))
|
||||
goto cleanup;
|
||||
|
||||
ASSERT_EQ(bpf_program__set_expected_attach_type(skel->progs.tc1,
|
||||
BPF_NETKIT_PRIMARY), 0, "tc1_attach_type");
|
||||
ASSERT_EQ(bpf_program__set_expected_attach_type(skel->progs.tc2,
|
||||
BPF_NETKIT_PEER), 0, "tc2_attach_type");
|
||||
|
||||
err = test_tc_link__load(skel);
|
||||
if (!ASSERT_OK(err, "skel_load"))
|
||||
goto cleanup;
|
||||
|
||||
pid1 = id_from_prog_fd(bpf_program__fd(skel->progs.tc1));
|
||||
pid2 = id_from_prog_fd(bpf_program__fd(skel->progs.tc2));
|
||||
|
||||
ASSERT_NEQ(pid1, pid2, "prog_ids_1_2");
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PRIMARY, 0);
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PEER, 0);
|
||||
|
||||
ASSERT_EQ(skel->bss->seen_tc1, false, "seen_tc1");
|
||||
ASSERT_EQ(skel->bss->seen_tc2, false, "seen_tc2");
|
||||
|
||||
link = bpf_program__attach_netkit(skel->progs.tc1, ifindex, &optl);
|
||||
if (!ASSERT_OK_PTR(link, "link_attach"))
|
||||
goto cleanup;
|
||||
|
||||
skel->links.tc1 = link;
|
||||
|
||||
lid1 = id_from_link_fd(bpf_link__fd(skel->links.tc1));
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PRIMARY, 1);
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PEER, 0);
|
||||
|
||||
optq.prog_ids = prog_ids;
|
||||
optq.link_ids = link_ids;
|
||||
|
||||
memset(prog_ids, 0, sizeof(prog_ids));
|
||||
memset(link_ids, 0, sizeof(link_ids));
|
||||
optq.count = ARRAY_SIZE(prog_ids);
|
||||
|
||||
err = bpf_prog_query_opts(ifindex, BPF_NETKIT_PRIMARY, &optq);
|
||||
if (!ASSERT_OK(err, "prog_query"))
|
||||
goto cleanup;
|
||||
|
||||
ASSERT_EQ(optq.count, 1, "count");
|
||||
ASSERT_EQ(optq.revision, 2, "revision");
|
||||
ASSERT_EQ(optq.prog_ids[0], pid1, "prog_ids[0]");
|
||||
ASSERT_EQ(optq.link_ids[0], lid1, "link_ids[0]");
|
||||
ASSERT_EQ(optq.prog_ids[1], 0, "prog_ids[1]");
|
||||
ASSERT_EQ(optq.link_ids[1], 0, "link_ids[1]");
|
||||
|
||||
tc_skel_reset_all_seen(skel);
|
||||
ASSERT_EQ(send_icmp(), 0, "icmp_pkt");
|
||||
|
||||
ASSERT_EQ(skel->bss->seen_tc1, true, "seen_tc1");
|
||||
ASSERT_EQ(skel->bss->seen_tc2, false, "seen_tc2");
|
||||
|
||||
link = bpf_program__attach_netkit(skel->progs.tc2, ifindex, &optl);
|
||||
if (!ASSERT_OK_PTR(link, "link_attach"))
|
||||
goto cleanup;
|
||||
|
||||
skel->links.tc2 = link;
|
||||
|
||||
lid2 = id_from_link_fd(bpf_link__fd(skel->links.tc2));
|
||||
ASSERT_NEQ(lid1, lid2, "link_ids_1_2");
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PRIMARY, 1);
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PEER, 1);
|
||||
|
||||
memset(prog_ids, 0, sizeof(prog_ids));
|
||||
memset(link_ids, 0, sizeof(link_ids));
|
||||
optq.count = ARRAY_SIZE(prog_ids);
|
||||
|
||||
err = bpf_prog_query_opts(ifindex, BPF_NETKIT_PEER, &optq);
|
||||
if (!ASSERT_OK(err, "prog_query"))
|
||||
goto cleanup;
|
||||
|
||||
ASSERT_EQ(optq.count, 1, "count");
|
||||
ASSERT_EQ(optq.revision, 2, "revision");
|
||||
ASSERT_EQ(optq.prog_ids[0], pid2, "prog_ids[0]");
|
||||
ASSERT_EQ(optq.link_ids[0], lid2, "link_ids[0]");
|
||||
ASSERT_EQ(optq.prog_ids[1], 0, "prog_ids[1]");
|
||||
ASSERT_EQ(optq.link_ids[1], 0, "link_ids[1]");
|
||||
|
||||
tc_skel_reset_all_seen(skel);
|
||||
ASSERT_EQ(send_icmp(), 0, "icmp_pkt");
|
||||
|
||||
ASSERT_EQ(skel->bss->seen_tc1, true, "seen_tc1");
|
||||
ASSERT_EQ(skel->bss->seen_tc2, true, "seen_tc2");
|
||||
cleanup:
|
||||
test_tc_link__destroy(skel);
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PRIMARY, 0);
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PEER, 0);
|
||||
destroy_netkit();
|
||||
}
|
||||
|
||||
static void serial_test_tc_netkit_multi_links_target(int mode, int target)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_prog_query_opts, optq);
|
||||
LIBBPF_OPTS(bpf_netkit_opts, optl);
|
||||
__u32 prog_ids[3], link_ids[3];
|
||||
__u32 pid1, pid2, lid1, lid2;
|
||||
struct test_tc_link *skel;
|
||||
struct bpf_link *link;
|
||||
int err, ifindex;
|
||||
|
||||
err = create_netkit(mode, NETKIT_PASS, NETKIT_PASS,
|
||||
&ifindex, false);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
skel = test_tc_link__open();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_open"))
|
||||
goto cleanup;
|
||||
|
||||
ASSERT_EQ(bpf_program__set_expected_attach_type(skel->progs.tc1,
|
||||
target), 0, "tc1_attach_type");
|
||||
ASSERT_EQ(bpf_program__set_expected_attach_type(skel->progs.tc2,
|
||||
target), 0, "tc2_attach_type");
|
||||
|
||||
err = test_tc_link__load(skel);
|
||||
if (!ASSERT_OK(err, "skel_load"))
|
||||
goto cleanup;
|
||||
|
||||
pid1 = id_from_prog_fd(bpf_program__fd(skel->progs.tc1));
|
||||
pid2 = id_from_prog_fd(bpf_program__fd(skel->progs.tc2));
|
||||
|
||||
ASSERT_NEQ(pid1, pid2, "prog_ids_1_2");
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, target, 0);
|
||||
|
||||
ASSERT_EQ(skel->bss->seen_tc1, false, "seen_tc1");
|
||||
ASSERT_EQ(skel->bss->seen_eth, false, "seen_eth");
|
||||
ASSERT_EQ(skel->bss->seen_tc2, false, "seen_tc2");
|
||||
|
||||
link = bpf_program__attach_netkit(skel->progs.tc1, ifindex, &optl);
|
||||
if (!ASSERT_OK_PTR(link, "link_attach"))
|
||||
goto cleanup;
|
||||
|
||||
skel->links.tc1 = link;
|
||||
|
||||
lid1 = id_from_link_fd(bpf_link__fd(skel->links.tc1));
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, target, 1);
|
||||
|
||||
optq.prog_ids = prog_ids;
|
||||
optq.link_ids = link_ids;
|
||||
|
||||
memset(prog_ids, 0, sizeof(prog_ids));
|
||||
memset(link_ids, 0, sizeof(link_ids));
|
||||
optq.count = ARRAY_SIZE(prog_ids);
|
||||
|
||||
err = bpf_prog_query_opts(ifindex, target, &optq);
|
||||
if (!ASSERT_OK(err, "prog_query"))
|
||||
goto cleanup;
|
||||
|
||||
ASSERT_EQ(optq.count, 1, "count");
|
||||
ASSERT_EQ(optq.revision, 2, "revision");
|
||||
ASSERT_EQ(optq.prog_ids[0], pid1, "prog_ids[0]");
|
||||
ASSERT_EQ(optq.link_ids[0], lid1, "link_ids[0]");
|
||||
ASSERT_EQ(optq.prog_ids[1], 0, "prog_ids[1]");
|
||||
ASSERT_EQ(optq.link_ids[1], 0, "link_ids[1]");
|
||||
|
||||
tc_skel_reset_all_seen(skel);
|
||||
ASSERT_EQ(send_icmp(), 0, "icmp_pkt");
|
||||
|
||||
ASSERT_EQ(skel->bss->seen_tc1, true, "seen_tc1");
|
||||
ASSERT_EQ(skel->bss->seen_eth, true, "seen_eth");
|
||||
ASSERT_EQ(skel->bss->seen_tc2, false, "seen_tc2");
|
||||
|
||||
LIBBPF_OPTS_RESET(optl,
|
||||
.flags = BPF_F_BEFORE,
|
||||
.relative_fd = bpf_program__fd(skel->progs.tc1),
|
||||
);
|
||||
|
||||
link = bpf_program__attach_netkit(skel->progs.tc2, ifindex, &optl);
|
||||
if (!ASSERT_OK_PTR(link, "link_attach"))
|
||||
goto cleanup;
|
||||
|
||||
skel->links.tc2 = link;
|
||||
|
||||
lid2 = id_from_link_fd(bpf_link__fd(skel->links.tc2));
|
||||
ASSERT_NEQ(lid1, lid2, "link_ids_1_2");
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, target, 2);
|
||||
|
||||
memset(prog_ids, 0, sizeof(prog_ids));
|
||||
memset(link_ids, 0, sizeof(link_ids));
|
||||
optq.count = ARRAY_SIZE(prog_ids);
|
||||
|
||||
err = bpf_prog_query_opts(ifindex, target, &optq);
|
||||
if (!ASSERT_OK(err, "prog_query"))
|
||||
goto cleanup;
|
||||
|
||||
ASSERT_EQ(optq.count, 2, "count");
|
||||
ASSERT_EQ(optq.revision, 3, "revision");
|
||||
ASSERT_EQ(optq.prog_ids[0], pid2, "prog_ids[0]");
|
||||
ASSERT_EQ(optq.link_ids[0], lid2, "link_ids[0]");
|
||||
ASSERT_EQ(optq.prog_ids[1], pid1, "prog_ids[1]");
|
||||
ASSERT_EQ(optq.link_ids[1], lid1, "link_ids[1]");
|
||||
ASSERT_EQ(optq.prog_ids[2], 0, "prog_ids[2]");
|
||||
ASSERT_EQ(optq.link_ids[2], 0, "link_ids[2]");
|
||||
|
||||
tc_skel_reset_all_seen(skel);
|
||||
ASSERT_EQ(send_icmp(), 0, "icmp_pkt");
|
||||
|
||||
ASSERT_EQ(skel->bss->seen_tc1, true, "seen_tc1");
|
||||
ASSERT_EQ(skel->bss->seen_eth, true, "seen_eth");
|
||||
ASSERT_EQ(skel->bss->seen_tc2, true, "seen_tc2");
|
||||
cleanup:
|
||||
test_tc_link__destroy(skel);
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, target, 0);
|
||||
destroy_netkit();
|
||||
}
|
||||
|
||||
void serial_test_tc_netkit_multi_links(void)
|
||||
{
|
||||
serial_test_tc_netkit_multi_links_target(NETKIT_L2, BPF_NETKIT_PRIMARY);
|
||||
serial_test_tc_netkit_multi_links_target(NETKIT_L3, BPF_NETKIT_PRIMARY);
|
||||
serial_test_tc_netkit_multi_links_target(NETKIT_L2, BPF_NETKIT_PEER);
|
||||
serial_test_tc_netkit_multi_links_target(NETKIT_L3, BPF_NETKIT_PEER);
|
||||
}
|
||||
|
||||
static void serial_test_tc_netkit_multi_opts_target(int mode, int target)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_prog_attach_opts, opta);
|
||||
LIBBPF_OPTS(bpf_prog_detach_opts, optd);
|
||||
LIBBPF_OPTS(bpf_prog_query_opts, optq);
|
||||
__u32 pid1, pid2, fd1, fd2;
|
||||
__u32 prog_ids[3];
|
||||
struct test_tc_link *skel;
|
||||
int err, ifindex;
|
||||
|
||||
err = create_netkit(mode, NETKIT_PASS, NETKIT_PASS,
|
||||
&ifindex, false);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
skel = test_tc_link__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_load"))
|
||||
goto cleanup;
|
||||
|
||||
fd1 = bpf_program__fd(skel->progs.tc1);
|
||||
fd2 = bpf_program__fd(skel->progs.tc2);
|
||||
|
||||
pid1 = id_from_prog_fd(fd1);
|
||||
pid2 = id_from_prog_fd(fd2);
|
||||
|
||||
ASSERT_NEQ(pid1, pid2, "prog_ids_1_2");
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, target, 0);
|
||||
|
||||
ASSERT_EQ(skel->bss->seen_tc1, false, "seen_tc1");
|
||||
ASSERT_EQ(skel->bss->seen_eth, false, "seen_eth");
|
||||
ASSERT_EQ(skel->bss->seen_tc2, false, "seen_tc2");
|
||||
|
||||
err = bpf_prog_attach_opts(fd1, ifindex, target, &opta);
|
||||
if (!ASSERT_EQ(err, 0, "prog_attach"))
|
||||
goto cleanup;
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, target, 1);
|
||||
|
||||
optq.prog_ids = prog_ids;
|
||||
|
||||
memset(prog_ids, 0, sizeof(prog_ids));
|
||||
optq.count = ARRAY_SIZE(prog_ids);
|
||||
|
||||
err = bpf_prog_query_opts(ifindex, target, &optq);
|
||||
if (!ASSERT_OK(err, "prog_query"))
|
||||
goto cleanup_fd1;
|
||||
|
||||
ASSERT_EQ(optq.count, 1, "count");
|
||||
ASSERT_EQ(optq.revision, 2, "revision");
|
||||
ASSERT_EQ(optq.prog_ids[0], pid1, "prog_ids[0]");
|
||||
ASSERT_EQ(optq.prog_ids[1], 0, "prog_ids[1]");
|
||||
|
||||
tc_skel_reset_all_seen(skel);
|
||||
ASSERT_EQ(send_icmp(), 0, "icmp_pkt");
|
||||
|
||||
ASSERT_EQ(skel->bss->seen_tc1, true, "seen_tc1");
|
||||
ASSERT_EQ(skel->bss->seen_eth, true, "seen_eth");
|
||||
ASSERT_EQ(skel->bss->seen_tc2, false, "seen_tc2");
|
||||
|
||||
LIBBPF_OPTS_RESET(opta,
|
||||
.flags = BPF_F_BEFORE,
|
||||
.relative_fd = fd1,
|
||||
);
|
||||
|
||||
err = bpf_prog_attach_opts(fd2, ifindex, target, &opta);
|
||||
if (!ASSERT_EQ(err, 0, "prog_attach"))
|
||||
goto cleanup_fd1;
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, target, 2);
|
||||
|
||||
memset(prog_ids, 0, sizeof(prog_ids));
|
||||
optq.count = ARRAY_SIZE(prog_ids);
|
||||
|
||||
err = bpf_prog_query_opts(ifindex, target, &optq);
|
||||
if (!ASSERT_OK(err, "prog_query"))
|
||||
goto cleanup_fd2;
|
||||
|
||||
ASSERT_EQ(optq.count, 2, "count");
|
||||
ASSERT_EQ(optq.revision, 3, "revision");
|
||||
ASSERT_EQ(optq.prog_ids[0], pid2, "prog_ids[0]");
|
||||
ASSERT_EQ(optq.prog_ids[1], pid1, "prog_ids[1]");
|
||||
ASSERT_EQ(optq.prog_ids[2], 0, "prog_ids[2]");
|
||||
|
||||
tc_skel_reset_all_seen(skel);
|
||||
ASSERT_EQ(send_icmp(), 0, "icmp_pkt");
|
||||
|
||||
ASSERT_EQ(skel->bss->seen_tc1, true, "seen_tc1");
|
||||
ASSERT_EQ(skel->bss->seen_eth, true, "seen_eth");
|
||||
ASSERT_EQ(skel->bss->seen_tc2, true, "seen_tc2");
|
||||
|
||||
cleanup_fd2:
|
||||
err = bpf_prog_detach_opts(fd2, ifindex, target, &optd);
|
||||
ASSERT_OK(err, "prog_detach");
|
||||
assert_mprog_count_ifindex(ifindex, target, 1);
|
||||
cleanup_fd1:
|
||||
err = bpf_prog_detach_opts(fd1, ifindex, target, &optd);
|
||||
ASSERT_OK(err, "prog_detach");
|
||||
assert_mprog_count_ifindex(ifindex, target, 0);
|
||||
cleanup:
|
||||
test_tc_link__destroy(skel);
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, target, 0);
|
||||
destroy_netkit();
|
||||
}
|
||||
|
||||
void serial_test_tc_netkit_multi_opts(void)
|
||||
{
|
||||
serial_test_tc_netkit_multi_opts_target(NETKIT_L2, BPF_NETKIT_PRIMARY);
|
||||
serial_test_tc_netkit_multi_opts_target(NETKIT_L3, BPF_NETKIT_PRIMARY);
|
||||
serial_test_tc_netkit_multi_opts_target(NETKIT_L2, BPF_NETKIT_PEER);
|
||||
serial_test_tc_netkit_multi_opts_target(NETKIT_L3, BPF_NETKIT_PEER);
|
||||
}
|
||||
|
||||
void serial_test_tc_netkit_device(void)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_prog_query_opts, optq);
|
||||
LIBBPF_OPTS(bpf_netkit_opts, optl);
|
||||
__u32 prog_ids[2], link_ids[2];
|
||||
__u32 pid1, pid2, lid1;
|
||||
struct test_tc_link *skel;
|
||||
struct bpf_link *link;
|
||||
int err, ifindex, ifindex2;
|
||||
|
||||
err = create_netkit(NETKIT_L3, NETKIT_PASS, NETKIT_PASS,
|
||||
&ifindex, true);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
ifindex2 = if_nametoindex(netkit_peer);
|
||||
ASSERT_NEQ(ifindex, ifindex2, "ifindex_1_2");
|
||||
|
||||
skel = test_tc_link__open();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_open"))
|
||||
goto cleanup;
|
||||
|
||||
ASSERT_EQ(bpf_program__set_expected_attach_type(skel->progs.tc1,
|
||||
BPF_NETKIT_PRIMARY), 0, "tc1_attach_type");
|
||||
ASSERT_EQ(bpf_program__set_expected_attach_type(skel->progs.tc2,
|
||||
BPF_NETKIT_PEER), 0, "tc2_attach_type");
|
||||
ASSERT_EQ(bpf_program__set_expected_attach_type(skel->progs.tc3,
|
||||
BPF_NETKIT_PRIMARY), 0, "tc3_attach_type");
|
||||
|
||||
err = test_tc_link__load(skel);
|
||||
if (!ASSERT_OK(err, "skel_load"))
|
||||
goto cleanup;
|
||||
|
||||
pid1 = id_from_prog_fd(bpf_program__fd(skel->progs.tc1));
|
||||
pid2 = id_from_prog_fd(bpf_program__fd(skel->progs.tc2));
|
||||
|
||||
ASSERT_NEQ(pid1, pid2, "prog_ids_1_2");
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PRIMARY, 0);
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PEER, 0);
|
||||
|
||||
ASSERT_EQ(skel->bss->seen_tc1, false, "seen_tc1");
|
||||
ASSERT_EQ(skel->bss->seen_tc2, false, "seen_tc2");
|
||||
|
||||
link = bpf_program__attach_netkit(skel->progs.tc1, ifindex, &optl);
|
||||
if (!ASSERT_OK_PTR(link, "link_attach"))
|
||||
goto cleanup;
|
||||
|
||||
skel->links.tc1 = link;
|
||||
|
||||
lid1 = id_from_link_fd(bpf_link__fd(skel->links.tc1));
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PRIMARY, 1);
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PEER, 0);
|
||||
|
||||
optq.prog_ids = prog_ids;
|
||||
optq.link_ids = link_ids;
|
||||
|
||||
memset(prog_ids, 0, sizeof(prog_ids));
|
||||
memset(link_ids, 0, sizeof(link_ids));
|
||||
optq.count = ARRAY_SIZE(prog_ids);
|
||||
|
||||
err = bpf_prog_query_opts(ifindex, BPF_NETKIT_PRIMARY, &optq);
|
||||
if (!ASSERT_OK(err, "prog_query"))
|
||||
goto cleanup;
|
||||
|
||||
ASSERT_EQ(optq.count, 1, "count");
|
||||
ASSERT_EQ(optq.revision, 2, "revision");
|
||||
ASSERT_EQ(optq.prog_ids[0], pid1, "prog_ids[0]");
|
||||
ASSERT_EQ(optq.link_ids[0], lid1, "link_ids[0]");
|
||||
ASSERT_EQ(optq.prog_ids[1], 0, "prog_ids[1]");
|
||||
ASSERT_EQ(optq.link_ids[1], 0, "link_ids[1]");
|
||||
|
||||
tc_skel_reset_all_seen(skel);
|
||||
ASSERT_EQ(send_icmp(), 0, "icmp_pkt");
|
||||
|
||||
ASSERT_EQ(skel->bss->seen_tc1, true, "seen_tc1");
|
||||
ASSERT_EQ(skel->bss->seen_tc2, false, "seen_tc2");
|
||||
|
||||
memset(prog_ids, 0, sizeof(prog_ids));
|
||||
memset(link_ids, 0, sizeof(link_ids));
|
||||
optq.count = ARRAY_SIZE(prog_ids);
|
||||
|
||||
err = bpf_prog_query_opts(ifindex2, BPF_NETKIT_PRIMARY, &optq);
|
||||
ASSERT_EQ(err, -EACCES, "prog_query_should_fail");
|
||||
|
||||
err = bpf_prog_query_opts(ifindex2, BPF_NETKIT_PEER, &optq);
|
||||
ASSERT_EQ(err, -EACCES, "prog_query_should_fail");
|
||||
|
||||
link = bpf_program__attach_netkit(skel->progs.tc2, ifindex2, &optl);
|
||||
if (!ASSERT_ERR_PTR(link, "link_attach_should_fail")) {
|
||||
bpf_link__destroy(link);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
link = bpf_program__attach_netkit(skel->progs.tc3, ifindex2, &optl);
|
||||
if (!ASSERT_ERR_PTR(link, "link_attach_should_fail")) {
|
||||
bpf_link__destroy(link);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PRIMARY, 1);
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PEER, 0);
|
||||
cleanup:
|
||||
test_tc_link__destroy(skel);
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PRIMARY, 0);
|
||||
assert_mprog_count_ifindex(ifindex, BPF_NETKIT_PEER, 0);
|
||||
destroy_netkit();
|
||||
}
|
||||
|
||||
static void serial_test_tc_netkit_neigh_links_target(int mode, int target)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_prog_query_opts, optq);
|
||||
LIBBPF_OPTS(bpf_netkit_opts, optl);
|
||||
__u32 prog_ids[2], link_ids[2];
|
||||
__u32 pid1, lid1;
|
||||
struct test_tc_link *skel;
|
||||
struct bpf_link *link;
|
||||
int err, ifindex;
|
||||
|
||||
err = create_netkit(mode, NETKIT_PASS, NETKIT_PASS,
|
||||
&ifindex, false);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
skel = test_tc_link__open();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_open"))
|
||||
goto cleanup;
|
||||
|
||||
ASSERT_EQ(bpf_program__set_expected_attach_type(skel->progs.tc1,
|
||||
BPF_NETKIT_PRIMARY), 0, "tc1_attach_type");
|
||||
|
||||
err = test_tc_link__load(skel);
|
||||
if (!ASSERT_OK(err, "skel_load"))
|
||||
goto cleanup;
|
||||
|
||||
pid1 = id_from_prog_fd(bpf_program__fd(skel->progs.tc1));
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, target, 0);
|
||||
|
||||
ASSERT_EQ(skel->bss->seen_tc1, false, "seen_tc1");
|
||||
ASSERT_EQ(skel->bss->seen_eth, false, "seen_eth");
|
||||
|
||||
link = bpf_program__attach_netkit(skel->progs.tc1, ifindex, &optl);
|
||||
if (!ASSERT_OK_PTR(link, "link_attach"))
|
||||
goto cleanup;
|
||||
|
||||
skel->links.tc1 = link;
|
||||
|
||||
lid1 = id_from_link_fd(bpf_link__fd(skel->links.tc1));
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, target, 1);
|
||||
|
||||
optq.prog_ids = prog_ids;
|
||||
optq.link_ids = link_ids;
|
||||
|
||||
memset(prog_ids, 0, sizeof(prog_ids));
|
||||
memset(link_ids, 0, sizeof(link_ids));
|
||||
optq.count = ARRAY_SIZE(prog_ids);
|
||||
|
||||
err = bpf_prog_query_opts(ifindex, target, &optq);
|
||||
if (!ASSERT_OK(err, "prog_query"))
|
||||
goto cleanup;
|
||||
|
||||
ASSERT_EQ(optq.count, 1, "count");
|
||||
ASSERT_EQ(optq.revision, 2, "revision");
|
||||
ASSERT_EQ(optq.prog_ids[0], pid1, "prog_ids[0]");
|
||||
ASSERT_EQ(optq.link_ids[0], lid1, "link_ids[0]");
|
||||
ASSERT_EQ(optq.prog_ids[1], 0, "prog_ids[1]");
|
||||
ASSERT_EQ(optq.link_ids[1], 0, "link_ids[1]");
|
||||
|
||||
tc_skel_reset_all_seen(skel);
|
||||
ASSERT_EQ(__send_icmp(ping_addr_noneigh), 0, "icmp_pkt");
|
||||
|
||||
ASSERT_EQ(skel->bss->seen_tc1, true /* L2: ARP */, "seen_tc1");
|
||||
ASSERT_EQ(skel->bss->seen_eth, mode == NETKIT_L3, "seen_eth");
|
||||
cleanup:
|
||||
test_tc_link__destroy(skel);
|
||||
|
||||
assert_mprog_count_ifindex(ifindex, target, 0);
|
||||
destroy_netkit();
|
||||
}
|
||||
|
||||
void serial_test_tc_netkit_neigh_links(void)
|
||||
{
|
||||
serial_test_tc_netkit_neigh_links_target(NETKIT_L2, BPF_NETKIT_PRIMARY);
|
||||
serial_test_tc_netkit_neigh_links_target(NETKIT_L3, BPF_NETKIT_PRIMARY);
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2023 Isovalent */
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/if_ether.h>
|
||||
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
@ -12,10 +16,19 @@ bool seen_tc3;
|
||||
bool seen_tc4;
|
||||
bool seen_tc5;
|
||||
bool seen_tc6;
|
||||
bool seen_eth;
|
||||
|
||||
SEC("tc/ingress")
|
||||
int tc1(struct __sk_buff *skb)
|
||||
{
|
||||
struct ethhdr eth = {};
|
||||
|
||||
if (skb->protocol != __bpf_constant_htons(ETH_P_IP))
|
||||
goto out;
|
||||
if (bpf_skb_load_bytes(skb, 0, ð, sizeof(eth)))
|
||||
goto out;
|
||||
seen_eth = eth.h_proto == bpf_htons(ETH_P_IP);
|
||||
out:
|
||||
seen_tc1 = true;
|
||||
return TCX_NEXT;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user