Skip to content

Commit

Permalink
Merge branch 'bpf-xdp-adjust-tail'
Browse files Browse the repository at this point in the history
Nikita V. Shirokov says:

====================
In this patch series i'm add new bpf helper which allow to manupulate
xdp's data_end pointer. right now only "shrinking" (reduce packet's size
by moving pointer) is supported (and i see no use case for "growing").
Main use case for such helper is to be able to generate controll (ICMP)
messages from XDP context. such messages usually contains first N bytes
from original packets as a payload, and this is exactly what this helper
would allow us to do (see patch 3 for sample program, where we generate
ICMP "packet too big" message). This helper could be usefull for load
balancing applications where after additional encapsulation, resulting
packet could be bigger then interface MTU.
Aside from new helper this patch series contains minor changes in device
drivers (for ones which requires), so they would recal packet's length
not only when head pointer was adjusted, but if tail's one as well.

v2->v3:
 * adding missed "signed off by" in v2

v1->v2:
 * fixed kbuild warning
 * made offset eq 0 invalid for xdp_bpf_adjust_tail
 * splitted bpf_prog_test_run fix and selftests in sep commits
 * added SPDX licence where applicable
 * some reshuffling in patches order (tests now in the end)
====================

Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
  • Loading branch information
Daniel Borkmann committed Apr 18, 2018
2 parents 0c90f22 + c6ffd1f commit 7f3249f
Show file tree
Hide file tree
Showing 18 changed files with 435 additions and 12 deletions.
2 changes: 1 addition & 1 deletion drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ bool bnxt_rx_xdp(struct bnxt *bp, struct bnxt_rx_ring_info *rxr, u16 cons,
if (tx_avail != bp->tx_ring_size)
*event &= ~BNXT_RX_EVENT;

*len = xdp.data_end - xdp.data;
if (orig_data != xdp.data) {
offset = xdp.data - xdp.data_hard_start;
*data_ptr = xdp.data_hard_start + offset;
*len = xdp.data_end - xdp.data;
}
switch (act) {
case XDP_PASS:
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/ethernet/cavium/thunder/nicvf_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -538,9 +538,9 @@ static inline bool nicvf_xdp_rx(struct nicvf *nic, struct bpf_prog *prog,
action = bpf_prog_run_xdp(prog, &xdp);
rcu_read_unlock();

len = xdp.data_end - xdp.data;
/* Check if XDP program has changed headers */
if (orig_data != xdp.data) {
len = xdp.data_end - xdp.data;
offset = orig_data - xdp.data;
dma_addr -= offset;
}
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/ethernet/mellanox/mlx4/en_rx.c
Original file line number Diff line number Diff line change
Expand Up @@ -775,8 +775,8 @@ int mlx4_en_process_rx_cq(struct net_device *dev, struct mlx4_en_cq *cq, int bud

act = bpf_prog_run_xdp(xdp_prog, &xdp);

length = xdp.data_end - xdp.data;
if (xdp.data != orig_data) {
length = xdp.data_end - xdp.data;
frags[0].page_offset = xdp.data -
xdp.data_hard_start;
va = xdp.data;
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/ethernet/netronome/nfp/nfp_net_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -1722,7 +1722,7 @@ static int nfp_net_rx(struct nfp_net_rx_ring *rx_ring, int budget)

act = bpf_prog_run_xdp(xdp_prog, &xdp);

pkt_len -= xdp.data - orig_data;
pkt_len = xdp.data_end - xdp.data;
pkt_off += xdp.data - orig_data;

switch (act) {
Expand Down
3 changes: 2 additions & 1 deletion drivers/net/tun.c
Original file line number Diff line number Diff line change
Expand Up @@ -1696,6 +1696,7 @@ static struct sk_buff *tun_build_skb(struct tun_struct *tun,
return NULL;
case XDP_PASS:
delta = orig_data - xdp.data;
len = xdp.data_end - xdp.data;
break;
default:
bpf_warn_invalid_xdp_action(act);
Expand All @@ -1716,7 +1717,7 @@ static struct sk_buff *tun_build_skb(struct tun_struct *tun,
}

skb_reserve(skb, pad - delta);
skb_put(skb, len + delta);
skb_put(skb, len);
get_page(alloc_frag->page);
alloc_frag->offset += buflen;

Expand Down
7 changes: 6 additions & 1 deletion drivers/net/virtio_net.c
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,7 @@ static struct sk_buff *receive_small(struct net_device *dev,
case XDP_PASS:
/* Recalculate length in case bpf program changed it */
delta = orig_data - xdp.data;
len = xdp.data_end - xdp.data;
break;
case XDP_TX:
xdpf = convert_to_xdp_frame(&xdp);
Expand Down Expand Up @@ -642,7 +643,7 @@ static struct sk_buff *receive_small(struct net_device *dev,
goto err;
}
skb_reserve(skb, headroom - delta);
skb_put(skb, len + delta);
skb_put(skb, len);
if (!delta) {
buf += header_offset;
memcpy(skb_vnet_hdr(skb), buf, vi->hdr_len);
Expand Down Expand Up @@ -757,6 +758,10 @@ static struct sk_buff *receive_mergeable(struct net_device *dev,
offset = xdp.data -
page_address(xdp_page) - vi->hdr_len;

/* recalculate len if xdp.data or xdp.data_end were
* adjusted
*/
len = xdp.data_end - xdp.data;
/* We can only create skb based on xdp_page. */
if (unlikely(xdp_page != page)) {
rcu_read_unlock();
Expand Down
10 changes: 9 additions & 1 deletion include/uapi/linux/bpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,13 @@ union bpf_attr {
* @addr: pointer to struct sockaddr to bind socket to
* @addr_len: length of sockaddr structure
* Return: 0 on success or negative error code
*
* int bpf_xdp_adjust_tail(xdp_md, delta)
* Adjust the xdp_md.data_end by delta. Only shrinking of packet's
* size is supported.
* @xdp_md: pointer to xdp_md
* @delta: A negative integer to be added to xdp_md.data_end
* Return: 0 on success or negative on error
*/
#define __BPF_FUNC_MAPPER(FN) \
FN(unspec), \
Expand Down Expand Up @@ -821,7 +828,8 @@ union bpf_attr {
FN(msg_apply_bytes), \
FN(msg_cork_bytes), \
FN(msg_pull_data), \
FN(bind),
FN(bind), \
FN(xdp_adjust_tail),

/* integer value in 'imm' field of BPF_CALL instruction selects which helper
* function eBPF program intends to call
Expand Down
3 changes: 2 additions & 1 deletion net/bpf/test_run.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ int bpf_prog_test_run_xdp(struct bpf_prog *prog, const union bpf_attr *kattr,
xdp.rxq = &rxqueue->xdp_rxq;

retval = bpf_test_run(prog, &xdp, repeat, &duration);
if (xdp.data != data + XDP_PACKET_HEADROOM + NET_IP_ALIGN)
if (xdp.data != data + XDP_PACKET_HEADROOM + NET_IP_ALIGN ||
xdp.data_end != xdp.data + size)
size = xdp.data_end - xdp.data;
ret = bpf_test_finish(kattr, uattr, xdp.data, size, retval, duration);
kfree(data);
Expand Down
10 changes: 9 additions & 1 deletion net/core/dev.c
Original file line number Diff line number Diff line change
Expand Up @@ -3996,9 +3996,9 @@ static u32 netif_receive_generic_xdp(struct sk_buff *skb,
struct bpf_prog *xdp_prog)
{
struct netdev_rx_queue *rxqueue;
void *orig_data, *orig_data_end;
u32 metalen, act = XDP_DROP;
struct xdp_buff xdp;
void *orig_data;
int hlen, off;
u32 mac_len;

Expand Down Expand Up @@ -4037,6 +4037,7 @@ static u32 netif_receive_generic_xdp(struct sk_buff *skb,
xdp.data_meta = xdp.data;
xdp.data_end = xdp.data + hlen;
xdp.data_hard_start = skb->data - skb_headroom(skb);
orig_data_end = xdp.data_end;
orig_data = xdp.data;

rxqueue = netif_get_rxqueue(skb);
Expand All @@ -4051,6 +4052,13 @@ static u32 netif_receive_generic_xdp(struct sk_buff *skb,
__skb_push(skb, -off);
skb->mac_header += off;

/* check if bpf_xdp_adjust_tail was used. it can only "shrink"
* pckt.
*/
off = orig_data_end - xdp.data_end;
if (off != 0)
skb_set_tail_pointer(skb, xdp.data_end - xdp.data);

switch (act) {
case XDP_REDIRECT:
case XDP_TX:
Expand Down
29 changes: 28 additions & 1 deletion net/core/filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -2725,6 +2725,30 @@ static const struct bpf_func_proto bpf_xdp_adjust_head_proto = {
.arg2_type = ARG_ANYTHING,
};

BPF_CALL_2(bpf_xdp_adjust_tail, struct xdp_buff *, xdp, int, offset)
{
void *data_end = xdp->data_end + offset;

/* only shrinking is allowed for now. */
if (unlikely(offset >= 0))
return -EINVAL;

if (unlikely(data_end < xdp->data + ETH_HLEN))
return -EINVAL;

xdp->data_end = data_end;

return 0;
}

static const struct bpf_func_proto bpf_xdp_adjust_tail_proto = {
.func = bpf_xdp_adjust_tail,
.gpl_only = false,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_ANYTHING,
};

BPF_CALL_2(bpf_xdp_adjust_meta, struct xdp_buff *, xdp, int, offset)
{
void *meta = xdp->data_meta + offset;
Expand Down Expand Up @@ -3074,7 +3098,8 @@ bool bpf_helper_changes_pkt_data(void *func)
func == bpf_l4_csum_replace ||
func == bpf_xdp_adjust_head ||
func == bpf_xdp_adjust_meta ||
func == bpf_msg_pull_data)
func == bpf_msg_pull_data ||
func == bpf_xdp_adjust_tail)
return true;

return false;
Expand Down Expand Up @@ -3888,6 +3913,8 @@ xdp_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
return &bpf_xdp_redirect_proto;
case BPF_FUNC_redirect_map:
return &bpf_xdp_redirect_map_proto;
case BPF_FUNC_xdp_adjust_tail:
return &bpf_xdp_adjust_tail_proto;
default:
return bpf_base_func_proto(func_id);
}
Expand Down
4 changes: 4 additions & 0 deletions samples/bpf/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ hostprogs-y += xdp_monitor
hostprogs-y += xdp_rxq_info
hostprogs-y += syscall_tp
hostprogs-y += cpustat
hostprogs-y += xdp_adjust_tail

# Libbpf dependencies
LIBBPF := ../../tools/lib/bpf/bpf.o ../../tools/lib/bpf/nlattr.o
Expand Down Expand Up @@ -95,6 +96,7 @@ xdp_monitor-objs := bpf_load.o $(LIBBPF) xdp_monitor_user.o
xdp_rxq_info-objs := bpf_load.o $(LIBBPF) xdp_rxq_info_user.o
syscall_tp-objs := bpf_load.o $(LIBBPF) syscall_tp_user.o
cpustat-objs := bpf_load.o $(LIBBPF) cpustat_user.o
xdp_adjust_tail-objs := bpf_load.o $(LIBBPF) xdp_adjust_tail_user.o

# Tell kbuild to always build the programs
always := $(hostprogs-y)
Expand Down Expand Up @@ -148,6 +150,7 @@ always += xdp_rxq_info_kern.o
always += xdp2skb_meta_kern.o
always += syscall_tp_kern.o
always += cpustat_kern.o
always += xdp_adjust_tail_kern.o

HOSTCFLAGS += -I$(objtree)/usr/include
HOSTCFLAGS += -I$(srctree)/tools/lib/
Expand Down Expand Up @@ -193,6 +196,7 @@ HOSTLOADLIBES_xdp_monitor += -lelf
HOSTLOADLIBES_xdp_rxq_info += -lelf
HOSTLOADLIBES_syscall_tp += -lelf
HOSTLOADLIBES_cpustat += -lelf
HOSTLOADLIBES_xdp_adjust_tail += -lelf

# Allows pointing LLC/CLANG to a LLVM backend with bpf support, redefine on cmdline:
# make samples/bpf/ LLC=~/git/llvm/build/bin/llc CLANG=~/git/llvm/build/bin/clang
Expand Down
152 changes: 152 additions & 0 deletions samples/bpf/xdp_adjust_tail_kern.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/* SPDX-License-Identifier: GPL-2.0
* Copyright (c) 2018 Facebook
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program shows how to use bpf_xdp_adjust_tail() by
* generating ICMPv4 "packet to big" (unreachable/ df bit set frag needed
* to be more preice in case of v4)" where receiving packets bigger then
* 600 bytes.
*/
#define KBUILD_MODNAME "foo"
#include <uapi/linux/bpf.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/if_vlan.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include "bpf_helpers.h"

#define DEFAULT_TTL 64
#define MAX_PCKT_SIZE 600
#define ICMP_TOOBIG_SIZE 98
#define ICMP_TOOBIG_PAYLOAD_SIZE 92

struct bpf_map_def SEC("maps") icmpcnt = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(__u64),
.max_entries = 1,
};

static __always_inline void count_icmp(void)
{
u64 key = 0;
u64 *icmp_count;

icmp_count = bpf_map_lookup_elem(&icmpcnt, &key);
if (icmp_count)
*icmp_count += 1;
}

static __always_inline void swap_mac(void *data, struct ethhdr *orig_eth)
{
struct ethhdr *eth;

eth = data;
memcpy(eth->h_source, orig_eth->h_dest, ETH_ALEN);
memcpy(eth->h_dest, orig_eth->h_source, ETH_ALEN);
eth->h_proto = orig_eth->h_proto;
}

static __always_inline __u16 csum_fold_helper(__u32 csum)
{
return ~((csum & 0xffff) + (csum >> 16));
}

static __always_inline void ipv4_csum(void *data_start, int data_size,
__u32 *csum)
{
*csum = bpf_csum_diff(0, 0, data_start, data_size, *csum);
*csum = csum_fold_helper(*csum);
}

static __always_inline int send_icmp4_too_big(struct xdp_md *xdp)
{
int headroom = (int)sizeof(struct iphdr) + (int)sizeof(struct icmphdr);

if (bpf_xdp_adjust_head(xdp, 0 - headroom))
return XDP_DROP;
void *data = (void *)(long)xdp->data;
void *data_end = (void *)(long)xdp->data_end;

if (data + (ICMP_TOOBIG_SIZE + headroom) > data_end)
return XDP_DROP;

struct iphdr *iph, *orig_iph;
struct icmphdr *icmp_hdr;
struct ethhdr *orig_eth;
__u32 csum = 0;
__u64 off = 0;

orig_eth = data + headroom;
swap_mac(data, orig_eth);
off += sizeof(struct ethhdr);
iph = data + off;
off += sizeof(struct iphdr);
icmp_hdr = data + off;
off += sizeof(struct icmphdr);
orig_iph = data + off;
icmp_hdr->type = ICMP_DEST_UNREACH;
icmp_hdr->code = ICMP_FRAG_NEEDED;
icmp_hdr->un.frag.mtu = htons(MAX_PCKT_SIZE-sizeof(struct ethhdr));
icmp_hdr->checksum = 0;
ipv4_csum(icmp_hdr, ICMP_TOOBIG_PAYLOAD_SIZE, &csum);
icmp_hdr->checksum = csum;
iph->ttl = DEFAULT_TTL;
iph->daddr = orig_iph->saddr;
iph->saddr = orig_iph->daddr;
iph->version = 4;
iph->ihl = 5;
iph->protocol = IPPROTO_ICMP;
iph->tos = 0;
iph->tot_len = htons(
ICMP_TOOBIG_SIZE + headroom - sizeof(struct ethhdr));
iph->check = 0;
csum = 0;
ipv4_csum(iph, sizeof(struct iphdr), &csum);
iph->check = csum;
count_icmp();
return XDP_TX;
}


static __always_inline int handle_ipv4(struct xdp_md *xdp)
{
void *data_end = (void *)(long)xdp->data_end;
void *data = (void *)(long)xdp->data;
int pckt_size = data_end - data;
int offset;

if (pckt_size > MAX_PCKT_SIZE) {
offset = pckt_size - ICMP_TOOBIG_SIZE;
if (bpf_xdp_adjust_tail(xdp, 0 - offset))
return XDP_PASS;
return send_icmp4_too_big(xdp);
}
return XDP_PASS;
}

SEC("xdp_icmp")
int _xdp_icmp(struct xdp_md *xdp)
{
void *data_end = (void *)(long)xdp->data_end;
void *data = (void *)(long)xdp->data;
struct ethhdr *eth = data;
__u16 h_proto;

if (eth + 1 > data_end)
return XDP_DROP;

h_proto = eth->h_proto;

if (h_proto == htons(ETH_P_IP))
return handle_ipv4(xdp);
else
return XDP_PASS;
}

char _license[] SEC("license") = "GPL";
Loading

0 comments on commit 7f3249f

Please sign in to comment.