From c26865987d1518edbbffe8aae9b59464f7451ad4 Mon Sep 17 00:00:00 2001 From: dmachard <5562930+dmachard@users.noreply.github.com> Date: Sun, 21 Apr 2024 20:03:29 +0200 Subject: [PATCH] move bpf to netutils --- collectors/sniffer_afpacket_linux.go | 89 ++-------------------------- netutils/bpf.go | 87 +++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 85 deletions(-) create mode 100644 netutils/bpf.go diff --git a/collectors/sniffer_afpacket_linux.go b/collectors/sniffer_afpacket_linux.go index 59f6f833..748498de 100644 --- a/collectors/sniffer_afpacket_linux.go +++ b/collectors/sniffer_afpacket_linux.go @@ -11,7 +11,6 @@ import ( "os" "syscall" "time" - "unsafe" "github.com/dmachard/go-dnscollector/dnsutils" "github.com/dmachard/go-dnscollector/netutils" @@ -21,88 +20,8 @@ import ( "github.com/dmachard/go-logger" "github.com/google/gopacket" "github.com/google/gopacket/layers" - "golang.org/x/net/bpf" - "golang.org/x/sys/unix" ) -// Convert a uint16 to host byte order (big endian) -func Htons(v uint16) int { - return int((v << 8) | (v >> 8)) -} - -func GetBpfFilter(port int) []bpf.Instruction { - // bpf filter: (ip or ip6 ) and (udp or tcp) and port 53 - // fragmented packets are ignored - var filter = []bpf.Instruction{ - // Load eth.type (2 bytes at offset 12) and push-it in register A - bpf.LoadAbsolute{Off: 12, Size: 2}, - // if eth.type == IPv4 continue with the next instruction - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x0800, SkipTrue: 0, SkipFalse: 10}, - // Load ip.proto (1 byte at offset 23) and push-it in register A - bpf.LoadAbsolute{Off: 23, Size: 1}, - // ip.proto == UDP ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x11, SkipTrue: 1, SkipFalse: 0}, - // ip.proto == TCP ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipTrue: 0, SkipFalse: 16}, - // load flags and fragment offset (2 bytes at offset 20) to ignore fragmented packet - bpf.LoadAbsolute{Off: 20, Size: 2}, - // Only look at the last 13 bits of the data saved in regiter A - // 0x1fff == 0001 1111 1111 1111 (fragment offset) - // If any of the data in fragment offset is true, ignore the packet - bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipTrue: 14, SkipFalse: 0}, - // Load ip.length - // Register X = ip header len * 4 - bpf.LoadMemShift{Off: 14}, - // Load source port in tcp or udp (2 bytes at offset x+14) - bpf.LoadIndirect{Off: 14, Size: 2}, - // source port equal to 53 ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 10, SkipFalse: 0}, - // Load destination port in tcp or udp (2 bytes at offset x+16) - bpf.LoadIndirect{Off: 16, Size: 2}, - // destination port equal to 53 ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 8, SkipFalse: 9}, - // if eth.type == IPv6 continue with the next instruction - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x86dd, SkipTrue: 0, SkipFalse: 8}, - // Load ipv6.nxt (2 bytes at offset 12) and push-it in register A - bpf.LoadAbsolute{Off: 20, Size: 1}, - // ip.proto == UDP ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x11, SkipTrue: 1, SkipFalse: 0}, - // ip.proto == TCP ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipTrue: 0, SkipFalse: 5}, - // Load source port tcp or udp (2 bytes at offset 54) - bpf.LoadAbsolute{Off: 54, Size: 2}, - // source port equal to 53 ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 2, SkipFalse: 0}, - // Load destination port tcp or udp (2 bytes at offset 56) - bpf.LoadAbsolute{Off: 56, Size: 2}, - // destination port equal to 53 ? - bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 0, SkipFalse: 1}, - // Keep the packet and send up to 65k of the packet to userspace - bpf.RetConstant{Val: 0xFFFF}, - // Ignore packet - bpf.RetConstant{Val: 0}, - } - return filter -} - -func ApplyBpfFilter(filter []bpf.Instruction, fd int) (err error) { - var assembled []bpf.RawInstruction - if assembled, err = bpf.Assemble(filter); err != nil { - return err - } - - prog := &unix.SockFprog{ - Len: uint16(len(assembled)), - Filter: (*unix.SockFilter)(unsafe.Pointer(&assembled[0])), - } - - return unix.SetsockoptSockFprog(fd, syscall.SOL_SOCKET, syscall.SO_ATTACH_FILTER, prog) -} - -func RemoveBpfFilter(fd int) (err error) { - return syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_DETACH_FILTER, 0) -} - type AfpacketSniffer struct { *pkgutils.Collector fd int @@ -116,7 +35,7 @@ func NewAfpacketSniffer(next []pkgutils.Worker, config *pkgconfig.Config, logger func (c *AfpacketSniffer) Listen() error { // raw socket - fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, Htons(syscall.ETH_P_ALL)) + fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, netutils.Htons(syscall.ETH_P_ALL)) if err != nil { return err } @@ -145,8 +64,8 @@ func (c *AfpacketSniffer) Listen() error { return err } - filter := GetBpfFilter(c.GetConfig().Collectors.AfpacketLiveCapture.Port) - err = ApplyBpfFilter(filter, fd) + filter := netutils.GetBpfFilter(c.GetConfig().Collectors.AfpacketLiveCapture.Port) + err = netutils.ApplyBpfFilter(filter, fd) if err != nil { return err } @@ -196,7 +115,7 @@ func (c *AfpacketSniffer) Run() { go func(ctx context.Context) { defer func() { dnsProcessor.Stop() - RemoveBpfFilter(c.fd) + netutils.RemoveBpfFilter(c.fd) syscall.Close(c.fd) c.LogInfo("read data terminated") defer close(done) diff --git a/netutils/bpf.go b/netutils/bpf.go new file mode 100644 index 00000000..0f6d9123 --- /dev/null +++ b/netutils/bpf.go @@ -0,0 +1,87 @@ +package netutils + +import ( + "syscall" + "unsafe" + + "golang.org/x/net/bpf" + "golang.org/x/sys/unix" +) + +// Convert a uint16 to host byte order (big endian) +func Htons(v uint16) int { + return int((v << 8) | (v >> 8)) +} + +func GetBpfFilter(port int) []bpf.Instruction { + // bpf filter: (ip or ip6 ) and (udp or tcp) and port 53 + // fragmented packets are ignored + var filter = []bpf.Instruction{ + // Load eth.type (2 bytes at offset 12) and push-it in register A + bpf.LoadAbsolute{Off: 12, Size: 2}, + // if eth.type == IPv4 continue with the next instruction + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x0800, SkipTrue: 0, SkipFalse: 10}, + // Load ip.proto (1 byte at offset 23) and push-it in register A + bpf.LoadAbsolute{Off: 23, Size: 1}, + // ip.proto == UDP ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x11, SkipTrue: 1, SkipFalse: 0}, + // ip.proto == TCP ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipTrue: 0, SkipFalse: 16}, + // load flags and fragment offset (2 bytes at offset 20) to ignore fragmented packet + bpf.LoadAbsolute{Off: 20, Size: 2}, + // Only look at the last 13 bits of the data saved in regiter A + // 0x1fff == 0001 1111 1111 1111 (fragment offset) + // If any of the data in fragment offset is true, ignore the packet + bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipTrue: 14, SkipFalse: 0}, + // Load ip.length + // Register X = ip header len * 4 + bpf.LoadMemShift{Off: 14}, + // Load source port in tcp or udp (2 bytes at offset x+14) + bpf.LoadIndirect{Off: 14, Size: 2}, + // source port equal to 53 ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 10, SkipFalse: 0}, + // Load destination port in tcp or udp (2 bytes at offset x+16) + bpf.LoadIndirect{Off: 16, Size: 2}, + // destination port equal to 53 ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 8, SkipFalse: 9}, + // if eth.type == IPv6 continue with the next instruction + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x86dd, SkipTrue: 0, SkipFalse: 8}, + // Load ipv6.nxt (2 bytes at offset 12) and push-it in register A + bpf.LoadAbsolute{Off: 20, Size: 1}, + // ip.proto == UDP ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x11, SkipTrue: 1, SkipFalse: 0}, + // ip.proto == TCP ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x6, SkipTrue: 0, SkipFalse: 5}, + // Load source port tcp or udp (2 bytes at offset 54) + bpf.LoadAbsolute{Off: 54, Size: 2}, + // source port equal to 53 ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 2, SkipFalse: 0}, + // Load destination port tcp or udp (2 bytes at offset 56) + bpf.LoadAbsolute{Off: 56, Size: 2}, + // destination port equal to 53 ? + bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(port), SkipTrue: 0, SkipFalse: 1}, + // Keep the packet and send up to 65k of the packet to userspace + bpf.RetConstant{Val: 0xFFFF}, + // Ignore packet + bpf.RetConstant{Val: 0}, + } + return filter +} + +func ApplyBpfFilter(filter []bpf.Instruction, fd int) (err error) { + var assembled []bpf.RawInstruction + if assembled, err = bpf.Assemble(filter); err != nil { + return err + } + + prog := &unix.SockFprog{ + Len: uint16(len(assembled)), + Filter: (*unix.SockFilter)(unsafe.Pointer(&assembled[0])), + } + + return unix.SetsockoptSockFprog(fd, syscall.SOL_SOCKET, syscall.SO_ATTACH_FILTER, prog) +} + +func RemoveBpfFilter(fd int) (err error) { + return syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_DETACH_FILTER, 0) +}