GoShark is a Go port of the Python pyshark library: a wrapper around TShark (the command-line interface to Wireshark) for packet capture and analysis in Go applications.
- Multiple capture types — file, live, remote, pipe, and in-memory captures
- Flexible filtering — Wireshark display filters and BPF capture filters
- JSON / PDML / EK output — parse TShark output in JSON, XML (PDML), or Elastic Common Schema form
- Layer access — ordered protocol layers with prefix-aware field lookup
- Packet buffering — eager
LoadPacketswith indexed access, or streaming callbacks - Raw packet data — raw bytes, field offsets, and per-layer byte ranges (when the capture carries raw data)
- Session tracking — group packets into conversations by 5-tuple
- Configuration & caching — platform-specific config and cache directories
- Go 1.24 or higher
- Wireshark / TShark installed and on your
PATH(live capture also needsdumpcap)
Add GoShark to your project:
go get github.com/p-vbordei/GoSharkThen import the packages you need:
import (
"github.com/p-vbordei/GoShark/capture"
"github.com/p-vbordei/GoShark/packet"
"github.com/p-vbordei/GoShark/tshark"
)TSHARK_PATH— custom path to thetsharkexecutable (otherwise found onPATH)DUMPCAP_PATH— custom path to thedumpcapexecutable (used for live captures)GO_SHARK_CACHE_DIR— override the default cache directoryGO_SHARK_CONFIG_DIR— override the default config directory
capture— capture types (file, live, remote, pipe, in-memory) and the streaming enginepacket— thePacketandLayertypes, field access, and session trackingtshark— TShark process management, version detection, and JSON/PDML/EK parsersconfig,cache— configuration and output cachingutils,errors— shared helpers and error typestests— integration tests and fixturesdocs/superpowers— design spec and implementation plan
ApplyOnPackets streams packets to a callback; return true from the callback to stop early.
package main
import (
"context"
"fmt"
"log"
"github.com/p-vbordei/GoShark/capture"
"github.com/p-vbordei/GoShark/packet"
)
func main() {
cap, err := capture.NewFileCapture("capture.pcap",
capture.WithDisplayFilter("tcp"),
)
if err != nil {
log.Fatal(err)
}
err = cap.ApplyOnPackets(func(p *packet.Packet) bool {
sniffTime, _ := p.SniffTime()
fmt.Printf("Packet %s | len=%s | highest=%s | %s\n",
p.FrameNumber, p.FrameLen, p.HighestLayer(), sniffTime)
return false // return true to stop early
}, context.Background())
if err != nil {
log.Fatal(err)
}
}For indexed, random access — pyshark's keep_packets behaviour. Pass 0 to load every packet, or a positive count to cap it.
cap, _ := capture.NewFileCapture("capture.pcap")
packets, err := cap.LoadPackets(context.Background(), 0) // 0 = all packets
if err != nil {
log.Fatal(err)
}
fmt.Printf("loaded %d packets\n", cap.Len())
first := cap.Get(0)
fmt.Println("first packet highest layer:", first.HighestLayer())
_ = packets // cap.Packets() returns the same sliceApplyOnPacketsWithLimit adds pyshark's packet_count and timeout limits:
cap.ApplyOnPacketsWithLimit(func(p *packet.Packet) bool {
fmt.Println(p.HighestLayer())
return false
}, context.Background(), 100 /* packet_count */, 5*time.Second /* timeout */)Live capture reads from one or more interfaces and usually requires elevated privileges (e.g. sudo).
cap, err := capture.NewLiveCapture([]string{"en0"},
capture.WithBPFFilter("tcp port 80"),
capture.WithPacketCount(10),
)
if err != nil {
log.Fatal(err)
}
err = cap.ApplyOnPackets(func(p *packet.Packet) bool {
fmt.Println(p.HighestLayer())
return false
}, context.Background())Layers are exposed in protocol order. Field lookup is prefix-aware — on a tcp layer, Field("srcport") resolves tcp.srcport.
cap.ApplyOnPackets(func(p *packet.Packet) bool {
if ip := p.Layer("ip"); ip != nil {
fmt.Printf("%v -> %v\n", ip.Field("src"), ip.Field("dst"))
}
if tcp := p.Layer("tcp"); tcp != nil {
fmt.Println("src port:", tcp.Field("srcport")) // short name
fmt.Println("dst port:", tcp.GetField("tcp.dstport")) // fully-qualified name
}
fmt.Print(p.String()) // pretty-print every layer and field
return false
}, context.Background())cap := capture.NewInMemCapture(capture.WithLinkType(capture.LinkTypeEthernet))
defer cap.Close()
pkt, err := cap.ParsePacket(rawPacketBytes, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(pkt.HighestLayer())GoShark defaults to TShark's JSON output. Select PDML (XML) or EK explicitly:
capture.NewFileCapture("capture.pcap", capture.WithUseJSON(false)) // PDML/XML
capture.NewFileCapture("capture.pcap", capture.WithUseEK(true)) // Elastic Common Schematracker := packet.NewSessionTracker()
cap, _ := capture.NewFileCapture("capture.pcap")
cap.ApplyOnPackets(func(p *packet.Packet) bool {
tracker.AddPacket(p)
return false
}, context.Background())
for i, s := range tracker.GetAllSessions() {
fmt.Printf("Session %d: %s — %d packets, state %s\n",
i+1, s.Key.String(), s.GetPacketCount(), s.State)
}When the underlying TShark output carries raw bytes, packets expose them:
raw := p.GetRawPacket() // whole frame
ethBytes := p.GetLayerRawBytes("eth") // one layer's bytes
ipSrc := p.GetFieldRawBytes("ip", "ip.src") // one field's bytesmain.go reads the bundled test.pcap and prints a summary of each packet:
go run .go test ./...The suite includes integration tests that run real TShark against the bundled test.pcap for the file, in-memory, pipe, and EK capture paths. These tests skip cleanly when TShark is not installed.
The design spec and implementation plan for the pyshark-parity work live under docs/superpowers.
Vlad Bordei — github.com/p-vbordei
MIT License