Skip to content

Commit ed35685

Browse files
committed
Add epf2go example inspecting L3 IP header to acquire the protocol type.
Signed-off-by: David-VTUK <david.holder@gmail.com> revert README.org to README.md to retain formatting Signed-off-by: David-VTUK <david.holder@gmail.com> Resize diagram image Signed-off-by: David-VTUK <david.holder@gmail.com>
1 parent 8d53e6f commit ed35685

File tree

9 files changed

+367
-0
lines changed

9 files changed

+367
-0
lines changed

xdp-bpf2go-example/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
xdp-bpf2go-example

xdp-bpf2go-example/README.md

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# xdp-bpf2go-example
2+
3+
This examples leverages a `Go` based User Space application that reads from an eBPF map populated with packet protocol information.
4+
5+
The Kernel space application leverages `xdp` to inspect incoming packets and performs the following:
6+
7+
* Check for a valid IP packet
8+
* Extract the `protocol` header value
9+
* Place in a eBPF array map
10+
11+
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.
12+
13+
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:
14+
15+
```
16+
+----------------------------------------------------+
17+
| eBPF Array Map |
18+
| |
19+
| +------+ +------+ +------+ +------+ +------+ |
20+
| | 0 | | 1 | | 2 | | ... | | 254 | |
21+
| |------| |------| |------| |------| |------| |
22+
| | ? | | ? | | ? | | ... | | ? | |
23+
| +------+ +------+ +------+ +------+ +------+ |
24+
| |
25+
+----------------------------------------------------+
26+
```
27+
28+
Where `key` represents the IP Protocol Number, and `value` counting the number of instances.
29+
30+
The Go application reads this map array, and leverages a helper function to map the protocol number to a name and output to stdout:
31+
32+
![Diagram depicting ebpf kernel and user land applications](ebpf-diagram.png)
33+
34+
## To Run:
35+
36+
* Change the interface name if required in main.go
37+
38+
```
39+
go generate
40+
41+
go run .
42+
```
43+
44+
## To Build:
45+
46+
```
47+
go generate
48+
go build .
49+
```

xdp-bpf2go-example/ebpf-diagram.png

642 KB
Loading

xdp-bpf2go-example/gen.go

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package main
2+
3+
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go packetProtocol packetProtocol.c

xdp-bpf2go-example/go.mod

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module github.com/xdp-project/bpf-examples/xdp-bpf2go-example
2+
3+
go 1.22.10
4+
5+
require github.com/cilium/ebpf v0.17.2
6+
7+
require golang.org/x/sys v0.30.0 // indirect

xdp-bpf2go-example/go.sum

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
github.com/cilium/ebpf v0.17.2 h1:IQTaTVu0vKA8WTemFuBnxW9YbAwMkJVKHsNHW4lHv/g=
2+
github.com/cilium/ebpf v0.17.2/go.mod h1:9X5VAsIOck/nCAp0+nCSVzub1Q7x+zKXXItTMYfNE+E=
3+
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
4+
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
5+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
6+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
7+
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
8+
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
9+
github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM=
10+
github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
11+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
12+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
13+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
14+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
15+
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
16+
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
17+
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
18+
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
19+
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
20+
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
21+
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
22+
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
23+
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
24+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
25+
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
26+
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
27+
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
28+
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

xdp-bpf2go-example/l3-packet.png

85.4 KB
Loading

xdp-bpf2go-example/main.go

