github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/examples/tcprtt_sockops/main.go (about)

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