mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
180 lines
5.0 KiB
Go
180 lines
5.0 KiB
Go
// Copyright 2022 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
_ "embed"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/cilium/ebpf"
|
|
"github.com/google/subcommands"
|
|
"golang.org/x/sys/unix"
|
|
"gvisor.dev/gvisor/pkg/buffer"
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
"gvisor.dev/gvisor/pkg/tcpip/link/sniffer"
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
"gvisor.dev/gvisor/pkg/xdp"
|
|
"gvisor.dev/gvisor/runsc/flag"
|
|
)
|
|
|
|
//go:embed bpf/tcpdump_ebpf.o
|
|
var tcpdumpProgram []byte
|
|
|
|
// TcpdumpCommand is a subcommand for capturing incoming packets.
|
|
type TcpdumpCommand struct {
|
|
device string
|
|
deviceIndex int
|
|
}
|
|
|
|
// Name implements subcommands.Command.Name.
|
|
func (*TcpdumpCommand) Name() string {
|
|
return "tcpdump"
|
|
}
|
|
|
|
// Synopsis implements subcommands.Command.Synopsis.
|
|
func (*TcpdumpCommand) Synopsis() string {
|
|
return "Run tcpdump-like program that blocks incoming packets."
|
|
}
|
|
|
|
// Usage implements subcommands.Command.Usage.
|
|
func (*TcpdumpCommand) Usage() string {
|
|
return "tcpdump -device <device> or -devidx <device index>"
|
|
}
|
|
|
|
// SetFlags implements subcommands.Command.SetFlags.
|
|
func (pc *TcpdumpCommand) SetFlags(fs *flag.FlagSet) {
|
|
fs.StringVar(&pc.device, "device", "", "which device to attach to")
|
|
fs.IntVar(&pc.deviceIndex, "devidx", 0, "which device to attach to")
|
|
}
|
|
|
|
// Execute implements subcommands.Command.Execute.
|
|
func (pc *TcpdumpCommand) Execute(context.Context, *flag.FlagSet, ...any) subcommands.ExitStatus {
|
|
if err := pc.execute(); err != nil {
|
|
fmt.Printf("%v", err)
|
|
return subcommands.ExitFailure
|
|
}
|
|
return subcommands.ExitSuccess
|
|
}
|
|
|
|
func (pc *TcpdumpCommand) execute() error {
|
|
iface, err := getIface(pc.device, pc.deviceIndex)
|
|
if err != nil {
|
|
return fmt.Errorf("%v", err)
|
|
}
|
|
|
|
// Load into the kernel.
|
|
spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader(tcpdumpProgram))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load spec: %v", err)
|
|
}
|
|
|
|
var objects struct {
|
|
Program *ebpf.Program `ebpf:"xdp_prog"`
|
|
SockMap *ebpf.Map `ebpf:"sock_map"`
|
|
}
|
|
if err := spec.LoadAndAssign(&objects, nil); err != nil {
|
|
return fmt.Errorf("failed to load program: %v", err)
|
|
}
|
|
defer func() {
|
|
if err := objects.Program.Close(); err != nil {
|
|
log.Printf("failed to close program: %v", err)
|
|
}
|
|
if err := objects.SockMap.Close(); err != nil {
|
|
log.Printf("failed to close sock map: %v", err)
|
|
}
|
|
}()
|
|
|
|
_, cleanup, err := attach(objects.Program, iface)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to attach: %v", err)
|
|
}
|
|
defer cleanup()
|
|
|
|
controlBlock, err := xdp.New(
|
|
uint32(iface.Index), 0 /* queueID */, xdp.DefaultOpts())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create socket: %v", err)
|
|
}
|
|
|
|
// Insert our AF_XDP socket into the BPF map that dictates where
|
|
// packets are redirected to.
|
|
key := uint32(0)
|
|
val := controlBlock.UMEM.SockFD()
|
|
if err := objects.SockMap.Update(&key, &val, 0 /* flags */); err != nil {
|
|
return fmt.Errorf("failed to insert socket into BPF map: %v", err)
|
|
}
|
|
log.Printf("updated key %d to value %d", key, val)
|
|
|
|
// Put as many UMEM buffers into the fill queue as possible.
|
|
controlBlock.UMEM.Lock()
|
|
controlBlock.Fill.FillAll(&controlBlock.UMEM)
|
|
controlBlock.UMEM.Unlock()
|
|
|
|
go func() {
|
|
controlBlock.UMEM.Lock()
|
|
defer controlBlock.UMEM.Unlock()
|
|
for {
|
|
pfds := []unix.PollFd{{Fd: int32(controlBlock.UMEM.SockFD()), Events: unix.POLLIN}}
|
|
_, err := unix.Poll(pfds, -1)
|
|
if err != nil {
|
|
if errors.Is(err, unix.EINTR) {
|
|
continue
|
|
}
|
|
panic(fmt.Sprintf("poll failed: %v", err))
|
|
}
|
|
|
|
// How many packets did we get?
|
|
nReceived, rxIndex := controlBlock.RX.Peek()
|
|
if nReceived == 0 {
|
|
continue
|
|
}
|
|
|
|
// Keep the fill queue full.
|
|
controlBlock.Fill.FillAll(&controlBlock.UMEM)
|
|
|
|
// Read packets one-by-one and log them.
|
|
for i := uint32(0); i < nReceived; i++ {
|
|
// Wrap the packet in a PacketBuffer.
|
|
descriptor := controlBlock.RX.Get(rxIndex + i)
|
|
data := controlBlock.UMEM.Get(descriptor)
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
Payload: buffer.MakeWithData(data[header.EthernetMinimumSize:]),
|
|
})
|
|
|
|
sniffer.LogPacket("",
|
|
sniffer.DirectionRecv, // XDP operates only on ingress.
|
|
header.Ethernet(data).Type(),
|
|
pkt)
|
|
|
|
// NOTE: the address is always 256 bytes offset
|
|
// from a page boundary. The kernel masks the
|
|
// address to the frame size, so this isn't a
|
|
// problem.
|
|
//
|
|
// Note that this limits MTU to 4096-256 bytes.
|
|
controlBlock.UMEM.FreeFrame(descriptor.Addr)
|
|
}
|
|
controlBlock.RX.Release(nReceived)
|
|
}
|
|
}()
|
|
|
|
waitForever()
|
|
return nil
|
|
}
|