forked from wrenn/wrenn
Co-authored-by: Tasnim Kabir Sadik <tksadik92@gmail.com> Reviewed-on: wrenn/sandbox#8
166 lines
4.0 KiB
Go
166 lines
4.0 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package port
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
// ConnStat represents a single TCP connection read from /proc/net/tcp(6).
|
|
// It contains only the fields needed by the port scanner and forwarder.
|
|
type ConnStat struct {
|
|
LocalIP string
|
|
LocalPort uint32
|
|
Status string
|
|
Family uint32 // syscall.AF_INET or syscall.AF_INET6
|
|
Inode uint64 // socket inode, unique per connection
|
|
}
|
|
|
|
// tcpStates maps the hex state values from /proc/net/tcp to string names
|
|
// matching the gopsutil convention used by ScannerFilter.
|
|
var tcpStates = map[string]string{
|
|
"01": "ESTABLISHED",
|
|
"02": "SYN_SENT",
|
|
"03": "SYN_RECV",
|
|
"04": "FIN_WAIT1",
|
|
"05": "FIN_WAIT2",
|
|
"06": "TIME_WAIT",
|
|
"07": "CLOSE",
|
|
"08": "CLOSE_WAIT",
|
|
"09": "LAST_ACK",
|
|
"0A": "LISTEN",
|
|
"0B": "CLOSING",
|
|
}
|
|
|
|
// ReadTCPConnections reads /proc/net/tcp and /proc/net/tcp6 and returns
|
|
// all TCP connections. This avoids the /proc/{pid}/fd walk that gopsutil
|
|
// performs, which is unsafe across Firecracker snapshot/restore boundaries.
|
|
func ReadTCPConnections() ([]ConnStat, error) {
|
|
var conns []ConnStat
|
|
|
|
tcp4, err := parseProcNetTCP("/proc/net/tcp", syscall.AF_INET)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse /proc/net/tcp: %w", err)
|
|
}
|
|
conns = append(conns, tcp4...)
|
|
|
|
tcp6, err := parseProcNetTCP("/proc/net/tcp6", syscall.AF_INET6)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse /proc/net/tcp6: %w", err)
|
|
}
|
|
conns = append(conns, tcp6...)
|
|
|
|
return conns, nil
|
|
}
|
|
|
|
// parseProcNetTCP reads a single /proc/net/tcp or /proc/net/tcp6 file.
|
|
//
|
|
// Format (fields are whitespace-separated):
|
|
//
|
|
// sl local_address rem_address st tx_queue:rx_queue tr:tm->when retrnsmt uid timeout inode
|
|
// 0: 0100007F:1F90 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 12345
|
|
func parseProcNetTCP(path string, family uint32) ([]ConnStat, error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
var conns []ConnStat
|
|
scanner := bufio.NewScanner(f)
|
|
|
|
// Skip header line.
|
|
scanner.Scan()
|
|
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" {
|
|
continue
|
|
}
|
|
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 10 {
|
|
continue
|
|
}
|
|
|
|
// fields[1] = local_address (hex_ip:hex_port)
|
|
ip, port, err := parseHexAddr(fields[1], family)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// fields[3] = state (hex)
|
|
state, ok := tcpStates[fields[3]]
|
|
if !ok {
|
|
state = "UNKNOWN"
|
|
}
|
|
|
|
// fields[9] = inode
|
|
inode, err := strconv.ParseUint(fields[9], 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
conns = append(conns, ConnStat{
|
|
LocalIP: ip,
|
|
LocalPort: port,
|
|
Status: state,
|
|
Family: family,
|
|
Inode: inode,
|
|
})
|
|
}
|
|
|
|
return conns, scanner.Err()
|
|
}
|
|
|
|
// parseHexAddr parses "HEXIP:HEXPORT" from /proc/net/tcp.
|
|
// IPv4 addresses are 8 hex chars (4 bytes, little-endian per 32-bit word).
|
|
// IPv6 addresses are 32 hex chars (16 bytes, little-endian per 32-bit word).
|
|
func parseHexAddr(s string, family uint32) (string, uint32, error) {
|
|
parts := strings.SplitN(s, ":", 2)
|
|
if len(parts) != 2 {
|
|
return "", 0, fmt.Errorf("invalid address: %s", s)
|
|
}
|
|
|
|
port64, err := strconv.ParseUint(parts[1], 16, 32)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
|
|
ipHex := parts[0]
|
|
ipBytes, err := hex.DecodeString(ipHex)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
|
|
var ip net.IP
|
|
if family == syscall.AF_INET {
|
|
if len(ipBytes) != 4 {
|
|
return "", 0, fmt.Errorf("invalid IPv4 length: %d", len(ipBytes))
|
|
}
|
|
// /proc/net/tcp stores IPv4 as a single little-endian 32-bit word.
|
|
ip = net.IPv4(ipBytes[3], ipBytes[2], ipBytes[1], ipBytes[0])
|
|
} else {
|
|
if len(ipBytes) != 16 {
|
|
return "", 0, fmt.Errorf("invalid IPv6 length: %d", len(ipBytes))
|
|
}
|
|
// /proc/net/tcp6 stores IPv6 as four little-endian 32-bit words.
|
|
ip = make(net.IP, 16)
|
|
for i := 0; i < 4; i++ {
|
|
ip[i*4+0] = ipBytes[i*4+3]
|
|
ip[i*4+1] = ipBytes[i*4+2]
|
|
ip[i*4+2] = ipBytes[i*4+1]
|
|
ip[i*4+3] = ipBytes[i*4+0]
|
|
}
|
|
}
|
|
|
|
return ip.String(), uint32(port64), nil
|
|
}
|