diff --git a/devcontainer-example/README.md b/devcontainer-example/README.md new file mode 100644 index 00000000..9b7c3245 --- /dev/null +++ b/devcontainer-example/README.md @@ -0,0 +1,3 @@ +# Devcontainer Examples + +The following directory includes examples of [Visual Studio Code Devcontainer](https://code.visualstudio.com/docs/devcontainers/containers) configurations for developing ebpf applications. \ No newline at end of file diff --git a/devcontainer-example/ebpf-and-go/Dockerfile b/devcontainer-example/ebpf-and-go/Dockerfile new file mode 100644 index 00000000..9faa8086 --- /dev/null +++ b/devcontainer-example/ebpf-and-go/Dockerfile @@ -0,0 +1,33 @@ +FROM mcr.microsoft.com/devcontainers/go:1.23-bookworm + +RUN apt update + +RUN apt install -y build-essential +RUN apt install -y pkg-config +RUN apt install -y clang +RUN apt install -y llvm +RUN apt install -y git +RUN apt install -y libelf-dev +RUN apt install -y libpcap-dev +RUN apt install -y iproute2 +RUN apt install -y iputils-ping +RUN apt install -y linux-headers-generic +RUN apt install -y libbpf-dev +RUN apt install -y linux-libc-dev +RUN apt install -y cmake +RUN apt install -y libpcap-dev +RUN apt install -y libcap-ng-dev +RUN apt install -y libbfd-dev +RUN apt install -y gcc-multilib +RUN apt install -y m4 +RUN ln -sf /usr/include/asm-generic/ /usr/include/asm +RUN apt install -y libcap-dev +RUN ln -sf /usr/local/go/bin/go /bin/go +RUN ln -sf /usr/include/asm-generic/ /usr/include/asm + +RUN mkdir /sources/ +WORKDIR /sources/ +RUN git clone --recurse-submodules https://github.com/libbpf/bpftool.git +RUN make -C bpftool/src/ install +RUN git clone --recurse-submodules https://github.com/xdp-project/xdp-tools.git +RUN make -C xdp-tools/ install \ No newline at end of file diff --git a/devcontainer-example/ebpf-and-go/devcontainer.json b/devcontainer-example/ebpf-and-go/devcontainer.json new file mode 100644 index 00000000..5a8b2dad --- /dev/null +++ b/devcontainer-example/ebpf-and-go/devcontainer.json @@ -0,0 +1,22 @@ +{ + "name": "Go-ebpf-env", + "build": {"dockerfile": "Dockerfile"}, + + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + "settings": {}, + "extensions": [ + "streetsidesoftware.code-spell-checker", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.cpptools", + "nicknickolaev.ebpf-assembly", + "golang.Go" + ] + } + }, + + "runArgs": ["--network=host"], + "privileged": true +} \ No newline at end of file diff --git a/xdp-bpf2go-example/.gitignore b/xdp-bpf2go-example/.gitignore new file mode 100644 index 00000000..431f7845 --- /dev/null +++ b/xdp-bpf2go-example/.gitignore @@ -0,0 +1,3 @@ +xdp-bpf2go-example +**bpfeb.go +**bpfel.go \ No newline at end of file diff --git a/xdp-bpf2go-example/README.md b/xdp-bpf2go-example/README.md new file mode 100644 index 00000000..43546a11 --- /dev/null +++ b/xdp-bpf2go-example/README.md @@ -0,0 +1,49 @@ +# xdp-bpf2go-example + +This examples leverages a `Go` based User Space application that reads from an eBPF map populated with packet protocol information. + +The Kernel space application leverages `xdp` to inspect incoming packets and performs the following: + +* Check for a valid IP packet +* Extract the `protocol` header value +* Place in a eBPF array map + +The protocol header field will contain a [protocol number](https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers) that corresponds to the respective protocol. For example, `ICMP=1`, `IGMP=2`, and so on. + +The Kernel Space application keeps count of each recorded instance of a protocol and stores it into a array map, which can be visualised as: + +``` ++----------------------------------------------------+ +| eBPF Array Map | +| | +| +------+ +------+ +------+ +------+ +------+ | +| | 0 | | 1 | | 2 | | ... | | 254 | | +| |------| |------| |------| |------| |------| | +| | ? | | ? | | ? | | ... | | ? | | +| +------+ +------+ +------+ +------+ +------+ | +| | ++----------------------------------------------------+ +``` + +Where `key` represents the IP Protocol Number, and `value` counting the number of instances. + +The Go application reads this map array, and leverages a helper function to map the protocol number to a name and output to stdout: + +![Diagram depicting ebpf kernel and user land applications](ebpf-diagram.png) + +## To Run: + +* Change the interface name if required in main.go + +``` +go generate + +go run . +``` + +## To Build: + +``` +go generate +go build . +``` \ No newline at end of file diff --git a/xdp-bpf2go-example/ebpf-diagram.png b/xdp-bpf2go-example/ebpf-diagram.png new file mode 100644 index 00000000..1f00cf34 Binary files /dev/null and b/xdp-bpf2go-example/ebpf-diagram.png differ diff --git a/xdp-bpf2go-example/gen.go b/xdp-bpf2go-example/gen.go new file mode 100644 index 00000000..9a8e1edc --- /dev/null +++ b/xdp-bpf2go-example/gen.go @@ -0,0 +1,3 @@ +package main + +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go packetProtocol packetProtocol.c diff --git a/xdp-bpf2go-example/go.mod b/xdp-bpf2go-example/go.mod new file mode 100644 index 00000000..e56c520d --- /dev/null +++ b/xdp-bpf2go-example/go.mod @@ -0,0 +1,7 @@ +module github.com/xdp-project/bpf-examples/xdp-bpf2go-example + +go 1.22.10 + +require github.com/cilium/ebpf v0.17.2 + +require golang.org/x/sys v0.30.0 // indirect diff --git a/xdp-bpf2go-example/go.sum b/xdp-bpf2go-example/go.sum new file mode 100644 index 00000000..4d7621fa --- /dev/null +++ b/xdp-bpf2go-example/go.sum @@ -0,0 +1,28 @@ +github.com/cilium/ebpf v0.17.2 h1:IQTaTVu0vKA8WTemFuBnxW9YbAwMkJVKHsNHW4lHv/g= +github.com/cilium/ebpf v0.17.2/go.mod h1:9X5VAsIOck/nCAp0+nCSVzub1Q7x+zKXXItTMYfNE+E= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= +github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/xdp-bpf2go-example/l3-packet.png b/xdp-bpf2go-example/l3-packet.png new file mode 100644 index 00000000..319ff819 Binary files /dev/null and b/xdp-bpf2go-example/l3-packet.png differ diff --git a/xdp-bpf2go-example/main.go b/xdp-bpf2go-example/main.go new file mode 100644 index 00000000..fa4e9ec9 --- /dev/null +++ b/xdp-bpf2go-example/main.go @@ -0,0 +1,241 @@ +package main + +import ( + "C" + "log" + "net" + "os" + "os/signal" + "time" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/rlimit" +) + +func main() { + // Check if running as root + if os.Geteuid() != 0 { + log.Fatal("This program must be run as root!") + } + + // Remove resource limits for kernels <5.11. + if err := rlimit.RemoveMemlock(); err != nil { + log.Fatal("Removing memlock:", err) + } + + // Load the compiled eBPF ELF and load it into the kernel. + var objs packetProtocolObjects + if err := loadPacketProtocolObjects(&objs, nil); err != nil { + log.Fatal("Loading eBPF objects:", err) + } + defer objs.Close() + + // Capture user input from command-line arguments + if len(os.Args) < 2 { + log.Fatal("Please provide a network interface name as an argument") + } + ifname := os.Args[1] // Get the interface name from the first argument + + iface, err := net.InterfaceByName(ifname) + if err != nil { + log.Fatalf("Getting interface %s: %s", ifname, err) + } + + // Attach count_packets to the network interface. + link, err := link.AttachXDP(link.XDPOptions{ + Program: objs.GetPacketProtocol, + Interface: iface.Index, + }) + if err != nil { + log.Fatal("Attaching XDP:", err) + } + defer link.Close() + + log.Printf("Analysing packets on %s..", ifname) + + tick := time.Tick(time.Second) + stop := make(chan os.Signal, 5) + signal.Notify(stop, os.Interrupt) + for { + select { + case <-tick: + printMap(objs.ProtocolCount) + if err != nil { + log.Fatal("Map lookup:", err) + } + case <-stop: + log.Print("Received signal, exiting..") + return + } + } +} + +func printMap(protocol_map *ebpf.Map) { + + // Iterate through the map + var key uint32 + var value uint64 + iterator := protocol_map.Iterate() + for iterator.Next(&key, &value) { + if value != 0 && key != 0 { + protocolName := translate(int(key)) + log.Printf("Key: %d, Protocol Name: %s Value: %d\n", key, protocolName, value) + } + } + if err := iterator.Err(); err != nil { + log.Fatalf("Error during map iteration: %v", err) + } +} + +func translate(protocolNumber int) string { + if name, exists := ProtocolMap[protocolNumber]; exists { + return name + } + return "Unknown" +} + +var ProtocolMap = map[int]string{ + 0: "HOPOPT", + 1: "ICMP", + 2: "IGMP", + 3: "GGP", + 4: "IPv4", + 5: "ST", + 6: "TCP", + 7: "CBT", + 8: "EGP", + 9: "IGP", + 10: "BBN-RCC-MON", + 11: "NVP-II", + 12: "PUP", + 13: "ARGUS", + 14: "EMCON", + 15: "XNET", + 16: "CHAOS", + 17: "UDP", + 18: "MUX", + 19: "DCN-MEAS", + 20: "HMP", + 21: "PRM", + 22: "XNS-IDP", + 23: "TRUNK-1", + 24: "TRUNK-2", + 25: "LEAF-1", + 26: "LEAF-2", + 27: "RDP", + 28: "IRTP", + 29: "ISO-TP4", + 30: "NETBLT", + 31: "MFE-NSP", + 32: "MERIT-INP", + 33: "DCCP", + 34: "3PC", + 35: "IDPR", + 36: "XTP", + 37: "DDP", + 38: "IDPR-CMTP", + 39: "TP++", + 40: "IL", + 41: "IPv6", + 42: "SDRP", + 43: "IPv6-Route", + 44: "IPv6-Frag", + 45: "IDRP", + 46: "RSVP", + 47: "GRE", + 48: "DSR", + 49: "BNA", + 50: "ESP", + 51: "AH", + 52: "I-NLSP", + 53: "SWIPE: (deprecated)", + 54: "NARP", + 55: "Min-IPv4", + 56: "TLSP", + 57: "SKIP", + 58: "IPv6-ICMP", + 59: "IPv6-NoNxt", + 60: "IPv6-Opts", + 62: "CFTP", + 64: "SAT-EXPAK", + 65: "KRYPTOLAN", + 66: "RVD", + 67: "IPPC", + 69: "SAT-MON", + 70: "VISA", + 71: "IPCV", + 72: "CPNX", + 73: "CPHB", + 74: "WSN", + 75: "PVP", + 76: "BR-SAT-MON", + 77: "SUN-ND", + 78: "WB-MON", + 79: "WB-EXPAK", + 80: "ISO-IP", + 81: "VMTP", + 82: "SECURE-VMTP", + 83: "VINES", + 84: "IPTM", + 85: "NSFNET-IGP", + 86: "DGP", + 87: "TCF", + 88: "EIGRP", + 89: "OSPFIGP", + 90: "Sprite-RPC", + 91: "LARP", + 92: "MTP", + 93: "AX.25", + 94: "IPIP", + 95: "MICP", + 96: "SCC-SP", + 97: "ETHERIP", + 98: "ENCAP", + 100: "GMTP", + 101: "IFMP", + 102: "PNNI", + 103: "PIM", + 104: "ARIS", + 105: "SCPS", + 106: "QNX", + 107: "A/N", + 108: "IPComp", + 109: "SNP", + 110: "Compaq-Peer", + 111: "IPX-in-IP", + 112: "VRRP", + 113: "PGM", + 115: "L2TP", + 116: "DDX", + 117: "IATP", + 118: "STP", + 119: "SRP", + 120: "UTI", + 121: "SMP", + 122: "SM: (deprecated)", + 123: "PTP", + 124: "ISIS: over: IPv4", + 125: "FIRE", + 126: "CRTP", + 127: "CRUDP", + 128: "SSCOPMCE", + 129: "IPLT", + 130: "SPS", + 131: "PIPE", + 132: "SCTP", + 133: "FC", + 134: "RSVP-E2E-IGNORE", + 135: "Mobility: Header", + 136: "UDPLite", + 137: "MPLS-in-IP", + 138: "manet", + 139: "HIP", + 140: "Shim6", + 141: "WESP", + 142: "ROHC", + 143: "Ethernet", + 144: "AGGFRAG", + 145: "NSH", + 255: "Reserved", +} diff --git a/xdp-bpf2go-example/packetProtocol.c b/xdp-bpf2go-example/packetProtocol.c new file mode 100644 index 00000000..5b0b8aec --- /dev/null +++ b/xdp-bpf2go-example/packetProtocol.c @@ -0,0 +1,44 @@ +// go:build ignore + +#include +#include +#include +#include +#include + +struct{ + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, __u32); + __type(value, __u64); + __uint(max_entries, 255); +} protocol_count SEC(".maps"); + +SEC("xdp") +int get_packet_protocol(struct xdp_md *ctx) { + + void *data_end = (void *)(long)ctx->data_end; + void *data = (void *)(long)ctx->data; + struct ethhdr *eth = data; + struct iphdr *ip = data + sizeof(struct ethhdr); + __u32 key = ip->protocol; // Using IP protocol as the key + __u64 *count = bpf_map_lookup_elem(&protocol_count, &key); + + // Parse Ethernet header + if ((void *)(eth + 1) > data_end) + return XDP_PASS; + + // Check if the packet is an IP packet + if (eth->h_proto != bpf_htons(ETH_P_IP)) + return XDP_PASS; + + // Parse IP header + if ((void *)(ip + 1) > data_end) + return XDP_PASS; + + if (count) + __sync_fetch_and_add(count, 1); + + return XDP_PASS; +} + +char __license[] SEC("license") = "GPL"; \ No newline at end of file