Skip to content

Instantly share code, notes, and snippets.

@ibaldin
Created August 24, 2017 19:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ibaldin/716d70d490b44e05d15db59ee983c0c0 to your computer and use it in GitHub Desktop.
Save ibaldin/716d70d490b44e05d15db59ee983c0c0 to your computer and use it in GitHub Desktop.
#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/tcp.h>
#include <bcc/proto.h>
// modify random packet's TCP payload by swapping two 16-bit aligned
// values to preserve TCP checksum.
BPF_ARRAY(flowcnt, uint32_t, 3);
// get IP protocol
static inline struct iphdr* parse_ipv4(void *data, u64 nh_off, void *data_end) {
struct iphdr *iph = data + nh_off;
if ((void*)&iph[1] > data_end)
return 0;
return iph;
}
// get IPv6 protocol
/*
static inline int parse_ipv6(void *data, u64 nh_off, void *data_end) {
struct ipv6hdr *ip6h = data + nh_off;
if ((void*)&ip6h[1] > data_end)
return 0;
return ip6h->nexthdr;
}
*/
// swap u16-s at offset 1 and 2, but do not go outside the payload end
/*
static inline int swapu16(uint16_t *start, void *data_end, int off1, int off2) {
uint16_t poff1, poff2;
//uint16_t *p1, *p2;
// check first octet
if (((void*)&start[off1] > data_end) || ((void*)&start[off2] > data_end))
return 0;
// check second octet
if (((void*)&start[off1+1] > data_end) || ((void*)&start[off2 + 1] > data_end))
return 0;
//p1 = &start[off1];
//p2 = &start[off2];
poff1 = start[off1];
poff2 = start[off2];
start[off2] = poff1;
start[off1] = poff2;
//*p1 = poff2;
//*p2 = poff1;
return 1;
}
*/
static inline int swapu16(uint16_t *start, void *data_end, int off1, int off2) {
uint16_t poff1, poff2;
if (((void*)&start[off1 + 1] > data_end) || ((void*)&start[off2 + 1] > data_end))
return 0;
poff1 = start[off1];
poff2 = start[off2];
start[off2] = poff1;
start[off1] = poff2;
return 1;
}
// get TCP SYN flag value. BPF seems to like doing things like this
// otherwise you get permission errors on packet access when loading (not executing)
// the program, which indicates verifier issues
static inline struct tcphdr* parse_tcp(void *data, u64 nh_off, void *data_end) {
struct tcphdr *tcph = data + nh_off;
if ((void*)&tcph[1] > data_end)
return 0;
return tcph;
}
static inline uint16_t parse_tcp_pld(void *data, u64 nh_off, void *data_end) {
uint16_t *pld = data + nh_off;
if ((void*)&pld[1] > data_end)
return 0;
return pld;
}
// note that context type is passed in as a parameter by BCC
int xdp_tcp_mod_prog(struct CTXTYPE *ctx) {
void* data_end = (void*)(long)ctx->data_end;
void* data = (void*)(long)ctx->data;
struct ethhdr *eth = data;
// always pass packets
int rc = XDP_PASS;
uint16_t h_proto;
uint64_t nh_off = 0;
struct iphdr *ip_hdr;
struct tcphdr *tcp_hdr;
uint16_t *tcp_pld;
nh_off = sizeof(*eth);
if (data + nh_off > data_end)
return rc;
h_proto = eth->h_proto;
if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) {
struct vlan_hdr *vhdr;
vhdr = data + nh_off;
nh_off += sizeof(struct vlan_hdr);
if (data + nh_off > data_end)
return rc;
}
if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) {
struct vlan_hdr *vhdr;
vhdr = data + nh_off;
nh_off += sizeof(struct vlan_hdr);
if (data + nh_off > data_end)
return rc;
}
// add IPv6 code as needed
if (h_proto == htons(ETH_P_IP)) {
ip_hdr = parse_ipv4(data, nh_off, data_end);
} else
ip_hdr = 0;
if (ip_hdr != 0) {
if (ip_hdr->protocol == IPPROTO_TCP) {
nh_off += ip_hdr->ihl << 2;
tcp_hdr = parse_tcp(data, nh_off, data_end);
if (tcp_hdr != 0) {
nh_off += tcp_hdr->doff << 2;
tcp_pld = parse_tcp_pld(data, nh_off, data_end);
// note that syn packets may not contain any payload
if (tcp_pld != 0) {
int ret = swapu16(tcp_pld, data_end, 2, 4);
bpf_trace_printk("tcp_header length %d ret=%d\n", tcp_hdr->doff, ret);
}
}
}
}
return rc;
}
#!/usr/bin/env python
# modify TCP payloads to keep checksum
# probability distribution ?
# using either XDP or TC BPF hooks
from bcc import BPF
import pyroute2
import time
import sys
import argparse
flags = 0
parser = argparse.ArgumentParser()
parser.add_argument("interface", help="interface to bind program to")
parser.add_argument("-s", "--source", help="TCP flow source IP address")
parser.add_argument("-p", "--port", help="TCP flow destination port")
parser.add_argument("-t", "--tc", action="store_true", help="Use TC ingress hook instead of XDP")
args = parser.parse_args()
device = args.interface
if args.tc:
flags |= 2 << 0
mode = BPF.SCHED_CLS
ctxtype = "__sk_buff"
else:
mode = BPF.XDP
ctxtype = "xdp_md"
print("Binding to {} using method {}".format(device, mode))
# load from file
b = BPF(src_file = "tcp_modify.c", cflags=["-w", "-DCTXTYPE=%s" % ctxtype], debug = 0)
fn = b.load_func("xdp_tcp_mod_prog", mode)
if mode == BPF.XDP:
b.attach_xdp(device, fn, flags)
else:
ip = pyroute2.IPRoute()
ipdb = pyroute2.IPDB(nl=ip)
idx = ipdb.interfaces[device].index
ip.tc("add", "clsact", idx)
ip.tc("add-filter", "bpf", idx, ":1", fd=fn.fd, name=fn.name,
parent="ffff:fff2", classid=1, direct_action=True)
flowcnt = b["flowcnt"]
#prev = [0] * 256
#print("Printing SYN packets for ports 22, 80 and other, hit CTRL+C to stop")
while 1:
try:
b.trace_print()
for k in flowcnt.keys():
val = flowcnt.get(k).value
i = k.value
print("{}: {} syns".format(i, val))
time.sleep(1)
except KeyboardInterrupt:
print("Removing filter from device")
break;
if mode == BPF.XDP:
b.remove_xdp(device)
else:
ip.tc("del", "clsact", idx)
ipdb.release()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment