mirror of
https://github.com/evilsocket/opensnitch.git
synced 2025-03-04 08:34:40 +01:00
netstat: dump AF_PACKET sockets from the kernel
We'll try to dump the AF_PACKET sockets from the kernel. If it's not possible, we'll fallback to read /proc/net/packet.
This commit is contained in:
parent
335f2a783d
commit
1a39122c1d
3 changed files with 255 additions and 36 deletions
172
daemon/netlink/socket_packet.go
Normal file
172
daemon/netlink/socket_packet.go
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/evilsocket/opensnitch/daemon/log"
|
||||||
|
"github.com/vishvananda/netlink/nl"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// request:
|
||||||
|
// {nlmsg_len=36, nlmsg_type=SOCK_DIAG_BY_FAMILY, nlmsg_flags=NLM_F_REQUEST|NLM_F_DUMP, nlmsg_seq=123456, nlmsg_pid=0},
|
||||||
|
// {sdiag_family=AF_PACKET, sdiag_protocol=0, pdiag_ino=0, pdiag_show=PACKET_SHOW_INFO, pdiag_cookie=[0, 0]}
|
||||||
|
|
||||||
|
// responses (depends on what filters are passed in the request):
|
||||||
|
// {pdiag_family=AF_PACKET, pdiag_type=SOCK_RAW, pdiag_num=ETH_P_ALL, pdiag_ino=257944535, pdiag_cookie=[1291434, 0]},
|
||||||
|
// {nla_len=28, nla_type=PACKET_DIAG_INFO},
|
||||||
|
// {pdi_index=if_nametoindex("wifi0"), pdi_version=TPACKET_V3, pdi_reserve=4, pdi_copy_thresh=0, pdi_tstamp=0, pdi_flags=PDI_RUNNING|PDI_AUXDATA}
|
||||||
|
// {nla_len=8, nla_type=PACKET_DIAG_UID}, 0}
|
||||||
|
// {nla_len=32, nla_type=PACKET_DIAG_RX_RING},
|
||||||
|
// {pdr_block_size=262144, pdr_block_nr=8, pdr_frame_size=262144, pdr_frame_nr=8, pdr_retire_tmo=10, pdr_sizeof_priv=0, pdr_features=0}
|
||||||
|
// {nla_len=40, nla_type=PACKET_DIAG_MEMINFO},
|
||||||
|
// [[SK_MEMINFO_RMEM_ALLOC]=0, [SK_MEMINFO_RCVBUF]=212992, [SK_MEMINFO_WMEM_ALLOC]=0, [SK_MEMINFO_SNDBUF]=212992, [SK_MEMINFO_FWD_ALLOC]=0, [SK_MEMINFO_WMEM_QUEUED]=0, [SK_MEMINFO_OPTMEM]=128, [SK_MEMINFO_BACKLOG]=0, [SK_MEMINFO_DROPS]=0]],
|
||||||
|
// {nla_len=12, nla_type=PACKET_DIAG_FILTER}, 0x512e76dfc140}
|
||||||
|
|
||||||
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/packet_diag.h#L16
|
||||||
|
// list of possible information to request
|
||||||
|
const (
|
||||||
|
PACKET_SHOW_INFO = 0x00000001 /* Basic packet_sk information */
|
||||||
|
PACKET_SHOW_MCLIST = 0x00000002 /* A set of packet_diag_mclist-s */
|
||||||
|
PACKET_SHOW_RING_CFG = 0x00000004 /* Rings configuration parameters */
|
||||||
|
PACKET_SHOW_FANOUT = 0x00000008
|
||||||
|
PACKET_SHOW_MEMINFO = 0x00000010
|
||||||
|
PACKET_SHOW_FILTER = 0x00000020
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/packet_diag.h#L32
|
||||||
|
// types of messages retrieved from kernel
|
||||||
|
const (
|
||||||
|
PACKET_DIAG_INFO = iota
|
||||||
|
PACKET_DIAG_MCLIST
|
||||||
|
PACKET_DIAG_RX_RING
|
||||||
|
PACKET_DIAG_TX_RING
|
||||||
|
PACKET_DIAG_FANOUT
|
||||||
|
PACKET_DIAG_UID
|
||||||
|
PACKET_DIAG_MEMINFO
|
||||||
|
PACKET_DIAG_FILTER
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sizePktDiagReq = 20
|
||||||
|
sizePktDiagMclist = 28
|
||||||
|
)
|
||||||
|
|
||||||
|
// PacketDiagMsg holds the message(s) sent by the kernel
|
||||||
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/packet_diag.h#L23
|
||||||
|
type PacketDiagMsg struct {
|
||||||
|
Mclist PacketDiagMclist
|
||||||
|
Cookie [2]uint32
|
||||||
|
Inode uint32
|
||||||
|
UID uint32
|
||||||
|
Num uint16 // ETH_P_ALL, etc
|
||||||
|
Family uint8
|
||||||
|
Type uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// PacketDiagMclist struct
|
||||||
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/packet_diag.h#L63
|
||||||
|
type PacketDiagMclist struct {
|
||||||
|
Index uint32
|
||||||
|
Count uint32
|
||||||
|
Type uint16
|
||||||
|
Alen uint16
|
||||||
|
Addr [32]uint8 /* MAX_ADDR_LEN */
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PacketDiagMsg) deserialize(b []byte) error {
|
||||||
|
rb := readBuffer{Bytes: b}
|
||||||
|
|
||||||
|
// 1st message: PacketDiagMsg
|
||||||
|
pm.Family = rb.Read()
|
||||||
|
pm.Type = rb.Read()
|
||||||
|
pm.Num = native.Uint16(rb.Next(2))
|
||||||
|
pm.Inode = native.Uint32(rb.Next(4))
|
||||||
|
pm.Cookie[0] = native.Uint32(rb.Next(4))
|
||||||
|
pm.Cookie[1] = native.Uint32(rb.Next(4))
|
||||||
|
|
||||||
|
nextMsg := rb.Read() // next msg size
|
||||||
|
if nextMsg == sizePktDiagMclist {
|
||||||
|
pm.Mclist = PacketDiagMclist{
|
||||||
|
// XXX: wrong values with native.Uint32()
|
||||||
|
Index: binary.BigEndian.Uint32(rb.Next(4)),
|
||||||
|
Count: binary.BigEndian.Uint32(rb.Next(4)),
|
||||||
|
Type: binary.BigEndian.Uint16(rb.Next(2)),
|
||||||
|
Alen: binary.BigEndian.Uint16(rb.Next(2)),
|
||||||
|
}
|
||||||
|
copy(pm.Mclist.Addr[:], rb.Next(16))
|
||||||
|
}
|
||||||
|
|
||||||
|
// {nla_len=8, nla_type=PACKET_DIAG_UID}, 1000}
|
||||||
|
nextMsg = rb.Read() // 8, size of next msg
|
||||||
|
nextMsg = rb.Read() // pad?
|
||||||
|
if nextMsg == PACKET_DIAG_UID {
|
||||||
|
rb.Read() // pad?
|
||||||
|
pm.UID = native.Uint32(rb.Next(4))
|
||||||
|
}
|
||||||
|
log.Trace("PktDiagMsg.deserialize (size: %d, sizeOf(PacketDiagMsg): %d): %+v", len(b), unsafe.Sizeof(b), pm)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PacketDiagReq struct to request data from the kernel
|
||||||
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/packet_diag.h#L7
|
||||||
|
type PacketDiagReq struct {
|
||||||
|
Family uint8
|
||||||
|
Protocol uint8
|
||||||
|
Pad uint16
|
||||||
|
Inode uint32
|
||||||
|
Show uint32
|
||||||
|
Cookie [2]uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize ...
|
||||||
|
func (p *PacketDiagReq) Serialize() []byte {
|
||||||
|
b := writeBuffer{Bytes: make([]byte, sizePktDiagReq)}
|
||||||
|
b.Write(p.Family)
|
||||||
|
b.Write(p.Protocol)
|
||||||
|
native.PutUint16(b.Next(2), p.Pad)
|
||||||
|
native.PutUint32(b.Next(4), p.Inode)
|
||||||
|
native.PutUint32(b.Next(4), p.Show)
|
||||||
|
native.PutUint32(b.Next(4), p.Cookie[0])
|
||||||
|
native.PutUint32(b.Next(4), p.Cookie[1])
|
||||||
|
|
||||||
|
return b.Bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len ...
|
||||||
|
func (p *PacketDiagReq) Len() int { return sizePktDiagReq }
|
||||||
|
|
||||||
|
// SocketDiagPacket dumps AF_PACKET sockets from kernel
|
||||||
|
func SocketDiagPacket(proto uint8) ([]*PacketDiagMsg, error) {
|
||||||
|
req := nl.NewNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, syscall.NLM_F_DUMP)
|
||||||
|
req.AddData(&PacketDiagReq{
|
||||||
|
Family: unix.AF_PACKET,
|
||||||
|
Protocol: proto,
|
||||||
|
// TODO: dump bpf filters | PACKET_SHOW_FILTER
|
||||||
|
Show: PACKET_SHOW_INFO | PACKET_SHOW_MCLIST,
|
||||||
|
})
|
||||||
|
msgs, err := req.Execute(syscall.NETLINK_INET_DIAG, 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("[netlink] socket.packetRequest: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(msgs) == 0 {
|
||||||
|
log.Debug("[netlink] socket.packetRequest: 0 msgs")
|
||||||
|
return []*PacketDiagMsg{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pkts := make([]*PacketDiagMsg, len(msgs))
|
||||||
|
for n, m := range msgs {
|
||||||
|
log.Trace("[netlink] AF_PACKET, size: %d, %+v", len(m), m)
|
||||||
|
p := &PacketDiagMsg{}
|
||||||
|
if err = p.deserialize(m); err != nil {
|
||||||
|
log.Trace("[%d] netlink socket.packet error: %s", n, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pkts[n] = p
|
||||||
|
}
|
||||||
|
return pkts, nil
|
||||||
|
}
|
|
@ -21,12 +21,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Socket represents every socket dumped from the kernel for the given filter.
|
// Socket represents every socket dumped from the kernel for the given filter.
|
||||||
|
// Internal to this package, and sent to the GUI as JSON.
|
||||||
type Socket struct {
|
type Socket struct {
|
||||||
Socket *netlink.Socket
|
Socket *netlink.Socket
|
||||||
Iface string
|
Iface string
|
||||||
PID int
|
PID int
|
||||||
Mark uint32
|
Mark uint32
|
||||||
Proto uint8
|
Proto uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// SocketsTable holds all the dumped sockets, after applying the filters, if any.
|
// SocketsTable holds all the dumped sockets, after applying the filters, if any.
|
||||||
|
@ -69,14 +70,25 @@ func (pm *SocketsMonitor) dumpSockets() *SocketsTable {
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
// XXX: firing a goroutine per socket may be too much on some scenarios
|
// XXX: firing a goroutine per socket may be too much on some scenarios
|
||||||
go addSocketToTable(pm.Ctx, &wg, opt.Proto, socketList, *sock)
|
go addSocketToTable(pm.Ctx, &wg, uint16(opt.Proto), socketList, *sock)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exclude(pm.Config.Family, unix.AF_XDP) && !exclude(pm.Config.Proto, syscall.IPPROTO_RAW) {
|
dumpXDPSockets(pm.Ctx, pm.Config, socketList)
|
||||||
|
dumpPacketSockets(pm.Ctx, pm.Config, socketList)
|
||||||
|
|
||||||
|
return socketList
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpXDPSockets(ctx context.Context, conf *monConfig, socketList *SocketsTable) {
|
||||||
|
if exclude(conf.Family, unix.AF_XDP) && exclude(conf.Proto, syscall.IPPROTO_RAW) {
|
||||||
|
return
|
||||||
|
}
|
||||||
xdpList, err := netlink.SocketGetXDP()
|
xdpList, err := netlink.SocketGetXDP()
|
||||||
if err == nil {
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for _, xdp := range xdpList {
|
for _, xdp := range xdpList {
|
||||||
s := netlink.Socket{}
|
s := netlink.Socket{}
|
||||||
|
@ -88,29 +100,56 @@ func (pm *SocketsMonitor) dumpSockets() *SocketsTable {
|
||||||
Cookie: xdp.XDPDiagMsg.Cookie,
|
Cookie: xdp.XDPDiagMsg.Cookie,
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go addSocketToTable(pm.Ctx, &wg, syscall.IPPROTO_RAW, socketList, s)
|
go addSocketToTable(ctx, &wg, syscall.IPPROTO_RAW, socketList, s)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if exclude(pm.Config.Family, unix.AF_PACKET) {
|
func dumpPacketSockets(ctx context.Context, conf *monConfig, socketList *SocketsTable) {
|
||||||
return socketList
|
if exclude(conf.Family, unix.AF_PACKET) {
|
||||||
}
|
return
|
||||||
entries, err := netstat.ParsePacket()
|
|
||||||
if err != nil {
|
|
||||||
return socketList
|
|
||||||
}
|
}
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
pktList := make(map[int]struct{}, len(entries))
|
pktList, err := netlink.SocketDiagPacket(0)
|
||||||
|
for _, pkt := range pktList {
|
||||||
|
/*if excludePacket(pm.Config.Proto, pkt.Num) {
|
||||||
|
continue
|
||||||
|
}*/
|
||||||
|
|
||||||
|
s := netlink.Socket{}
|
||||||
|
s.Family = unix.AF_PACKET
|
||||||
|
s.INode = uint32(pkt.Inode)
|
||||||
|
s.UID = uint32(pkt.UID)
|
||||||
|
s.ID = netlink.SocketID{
|
||||||
|
Interface: uint32(pkt.Mclist.Index),
|
||||||
|
Cookie: pkt.Cookie,
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
go addSocketToTable(ctx, &wg, pkt.Num /* proto */, socketList, s)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumping AF_PACKET from kernel failed. Try it with /proc
|
||||||
|
entries, err := netstat.ParsePacket()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pktEntr := make(map[int]struct{}, len(entries))
|
||||||
for n, e := range entries {
|
for n, e := range entries {
|
||||||
if _, isDup := pktList[n]; isDup {
|
if _, isDup := pktEntr[n]; isDup {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pktList[n] = struct{}{}
|
pktEntr[n] = struct{}{}
|
||||||
|
|
||||||
wg.Add(1)
|
/*if excludePacket(conf.Proto, opt.Proto) {
|
||||||
|
continue
|
||||||
|
}*/
|
||||||
s := netlink.Socket{}
|
s := netlink.Socket{}
|
||||||
s.Family = unix.AF_PACKET
|
s.Family = unix.AF_PACKET
|
||||||
s.INode = uint32(e.INode)
|
s.INode = uint32(e.INode)
|
||||||
|
@ -118,19 +157,18 @@ func (pm *SocketsMonitor) dumpSockets() *SocketsTable {
|
||||||
s.ID = netlink.SocketID{
|
s.ID = netlink.SocketID{
|
||||||
Interface: uint32(e.Iface),
|
Interface: uint32(e.Iface),
|
||||||
}
|
}
|
||||||
// TODO: report the protocol and type
|
// TODO: report sock type
|
||||||
go addSocketToTable(pm.Ctx, &wg, syscall.IPPROTO_RAW, socketList, s)
|
wg.Add(1)
|
||||||
|
go addSocketToTable(ctx, &wg, syscall.IPPROTO_RAW, socketList, s)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
return socketList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func exclude(expected, what uint8) bool {
|
func exclude(expected, what uint8) bool {
|
||||||
return expected > AnySocket && expected != what
|
return expected > AnySocket && expected != what
|
||||||
}
|
}
|
||||||
|
|
||||||
func addSocketToTable(ctx context.Context, wg *sync.WaitGroup, proto uint8, st *SocketsTable, s netlink.Socket) {
|
func addSocketToTable(ctx context.Context, wg *sync.WaitGroup, proto uint16, st *SocketsTable, s netlink.Socket) {
|
||||||
inode := int(s.INode)
|
inode := int(s.INode)
|
||||||
pid := procmon.GetPIDFromINode(inode, fmt.Sprint(inode,
|
pid := procmon.GetPIDFromINode(inode, fmt.Sprint(inode,
|
||||||
s.ID.Source, s.ID.SourcePort, s.ID.Destination, s.ID.DestinationPort),
|
s.ID.Source, s.ID.SourcePort, s.ID.Destination, s.ID.DestinationPort),
|
||||||
|
|
|
@ -15,7 +15,6 @@ Proto = {
|
||||||
'0': 'IP',
|
'0': 'IP',
|
||||||
'1': 'ICMP',
|
'1': 'ICMP',
|
||||||
'2': 'IGMP',
|
'2': 'IGMP',
|
||||||
'3': 'ETH_P_ALL',
|
|
||||||
'6': 'TCP',
|
'6': 'TCP',
|
||||||
'17': 'UDP',
|
'17': 'UDP',
|
||||||
'33': 'DCCP',
|
'33': 'DCCP',
|
||||||
|
@ -23,7 +22,17 @@ Proto = {
|
||||||
'58': 'ICMPv6',
|
'58': 'ICMPv6',
|
||||||
'132': 'SCTP',
|
'132': 'SCTP',
|
||||||
'136': 'UDPLITE',
|
'136': 'UDPLITE',
|
||||||
'255': 'RAW'
|
'255': 'RAW',
|
||||||
|
'3': 'ETH_P_ALL',
|
||||||
|
'2048': 'ETH_P_IP',
|
||||||
|
'34525': 'ETH_P_IPV6',
|
||||||
|
'2054': 'ETH_P_ARP',
|
||||||
|
'32821': 'ETH_P_RARP',
|
||||||
|
'33024': 'ETH_P_8021Q',
|
||||||
|
'4': 'ETH_P_802_2',
|
||||||
|
'34916': 'ETH_P_PPPOE',
|
||||||
|
'34958': 'ETH_P_PAE',
|
||||||
|
'35085': 'ETH_P_FCOE'
|
||||||
}
|
}
|
||||||
|
|
||||||
State = {
|
State = {
|
||||||
|
|
Loading…
Add table
Reference in a new issue