Skip to content

Commit

Permalink
init ebpf tc prgram from managing gtp u tunnel and ue interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
tariromukute committed Mar 14, 2024
0 parents commit b3ef411
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 0 deletions.
16 changes: 16 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM ubuntu:latest

RUN apt-get update && \
apt-get install -y clang llvm libbpf-dev iproute2 iputils-ping && \
rm -rf /var/lib/apt/lists/*

WORKDIR /home

COPY gtpu.bpf.c ./

# RUN clang -O2 -emit-llvm -c gtpu.bpf.c -o - | llc -march=bpf -mcpu=probe -filetype=obj -o gtpu.bpf.o

# RUN tc qdisc add dev eth0 clsact
# RUN tc filter add dev eth0 ingress bpf direct-action obj gtpu.bpf.o sec .text
# RUN tc filter show dev eth0
# RUN tc filter show dev eth0 ingress
72 changes: 72 additions & 0 deletions NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

## Running on docker

```bash
# --cap-add=NET_ADMIN to allow tc to add dev (avoids error: RTNETLINK answers: Operation not permitted)
# --cap-add=SYS_ADMIN to allow ebpf to mount to bpf fs (avoids error: mount --make-private /sys/fs/bpf failed: Operation not permitted)

docker run \
--cap-add=NET_ADMIN \
--cap-add=SYS_ADMIN \
-it \
-v /sys/kernel/debug/:/sys/kernel/debug/ \
-v `pwd`/:/home ubuntu:latest

apt update

apt install clang llvm libbpf-dev iproute2 iputils-ping

clang -O2 -emit-llvm -c gtpu.bpf.c -o - | \
llc -march=bpf -mcpu=probe -filetype=obj -o gtpu.bpf.o

tc qdisc add dev eth0 clsact
tc filter add dev eth0 ingress bpf direct-action obj gtpu.bpf.o sec .text
tc filter add dev eth0 egress bpf direct-action obj gtpu.bpf.o sec .text
tc filter show dev eth0
tc filter show dev eth0 ingress

# On seperate terminal, print logs
cat /sys/kernel/debug/tracing/trace_pipe
```

Build with Dockerfile

Either:
1. create container and the mount the debugfs inside the container.

```bash
docker run \
-it \
--cap-add=NET_ADMIN \
--cap-add=SYS_ADMIN \
-v /sys/kernel/debug/:/sys/kernel/debug/ \
-v `pwd`/:/home \
tariromukute/tc-gtpu:latest

mount -t debugfs debugfs /sys/kernel/debug
```

Or
2. Create a debugfs volume and then add it to the container

```bash
docker volume create --driver local --opt type=debugfs --opt device=debugfs debugfs

docker run \
-it \
--cap-add=NET_ADMIN \
--cap-add=SYS_ADMIN \
-v debugfs:/sys/kernel/debug:rw \
tariromukute/tc-gtpu:latest
```

Or
3. Docker compose file

## Useful Resources

- [Understanding tc “direct action” mode for BPF](https://qmonnet.github.io/whirl-offload/2020/04/11/tc-bpf-direct-action/)
- [Run eBPF Programs in Docker using docker-bpf](https://hemslo.io/run-ebpf-programs-in-docker-using-docker-bpf/)
- https://github.com/edgecomllc/eupf/issues/509
- http://arthurchiao.art/blog/differentiate-bpf-redirects/

Empty file added README.md
Empty file.
18 changes: 18 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: "3"

volumes:
debugfs:
driver: local
driver_opts:
type: debugfs
device: debugfs

services:
tc-gtpu:
image: tariromukute/tc-gtpu:latest
command: tail -f /dev/null
cap_add:
- NET_ADMIN
- SYS_ADMIN
volumes:
- debugfs:/sys/kernel/debug:rw
9 changes: 9 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/env sh

# Mount bpffs and debugfs if not present already
if [[ $(/bin/mount | /bin/grep /sys/fs/bpf -c) -eq 0 ]]; then
/bin/mount bpffs /sys/fs/bpf -t bpf;
fi
if [[ $(/bin/mount | /bin/grep /sys/kernel/debug -c) -eq 0 ]]; then
/bin/mount debugfs /sys/kernel/debug -t debugfs;
fi
203 changes: 203 additions & 0 deletions gtpu.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/pkt_cls.h>
#include <linux/swab.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_endian.h>
#include <linux/ip.h>
#include "gtpu.h"

#define TC_ACT_UNSPEC (-1)
#define TC_ACT_OK 0
#define TC_ACT_SHOT 2
#define TC_ACT_STOLEN 4
#define TC_ACT_REDIRECT 7

#define ETH_P_IP 0x0800 /* Internet Protocol packet */
#define __section(x) __attribute__((section(x), used))