+232
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
package main
2+
3+
import (
4+
"C"
5+
"log"
6+
"net"
7+
"os"
8+
"os/signal"
9+
"time"
10+
11+
"github.com/cilium/ebpf"
12+
"github.com/cilium/ebpf/link"
13+
"github.com/cilium/ebpf/rlimit"
14+
)
15+
16+
func main() {
17+
// Remove resource limits for kernels <5.11.
18+
if err := rlimit.RemoveMemlock(); err != nil {
19+
log.Fatal("Removing memlock:", err)
20+
}
21+
22+
// Load the compiled eBPF ELF and load it into the kernel.
23+
var objs packetProtocolObjects
24+
if err := loadPacketProtocolObjects(&objs, nil); err != nil {
25+
log.Fatal("Loading eBPF objects:", err)
26+
}
27+
defer objs.Close()
28+
29+
ifname := "eno2" // Change this to an interface on your machine.
30+
iface, err := net.InterfaceByName(ifname)
31+
if err != nil {
32+
log.Fatalf("Getting interface %s: %s", ifname, err)
33+
}
34+
35+
// Attach count_packets to the network interface.
36+
link, err := link.AttachXDP(link.XDPOptions{
37+
Program: objs.GetPacketProtocol,
38+
Interface: iface.Index,
39+
})
40+
if err != nil {
41+
log.Fatal("Attaching XDP:", err)
42+
}
43+
defer link.Close()
44+
45+
log.Printf("Analysing packets on %s..", ifname)
46+
47+
tick := time.Tick(time.Second)
48+
stop := make(chan os.Signal, 5)
49+
signal.Notify(stop, os.Interrupt)
50+
for {
51+
select {
52+
case <-tick:
53+
// log.Print(objs.ProtocolCount)
54+
printMap(objs.ProtocolCount)
55+
if err != nil {
56+
log.Fatal("Map lookup:", err)
57+
}
58+
case <-stop:
59+
log.Print("Received signal, exiting..")
60+
return
61+
}
62+
}
63+
}
64+
65+
func printMap(protocol_map *ebpf.Map) {
66+
67+
// Iterate through the map
68+
var key uint32
69+
var value uint64
70+
iterator := protocol_map.Iterate()
71+
for iterator.Next(&key, &value) {
72+
if value != 0 && key != 0 {
73+
protocolName := translate(int(key))
74+
log.Printf("Key: %d, Protocol Name: %s Value: %d\n", key, protocolName, value)
75+
}
76+
}
77+
if err := iterator.Err(); err != nil {
78+
log.Fatalf("Error during map iteration: %v", err)
79+
}
80+
}
81+
82+
func translate(protocolNumber int) string {
83+
if name, exists := ProtocolMap[protocolNumber]; exists {
84+
return name
85+
}
86+
return "Unknown"
87+
}
88+
89+
var ProtocolMap = map[int]string{
90+
0: "HOPOPT",
91+
1: "ICMP",
92+
2: "IGMP",
93+
3: "GGP",
94+
4: "IPv4",
95+
5: "ST",
96+
6: "TCP",
97+
7: "CBT",
98+
8: "EGP",
99+
9: "IGP",
100+
10: "BBN-RCC-MON",
101+
11: "NVP-II",
102+
12: "PUP",
103+
13: "ARGUS",
104+
14: "EMCON",
105+
15: "XNET",
106+
16: "CHAOS",
107+
17: "UDP",
108+
18: "MUX",
109+
19: "DCN-MEAS",
110+
20: "HMP",
111+
21: "PRM",
112+
22: "XNS-IDP",
113+
23: "TRUNK-1",
114+
24: "TRUNK-2",
115+
25: "LEAF-1",
116+
26: "LEAF-2",
117+
27: "RDP",
118+
28: "IRTP",
119+
29: "ISO-TP4",
120+
30: "NETBLT",
121+
31: "MFE-NSP",
122+
32: "MERIT-INP",
123+
33: "DCCP",
124+
34: "3PC",
125+
35: "IDPR",
126+
36: "XTP",
127+
37: "DDP",
128+
38: "IDPR-CMTP",
129+
39: "TP++",
130+
40: "IL",
131+
41: "IPv6",
132+
42: "SDRP",
133+
43: "IPv6-Route",
134+
44: "IPv6-Frag",
135+
45: "IDRP",
136+
46: "RSVP",
137+
47: "GRE",
138+
48: "DSR",
139+
49: "BNA",
140+
50: "ESP",
141+
51: "AH",
142+
52: "I-NLSP",
143+
53: "SWIPE: (deprecated)",
144+
54: "NARP",
145+
55: "Min-IPv4",
146+
56: "TLSP",
147+
57: "SKIP",
148+
58: "IPv6-ICMP",
149+
59: "IPv6-NoNxt",
150+
60: "IPv6-Opts",
151+
62: "CFTP",
152+
64: "SAT-EXPAK",
153+
65: "KRYPTOLAN",
154+
66: "RVD",
155+
67: "IPPC",
156+
69: "SAT-MON",
157+
70: "VISA",
158+
71: "IPCV",
159+
72: "CPNX",
160+
73: "CPHB",
161+
74: "WSN",
162+
75: "PVP",
163+
76: "BR-SAT-MON",
164+
77: "SUN-ND",
165+
78: "WB-MON",
166+
79: "WB-EXPAK",
167+
80: "ISO-IP",
168+
81: "VMTP",
169+
82: "SECURE-VMTP",
170+
83: "VINES",
171+
84: "IPTM",
172+
85: "NSFNET-IGP",
173+
86: "DGP",
174+
87: "TCF",
175+
88: "EIGRP",
176+
89: "OSPFIGP",
177+
90: "Sprite-RPC",
178+
91: "LARP",
179+
92: "MTP",
180+
93: "AX.25",
181+
94: "IPIP",
182+
95: "MICP",
183+
96: "SCC-SP",
184+
97: "ETHERIP",
185+
98: "ENCAP",
186+
100: "GMTP",
187+
101: "IFMP",
188+
102: "PNNI",
189+
103: "PIM",
190+
104: "ARIS",
191+
105: "SCPS",
192+
106: "QNX",
193+
107: "A/N",
194+
108: "IPComp",
195+
109: "SNP",
196+
110: "Compaq-Peer",
197+
111: "IPX-in-IP",
198+
112: "VRRP",
199+
113: "PGM",
200+
115: "L2TP",
201+
116: "DDX",
202+
117: "IATP",
203+
118: "STP",
204+
119: "SRP",
205+
120: "UTI",
206+
121: "SMP",
207+
122: "SM: (deprecated)",
208+
123: "PTP",
209+
124: "ISIS: over: IPv4",
210+
125: "FIRE",
211+
126: "CRTP",
212+
127: "CRUDP",
213+
128: "SSCOPMCE",
214+
129: "IPLT",
215+
130: "SPS",
216+
131: "PIPE",
217+
132: "SCTP",
218+
133: "FC",
219+
134: "RSVP-E2E-IGNORE",
220+
135: "Mobility: Header",
221+
136: "UDPLite",
222+
137: "MPLS-in-IP",
223+
138: "manet",
224+
139: "HIP",
225+
140: "Shim6",
226+
141: "WESP",
227+
142: "ROHC",
228+
143: "Ethernet",
229+
144: "AGGFRAG",
230+
145: "NSH",
231+
255: "Reserved",
232+
}

