f74599f7c5
Adds a series of tests to verify the functionality of attaching BPF programs at LWT hooks. Also adds a sample which collects a histogram of packet sizes which pass through an LWT hook. $ ./lwt_len_hist.sh Starting netserver with host 'IN(6)ADDR_ANY' port '12865' and family AF_UNSPEC MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 192.168.253.2 () port 0 AF_INET : demo Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 87380 16384 16384 10.00 39857.69 1 -> 1 : 0 | | 2 -> 3 : 0 | | 4 -> 7 : 0 | | 8 -> 15 : 0 | | 16 -> 31 : 0 | | 32 -> 63 : 22 | | 64 -> 127 : 98 | | 128 -> 255 : 213 | | 256 -> 511 : 1444251 |******** | 512 -> 1023 : 660610 |*** | 1024 -> 2047 : 535241 |** | 2048 -> 4095 : 19 | | 4096 -> 8191 : 180 | | 8192 -> 16383 : 5578023 |************************************* | 16384 -> 32767 : 632099 |*** | 32768 -> 65535 : 6575 | | Signed-off-by: Thomas Graf <tgraf@suug.ch> Acked-by: Alexei Starovoitov <ast@kernel.org> Signed-off-by: David S. Miller <davem@davemloft.net>
254 lines
6.0 KiB
C
254 lines
6.0 KiB
C
/* Copyright (c) 2016 Thomas Graf <tgraf@tgraf.ch>
|
|
*
|
|
* 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 is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <linux/bpf.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/in.h>
|
|
#include <linux/in6.h>
|
|
#include <linux/tcp.h>
|
|
#include <linux/udp.h>
|
|
#include <linux/icmpv6.h>
|
|
#include <linux/if_ether.h>
|
|
#include "bpf_helpers.h"
|
|
#include <string.h>
|
|
|
|
# define printk(fmt, ...) \
|
|
({ \
|
|
char ____fmt[] = fmt; \
|
|
bpf_trace_printk(____fmt, sizeof(____fmt), \
|
|
##__VA_ARGS__); \
|
|
})
|
|
|
|
#define CB_MAGIC 1234
|
|
|
|
/* Test: Pass all packets through */
|
|
SEC("nop")
|
|
int do_nop(struct __sk_buff *skb)
|
|
{
|
|
return BPF_OK;
|
|
}
|
|
|
|
/* Test: Verify context information can be accessed */
|
|
SEC("test_ctx")
|
|
int do_test_ctx(struct __sk_buff *skb)
|
|
{
|
|
skb->cb[0] = CB_MAGIC;
|
|
printk("len %d hash %d protocol %d\n", skb->len, skb->hash,
|
|
skb->protocol);
|
|
printk("cb %d ingress_ifindex %d ifindex %d\n", skb->cb[0],
|
|
skb->ingress_ifindex, skb->ifindex);
|
|
|
|
return BPF_OK;
|
|
}
|
|
|
|
/* Test: Ensure skb->cb[] buffer is cleared */
|
|
SEC("test_cb")
|
|
int do_test_cb(struct __sk_buff *skb)
|
|
{
|
|
printk("cb0: %x cb1: %x cb2: %x\n", skb->cb[0], skb->cb[1],
|
|
skb->cb[2]);
|
|
printk("cb3: %x cb4: %x\n", skb->cb[3], skb->cb[4]);
|
|
|
|
return BPF_OK;
|
|
}
|
|
|
|
/* Test: Verify skb data can be read */
|
|
SEC("test_data")
|
|
int do_test_data(struct __sk_buff *skb)
|
|
{
|
|
void *data = (void *)(long)skb->data;
|
|
void *data_end = (void *)(long)skb->data_end;
|
|
struct iphdr *iph = data;
|
|
|
|
if (data + sizeof(*iph) > data_end) {
|
|
printk("packet truncated\n");
|
|
return BPF_DROP;
|
|
}
|
|
|
|
printk("src: %x dst: %x\n", iph->saddr, iph->daddr);
|
|
|
|
return BPF_OK;
|
|
}
|
|
|
|
#define IP_CSUM_OFF offsetof(struct iphdr, check)
|
|
#define IP_DST_OFF offsetof(struct iphdr, daddr)
|
|
#define IP_SRC_OFF offsetof(struct iphdr, saddr)
|
|
#define IP_PROTO_OFF offsetof(struct iphdr, protocol)
|
|
#define TCP_CSUM_OFF offsetof(struct tcphdr, check)
|
|
#define UDP_CSUM_OFF offsetof(struct udphdr, check)
|
|
#define IS_PSEUDO 0x10
|
|
|
|
static inline int rewrite(struct __sk_buff *skb, uint32_t old_ip,
|
|
uint32_t new_ip, int rw_daddr)
|
|
{
|
|
int ret, off = 0, flags = IS_PSEUDO;
|
|
uint8_t proto;
|
|
|
|
ret = bpf_skb_load_bytes(skb, IP_PROTO_OFF, &proto, 1);
|
|
if (ret < 0) {
|
|
printk("bpf_l4_csum_replace failed: %d\n", ret);
|
|
return BPF_DROP;
|
|
}
|
|
|
|
switch (proto) {
|
|
case IPPROTO_TCP:
|
|
off = TCP_CSUM_OFF;
|
|
break;
|
|
|
|
case IPPROTO_UDP:
|
|
off = UDP_CSUM_OFF;
|
|
flags |= BPF_F_MARK_MANGLED_0;
|
|
break;
|
|
|
|
case IPPROTO_ICMPV6:
|
|
off = offsetof(struct icmp6hdr, icmp6_cksum);
|
|
break;
|
|
}
|
|
|
|
if (off) {
|
|
ret = bpf_l4_csum_replace(skb, off, old_ip, new_ip,
|
|
flags | sizeof(new_ip));
|
|
if (ret < 0) {
|
|
printk("bpf_l4_csum_replace failed: %d\n");
|
|
return BPF_DROP;
|
|
}
|
|
}
|
|
|
|
ret = bpf_l3_csum_replace(skb, IP_CSUM_OFF, old_ip, new_ip, sizeof(new_ip));
|
|
if (ret < 0) {
|
|
printk("bpf_l3_csum_replace failed: %d\n", ret);
|
|
return BPF_DROP;
|
|
}
|
|
|
|
if (rw_daddr)
|
|
ret = bpf_skb_store_bytes(skb, IP_DST_OFF, &new_ip, sizeof(new_ip), 0);
|
|
else
|
|
ret = bpf_skb_store_bytes(skb, IP_SRC_OFF, &new_ip, sizeof(new_ip), 0);
|
|
|
|
if (ret < 0) {
|
|
printk("bpf_skb_store_bytes() failed: %d\n", ret);
|
|
return BPF_DROP;
|
|
}
|
|
|
|
return BPF_OK;
|
|
}
|
|
|
|
/* Test: Verify skb data can be modified */
|
|
SEC("test_rewrite")
|
|
int do_test_rewrite(struct __sk_buff *skb)
|
|
{
|
|
uint32_t old_ip, new_ip = 0x3fea8c0;
|
|
int ret;
|
|
|
|
ret = bpf_skb_load_bytes(skb, IP_DST_OFF, &old_ip, 4);
|
|
if (ret < 0) {
|
|
printk("bpf_skb_load_bytes failed: %d\n", ret);
|
|
return BPF_DROP;
|
|
}
|
|
|
|
if (old_ip == 0x2fea8c0) {
|
|
printk("out: rewriting from %x to %x\n", old_ip, new_ip);
|
|
return rewrite(skb, old_ip, new_ip, 1);
|
|
}
|
|
|
|
return BPF_OK;
|
|
}
|
|
|
|
static inline int __do_push_ll_and_redirect(struct __sk_buff *skb)
|
|
{
|
|
uint64_t smac = SRC_MAC, dmac = DST_MAC;
|
|
int ret, ifindex = DST_IFINDEX;
|
|
struct ethhdr ehdr;
|
|
|
|
ret = bpf_skb_change_head(skb, 14, 0);
|
|
if (ret < 0) {
|
|
printk("skb_change_head() failed: %d\n", ret);
|
|
}
|
|
|
|
ehdr.h_proto = __constant_htons(ETH_P_IP);
|
|
memcpy(&ehdr.h_source, &smac, 6);
|
|
memcpy(&ehdr.h_dest, &dmac, 6);
|
|
|
|
ret = bpf_skb_store_bytes(skb, 0, &ehdr, sizeof(ehdr), 0);
|
|
if (ret < 0) {
|
|
printk("skb_store_bytes() failed: %d\n", ret);
|
|
return BPF_DROP;
|
|
}
|
|
|
|
return bpf_redirect(ifindex, 0);
|
|
}
|
|
|
|
SEC("push_ll_and_redirect_silent")
|
|
int do_push_ll_and_redirect_silent(struct __sk_buff *skb)
|
|
{
|
|
return __do_push_ll_and_redirect(skb);
|
|
}
|
|
|
|
SEC("push_ll_and_redirect")
|
|
int do_push_ll_and_redirect(struct __sk_buff *skb)
|
|
{
|
|
int ret, ifindex = DST_IFINDEX;
|
|
|
|
ret = __do_push_ll_and_redirect(skb);
|
|
if (ret >= 0)
|
|
printk("redirected to %d\n", ifindex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline void __fill_garbage(struct __sk_buff *skb)
|
|
{
|
|
uint64_t f = 0xFFFFFFFFFFFFFFFF;
|
|
|
|
bpf_skb_store_bytes(skb, 0, &f, sizeof(f), 0);
|
|
bpf_skb_store_bytes(skb, 8, &f, sizeof(f), 0);
|
|
bpf_skb_store_bytes(skb, 16, &f, sizeof(f), 0);
|
|
bpf_skb_store_bytes(skb, 24, &f, sizeof(f), 0);
|
|
bpf_skb_store_bytes(skb, 32, &f, sizeof(f), 0);
|
|
bpf_skb_store_bytes(skb, 40, &f, sizeof(f), 0);
|
|
bpf_skb_store_bytes(skb, 48, &f, sizeof(f), 0);
|
|
bpf_skb_store_bytes(skb, 56, &f, sizeof(f), 0);
|
|
bpf_skb_store_bytes(skb, 64, &f, sizeof(f), 0);
|
|
bpf_skb_store_bytes(skb, 72, &f, sizeof(f), 0);
|
|
bpf_skb_store_bytes(skb, 80, &f, sizeof(f), 0);
|
|
bpf_skb_store_bytes(skb, 88, &f, sizeof(f), 0);
|
|
}
|
|
|
|
SEC("fill_garbage")
|
|
int do_fill_garbage(struct __sk_buff *skb)
|
|
{
|
|
__fill_garbage(skb);
|
|
printk("Set initial 96 bytes of header to FF\n");
|
|
return BPF_OK;
|
|
}
|
|
|
|
SEC("fill_garbage_and_redirect")
|
|
int do_fill_garbage_and_redirect(struct __sk_buff *skb)
|
|
{
|
|
int ifindex = DST_IFINDEX;
|
|
__fill_garbage(skb);
|
|
printk("redirected to %d\n", ifindex);
|
|
return bpf_redirect(ifindex, 0);
|
|
}
|
|
|
|
/* Drop all packets */
|
|
SEC("drop_all")
|
|
int do_drop_all(struct __sk_buff *skb)
|
|
{
|
|
printk("dropping with: %d\n", BPF_DROP);
|
|
return BPF_DROP;
|
|
}
|
|
|
|
char _license[] SEC("license") = "GPL";
|