Last active
October 1, 2025 04:28
-
-
Save fyxme/205c2803cc7eb0aca256b5e9cc4017fc to your computer and use it in GitHub Desktop.
Mininal golang pcap capture
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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