xdp-bpf2go-example/packetProtocol.c

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// go:build ignore
2+
3+
#include <linux/bpf.h>
4+
#include <bpf/bpf_helpers.h>
5+
#include <linux/if_ether.h>
6+
#include <linux/ip.h>
7+
8+
struct{
9+
__uint(type, BPF_MAP_TYPE_ARRAY);
10+
__type(key, __u32);
11+
__type(value, __u64);
12+
__uint(max_entries, 255);
13+
} protocol_count SEC(".maps");
14+
15+
SEC("xdp")
16+
int get_packet_protocol(struct xdp_md *ctx) {
17+
18+
void *data_end = (void *)(long)ctx->data_end;
19+
void *data = (void *)(long)ctx->data;
20+
21+
// Parse Ethernet header
22+
struct ethhdr *eth = data;
23+
if ((void *)(eth + 1) > data_end) {
24+
return XDP_PASS;
25+
}
26+
27+
// Check if the packet is an IP packet
28+
if (eth->h_proto != __constant_htons(ETH_P_IP)) {
29+
return XDP_PASS;
30+
}
31+
32+
// Parse IP header
33+
struct iphdr *ip = data + sizeof(struct ethhdr);
34+
if ((void *)(ip + 1) > data_end) {
35+
return XDP_PASS;
36+
}
37+
38+
__u32 key = ip->protocol; // Using IP protocol as the key
39+
__u64 *count = bpf_map_lookup_elem(&protocol_count, &key);
40+
if (count) {
41+
__sync_fetch_and_add(count, 1);
42+
}
43+
44+
return XDP_PASS;
45+
}
46+
47+
char __license[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)