opensnitch/daemon/netlink/socket_linux.go
Gustavo Iñiguez Goia e5b54f0a6b eBPF: ignore netlink errors if there're no connections
When enabling the eBPF monitor method we dump the active connections,
but in some cases there're no active connections, and because of this
we're failing enabling this monitor method.

If there're no connections established, netlink returns 0 entries. It's
not clear if it's an indication of error in some cases or the expected
result.

Either way:
- fail only if we're unable to load the eBPF module.
- dump TCP IPv6 connections only if IPv6 is enabled in the syste,-
2021-05-29 00:16:18 +02:00

245 lines
5.9 KiB
Go

package netlink
import (
"encoding/binary"
"errors"
"fmt"
"net"
"syscall"
"github.com/evilsocket/opensnitch/daemon/log"
"github.com/vishvananda/netlink/nl"
)
// This is a modification of https://github.com/vishvananda/netlink socket_linux.go - Apache2.0 license
// which adds support for query UDP, UDPLITE and IPv6 sockets to SocketGet()
const (
sizeofSocketID = 0x30
sizeofSocketRequest = sizeofSocketID + 0x8
sizeofSocket = sizeofSocketID + 0x18
)
var (
native = nl.NativeEndian()
networkOrder = binary.BigEndian
TCP_ALL = uint32(0xfff)
)
// https://elixir.bootlin.com/linux/latest/source/include/net/tcp_states.h
const (
TCP_INVALID = iota
TCP_ESTABLISHED
TCP_SYN_SENT
TCP_SYN_RECV
TCP_FIN_WAIT1
TCP_FIN_WAIT2
TCP_TIME_WAIT
TCP_CLOSE
TCP_CLOSE_WAIT
TCP_LAST_ACK
TCP_LISTEN
TCP_CLOSING
TCP_NEW_SYN_REC
TCP_MAX_STATES
)
// TCPStatesMap holds the list of TCP states
var TCPStatesMap = map[uint8]string{
TCP_INVALID: "invalid",
TCP_ESTABLISHED: "established",
TCP_SYN_SENT: "syn_sent",
TCP_SYN_RECV: "syn_recv",
TCP_FIN_WAIT1: "fin_wait1",
TCP_FIN_WAIT2: "fin_wait2",
TCP_TIME_WAIT: "time_wait",
TCP_CLOSE: "close",
TCP_CLOSE_WAIT: "close_wait",
TCP_LAST_ACK: "last_ack",
TCP_LISTEN: "listen",
TCP_CLOSING: "closing",
}
// SocketID holds the socket information of a request/response to the kernel
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
}
// SocketRequest holds the request/response of a connection to the kernel
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
}
// Serialize convert SocketRequest struct to bytes.
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)
if r.Family == syscall.AF_INET6 {
copy(b.Next(16), r.ID.Source)
copy(b.Next(16), r.ID.Destination)
} else {
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
}
// Len returns the size of a socket request
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))
if s.Family == syscall.AF_INET6 {
s.ID.Source = net.IP(rb.Next(16))
s.ID.Destination = net.IP(rb.Next(16))
} else {
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 list of active connections in the kernel
// filtered by several fields. Currently it returns connections
// filtered by source port and protocol.
func SocketGet(family uint8, proto uint8, srcPort, dstPort uint16, local, remote net.IP) ([]*Socket, error) {
_Id := SocketID{
SourcePort: srcPort,
Cookie: [2]uint32{nl.TCPDIAG_NOCOOKIE, nl.TCPDIAG_NOCOOKIE},
}
sockReq := &SocketRequest{
Family: family,
Protocol: proto,
States: TCP_ALL,
ID: _Id,
}
return netlinkRequest(sockReq, family, proto, srcPort, dstPort, local, remote)
}
// SocketsDump returns the list of all connections from the kernel
func SocketsDump(family uint8, proto uint8) ([]*Socket, error) {
sockReq := &SocketRequest{
Family: family,
Protocol: proto,
States: TCP_ALL,
}
return netlinkRequest(sockReq, 0, 0, 0, 0, nil, nil)
}
func netlinkRequest(sockReq *SocketRequest, family uint8, proto uint8, srcPort, dstPort uint16, local, remote net.IP) ([]*Socket, error) {
req := nl.NewNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, syscall.NLM_F_DUMP)
req.AddData(sockReq)
msgs, err := req.Execute(syscall.NETLINK_INET_DIAG, 0)
if err != nil {
return nil, err
}
if len(msgs) == 0 {
return nil, errors.New("Warning, no message nor error from netlink, or no connections found")
}
var sock []*Socket
for n, m := range msgs {
s := &Socket{}
if err = s.deserialize(m); err != nil {
log.Error("[%d] netlink socket error: %s, %d:%v -> %v:%d - %d:%v -> %v:%d",
n, TCPStatesMap[s.State],
srcPort, local, remote, dstPort,
s.ID.SourcePort, s.ID.Source, s.ID.Destination, s.ID.DestinationPort)
continue
}
if s.INode == 0 {
continue
}
sock = append([]*Socket{s}, sock...)
}
return sock, err
}