#define DEFAULT_QFI 9
const int gtpu_interface = 2;
const __u32 gtpu_dest_ip = 0xac120002; // 172.18.0.2

SEC("tnl_if_ingress")
int tnl_if_ingress_fn(struct __sk_buff *skb)
{
/**
* The function is attached to the ingress of the tunnel interface associated with a UE.
* The receives the data after it have been decapsulated at teh interface that receives
* the GTPU packets (gtpu ingress). This functions does nothing to the data except for other
* util functions like recording the number of received packets etc. It returns TC_ACT_OK
*/
bpf_printk("Received packet on tnl_if_ingress\n");
void *data_end = (void *)(unsigned long long)skb->data_end;
void *data = (void *)(unsigned long long)skb->data;
struct ethhdr *eth = data;

if (data + sizeof(struct ethhdr) > data_end)
return TC_ACT_SHOT;

if (eth->h_proto == ___constant_swab16(ETH_P_IP)) {
bpf_printk("Got IP packet");
return TC_ACT_OK;
} else {
return TC_ACT_OK;
}
};

SEC("tnl_if_egress")
int tnl_if_egress_fn(struct __sk_buff *skb)
{
/**
* This function is attached to the egress of the tunnel interface associated with a UE.
* The function encapsulates the IP data with a GTPU header. If the tnl_interfaces_map
* contains data for the index of the interface, it will encapsulate the IP data based on
* the values from the map i.e., qfi, tied etc. If the map doesn't have values, the qfi
* will equal the default qfi defined (DEFAULT_QFI) and tied will equal the interface
* index. The function then redirects the output to the egress of the gtpu_interface
* interface (the global const volatile variable gtpu_interface). It sets the destination
* ip to the gtpu_dest_ip (global const volatile variable gtpu_dest_ip).
*/
bpf_printk("Received packet on tnl_if_egress\n");
void *data_end = (void *)(unsigned long long)skb->data_end;
void *data = (void *)(unsigned long long)skb->data;
struct ethhdr *eth = data;

if (data + sizeof(struct ethhdr) > data_end)
return TC_ACT_SHOT;

if (eth->h_proto == ___constant_swab16(ETH_P_IP)) {
// TODO: Logic to fetch QFI and TEID from maps or use defaults
__u32 qfi = DEFAULT_QFI;
__u32 teid = skb->ifindex; // Use interface index as default TEID


int roomlen = sizeof(struct iphdr) + sizeof(struct gtpuhdr);
// Check if there is enough headroom in the skb
int ret = bpf_skb_adjust_room(skb, roomlen, BPF_ADJ_ROOM_MAC, 0);
if (ret) {
bpf_printk("error calling skb adjust room.\n");
return TC_ACT_SHOT;
}



// Adjust pointers to new packet location after possible linearization
data_end = (void *)(unsigned long long)skb->data_end;
data = (void *)(unsigned long long)skb->data;
eth = data;

// TODO: Build GTPU header
struct gtpuhdr gtpu_hdr = {
.teid = bpf_htonl(teid),
};

// TODO: Build the IP header to deliver the GTPU packet to
struct iphdr *ip = (struct iphdr*)(eth + 1);
__be32 saddr = ip->saddr;
__be32 daddr = bpf_htonl(gtpu_dest_ip);
ip->saddr = daddr;
ip->daddr = saddr;
ip->ttl -= 1;
ip->check = 0;
// ip->check = ip_fast_csum(ip, ip->ihl);

int offset = sizeof(struct ethhdr);
ret = bpf_skb_store_bytes(skb, offset, ip, sizeof(struct iphdr),
BPF_F_RECOMPUTE_CSUM);
if (ret) {
bpf_printk("error storing ip header\n");
return TC_ACT_SHOT;
}

offset += sizeof(struct iphdr);
ret = bpf_skb_store_bytes(skb, offset, &gtpu_hdr, sizeof(struct gtpuhdr),
BPF_F_RECOMPUTE_CSUM);
if (ret) {
bpf_printk("error storing gtpu header\n");
return TC_ACT_SHOT;
}

// TODO: Redirect to egress of gtpu_interface
skb->protocol = bpf_htons(0x86dd); // Change protocol to IPv6
// skb->priority = TC_PRIO_CONTROL;
// skb->mark = 0;
// skb->vlan_present = 0;
// skb->vlan_tci = 0;
// skb->tc_index = 0;
// skb->tc_classid = 0;
// skb->cb = 0;
// skb->dev = gtpu_interface;
// skb->offload_fwd_mark = 0;
return bpf_redirect(gtpu_interface, 0); // 0 for egress
// bpf_redirect_neigh might be a better call

} else {
return TC_ACT_OK;
}
}


