132 lines
3.0 KiB
Go
132 lines
3.0 KiB
Go
package collector
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// SortField represents a field to sort by
|
|
type SortField string
|
|
|
|
const (
|
|
SortByPID SortField = "pid"
|
|
SortByProcess SortField = "process"
|
|
SortByUser SortField = "user"
|
|
SortByProto SortField = "proto"
|
|
SortByState SortField = "state"
|
|
SortByLaddr SortField = "laddr"
|
|
SortByLport SortField = "lport"
|
|
SortByRaddr SortField = "raddr"
|
|
SortByRport SortField = "rport"
|
|
SortByInterface SortField = "if"
|
|
SortByRxBytes SortField = "rx_bytes"
|
|
SortByTxBytes SortField = "tx_bytes"
|
|
SortByRttMs SortField = "rtt_ms"
|
|
SortByTimestamp SortField = "ts"
|
|
)
|
|
|
|
// SortDirection represents ascending or descending order
|
|
type SortDirection int
|
|
|
|
const (
|
|
SortAsc SortDirection = iota
|
|
SortDesc
|
|
)
|
|
|
|
// SortOptions configures how connections are sorted
|
|
type SortOptions struct {
|
|
Field SortField
|
|
Direction SortDirection
|
|
}
|
|
|
|
// ParseSortOptions parses a sort string like "pid:desc" or "lport"
|
|
func ParseSortOptions(s string) SortOptions {
|
|
if s == "" {
|
|
return SortOptions{Field: SortByLport, Direction: SortAsc}
|
|
}
|
|
|
|
parts := strings.SplitN(s, ":", 2)
|
|
field := SortField(strings.ToLower(parts[0]))
|
|
direction := SortAsc
|
|
|
|
if len(parts) > 1 && strings.ToLower(parts[1]) == "desc" {
|
|
direction = SortDesc
|
|
}
|
|
|
|
return SortOptions{Field: field, Direction: direction}
|
|
}
|
|
|
|
// SortConnections sorts a slice of connections in place
|
|
func SortConnections(conns []Connection, opts SortOptions) {
|
|
if len(conns) < 2 {
|
|
return
|
|
}
|
|
|
|
sort.SliceStable(conns, func(i, j int) bool {
|
|
less := compareConnections(conns[i], conns[j], opts.Field)
|
|
if opts.Direction == SortDesc {
|
|
return !less
|
|
}
|
|
return less
|
|
})
|
|
}
|
|
|
|
func compareConnections(a, b Connection, field SortField) bool {
|
|
switch field {
|
|
case SortByPID:
|
|
return a.PID < b.PID
|
|
case SortByProcess:
|
|
return strings.ToLower(a.Process) < strings.ToLower(b.Process)
|
|
case SortByUser:
|
|
return strings.ToLower(a.User) < strings.ToLower(b.User)
|
|
case SortByProto:
|
|
return a.Proto < b.Proto
|
|
case SortByState:
|
|
return stateOrder(a.State) < stateOrder(b.State)
|
|
case SortByLaddr:
|
|
return a.Laddr < b.Laddr
|
|
case SortByLport:
|
|
return a.Lport < b.Lport
|
|
case SortByRaddr:
|
|
return a.Raddr < b.Raddr
|
|
case SortByRport:
|
|
return a.Rport < b.Rport
|
|
case SortByInterface:
|
|
return a.Interface < b.Interface
|
|
case SortByRxBytes:
|
|
return a.RxBytes < b.RxBytes
|
|
case SortByTxBytes:
|
|
return a.TxBytes < b.TxBytes
|
|
case SortByRttMs:
|
|
return a.RttMs < b.RttMs
|
|
case SortByTimestamp:
|
|
return a.TS.Before(b.TS)
|
|
default:
|
|
return a.Lport < b.Lport
|
|
}
|
|
}
|
|
|
|
// stateOrder returns a numeric order for connection states
|
|
// puts LISTEN first, then ESTABLISHED, then others
|
|
func stateOrder(state string) int {
|
|
order := map[string]int{
|
|
"LISTEN": 0,
|
|
"ESTABLISHED": 1,
|
|
"SYN_SENT": 2,
|
|
"SYN_RECV": 3,
|
|
"FIN_WAIT1": 4,
|
|
"FIN_WAIT2": 5,
|
|
"TIME_WAIT": 6,
|
|
"CLOSE_WAIT": 7,
|
|
"LAST_ACK": 8,
|
|
"CLOSING": 9,
|
|
"CLOSED": 10,
|
|
}
|
|
|
|
if o, exists := order[strings.ToUpper(state)]; exists {
|
|
return o
|
|
}
|
|
return 99
|
|
}
|
|
|