Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ hugo.linux
/.hugo_build.lock

# IntelliJ project files
.idea/
.idea/

# Vagrant files
.vagrant/
5 changes: 5 additions & 0 deletions code/ebpf-xdp-tc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.o
CMakeLists.txt

out/
cmake-build-debug/
7 changes: 7 additions & 0 deletions code/ebpf-xdp-tc/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM docker.io/alpine:3.15

WORKDIR /app

COPY out/ebpf-xdp-tc ./

ENTRYPOINT ["/app/ebpf-xdp-tc"]
58 changes: 58 additions & 0 deletions code/ebpf-xdp-tc/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# https://taskfile.dev

version: '3'

tasks:
default:

build-ebpf:
cmds:
- mkdir -p ebpf/bin
- |
clang \
-Wno-unused-value \
-Wno-pointer-sign \
-Wno-compare-distinct-pointer-types \
-Wunused \
-Wall \
-fno-stack-protector \
-fno-ident \
-g \
-O2 -emit-llvm \
ebpf/main.c \
-c -o - | llc -march=bpf -mcpu=probe -filetype=obj -o ebpf/bin/probe.o

build-bin:
deps:
- build-ebpf
env:
GOOS: linux
GOARCH: amd64
cmds:
- mkdir -p out/
- go build -o out/ebpf-xdp-tc -trimpath -a -installsuffix=cgo -ldflags "-w -s -linkmode external -extldflags -static" ./

build-docker:
deps:
- build-bin
cmds:
- buildah bud -t ebpf-xdp-tc .

run-in-container:
deps:
- build-docker
cmds:
- |
podman run \
--rm \
-ti \
-v /sys:/sys:ro \
--security-opt=seccomp=unconfined \
--network=libvirt \
--ip "10.10.1.1" \
--name ebpf-xdp-tc \
--cap-add=CAP_SYS_ADMIN \
--cap-add=CAP_NET_RAW \
--cap-add=CAP_NET_BIND_SERVICE \
--cap-add=CAP_NET_ADMIN \
ebpf-xdp-tc
22 changes: 22 additions & 0 deletions code/ebpf-xdp-tc/Vagrantfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Vagrant.configure("2") do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.

# Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search.
config.vm.box = "peru/windows-10-enterprise-x64-eval"
config.vm.box_version = "20220202.01"

config.vm.provider "libvirt" do |libvirt|
libvirt.management_network_mode = 'veryisoled'
end


config.vm.define :win_victim do |win_victim|
win_victim.vm.network :private_network,
:libvirt__network_name => "containers"
end

config.vm.box_check_update = false
end
53 changes: 53 additions & 0 deletions code/ebpf-xdp-tc/ebpf/helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/in.h>
#include <linux/tcp.h>
#include <linux/udp.h>

#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

#include "types.h"

static inline unsigned short checksum(unsigned short *buf, int bufsz) {
unsigned long sum = 0;

while (bufsz > 1) {
sum += *buf;
buf++;
bufsz -= 2;
}

if (bufsz == 1) {
sum += *(unsigned char *) buf;
}

sum = (sum & 0xffff) + (sum >> 16);
sum = (sum & 0xffff) + (sum >> 16);

return ~sum;
}

static inline struct tcphdr *extract_tcp_meta(struct observed_packet *pkt, void *iph, __u64 off, void *data_end) {
struct tcphdr *hdr = iph + off;
if ((void *) hdr + sizeof(*hdr) > data_end) {
return NULL;
}
pkt->transport_proto = TCP;
pkt->sourcePort = bpf_ntohs(hdr->source);
pkt->destPort = bpf_ntohs(hdr->dest);

return hdr;
}

static inline struct udphdr *extract_udp_meta(struct observed_packet *pkt, void *iph, __u64 off, void *data_end) {
struct udphdr *hdr = iph + off;
if ((void *) hdr + sizeof(*hdr) > data_end) {
return NULL;
}

pkt->transport_proto = UDP;
pkt->sourcePort = bpf_ntohs(hdr->source);
pkt->destPort = bpf_ntohs(hdr->dest);
return hdr;
}
222 changes: 222 additions & 0 deletions code/ebpf-xdp-tc/ebpf/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#include <linux/bpf.h>
#include <linux/pkt_cls.h>

#include "helpers.h"

#define IP_FRAGMENTED 65343

char LICENSE[] SEC("license") = "Dual MIT/GPL";

struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
} perf_observed_packets SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} ring_observed_packets SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, sizeof(struct two_tuple));
__type(value, sizeof(struct two_tuple));
__uint(max_entries, 1024);
} conn_track SEC(".maps");

SEC("classifier/egress")
int egress(struct __sk_buff *skb) {
bpf_printk("new packet captured on egress (TC)\n");
void *data = (void *) (long) skb->data;
void *data_end = (void *) (long) skb->data_end;

struct ethhdr *eth = data;

if ((void *) eth + sizeof(*eth) > data_end) {
return TC_ACT_OK;
}

if(eth->h_proto != ETH_P_IP && eth->h_proto != ETH_P_IPV6) {
return TC_ACT_OK;
}

struct iphdr *iph = data + sizeof(*eth);
if ((void *) iph + sizeof(*iph) > data_end) {
return TC_ACT_OK;
}

/* do not support fragmented packets as L4 headers may be missing */
if (iph->frag_off & IP_FRAGMENTED) {
return TC_ACT_OK;
}

if (iph->protocol != IPPROTO_TCP) {
bpf_printk("Packet's not TCP - forwarding");
return TC_ACT_OK;
}

struct tcphdr *tcp = (void *) iph + sizeof(*iph);
if ((void *) tcp + sizeof(*tcp) > data_end) {
return TC_ACT_SHOT;
}

struct two_tuple dst = {
.ip = iph->daddr,
.port = tcp->dest
};

struct two_tuple *orig_src = bpf_map_lookup_elem(&conn_track, &dst);

if (orig_src == NULL) {
bpf_printk("No translation found - pass it through");
return TC_ACT_OK;
}

bpf_printk("Restore original source IP");

iph->saddr = orig_src->ip;
tcp->source = orig_src->port;

iph->tos = 7 << 2;
iph->check = 0;
iph->check = checksum((unsigned short *) iph, sizeof(struct iphdr));

return TC_ACT_OK;
};

SEC("classifier/ingress")
int ingress(struct __sk_buff *skb) {
bpf_printk("new packet captured on ingress (TC)");
void *data = (void *) (long) skb->data;
void *data_end = (void *) (long) skb->data_end;

struct ethhdr *eth = data;

if ((void *) eth + sizeof(*eth) > data_end) {
return TC_ACT_OK;
}

struct iphdr *iph = data + sizeof(*eth);
if ((void *) iph + sizeof(*iph) > data_end) {
return TC_ACT_OK;
}

/* do not support fragmented packets as L4 headers may be missing */
if (iph->frag_off & IP_FRAGMENTED) {
return TC_ACT_OK;
}

if (iph->protocol != IPPROTO_TCP) {
bpf_printk("Packet's not TCP - forwarding");
return TC_ACT_OK;
}

if (iph->daddr == 16845322) {
bpf_printk("We're the destination - don't touch it");
return TC_ACT_OK;
}

struct tcphdr *tcp = (void *) iph + sizeof(*iph);
if ((void *) tcp + sizeof(*tcp) > data_end) {
return TC_ACT_SHOT;
}

struct two_tuple src = {
.ip = iph->saddr,
.port = tcp->source
};

struct two_tuple dst = {
.ip = iph->daddr,
.port = tcp->dest
};

bpf_map_update_elem(&conn_track, &src, &dst, 0);

bpf_printk("Forward packet to localhost (TC)");
iph->daddr = 16845322;
iph->tos = 7 << 2;
iph->check = 0;
iph->check = checksum((unsigned short *) iph, sizeof(struct iphdr));
return TC_ACT_OK;
};

static inline enum xdp_action extract_meta(struct xdp_md *ctx, struct observed_packet *pkt) {
void *data = (void *) (long) ctx->data;
void *data_end = (void *) (long) ctx->data_end;
struct ethhdr *eth = data;
__u16 proto;

if (data + sizeof(struct ethhdr) > data_end) {
bpf_printk("Packet apparently not ethernet");
return XDP_DROP;
}

proto = eth->h_proto;
if (proto != bpf_htons(ETH_P_IP) && proto != bpf_htons(ETH_P_IPV6)) {
bpf_printk("Not an IP packet");
return XDP_PASS;
}

struct iphdr *iph = data + sizeof(*eth);
if ((void *) iph + sizeof(struct iphdr) > data_end) {
return XDP_DROP;
}

/* do not support fragmented packets as L4 headers may be missing */
if (iph->frag_off & IP_FRAGMENTED) {
return XDP_DROP;
}

pkt->sourceIp = iph->saddr;
pkt->destIp = iph->daddr;

__u8 ip_proto = iph->protocol;

if (ip_proto == IPPROTO_TCP) {
struct tcphdr *tcph = extract_tcp_meta(pkt, (void *) iph, sizeof(struct iphdr), data_end);
// if ACK flag is set we just pass it through because it belongs to an already established connection
if (tcph == NULL || tcph->ack) {
return XDP_PASS;
}
} else if (ip_proto == IPPROTO_UDP) {
struct udphdr *udph = extract_udp_meta(pkt, (void *) iph, sizeof(struct iphdr), data_end);
// could also check if we're the source
if (udph == NULL) {
return XDP_PASS;
}
}

return XDP_PASS;
}

SEC("xdp/perf")
int xdp_ingress_perf(struct xdp_md *ctx) {
struct observed_packet pkt;

enum xdp_action action = extract_meta(ctx, &pkt);

if (pkt.destIp == 0 || pkt.sourceIp == 0) {
return action;
}

if (!bpf_perf_event_output(ctx, &perf_observed_packets, BPF_F_CURRENT_CPU, &pkt, sizeof(struct observed_packet))) {
bpf_printk("Failed to submit observed packet");
}

return XDP_PASS;
}

SEC("xdp/ring")
int xdp_ingress_ring(struct xdp_md *ctx) {
struct observed_packet pkt = {};

enum xdp_action action = extract_meta(ctx, &pkt);

if (pkt.destIp == 0 || pkt.sourceIp == 0) {
return action;
}

bpf_ringbuf_output(&ring_observed_packets, &pkt, sizeof(pkt), 0);

return XDP_PASS;
}
Loading