Skip to content

Instantly share code, notes, and snippets.

@fyxme
Last active October 1, 2025 04:28
Show Gist options
  • Select an option

  • Save fyxme/205c2803cc7eb0aca256b5e9cc4017fc to your computer and use it in GitHub Desktop.

Select an option

Save fyxme/205c2803cc7eb0aca256b5e9cc4017fc to your computer and use it in GitHub Desktop.
Mininal golang pcap capture
package main
import (
"context"
"encoding/binary"
"flag"
"fmt"
"net"
"os"
"os/signal"
"syscall"
"time"
)
const (
// ETH_P_ALL in host byte order before htons
ethPAll = 0x0003
// DLT_EN10MB = Ethernet linktype for pcap
dltEthernet = 1
)
func htons(i uint16) uint16 { return (i<<8)&0xff00 | i>>8 }
func must(err error, msg string) {
if err != nil {
fmt.Fprintf(os.Stderr, "error: %s: %v\n", msg, err)
os.Exit(1)
}
}
func writePCAPGlobal(w *os.File, snaplen uint32) {
// Big-endian PCAP header (Wireshark handles both endians)
// Magic 0xa1b2c3d4, v2.4, thiszone=0, sigfigs=0, snaplen, network=1(Ethernet)
var hdr = struct {
Magic uint32
VersionMajor uint16
VersionMinor uint16
ThisZone int32
SigFigs uint32
SnapLen uint32
Network uint32
}{0xa1b2c3d4, 2, 4, 0, 0, snaplen, dltEthernet}
binary.Write(w, binary.BigEndian, hdr)
}
func writePCAPPacket(w *os.File, data []byte) {
now := time.Now()
sec := uint32(now.Unix())
usec := uint32(now.Nanosecond() / 1000)
ph := struct {
TsSec uint32
TsUsec uint32
InclLen uint32
OrigLen uint32
}{sec, usec, uint32(len(data)), uint32(len(data))}
binary.Write(w, binary.BigEndian, ph)
w.Write(data)
}
func main() {
ifaceName := flag.String("i", "eth0", "interface (e.g., eth0, lo)")
outPath := flag.String("o", "/tmp/cap.pcap", "output pcap path (use - for stdout)")
duration := flag.Duration("d", 30*time.Second, "capture duration (e.g., 30s, 2m)")
snap := flag.Int("s", 65535, "snaplen (max bytes per packet)")
flag.Parse()
ifi, err := net.InterfaceByName(*ifaceName)
must(err, "lookup interface")
fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, int(htons(ethPAll)))
must(err, "open AF_PACKET socket")
defer syscall.Close(fd)
sll := &syscall.SockaddrLinklayer{
Protocol: htons(ethPAll),
Ifindex: ifi.Index,
}
must(syscall.Bind(fd, sll), "bind to interface")
// modest RX buffer for stability in small containers
// (best-effort; ignore error if kernel says no)
_ = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_RCVBUF, 4<<20)
var f *os.File
if *outPath == "-" {
f = os.Stdout
} else {
var err error
f, err = os.Create(*outPath)
must(err, "create pcap file")
defer f.Close()
}
writePCAPGlobal(f, uint32(*snap))
// Stop on duration or Ctrl-C
ctx, cancel := context.WithTimeout(context.Background(), *duration)
defer cancel()
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt)
go func() {
select {
case <-sig:
cancel()
case <-ctx.Done():
}
// Closing fd unblocks Recvfrom
syscall.Close(fd)
}()
buf := make([]byte, *snap)
for {
n, _, err := syscall.Recvfrom(fd, buf, 0)
if err != nil {
// closed or timed out -> exit
break
}
if n > 0 {
// truncate to snaplen just in case
if n > *snap {
n = *snap
}
writePCAPPacket(f, buf[:n])
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment