36b3af2e3d
PR Check / Code Quality: Format (push) Failing after 1s
PR Check / Code Quality: Lint (darwin) (push) Failing after 0s
PR Check / Code Quality: Lint (freebsd) (push) Failing after 1s
PR Check / Code Quality: Lint (windows) (push) Failing after 1s
PR Check / Code Quality: Lint (linux) (push) Failing after 1s
PR Check / Security: Vulnerability Scan (push) Failing after 0s
Update Documentation / update-docs (push) Failing after 2s
PR Check / Code Quality: Vendor (push) Failing after 1s
PR Check / Code Quality: Coverage (push) Failing after 0s
PR Check / Tests: Unit (macos-latest) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04-arm) (push) Has been cancelled
PR Check / Tests: Unit (windows-latest) (push) Has been cancelled
157 lines
3.4 KiB
Go
157 lines
3.4 KiB
Go
//go:build linux
|
|
|
|
package proc
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/pranshuparmar/witr/pkg/model"
|
|
)
|
|
|
|
// GetSocketStateForPort returns the socket state for a port
|
|
// Linux implementation using /proc/net/tcp and /proc/net/tcp6
|
|
func GetSocketStateForPort(port int) *model.SocketInfo {
|
|
// Check both IPv4 and IPv6
|
|
files := []string{"/proc/net/tcp", "/proc/net/tcp6"}
|
|
|
|
var states []model.SocketInfo
|
|
|
|
for _, file := range files {
|
|
isIPv6 := strings.HasSuffix(file, "tcp6")
|
|
|
|
func() {
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer f.Close()
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
scanner.Scan()
|
|
|
|
for scanner.Scan() {
|
|
fields := strings.Fields(scanner.Text())
|
|
if len(fields) < 10 {
|
|
continue
|
|
}
|
|
|
|
localAddrHex := fields[1]
|
|
localIP, localPort := parseAddr(localAddrHex, isIPv6)
|
|
|
|
if localPort != port {
|
|
continue
|
|
}
|
|
|
|
remoteAddrHex := fields[2]
|
|
remoteIP, _ := parseAddr(remoteAddrHex, isIPv6)
|
|
|
|
stateHex := fields[3]
|
|
stateVal, _ := strconv.ParseInt(stateHex, 16, 0)
|
|
stateStr := mapTCPState(int(stateVal))
|
|
|
|
info := model.SocketInfo{
|
|
Port: port,
|
|
State: stateStr,
|
|
LocalAddr: localIP,
|
|
RemoteAddr: remoteIP,
|
|
}
|
|
|
|
addStateExplanation(&info)
|
|
states = append(states, info)
|
|
}
|
|
}()
|
|
}
|
|
|
|
if len(states) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Prioritize problematic states just like the Darwin implementation
|
|
for _, s := range states {
|
|
if isProblematicState(s.State) {
|
|
return &s
|
|
}
|
|
}
|
|
|
|
// Then prioritize LISTEN
|
|
for _, s := range states {
|
|
if s.State == "LISTEN" {
|
|
return &s
|
|
}
|
|
}
|
|
|
|
// Default to first found
|
|
return &states[0]
|
|
}
|
|
|
|
// mapTCPState maps Linux kernel TCP states (from include/net/tcp_states.h) to strings
|
|
func mapTCPState(state int) string {
|
|
switch state {
|
|
case 1:
|
|
return "ESTABLISHED"
|
|
case 2:
|
|
return "SYN_SENT"
|
|
case 3:
|
|
return "SYN_RECV"
|
|
case 4:
|
|
return "FIN_WAIT_1"
|
|
case 5:
|
|
return "FIN_WAIT_2"
|
|
case 6:
|
|
return "TIME_WAIT"
|
|
case 7:
|
|
return "CLOSE"
|
|
case 8:
|
|
return "CLOSE_WAIT"
|
|
case 9:
|
|
return "LAST_ACK"
|
|
case 10:
|
|
return "LISTEN"
|
|
case 11:
|
|
return "CLOSING"
|
|
default:
|
|
return fmt.Sprintf("UNKNOWN (%02X)", state)
|
|
}
|
|
}
|
|
|
|
func isProblematicState(state string) bool {
|
|
switch state {
|
|
case "TIME_WAIT", "CLOSE_WAIT", "FIN_WAIT_1", "FIN_WAIT_2":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func addStateExplanation(info *model.SocketInfo) {
|
|
switch info.State {
|
|
case "LISTEN":
|
|
info.Explanation = "Actively listening for connections"
|
|
case "TIME_WAIT":
|
|
info.Explanation = "Connection closed, waiting for delayed packets"
|
|
info.Workaround = "Wait for timeout (usually 60s) or use SO_REUSEADDR"
|
|
case "CLOSE_WAIT":
|
|
info.Explanation = "Remote side closed connection, local side has not closed yet"
|
|
info.Workaround = "The application should call close() on the socket"
|
|
case "FIN_WAIT_1":
|
|
info.Explanation = "Local side initiated close, waiting for acknowledgment"
|
|
case "FIN_WAIT_2":
|
|
info.Explanation = "Local close acknowledged, waiting for remote close"
|
|
case "ESTABLISHED":
|
|
info.Explanation = "Active connection"
|
|
case "SYN_SENT":
|
|
info.Explanation = "Connection request sent, waiting for response"
|
|
case "SYN_RECEIVED":
|
|
info.Explanation = "Connection request received, sending acknowledgment"
|
|
case "CLOSING":
|
|
info.Explanation = "Both sides initiated close simultaneously"
|
|
case "LAST_ACK":
|
|
info.Explanation = "Waiting for final acknowledgment of close"
|
|
default:
|
|
info.Explanation = "Socket in " + info.State + " state"
|
|
}
|
|
}
|