SEC("gtpu_ingress")
int gtpu_ingress_fn(struct __sk_buff *skb)
{
/**
* The function is attched to the ingress of the interface attached to the external
* network, the gtpu_interface. The function decapsulates the GTPU header of the
* incoming packets and sends it to the ingress of the tunnel interface (tnl_interface).
* The function gets the tunnel interface by checking the tied_map to get interface to
* send to based on the tied of the incoming GTPU packet. If the tied_map does not contain
* a valid value, it will treat the tied as the interface index to send to.
*/
bpf_printk("Received packet on gtpu_ingress\n");
void *data_end = (void *)(unsigned long long)skb->data_end;
void *data = (void *)(unsigned long long)skb->data;
struct ethhdr *eth = data;

if (data + sizeof(struct ethhdr) > data_end)
return TC_ACT_SHOT;

if (eth->h_proto == ___constant_swab16(ETH_P_IP)) {
// TODO: Check if it's a GTPU packet

// TODO: Logic to get the tunnel interface from tied_map or use default

// TODO: Parse and remove GTPU header

// TODO: Send packet to tunnel interface using bpf_redirect(tnl_interface, BPF_F_INGRESS)
return TC_ACT_OK;
// bpf_redirect_peer might be a better call
} else {
return TC_ACT_OK;
}
};

SEC("gtpu_egress")
int gtpu_egress_fn(struct __sk_buff *skb)
{
/**
* The function is attched to the egress of the interface attached to the external
* network. It receives GTPU encapuslated packets from the tunnel interfaces. This
* functions does nothing to the packet data except for other util functions like
* recording the number of received packets etc. It the sends the packet out.
*/
bpf_printk("Received packet on gtpu_egress\n");
void *data_end = (void *)(unsigned long long)skb->data_end;
void *data = (void *)(unsigned long long)skb->data;
struct ethhdr *eth = data;

if (data + sizeof(struct ethhdr) > data_end)
return TC_ACT_SHOT;

if (eth->h_proto == ___constant_swab16(ETH_P_IP)) {


bpf_printk("Got IP packet");
return TC_ACT_OK;
} else {
return TC_ACT_OK;
}
};

char __license[] __section("license") = "GPL";
57 changes: 57 additions & 0 deletions gtpu.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <linux/types.h>
#include <linux/unistd.h>

#define GTP_UDP_PORT 2152u

/* Version: GTPv1, Protocol Type: GTP, Others: 0 */
#define GTP_FLAGS 0x30

#define GTPU_ECHO_REQUEST (1)
#define GTPU_ECHO_RESPONSE (2)
#define GTPU_ERROR_INDICATION (26)
#define GTPU_SUPPORTED_EXTENSION_HEADERS_NOTIFICATION (31)
#define GTPU_END_MARKER (254)
#define GTPU_G_PDU (255)

struct gtpuhdr {
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int pn : 1;
unsigned int s : 1;
unsigned int e : 1;
unsigned int spare : 1;
unsigned int pt : 1;
unsigned int version : 3;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int version : 3;
unsigned int pt : 1;
unsigned int spare : 1;
unsigned int e : 1;
unsigned int s : 1;
unsigned int pn : 1;
#else
#error "Please fix <bits/endian.h>"
#endif
__u8 message_type;
__u16 message_length;
__u32 teid;
} __attribute__((packed));

struct gtpu_hdr_ext {
__u16 sqn;
__u8 npdu;
__u8 next_ext;
} __attribute__((packed));

#define GTPU_EXT_TYPE_PDU_SESSION_CONTAINER (0x85)
#define PDU_SESSION_CONTAINER_PDU_TYPE_DL_PSU (0x00)
#define PDU_SESSION_CONTAINER_PDU_TYPE_UL_PSU (0x01)

struct gtp_pdu_session_container {
__u8 length;
__u8 spare1 : 4;
__u8 pdu_type : 4;
__u8 qfi : 6;
__u8 rqi : 1;
__u8 spare2 : 1;
__u8 next_ext;
} __attribute__((packed));

0 comments on commit b3ef411

Please sign in to comment.