Lookup inode and uid via netlink

It has some advantages over parsing /proc, like performance and
reliability.
This commit is contained in:
Gustavo Iñiguez Goia 2019-12-01 20:10:49 +01:00
parent 4a30cc5b84
commit d8ad8de6ef
3 changed files with 246 additions and 1 deletions

View file

@ -11,6 +11,7 @@ import (
"github.com/gustavo-iniguez-goya/opensnitch/daemon/netstat"
"github.com/gustavo-iniguez-goya/opensnitch/daemon/procmon"
"github.com/gustavo-iniguez-goya/opensnitch/daemon/ui/protocol"
"github.com/gustavo-iniguez-goya/opensnitch/daemon/netlink"
"github.com/google/gopacket/layers"
)
@ -75,11 +76,22 @@ func newConnectionImpl(nfp *netfilter.Packet, c *Connection) (cr *Connection, er
return nil, nil
}
// 0. lookup uid and inode via netlink
// 1. lookup uid and inode using /proc/net/(udp|tcp)
// 2. lookup pid by inode
// 3. if this is coming from us, just accept
// 4. lookup process info by pid
if c.Entry = netstat.FindEntry(c.Protocol, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort); c.Entry == nil {
if _uid, _inode := netlink.GetSocketInfo(c.Protocol, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort); _uid != -1 && _inode != -1 {
c.Entry = &netstat.Entry {
Proto: c.Protocol,
SrcIP: c.SrcIP,
SrcPort: c.SrcPort,
DstIP: c.DstIP,
DstPort: c.DstPort,
UserId: _uid,
INode: _inode,
}
}else if c.Entry = netstat.FindEntry(c.Protocol, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort); c.Entry == nil {
return nil, fmt.Errorf("Could not find netstat entry for: %s", c)
}
if c.Entry.UserId == -1 {

36
daemon/netlink/socket.go Normal file
View file

@ -0,0 +1,36 @@
package netlink
import (
"syscall"
"net"
)
func GetSocketInfo(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint) (uid, inode int) {
family := uint8(syscall.AF_INET)
ipproto := uint8(syscall.IPPROTO_TCP)
protoLen := len(proto)
if proto[protoLen-1:protoLen] == "6" {
family = syscall.AF_INET6
}
var s *Socket
var err error
if proto[:3] == "udp" {
ipproto = syscall.IPPROTO_UDP
if protoLen >=7 && proto[:7] == "udplite" {
ipproto = syscall.IPPROTO_UDPLITE
}
srcAddr := &net.UDPAddr{ IP: srcIP, Port: int(srcPort), }
dstAddr := &net.UDPAddr{ IP: dstIP, Port: int(dstPort), }
s, err = SocketGet(family, ipproto, srcAddr, dstAddr)
} else {
srcAddr := &net.TCPAddr{ IP: srcIP, Port: int(srcPort), }
dstAddr := &net.TCPAddr{ IP: dstIP, Port: int(dstPort), }
s, err = SocketGet(family, ipproto, srcAddr, dstAddr)
}
if err == nil && s.INode != 0xffffffff {
return int(s.UID), int(s.INode)
}
return -1, -1
}

View file

@ -0,0 +1,197 @@
package netlink
import (
"errors"
"fmt"
"net"
"encoding/binary"
"github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix"
)
// This is a copy of https://github.com/vishvananda/netlink socket_linux.go
// which adds support for query UDP, UDPLITE and IPv6 sockets
const (
sizeofSocketID = 0x30
sizeofSocketRequest = sizeofSocketID + 0x8
sizeofSocket = sizeofSocketID + 0x18
)
var (
ErrNotImplemented = errors.New("not implemented")
native = nl.NativeEndian()
networkOrder = binary.BigEndian
)
type SocketID struct {
SourcePort uint16
DestinationPort uint16
Source net.IP
Destination net.IP
Interface uint32
Cookie [2]uint32
}
// Socket represents a netlink socket.
type Socket struct {
Family uint8
State uint8
Timer uint8
Retrans uint8
ID SocketID
Expires uint32
RQueue uint32
WQueue uint32
UID uint32
INode uint32
}
type socketRequest struct {
Family uint8
Protocol uint8
Ext uint8
pad uint8
States uint32
ID SocketID
}
type writeBuffer struct {
Bytes []byte
pos int
}
func (b *writeBuffer) Write(c byte) {
b.Bytes[b.pos] = c
b.pos++
}
func (b *writeBuffer) Next(n int) []byte {
s := b.Bytes[b.pos : b.pos+n]
b.pos += n
return s
}
func (r *socketRequest) Serialize() []byte {
b := writeBuffer{Bytes: make([]byte, sizeofSocketRequest)}
b.Write(r.Family)
b.Write(r.Protocol)
b.Write(r.Ext)
b.Write(r.pad)
native.PutUint32(b.Next(4), r.States)
networkOrder.PutUint16(b.Next(2), r.ID.SourcePort)
networkOrder.PutUint16(b.Next(2), r.ID.DestinationPort)
copy(b.Next(4), r.ID.Source.To4())
b.Next(12)
copy(b.Next(4), r.ID.Destination.To4())
b.Next(12)
native.PutUint32(b.Next(4), r.ID.Interface)
native.PutUint32(b.Next(4), r.ID.Cookie[0])
native.PutUint32(b.Next(4), r.ID.Cookie[1])
return b.Bytes
}
func (r *socketRequest) Len() int { return sizeofSocketRequest }
type readBuffer struct {
Bytes []byte
pos int
}
func (b *readBuffer) Read() byte {
c := b.Bytes[b.pos]
b.pos++
return c
}
func (b *readBuffer) Next(n int) []byte {
s := b.Bytes[b.pos : b.pos+n]
b.pos += n
return s
}
func (s *Socket) deserialize(b []byte) error {
if len(b) < sizeofSocket {
return fmt.Errorf("socket data short read (%d); want %d", len(b), sizeofSocket)
}
rb := readBuffer{Bytes: b}
s.Family = rb.Read()
s.State = rb.Read()
s.Timer = rb.Read()
s.Retrans = rb.Read()
s.ID.SourcePort = networkOrder.Uint16(rb.Next(2))
s.ID.DestinationPort = networkOrder.Uint16(rb.Next(2))
s.ID.Source = net.IPv4(rb.Read(), rb.Read(), rb.Read(), rb.Read())
rb.Next(12)
s.ID.Destination = net.IPv4(rb.Read(), rb.Read(), rb.Read(), rb.Read())
rb.Next(12)
s.ID.Interface = native.Uint32(rb.Next(4))
s.ID.Cookie[0] = native.Uint32(rb.Next(4))
s.ID.Cookie[1] = native.Uint32(rb.Next(4))
s.Expires = native.Uint32(rb.Next(4))
s.RQueue = native.Uint32(rb.Next(4))
s.WQueue = native.Uint32(rb.Next(4))
s.UID = native.Uint32(rb.Next(4))
s.INode = native.Uint32(rb.Next(4))
return nil
}
// SocketGet returns the Socket identified by its local and remote addresses.
func SocketGet(family uint8, proto uint8, local, remote net.Addr) (*Socket, error) {
_Id := SocketID{}
if proto == unix.IPPROTO_UDP || proto == unix.IPPROTO_UDPLITE {
localUDP, ok := local.(*net.UDPAddr)
if !ok {
return nil, ErrNotImplemented
}
remoteUDP, _ := remote.(*net.UDPAddr)
_Id = SocketID{
SourcePort: uint16(localUDP.Port),
DestinationPort: uint16(remoteUDP.Port),
Source: localUDP.IP.To4(),
Destination: remoteUDP.IP.To4(),
Cookie: [2]uint32{nl.TCPDIAG_NOCOOKIE, nl.TCPDIAG_NOCOOKIE},
}
} else {
localTCP, _ := local.(*net.TCPAddr)
remoteTCP, _ := remote.(*net.TCPAddr)
_Id = SocketID{
SourcePort: uint16(localTCP.Port),
DestinationPort: uint16(remoteTCP.Port),
Source: localTCP.IP.To4(),
Destination: remoteTCP.IP.To4(),
Cookie: [2]uint32{nl.TCPDIAG_NOCOOKIE, nl.TCPDIAG_NOCOOKIE},
}
}
s, err := nl.Subscribe(unix.NETLINK_INET_DIAG)
if err != nil {
return nil, err
}
defer s.Close()
req := nl.NewNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, 0)
req.AddData(&socketRequest{
Family: family,
Protocol: proto,
ID: _Id,
})
s.Send(req)
msgs, err := s.Receive()
if err != nil {
return nil, err
}
if len(msgs) == 0 {
return nil, errors.New("no message nor error from netlink")
}
if len(msgs) > 2 {
return nil, fmt.Errorf("multiple (%d) matching sockets", len(msgs))
}
sock := &Socket{}
if err := sock.deserialize(msgs[0].Data); err != nil {
return nil, err
}
return sock, nil
}