Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MapIterator support for Queues/Stacks + further testing #1349

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* [tcp_close](tcprtt/) - Log RTT of IPv4 TCP connections using eBPF CO-RE helpers.
* XDP - Attach a program to a network interface to process incoming packets.
* [xdp](xdp/) - Print packet counts by IPv4 source address.
* [queue](queue/) - Print IPv4 Address and Unix Timestamp of each incoming packet.
* Add your use case(s) here!

## How to run
Expand Down
125 changes: 125 additions & 0 deletions examples/queue/bpf_bpfeb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added examples/queue/bpf_bpfeb.o
Binary file not shown.
125 changes: 125 additions & 0 deletions examples/queue/bpf_bpfel.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added examples/queue/bpf_bpfel.o
Binary file not shown.
115 changes: 115 additions & 0 deletions examples/queue/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// This program demonstrates attaching an eBPF program to a network interface
// with XDP (eXpress Data Path). The program parses the IPv4 source address
// from packets and pushes the address alongside the computed packet arrival timestamp
// into a Queue. This is just an example and probably does not represent the most
// efficient way to perform such a task. Another potential solution would be to use
// an HashMap with a small __u64 arrays associated to each IPv4 address (key).
// In both the two ways it is possible to lose some packet if (a) queue is not large
// enough or the packet processing time is slow or (b) if the associated array is
// smaller than the actual received packet from an address.
// The userspace program (Go code in this file) prints the contents
// of the map to stdout every second, parsing the raw structure into a human-readable
// IPv4 address and Unix timestamp.
// It is possible to modify the XDP program to drop or redirect packets
// as well -- give it a try!
// This example depends on bpf_link, available in Linux kernel version 5.7 or newer.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like you mention yourself, this isn't a prime example of queues or stacks. I understand you want an example to showcase those map types. A good example is small and doesn't add to much extra info. I honestly don't think it adds much to your PR, I suggest dropping it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point and I agree.
Let me know if the ktime -> Unix time conversion utility function could be useful somehow. I'd leave it up to the user and wouldn't insert it as part of the library.

package main

import (
"encoding/binary"
"fmt"
"log"
"net"
"net/netip"
"os"
"strings"
"syscall"
"time"

"github.com/cilium/ebpf"
"github.com/cilium/ebpf/link"
)

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go bpf xdp.c -- -I../headers

func main() {
if len(os.Args) < 2 {
log.Fatalf("Please specify a network interface")
}

// Look up the network interface by name.
ifaceName := os.Args[1]
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
log.Fatalf("lookup network iface %q: %s", ifaceName, err)
}

// Load pre-compiled programs into the kernel.
objs := bpfObjects{}
if err := loadBpfObjects(&objs, nil); err != nil {
log.Fatalf("loading objects: %s", err)
}
defer objs.Close()

// Attach the program.
l, err := link.AttachXDP(link.XDPOptions{
Program: objs.XdpProgFunc,
Interface: iface.Index,
})
if err != nil {
log.Fatalf("could not attach XDP program: %s", err)
}
defer l.Close()

log.Printf("Attached XDP program to iface %q (index %d)", iface.Name, iface.Index)
log.Printf("Press Ctrl-C to exit and remove the program")

// Retrieve boot time once, and use it for every later ktime conversions
bootTime := getSysBoot()

// Print the contents of the BPF queue (packet source IP address and timestamp).
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for range ticker.C {
s, err := formatMapContents(objs.QueueWithData, bootTime)
if err != nil {
log.Printf("Error reading map: %s", err)
continue
}
log.Printf("Map contents:\n%s", s)
}
}

// formatMapContents formats an output string with the content of the provided map.
//
// For each entry, the function outputs a line containing the human-readable IPv4 address
// retrieved from the packet structure formatted and the converted ktime_ns into Unix Time
//
// In case of error or empty map, the function returns the corresponding error.
func formatMapContents(m *ebpf.Map, bootTime time.Time) (string, error) {
var (
sb strings.Builder
val bpfPacketData
)
iter := m.Iterate()
for iter.Next(nil, &val) {
// Convert the __u32 into human-readable IPv4
a4 := [4]byte{}
binary.LittleEndian.PutUint32(a4[:], val.SrcIp)
addr := netip.AddrFrom4(a4)

// Convert ktime timestamp into Time struct, adding the retrieved
// timestamp to the previously computer boot time
t := bootTime.Add(time.Duration(val.Timestamp) * time.Nanosecond)

sb.WriteString(fmt.Sprintf("\t%s - %s\n", addr, t))
}
return sb.String(), iter.Err()
}

// Retrieve system boot time and convert it into Time struct
func getSysBoot() time.Time {
sysInfo := &syscall.Sysinfo_t{}
syscall.Sysinfo(sysInfo)
return time.Now().Add(-time.Duration(sysInfo.Uptime) * time.Second)
}