netstat: added option to monitor AF_PACKET sockets

For now, we parse /proc/net/packet, because vishvananda/netlink does not
have support to dump AF_PACKET sockets from kernel.
This commit is contained in:
Gustavo Iñiguez Goia 2025-02-03 21:05:25 +01:00
parent 70e868ad8c
commit 83eb82a343
6 changed files with 107 additions and 2 deletions

View file

@ -14,6 +14,7 @@ type Entry struct {
DstIP net.IP
SrcPort uint
DstPort uint
Iface int
UserId int
INode int
}

View file

@ -0,0 +1,61 @@
package netstat
import (
"bufio"
"os"
"regexp"
"github.com/evilsocket/opensnitch/daemon/core"
"github.com/evilsocket/opensnitch/daemon/log"
)
var (
// sk RefCnt Type Proto Iface R Rmem User Inode
// ffff90b72f893800 3 3 0003 3 1 0 0 257944535
packetParser = regexp.MustCompile(`(?i)` +
`[a-z0-9]+\s+` + // sk
`[0-9]\s+` + // refCnt
`([0-9])\s+` + // Type
`([0-9a-z]+)\s+` + // proto
`([0-9])\s+` + // iface
`[0-9]\s+` + // r
`[0-9]+\s+` + // rmem
`([0-9]+)\s+` + // user
`([0-9]+)`, // inode
)
)
// ParsePacket scans and retrieves the opened sockets from /proc/net/packet
func ParsePacket() ([]Entry, error) {
filename := core.ConcatStrings("/proc/net/packet")
fd, err := os.Open(filename)
if err != nil {
return nil, err
}
defer fd.Close()
entries := make([]Entry, 0)
scanner := bufio.NewScanner(fd)
for lineno := 0; scanner.Scan(); lineno++ {
// skip column names
if lineno == 0 {
continue
}
line := core.Trim(scanner.Text())
m := packetParser.FindStringSubmatch(line)
if m == nil {
log.Warning("Could not parse netstat line from %s: %s", filename, line)
continue
}
// TODO: get proto, type, etc.
en := Entry{}
en.Iface = decToInt(m[3])
en.UserId = decToInt(m[4])
en.INode = decToInt(m[5])
entries = append(entries, en)
}
return entries, nil
}

View file

@ -5,10 +5,13 @@ import (
"fmt"
"net"
"sync"
"syscall"
"github.com/evilsocket/opensnitch/daemon/log"
daemonNetlink "github.com/evilsocket/opensnitch/daemon/netlink"
"github.com/evilsocket/opensnitch/daemon/netstat"
"github.com/evilsocket/opensnitch/daemon/procmon"
"golang.org/x/sys/unix"
)
const (
@ -70,6 +73,35 @@ func (pm *SocketsMonitor) dumpSockets() *SocketsTable {
wg.Wait()
}
if exclude(pm.Config.Family, unix.AF_PACKET) {
return socketList
}
entries, err := netstat.ParsePacket()
if err != nil {
return socketList
}
var wg sync.WaitGroup
pktList := make(map[int]struct{}, len(entries))
for n, e := range entries {
if _, isDup := pktList[n]; isDup {
continue
}
pktList[n] = struct{}{}
wg.Add(1)
s := daemonNetlink.Socket{}
s.Family = unix.AF_PACKET
s.INode = uint32(e.INode)
s.UID = uint32(e.UserId)
s.ID = daemonNetlink.SocketID{
Interface: uint32(e.Iface),
}
// TODO: report the protocol and type
go addSocketToTable(pm.Ctx, &wg, syscall.IPPROTO_RAW, socketList, s)
}
wg.Wait()
return socketList
}

View file

@ -1,8 +1,9 @@
package socketsmonitor
import (
//"golang.org/x/sys/unix"
"syscall"
"golang.org/x/sys/unix"
)
// Protos holds valid combinations of protocols, families and socket types that can be created.
@ -28,4 +29,11 @@ var options = []Protos{
{syscall.IPPROTO_UDP, syscall.AF_INET6},
{syscall.IPPROTO_UDPLITE, syscall.AF_INET},
{syscall.IPPROTO_UDPLITE, syscall.AF_INET6},
// for AF_PACKET, Type is the "Protocol" (SOCK_DGRAM, SOCK_RAW)
{syscall.IPPROTO_RAW, unix.AF_PACKET},
// here UDP is SOCK_DGRAM. Does not imply UDP protocol.
{syscall.IPPROTO_UDP, unix.AF_PACKET},
//{syscall.IPPROTO_IP, unix.AF_PACKET},
//{unix.ETH_P_ALL, syscall.AF_PACKET},
}

View file

@ -539,6 +539,7 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
self.comboNetstatFamily.addItem(QC.translate("stats", "ALL"), 0)
self.comboNetstatFamily.addItem("AF_INET", 2)
self.comboNetstatFamily.addItem("AF_INET6", 10)
self.comboNetstatFamily.addItem("AF_PACKET", 17) # 0x11
self.comboNetstatInterval.currentIndexChanged.connect(lambda index: self._cb_combo_netstat_changed(0, index))
self.comboNetstatNodes.activated.connect(lambda index: self._cb_combo_netstat_changed(1, index))
self.comboNetstatProto.currentIndexChanged.connect(lambda index: self._cb_combo_netstat_changed(2, index))

View file

@ -5,7 +5,7 @@ Family = {
'0': 'AF_UNSPEC',
'2': 'AF_INET',
'10': 'AF_INET6',
'11': 'AF_PACKET',
'17': 'AF_PACKET',
'40': 'AF_VSOCK',
'44': 'AF_XDP',
'45': 'AF_MCTP',
@ -27,6 +27,8 @@ Proto = {
}
State = {
# special case for protos that don't report state (AF_PACKET)
'0': 'LISTEN',
'1': 'Established',
'2': 'TCP_SYN_SENT',
'3': 'TCP_SYN_RECV',