From 7f5d9a36c9e587c5023d2ab1b61d5139514c0ddc Mon Sep 17 00:00:00 2001 From: fako1024 <> Date: Fri, 24 Apr 2026 16:33:52 +0200 Subject: [PATCH 1/3] [bugfix] Fix incorrect handling of setsockopt call during socket setup --- capture/afpacket/socket/socket.go | 20 +++++++------ capture/afpacket/socket/socket_test.go | 40 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/capture/afpacket/socket/socket.go b/capture/afpacket/socket/socket.go index 072d95f..4a7e1dd 100644 --- a/capture/afpacket/socket/socket.go +++ b/capture/afpacket/socket/socket.go @@ -18,6 +18,8 @@ import ( "golang.org/x/sys/unix" ) +var setPacketMembership = unix.SetsockoptPacketMreq + const ( TPacketVersion = unix.TPACKET_V3 // TPacketVersion : The TPacket header version to use ) @@ -97,14 +99,7 @@ func (sd FileDescriptor) SetSocketOptions(iface *link.Link, snapLen int, options // If the source is in promiscuous mode, set the required flag if options.Promiscuous { - mReq := unix.PacketMreq{ - Ifindex: int32(iface.Index), - Type: unix.PACKET_MR_PROMISC, - } - // #nosec: G103 - reqLen := unsafe.Sizeof(mReq) - // #nosec: G103 - if err := setsockopt(sd, unix.SOL_SOCKET, unix.PACKET_ADD_MEMBERSHIP, unsafe.Pointer(&mReq), uintptr(unsafe.Pointer(&reqLen))); err != nil { + if err := setPromiscuousMode(sd, iface); err != nil { return fmt.Errorf("failed to set promiscuous mode: %w", err) } } @@ -137,6 +132,15 @@ func (sd FileDescriptor) SetSocketOptions(iface *link.Link, snapLen int, options return nil } +func setPromiscuousMode(sd FileDescriptor, iface *link.Link) error { + mReq := unix.PacketMreq{ + Ifindex: int32(iface.Index), + Type: unix.PACKET_MR_PROMISC, + } + + return setPacketMembership(int(sd), unix.SOL_PACKET, unix.PACKET_ADD_MEMBERSHIP, &mReq) +} + // SetupRingBuffer peforms a call via setsockopt() to prepare a mmmap'ed ring buffer func (sd FileDescriptor) SetupRingBuffer(val unsafe.Pointer, vallen uintptr) error { return setsockopt(sd, unix.SOL_PACKET, unix.PACKET_RX_RING, val, vallen) diff --git a/capture/afpacket/socket/socket_test.go b/capture/afpacket/socket/socket_test.go index d32492f..53d4add 100644 --- a/capture/afpacket/socket/socket_test.go +++ b/capture/afpacket/socket/socket_test.go @@ -4,10 +4,13 @@ package socket import ( + "errors" "sync" "testing" + "github.com/fako1024/gotools/link" "github.com/stretchr/testify/require" + "golang.org/x/sys/unix" ) const nPolls = 1000000 @@ -65,3 +68,40 @@ func TestPacketCounter(t *testing.T) { require.Nil(t, sock.Close()) } + +func TestSetPromiscuousModeUsesPacketSocketOptions(t *testing.T) { + origSetPacketMembership := setPacketMembership + t.Cleanup(func() { + setPacketMembership = origSetPacketMembership + }) + + var called bool + setPacketMembership = func(fd, level, opt int, mreq *unix.PacketMreq) error { + called = true + require.Equal(t, 1234, fd) + require.Equal(t, unix.SOL_PACKET, level) + require.Equal(t, unix.PACKET_ADD_MEMBERSHIP, opt) + require.EqualValues(t, 42, mreq.Ifindex) + require.EqualValues(t, unix.PACKET_MR_PROMISC, mreq.Type) + return nil + } + + err := setPromiscuousMode(FileDescriptor(1234), &link.Link{Index: 42}) + require.Nil(t, err) + require.True(t, called) +} + +func TestSetPromiscuousModePropagatesError(t *testing.T) { + origSetPacketMembership := setPacketMembership + t.Cleanup(func() { + setPacketMembership = origSetPacketMembership + }) + + expectedErr := errors.New("membership failure") + setPacketMembership = func(fd, level, opt int, mreq *unix.PacketMreq) error { + return expectedErr + } + + err := setPromiscuousMode(FileDescriptor(1234), &link.Link{Index: 42}) + require.ErrorIs(t, err, expectedErr) +} From 1f20ff7b4df46fd7143e19977e94b62818b73d22 Mon Sep 17 00:00:00 2001 From: fako1024 <> Date: Sat, 25 Apr 2026 14:03:09 +0200 Subject: [PATCH 2/3] Bump all dependencies --- go.mod | 12 ++++++------ go.sum | 30 ++++++++++++------------------ 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 02dc580..b0e7de5 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,15 @@ module github.com/fako1024/slimcap go 1.25.0 require ( - github.com/els0r/telemetry/logging v0.0.0-20241007081432-5f966df95bbd - github.com/fako1024/gotools/link v0.0.0-20250828105752-6dffddfeea78 + github.com/els0r/telemetry/logging v0.0.0-20260406010724-0c813ed6284d + github.com/fako1024/gotools/link v0.0.0-20260108133916-d42cb4e89f05 github.com/stretchr/testify v1.11.1 - golang.org/x/net v0.47.0 - golang.org/x/sys v0.38.0 + golang.org/x/net v0.53.0 + golang.org/x/sys v0.43.0 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b0e33b8..6e06a51 100644 --- a/go.sum +++ b/go.sum @@ -1,23 +1,17 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/els0r/telemetry/logging v0.0.0-20241007081432-5f966df95bbd h1:y0S8nMvVTq1SarcZOLyIKutGrtU2pFVekBTWdJkS1Ls= -github.com/els0r/telemetry/logging v0.0.0-20241007081432-5f966df95bbd/go.mod h1:Nwfs00kMb5P8263qOrXMe+oYk5/GBZTN5LQhrxDFRyM= -github.com/fako1024/gotools/link v0.0.0-20250828105752-6dffddfeea78 h1:JnjMiPcNXI3JNaa3/8HMmsveXFtbYzqZvDOw1XzLcsM= -github.com/fako1024/gotools/link v0.0.0-20250828105752-6dffddfeea78/go.mod h1:wQzxOmWgVXZFU6V0/ND3SwUyzywsPBlz2bFiq1H9p98= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= -github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/els0r/telemetry/logging v0.0.0-20260406010724-0c813ed6284d h1:oFVErr74kfToJbtA3vmbZsyXeGYrFhte2qYtG0H81UI= +github.com/els0r/telemetry/logging v0.0.0-20260406010724-0c813ed6284d/go.mod h1:Nwfs00kMb5P8263qOrXMe+oYk5/GBZTN5LQhrxDFRyM= +github.com/fako1024/gotools/link v0.0.0-20260108133916-d42cb4e89f05 h1:1vwXd29aFsCvPDBZUHokxvWkEsqwU/hwSmMx4S0hHiE= +github.com/fako1024/gotools/link v0.0.0-20260108133916-d42cb4e89f05/go.mod h1:wQzxOmWgVXZFU6V0/ND3SwUyzywsPBlz2bFiq1H9p98= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From bc2a94ab442c71c7d3f7ba8df8c896944a9ab231 Mon Sep 17 00:00:00 2001 From: fako1024 <> Date: Sat, 25 Apr 2026 14:36:03 +0200 Subject: [PATCH 3/3] Fix logging SDK errors --- examples/dump/main.go | 3 ++- examples/pcap/main.go | 22 +++++++-------------- examples/trace/conf.go | 11 ----------- examples/trace/main.go | 4 +--- examples/trace/trace.go | 44 ++++++++++++++++++++--------------------- 5 files changed, 32 insertions(+), 52 deletions(-) diff --git a/examples/dump/main.go b/examples/dump/main.go index f423a2b..0678530 100644 --- a/examples/dump/main.go +++ b/examples/dump/main.go @@ -23,11 +23,12 @@ func main() { ignoreVLANs bool ) - logger, logErr := logging.New(logging.LevelInfo, logging.EncodingPlain) + logger, logShutdownFn, logErr := logging.New(logging.LevelInfo, logging.EncodingPlain) if logErr != nil { fmt.Fprintf(os.Stderr, "failed to instantiate CLI logger: %v\n", logErr) os.Exit(1) } + defer logShutdownFn() flag.StringVar(&devName, "d", "", "device / interface to capture on") flag.IntVar(&maxPkts, "n", 10, "maximum number of packets to capture") diff --git a/examples/pcap/main.go b/examples/pcap/main.go index 8fcc93d..c95f065 100644 --- a/examples/pcap/main.go +++ b/examples/pcap/main.go @@ -7,9 +7,7 @@ package main import ( "errors" "flag" - "fmt" "io" - "os" "github.com/els0r/telemetry/logging" "github.com/fako1024/slimcap/capture/pcap" @@ -23,44 +21,38 @@ func main() { raw bool ) - logger, logErr := logging.New(logging.LevelInfo, logging.EncodingPlain) - if logErr != nil { - fmt.Fprintf(os.Stderr, "failed to instantiate CLI logger: %v\n", logErr) - os.Exit(1) - } - flag.StringVar(&fileName, "f", "", "pcap file to read from") flag.IntVar(&maxPkts, "n", 10, "maximum number of packets to process") flag.BoolVar(&raw, "r", false, "output raw packet information") flag.Parse() if fileName == "" { - logger.Fatalf("no input file specified (-f)") + logging.Logger().Fatalf("no input file specified (-f)") } listener, err := pcap.NewSourceFromFile(fileName) if err != nil { - logger.Fatalf("failed to start pcap reader for `%s`: %s", fileName, err) + logging.Logger().Fatalf("failed to start pcap reader for `%s`: %s", fileName, err) } defer func() { if err := listener.Close(); err != nil { - logger.Fatalf("failed to close listener on `%s`: %s", fileName, err) + logging.Logger().Fatalf("failed to close listener on `%s`: %s", fileName, err) } }() - logger.Infof("Reading up to %d packets from `%s` (link type: %d)...", maxPkts, fileName, listener.Link().Type) + logging.Logger().Infof("Reading up to %d packets from `%s` (link type: %d)...", maxPkts, fileName, listener.Link().Type) for i := 0; i < maxPkts; i++ { p, err := listener.NextPacket(nil) if err != nil { if errors.Is(err, io.EOF) { return } - logger.Fatalf("error during packet reading (copy operation) on `%s`: %s", fileName, err) + logging.Logger().Fatalf("error during packet reading (copy operation) on `%s`: %s", fileName, err) } if raw { - logger.Infof("Read packet with Payload (total len %d): %v", p.TotalLen(), p.Payload()) + logging.Logger().Infof("Read packet with Payload (total len %d): %v", p.TotalLen(), p.Payload()) } else { - logger.Infof("Read packet (total len %d): %v", p.TotalLen(), p.IPLayer().String()) + logging.Logger().Infof("Read packet (total len %d): %v", p.TotalLen(), p.IPLayer().String()) } } } diff --git a/examples/trace/conf.go b/examples/trace/conf.go index 4878c66..d6b36ad 100644 --- a/examples/trace/conf.go +++ b/examples/trace/conf.go @@ -2,11 +2,7 @@ package main import ( "flag" - "fmt" - "os" "strings" - - "github.com/els0r/telemetry/logging" ) // Definitions for command line option names @@ -63,13 +59,6 @@ func ParseConfig() (cfg Config) { cfg.Ifaces = parseList(rawIfaces) cfg.SkipIfaces = parseList(rawSkipIfaces) - var logErr error - logger, logErr = logging.New(logging.LevelFromString(cfg.LogLevel), logging.EncodingPlain) - if logErr != nil { - fmt.Fprintf(os.Stderr, "failed to instantiate CLI logger: %v\n", logErr) - os.Exit(1) - } - return } diff --git a/examples/trace/main.go b/examples/trace/main.go index 0a893f4..2b5be09 100644 --- a/examples/trace/main.go +++ b/examples/trace/main.go @@ -9,8 +9,6 @@ import ( "github.com/els0r/telemetry/logging" ) -var logger *logging.L - func main() { cfg := ParseConfig() @@ -25,6 +23,6 @@ func main() { WithCPUProfiling(cfg.CPUProfileOutput). WithMemProfiling(cfg.MemProfileOutput). Run(); err != nil { - logger.Fatalf("critical error during capture: %s", err) + logging.Logger().Fatalf("critical error during capture: %s", err) } } diff --git a/examples/trace/trace.go b/examples/trace/trace.go index 4aa8f28..d42c233 100644 --- a/examples/trace/trace.go +++ b/examples/trace/trace.go @@ -6,10 +6,12 @@ import ( "os" "os/signal" "runtime/pprof" + "slices" "sort" "strings" "sync" + "github.com/els0r/telemetry/logging" "github.com/fako1024/gotools/link" "github.com/fako1024/slimcap/capture" "github.com/fako1024/slimcap/capture/afpacket/afpacket" @@ -44,8 +46,8 @@ func (c *Capture) SkipIfaces(ifaces []string) *Capture { } // MaxIfaceErrors sets the maximum number of errors allowed to occur before capture is terminated -func (c *Capture) MaxIfaceErrors(max int) *Capture { - c.maxIfaceErrors = max +func (c *Capture) MaxIfaceErrors(maxErrs int) *Capture { + c.maxIfaceErrors = maxErrs return c } @@ -111,7 +113,7 @@ func (c *Capture) Run() (err error) { return err } for _, iface := range links { - logger.Infof("Found interface `%s` (idx %d), link type %d", iface.Name, iface.Index, iface.Type) + logging.Logger().Infof("Found interface `%s` (idx %d), link type %d", iface.Name, iface.Index, iface.Type) } // construct list of skipped interfaces @@ -156,13 +158,13 @@ func (c *Capture) Run() (err error) { return skipped[i] < skipped[j] }) if len(skipped) > 0 { - logger.Warnf("skipping capture on interfaces [%s]", strings.Join(skipped, ",")) + logging.Logger().Warnf("skipping capture on interfaces [%s]", strings.Join(skipped, ",")) } sort.Slice(capturing, func(i, j int) bool { return capturing[i] < capturing[j] }) - logger.Infof("attempting capture on interfaces [%s]", strings.Join(capturing, ",")) + logging.Logger().Infof("attempting capture on interfaces [%s]", strings.Join(capturing, ",")) sigExitChan := make(chan os.Signal, 1) signal.Notify(sigExitChan, unix.SIGTERM, os.Interrupt) @@ -175,14 +177,12 @@ func (c *Capture) Run() (err error) { go func(l *link.Link) { defer wg.Done() - for _, skipLink := range skipped { - if l.Name == skipLink { - return - } + if slices.Contains(skipped, l.Name) { + return } if isUp, err := l.IsUp(); err != nil || !isUp { - logger.Warnf("skipping listener on non-up interface `%s`", l.Name) + logging.Logger().Warnf("skipping listener on non-up interface `%s`", l.Name) return } @@ -190,12 +190,12 @@ func (c *Capture) Run() (err error) { if c.useRingBuffer { listener, err = afring.NewSourceFromLink(l, afring.CaptureLength(filter.CaptureLengthMinimalIPv6Transport)) if err != nil { - logger.Errorf("error starting listener (with ring buffer) on `%s`: %s", l.Name, err) + logging.Logger().Errorf("error starting listener (with ring buffer) on `%s`: %s", l.Name, err) } } else { listener, err = afpacket.NewSourceFromLink(l) if err != nil { - logger.Errorf("error starting listener (no ring buffer) on `%s`: %s", l.Name, err) + logging.Logger().Errorf("error starting listener (no ring buffer) on `%s`: %s", l.Name, err) } } listeners = append(listeners, listener) @@ -203,19 +203,19 @@ func (c *Capture) Run() (err error) { var nErr int if c.useZeroCopy { for { - if err := listener.NextPacketFn(func(payload []byte, totalLen uint32, pktType byte, ipLayerOffset byte) error { + if err := listener.NextPacketFn(func(payload []byte, _ uint32, pktType byte, ipLayerOffset byte) error { if c.logPacketPayload { - logger.Infof("[%s] Got %v / %d", l.Name, payload[ipLayerOffset:ipLayerOffset+16], pktType) + logging.Logger().Infof("[%s] Got %v / %d", l.Name, payload[ipLayerOffset:ipLayerOffset+16], pktType) } return nil }); err != nil { if errors.Is(err, capture.ErrCaptureStopped) { - logger.Infof("gracefully stopped capture on `%s`", l.Name) + logging.Logger().Infof("gracefully stopped capture on `%s`", l.Name) return } nErr++ if nErr >= c.maxIfaceErrors { - logger.Errorf("too many errors (%d) on `%s`, stopping capture", nErr, l.Name) + logging.Logger().Errorf("too many errors (%d) on `%s`, stopping capture", nErr, l.Name) } } } @@ -224,17 +224,17 @@ func (c *Capture) Run() (err error) { pkt, err := listener.NextPacket(nil) if err != nil { if errors.Is(err, capture.ErrCaptureStopped) { - logger.Infof("gracefully stopped capture on `%s`", l.Name) + logging.Logger().Infof("gracefully stopped capture on `%s`", l.Name) return } nErr++ if nErr >= c.maxIfaceErrors { - logger.Errorf("too many errors (%d) on `%s`, stopping capture", nErr, l.Name) + logging.Logger().Errorf("too many errors (%d) on `%s`, stopping capture", nErr, l.Name) } } if c.logPacketPayload { - logger.Infof("[%s] Got IP layer %v / %d", l.Name, pkt.IPLayer()[:16], pkt.Type()) + logging.Logger().Infof("[%s] Got IP layer %v / %d", l.Name, pkt.IPLayer()[:16], pkt.Type()) } } } @@ -249,12 +249,12 @@ func (c *Capture) Run() (err error) { for _, listener := range listeners { stats, err := listener.Stats() if err != nil { - logger.Errorf("failed to retrieve socket stats: %s", err) + logging.Logger().Errorf("failed to retrieve socket stats: %s", err) } - logger.Infof("Packet stats for %s: %#v", listener.Link().Name, stats) + logging.Logger().Infof("Packet stats for %s: %#v", listener.Link().Name, stats) if err := listener.Close(); err != nil { - logger.Errorf("failed to gracefully stop capture: %s", err) + logging.Logger().Errorf("failed to gracefully stop capture: %s", err) } } }()