github.com/cilium/ebpf@v0.10.0/examples/tcprtt_sockops/main.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  // This program demonstrates attaching an eBPF program to
     5  // a cgroupv2 path and using sockops to process TCP socket events.
     6  // It prints the IPs/ports/RTT information every time TCP sockets
     7  // update their internal RTT value.
     8  // It supports only IPv4 for this example.
     9  //
    10  // Sample output:
    11  //
    12  // examples# go run -exec sudo ./tcprtt_sockops
    13  // 2022/08/14 20:58:03 eBPF program loaded and attached on cgroup /sys/fs/cgroup/unified
    14  // 2022/08/14 20:58:03 Src addr        Port   -> Dest addr       Port   RTT (ms)
    15  // 2022/08/14 20:58:09 10.0.1.205      54844  -> 20.42.73.25     443    67
    16  // 2022/08/14 20:58:09 10.0.1.205      54844  -> 20.42.73.25     443    67
    17  // 2022/08/14 20:58:33 10.0.1.205      38620  -> 140.82.121.4    443    26
    18  // 2022/08/14 20:58:33 10.0.1.205      38620  -> 140.82.121.4    443    26
    19  // 2022/08/14 20:58:43 34.67.40.146    45380  -> 10.0.1.205      5201   106
    20  // 2022/08/14 20:58:43 34.67.40.146    45380  -> 10.0.1.205      5201   106
    21  
    22  package main
    23  
    24  import (
    25  	"bytes"
    26  	"encoding/binary"
    27  	"errors"
    28  	"log"
    29  	"net"
    30  	"os"
    31  	"os/signal"
    32  	"path/filepath"
    33  	"syscall"
    34  
    35  	"github.com/cilium/ebpf"
    36  	"github.com/cilium/ebpf/internal"
    37  	"github.com/cilium/ebpf/link"
    38  	"github.com/cilium/ebpf/ringbuf"
    39  	"github.com/cilium/ebpf/rlimit"
    40  
    41  	"golang.org/x/sys/unix"
    42  )
    43  
    44  // $BPF_CLANG and $BPF_CFLAGS are set by the Makefile.
    45  //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -tags "linux" -cc $BPF_CLANG -cflags $BPF_CFLAGS -type rtt_event bpf tcprtt_sockops.c -- -I../headers
    46  
    47  func main() {
    48  	stopper := make(chan os.Signal, 1)
    49  	signal.Notify(stopper, os.Interrupt, syscall.SIGTERM)
    50  
    51  	// Allow the current process to lock memory for eBPF resources.
    52  	if err := rlimit.RemoveMemlock(); err != nil {
    53  		log.Fatal(err)
    54  	}
    55  
    56  	// Find the path to a cgroup enabled to version 2
    57  	cgroupPath, err := findCgroupPath()
    58  	if err != nil {
    59  		log.Fatal(err)
    60  	}
    61  
    62  	// Load pre-compiled programs and maps into the kernel.
    63  	objs := bpfObjects{}
    64  	if err := loadBpfObjects(&objs, nil); err != nil {
    65  		log.Fatalf("loading objects: %v", err)
    66  	}
    67  	defer objs.Close()
    68  
    69  	// Attach ebpf program to a cgroupv2
    70  	link, err := link.AttachCgroup(link.CgroupOptions{
    71  		Path:    cgroupPath,
    72  		Program: objs.bpfPrograms.BpfSockopsCb,
    73  		Attach:  ebpf.AttachCGroupSockOps,
    74  	})
    75  	if err != nil {
    76  		log.Fatal(err)
    77  	}
    78  	defer link.Close()
    79  
    80  	log.Printf("eBPF program loaded and attached on cgroup %s\n", cgroupPath)
    81  
    82  	rd, err := ringbuf.NewReader(objs.bpfMaps.RttEvents)
    83  	if err != nil {
    84  		log.Fatalf("opening ringbuf reader: %s", err)
    85  	}
    86  	defer rd.Close()
    87  
    88  	log.Printf("%-15s %-6s -> %-15s %-6s %-6s",
    89  		"Src addr",
    90  		"Port",
    91  		"Dest addr",
    92  		"Port",
    93  		"RTT (ms)",
    94  	)
    95  	go readLoop(rd)
    96  
    97  	// Wait
    98  	<-stopper
    99  }
   100  
   101  func findCgroupPath() (string, error) {
   102  	cgroupPath := "/sys/fs/cgroup"
   103  
   104  	var st syscall.Statfs_t
   105  	err := syscall.Statfs(cgroupPath, &st)
   106  	if err != nil {
   107  		return "", err
   108  	}
   109  	isCgroupV2Enabled := st.Type == unix.CGROUP2_SUPER_MAGIC
   110  	if !isCgroupV2Enabled {
   111  		cgroupPath = filepath.Join(cgroupPath, "unified")
   112  	}
   113  	return cgroupPath, nil
   114  }
   115  
   116  func readLoop(rd *ringbuf.Reader) {
   117  	// bpfRttEvent is generated by bpf2go.
   118  	var event bpfRttEvent
   119  	for {
   120  		record, err := rd.Read()
   121  		if err != nil {
   122  			if errors.Is(err, ringbuf.ErrClosed) {
   123  				log.Println("received signal, exiting..")
   124  				return
   125  			}
   126  			log.Printf("reading from reader: %s", err)
   127  			continue
   128  		}
   129  
   130  		// Parse the ringbuf event entry into a bpfRttEvent structure.
   131  		if err := binary.Read(bytes.NewBuffer(record.RawSample), internal.NativeEndian, &event); err != nil {
   132  			log.Printf("parsing ringbuf event: %s", err)
   133  			continue
   134  		}
   135  
   136  		log.Printf("%-15s %-6d -> %-15s %-6d %-6d",
   137  			intToIP(event.Saddr),
   138  			event.Sport,
   139  			intToIP(event.Daddr),
   140  			event.Dport,
   141  			event.Srtt,
   142  		)
   143  	}
   144  }
   145  
   146  // intToIP converts IPv4 number to net.IP
   147  func intToIP(ipNum uint32) net.IP {
   148  	ip := make(net.IP, 4)
   149  	binary.BigEndian.PutUint32(ip, ipNum)
   150  	return ip
   151  }