feat(go): port socket detectors
This commit is contained in:
parent
fc9b5f0448
commit
c6f7219de8
23 changed files with 693 additions and 28 deletions
|
|
@ -4,11 +4,15 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model"
|
||||
)
|
||||
|
|
@ -17,6 +21,34 @@ import (
|
|||
type Paths struct {
|
||||
ProcRoot string
|
||||
LDPreloadPath string
|
||||
SocketSource func() []model.Socket
|
||||
}
|
||||
|
||||
var (
|
||||
inodePattern = regexp.MustCompile(`\bino:(\d+)`)
|
||||
userPattern = regexp.MustCompile(`users:\(\("([^"]+)",pid=(\d+),fd=\d+`)
|
||||
)
|
||||
|
||||
var socketSpecs = []struct {
|
||||
args []string
|
||||
kind string
|
||||
}{
|
||||
{[]string{"-tanep"}, "tcp"},
|
||||
{[]string{"-uanep"}, "udp"},
|
||||
{[]string{"-wanep"}, "raw"},
|
||||
{[]string{"-Sanep"}, "sctp"},
|
||||
{[]string{"-danep"}, "dccp"},
|
||||
{[]string{"-0anep"}, "packet"},
|
||||
{[]string{"-Manep"}, "mptcp"},
|
||||
{[]string{"--tipc", "-anep"}, "tipc"},
|
||||
{[]string{"--xdp", "-anep"}, "xdp"},
|
||||
{[]string{"--vsock", "-anep"}, "vsock"},
|
||||
}
|
||||
|
||||
var socketNetIDs = map[string]bool{
|
||||
"tcp": true, "udp": true, "raw": true, "sctp": true, "dccp": true,
|
||||
"mptcp": true, "p_raw": true, "p_dgr": true, "packet": true,
|
||||
"tipc": true, "xdp": true, "vsock": true,
|
||||
}
|
||||
|
||||
// Capture reads process state once from procRoot. Transient per-process read
|
||||
|
|
@ -55,12 +87,79 @@ func CaptureWithPaths(paths Paths) (model.State, error) {
|
|||
})
|
||||
}
|
||||
sort.Slice(processes, func(i, j int) bool { return processes[i].PID < processes[j].PID })
|
||||
var sockets []model.Socket
|
||||
if paths.SocketSource != nil {
|
||||
sockets = paths.SocketSource()
|
||||
} else {
|
||||
sockets = captureSockets()
|
||||
}
|
||||
return model.State{
|
||||
Processes: processes,
|
||||
Sockets: sockets,
|
||||
LDPreload: strings.TrimSpace(readText(paths.LDPreloadPath)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func captureSockets() []model.Socket {
|
||||
result := make([]model.Socket, 0)
|
||||
for _, spec := range socketSpecs {
|
||||
// Python asks ss once per protocol family because several families use
|
||||
// incompatible flags. Preserve that behavior instead of hiding gaps
|
||||
// behind a single TCP/UDP-only invocation.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
command := exec.CommandContext(ctx, "ss", append([]string{"-H"}, spec.args...)...)
|
||||
output, err := command.Output()
|
||||
cancel()
|
||||
if err != nil {
|
||||
// Collection is fail-open by design: a missing/unsupported ss flag
|
||||
// drops only that socket family and never stops process detection.
|
||||
continue
|
||||
}
|
||||
result = append(result, parseSS(string(output), spec.kind)...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func parseSS(text, kind string) []model.Socket {
|
||||
result := make([]model.Socket, 0)
|
||||
for _, line := range strings.Split(text, "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 5 {
|
||||
continue
|
||||
}
|
||||
var state, local, peer string
|
||||
// Some ss versions prefix each row with the protocol netid and some
|
||||
// begin directly with state. Python accepts both layouts; Go must too.
|
||||
if socketNetIDs[strings.ToLower(fields[0])] && len(fields) >= 6 {
|
||||
state, local, peer = fields[1], fields[4], fields[5]
|
||||
} else {
|
||||
state, local, peer = fields[0], fields[3], fields[4]
|
||||
}
|
||||
var inode, pid *int
|
||||
// Ownership is best-effort. Nil values are meaningful because Python
|
||||
// serializes an unknown PID differently in keys (None) and prose (?).
|
||||
if match := inodePattern.FindStringSubmatch(line); len(match) == 2 {
|
||||
value, err := strconv.Atoi(match[1])
|
||||
if err == nil {
|
||||
inode = &value
|
||||
}
|
||||
}
|
||||
comm := ""
|
||||
if match := userPattern.FindStringSubmatch(line); len(match) == 3 {
|
||||
comm = match[1]
|
||||
value, err := strconv.Atoi(match[2])
|
||||
if err == nil {
|
||||
pid = &value
|
||||
}
|
||||
}
|
||||
result = append(result, model.Socket{
|
||||
State: state, Local: local, Peer: peer, Inode: inode,
|
||||
Comm: comm, PID: pid, Kind: kind,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func parseEnviron(raw string) map[string]string {
|
||||
result := make(map[string]string)
|
||||
for _, item := range strings.Split(raw, "\x00") {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue