sniffer/conn_linux.go

400 lines
8.5 KiB
Go

//go:build linux
// +build linux
package main
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"unsafe"
"golang.org/x/sys/unix"
)
const (
tcpEstablished = uint8(0x01)
udpConnection = uint8(0x07)
sizeOfInetDiagRequest = 72
sockDiagByFamily = 20
)
var nativeEndian binary.ByteOrder
// getNativeEndian gets native endianness for the system
func getNativeEndian() binary.ByteOrder {
if nativeEndian == nil {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
nativeEndian = binary.BigEndian
} else {
nativeEndian = binary.LittleEndian
}
}
return nativeEndian
}
type be16 [2]byte
// Int be16 to int
func (v be16) Int() int {
v2 := *(*uint16)(unsafe.Pointer(&v))
return int(v.Swap(v2))
}
// Swap swaps a 16 bit value if we aren't big endian
func (v be16) Swap(i uint16) uint16 {
if getNativeEndian() == binary.BigEndian {
return i
}
return (i&0xff00)>>8 | (i&0xff)<<8
}
// PortHex parses be16 to hex
func (v be16) PortHex() string {
return hex.EncodeToString(v[0:])
}
type be32 [4]byte
// inetDiagSockID sock_diag
/* inet_diag.h
struct inet_diag_sockid {
__be16 idiag_sport;
__be16 idiag_dport;
__be32 idiag_src[4];
__be32 idiag_dst[4];
__u32 idiag_if;
__u32 idiag_cookie[2];
#define INET_DIAG_NOCOOKIE (~0U)
};
*/
type inetDiagSockID struct {
IdiagSport be16
IdiagDport be16
IdiagSrc [4]be32
IdiagDst [4]be32
IdiagIF uint32
IdiagCookie [2]uint32
}
// inetDiagReqV2 sock_diag
/* inet_diag.h
struct inet_diag_req_v2 {
__u8 sdiag_family;
__u8 sdiag_protocol;
__u8 idiag_ext;
__u8 pad;
__u32 idiag_states;
struct inet_diag_sockid id;
};
*/
type inetDiagReqV2 struct {
Family uint8
Protocol uint8
Ext uint8
Pad uint8
States uint32
ID inetDiagSockID
}
// inetDiagMsg receiv msg
/* inet_diag.h
Base info structure. It contains Socket identity (addrs/ports/cookie) and, alas, the information shown by netstat.
struct inet_diag_msg {
__u8 idiag_family;
__u8 idiag_state;
__u8 idiag_timer;
__u8 idiag_retrans;
struct inet_diag_sockid id;
__u32 idiag_expires;
__u32 idiag_rqueue;
__u32 idiag_wqueue;
__u32 idiag_uid;
__u32 idiag_inode;
};
*/
type inetDiagMsg struct {
IDiagFamily uint8
IDiagState uint8
IDiagTimer uint8
IDiagRetrans uint8
ID inetDiagSockID
IDiagExpires uint32
IDiagRqueue uint32
IDiagWqueue uint32
IDiagUid uint32
IDiagInode uint32
}
// inetDiagRequest diag_request
/* go/src/syscall/ztypes_linux_amd64.go
type NlMsghdr struct {
Len uint32
Type uint16
Flags uint16
Seq uint32
Pid uint32
}
*/
type inetDiagRequest struct {
Nlh syscall.NlMsghdr
ReqDiag inetDiagReqV2
}
type netlinkConn struct{}
// ipv4 be32 to string
func (nl *netlinkConn) ipv4(b be32) string {
return net.IPv4(b[0], b[1], b[2], b[3]).String()
}
// ipv6 be32 to string
func (nl *netlinkConn) ipv6(b [4]be32) string {
ip := make(net.IP, net.IPv6len)
for i := 0; i < 4; i++ {
for j := 0; j < 4; j++ {
ip[4*i+j] = b[i][j]
}
}
return ip.String()
}
// ipHex2String ip hex to string
func (nl *netlinkConn) ipHex2String(family uint8, ip [4]be32) (string, error) {
switch family {
case unix.AF_INET:
return nl.ipv4(ip[0]), nil
case unix.AF_INET6:
return nl.ipv6(ip), nil
default:
return "", errors.New("family is not unix.AF_INET or unix.AF_INET6")
}
}
// sockdiagSend sends netlinkConn msgs
// see https://github.com/sivasankariit/iproute2/blob/1179ab033c31d2c67f406be5bcd5e4c0685855fe/misc/ss.c#L1575-L1640
func (nl *netlinkConn) sockdiagSend(proto, family uint8, states uint32) (skfd int, err error) {
if skfd, err = unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW, unix.NETLINK_SOCK_DIAG); err != nil {
return -1, err
}
var diagReq inetDiagRequest
diagReq.Nlh.Type = sockDiagByFamily
// man 7 netlinkConn: NLM_F_DUMP Convenience macro; equivalent to (NLM_F_ROOT|NLM_F_MATCH).
diagReq.Nlh.Flags = unix.NLM_F_DUMP | unix.NLM_F_REQUEST
diagReq.ReqDiag.Family = family
diagReq.ReqDiag.Protocol = proto
diagReq.ReqDiag.States = states
diagReq.Nlh.Len = uint32(unsafe.Sizeof(diagReq))
buffer := make([]byte, sizeOfInetDiagRequest)
*(*inetDiagRequest)(unsafe.Pointer(&buffer[0])) = diagReq
sockAddrNl := unix.SockaddrNetlink{Family: syscall.AF_NETLINK}
timeout := syscall.NsecToTimeval((200 * time.Millisecond).Nanoseconds())
if err = syscall.SetsockoptTimeval(skfd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &timeout); err != nil {
return 0, err
}
if err = unix.Sendmsg(skfd, buffer, nil, &sockAddrNl, 0); err != nil {
return -1, err
}
return skfd, nil
}
func (nl *netlinkConn) sockdiagRecv(skfd, proto int, inodeMap map[uint32]string) (OpenSockets, error) {
sockets := make(OpenSockets)
buffer := make([]byte, os.Getpagesize())
loop:
for {
n, _, _, _, err := unix.Recvmsg(skfd, buffer, nil, 0)
if err != nil {
return sockets, err
}
if n == 0 {
break loop
}
msgs, err := syscall.ParseNetlinkMessage(buffer[:n])
if err != nil {
return sockets, err
}
for _, msg := range msgs {
if msg.Header.Type == syscall.NLMSG_DONE {
break loop
}
m := (*inetDiagMsg)(unsafe.Pointer(&msg.Data[0]))
srcIP, _ := nl.ipHex2String(m.IDiagFamily, m.ID.IdiagSrc)
procInfo := ProcessInfo{
Pid: int(msg.Header.Pid),
Name: inodeMap[m.IDiagInode],
}
var p Protocol
switch proto {
case syscall.IPPROTO_TCP:
p = ProtoTCP
case syscall.IPPROTO_UDP:
p = ProtoUDP
}
sockets[LocalSocket{IP: srcIP, Port: uint16(m.ID.IdiagSport.Int()), Protocol: p}] = procInfo
}
}
return sockets, nil
}
func (nl *netlinkConn) getOpenSockets(inodeMap map[uint32]string) (OpenSockets, error) {
sockets := make(OpenSockets)
type Req struct {
Protocol int
Family uint8
State uint32
}
reqs := []Req{
{syscall.IPPROTO_TCP, syscall.AF_INET, uint32(1 | 1<<tcpEstablished)},
{syscall.IPPROTO_TCP, syscall.AF_INET6, uint32(1 | 1<<tcpEstablished)},
{syscall.IPPROTO_UDP, syscall.AF_INET, uint32(1 << udpConnection)},
{syscall.IPPROTO_UDP, syscall.AF_INET6, uint32(1 << udpConnection)},
}
type Fd struct {
fd, proto int
}
var fds []Fd
for _, req := range reqs {
fd, err := nl.sockdiagSend(uint8(req.Protocol), req.Family, req.State)
if err != nil {
return nil, err
}
defer syscall.Close(fd)
fds = append(fds, Fd{fd, req.Protocol})
}
for _, fd := range fds {
m, err := nl.sockdiagRecv(fd.fd, fd.proto, inodeMap)
if err != nil {
return sockets, err
}
for k, v := range m {
sockets[k] = v
}
}
return sockets, nil
}
func (nl *netlinkConn) getAllProcsInodes(pids ...int32) map[uint32]string {
inode2Procs := make(map[uint32]string)
for _, pid := range pids {
procName, inodes, err := nl.getProcInodes(pid)
if err != nil {
continue
}
for _, inode := range inodes {
inode2Procs[inode] = procName
}
}
return inode2Procs
}
func (nl *netlinkConn) getProcInodes(pid int32) (string, []uint32, error) {
var inodeFds []uint32
procName, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pid))
if err != nil {
return procName, inodeFds, err
}
f, err := os.Open(fmt.Sprintf("/proc/%d/fd", pid))
if err != nil {
return procName, inodeFds, err
}
defer f.Close()
files, err := f.Readdir(0)
if err != nil {
return procName, inodeFds, err
}
for _, file := range files {
inode, err := os.Readlink(fmt.Sprintf("/proc/%d/fd/%s", pid, file.Name()))
if err != nil {
continue
}
// Socket:[1070205860]
if !strings.HasPrefix(inode, "socket:[") {
continue
}
inodeInt, err := strconv.Atoi(inode[8 : len(inode)-1])
if err != nil {
continue
}
inodeFds = append(inodeFds, uint32(inodeInt))
}
return filepath.Base(procName), inodeFds, nil
}
func (nl *netlinkConn) listPids() ([]int32, error) {
var pids []int32
d, err := os.Open("/proc")
if err != nil {
return pids, err
}
defer d.Close()
fnames, err := d.Readdirnames(-1)
if err != nil {
return pids, err
}
for _, fname := range fnames {
pid, err := strconv.ParseInt(fname, 10, 32)
if err != nil {
continue
}
pids = append(pids, int32(pid))
}
return pids, nil
}
func (nl *netlinkConn) GetOpenSockets(pids ...int32) (OpenSockets, error) {
var err error
if len(pids) == 0 {
pids, err = nl.listPids()
if err != nil {
return nil, err
}
}
inodeMap := nl.getAllProcsInodes(pids...)
return nl.getOpenSockets(inodeMap)
}
func GetSocketFetcher() SocketFetcher {
return &netlinkConn{}
}