gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/xdp/cmd/tcpdump.go (about) 1 // Copyright 2022 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cmd 16 17 import ( 18 "bytes" 19 "context" 20 _ "embed" 21 "errors" 22 "fmt" 23 "log" 24 25 "github.com/cilium/ebpf" 26 "github.com/google/subcommands" 27 "golang.org/x/sys/unix" 28 "gvisor.dev/gvisor/pkg/buffer" 29 "gvisor.dev/gvisor/pkg/tcpip/header" 30 "gvisor.dev/gvisor/pkg/tcpip/link/sniffer" 31 "gvisor.dev/gvisor/pkg/tcpip/stack" 32 "gvisor.dev/gvisor/pkg/xdp" 33 "gvisor.dev/gvisor/runsc/flag" 34 ) 35 36 //go:embed bpf/tcpdump_ebpf.o 37 var tcpdumpProgram []byte 38 39 // TcpdumpCommand is a subcommand for capturing incoming packets. 40 type TcpdumpCommand struct { 41 device string 42 deviceIndex int 43 } 44 45 // Name implements subcommands.Command.Name. 46 func (*TcpdumpCommand) Name() string { 47 return "tcpdump" 48 } 49 50 // Synopsis implements subcommands.Command.Synopsis. 51 func (*TcpdumpCommand) Synopsis() string { 52 return "Run tcpdump-like program that blocks incoming packets." 53 } 54 55 // Usage implements subcommands.Command.Usage. 56 func (*TcpdumpCommand) Usage() string { 57 return "tcpdump -device <device> or -devidx <device index>" 58 } 59 60 // SetFlags implements subcommands.Command.SetFlags. 61 func (pc *TcpdumpCommand) SetFlags(fs *flag.FlagSet) { 62 fs.StringVar(&pc.device, "device", "", "which device to attach to") 63 fs.IntVar(&pc.deviceIndex, "devidx", 0, "which device to attach to") 64 } 65 66 // Execute implements subcommands.Command.Execute. 67 func (pc *TcpdumpCommand) Execute(context.Context, *flag.FlagSet, ...any) subcommands.ExitStatus { 68 if err := pc.execute(); err != nil { 69 fmt.Printf("%v", err) 70 return subcommands.ExitFailure 71 } 72 return subcommands.ExitSuccess 73 } 74 75 func (pc *TcpdumpCommand) execute() error { 76 iface, err := getIface(pc.device, pc.deviceIndex) 77 if err != nil { 78 return fmt.Errorf("%v", err) 79 } 80 81 // Load into the kernel. 82 spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader(tcpdumpProgram)) 83 if err != nil { 84 return fmt.Errorf("failed to load spec: %v", err) 85 } 86 87 var objects struct { 88 Program *ebpf.Program `ebpf:"xdp_prog"` 89 SockMap *ebpf.Map `ebpf:"sock_map"` 90 } 91 if err := spec.LoadAndAssign(&objects, nil); err != nil { 92 return fmt.Errorf("failed to load program: %v", err) 93 } 94 defer func() { 95 if err := objects.Program.Close(); err != nil { 96 log.Printf("failed to close program: %v", err) 97 } 98 if err := objects.SockMap.Close(); err != nil { 99 log.Printf("failed to close sock map: %v", err) 100 } 101 }() 102 103 _, cleanup, err := attach(objects.Program, iface) 104 if err != nil { 105 return fmt.Errorf("failed to attach: %v", err) 106 } 107 defer cleanup() 108 109 controlBlock, err := xdp.New( 110 uint32(iface.Index), 0 /* queueID */, xdp.DefaultOpts()) 111 if err != nil { 112 return fmt.Errorf("failed to create socket: %v", err) 113 } 114 115 // Insert our AF_XDP socket into the BPF map that dictates where 116 // packets are redirected to. 117 key := uint32(0) 118 val := controlBlock.UMEM.SockFD() 119 if err := objects.SockMap.Update(&key, &val, 0 /* flags */); err != nil { 120 return fmt.Errorf("failed to insert socket into BPF map: %v", err) 121 } 122 log.Printf("updated key %d to value %d", key, val) 123 124 // Put as many UMEM buffers into the fill queue as possible. 125 controlBlock.UMEM.Lock() 126 controlBlock.Fill.FillAll(&controlBlock.UMEM) 127 controlBlock.UMEM.Unlock() 128 129 go func() { 130 controlBlock.UMEM.Lock() 131 defer controlBlock.UMEM.Unlock() 132 for { 133 pfds := []unix.PollFd{{Fd: int32(controlBlock.UMEM.SockFD()), Events: unix.POLLIN}} 134 _, err := unix.Poll(pfds, -1) 135 if err != nil { 136 if errors.Is(err, unix.EINTR) { 137 continue 138 } 139 panic(fmt.Sprintf("poll failed: %v", err)) 140 } 141 142 // How many packets did we get? 143 nReceived, rxIndex := controlBlock.RX.Peek() 144 if nReceived == 0 { 145 continue 146 } 147 148 // Keep the fill queue full. 149 controlBlock.Fill.FillAll(&controlBlock.UMEM) 150 151 // Read packets one-by-one and log them. 152 for i := uint32(0); i < nReceived; i++ { 153 // Wrap the packet in a PacketBuffer. 154 descriptor := controlBlock.RX.Get(rxIndex + i) 155 data := controlBlock.UMEM.Get(descriptor) 156 pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ 157 Payload: buffer.MakeWithData(data[header.EthernetMinimumSize:]), 158 }) 159 160 sniffer.LogPacket("", 161 sniffer.DirectionRecv, // XDP operates only on ingress. 162 header.Ethernet(data).Type(), 163 pkt) 164 165 // NOTE: the address is always 256 bytes offset 166 // from a page boundary. The kernel masks the 167 // address to the frame size, so this isn't a 168 // problem. 169 // 170 // Note that this limits MTU to 4096-256 bytes. 171 controlBlock.UMEM.FreeFrame(descriptor.Addr) 172 } 173 controlBlock.RX.Release(nReceived) 174 } 175 }() 176 177 waitForever() 178 return nil 179 }