Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:22:06 +08:00

173 lines
4.3 KiB
Go

//go:build darwin
package proc
import (
"fmt"
"os/exec"
"strconv"
"strings"
"github.com/pranshuparmar/witr/pkg/model"
)
// GetSocketStates returns all socket states for a given port
func GetSocketStates(port int) ([]model.SocketInfo, error) {
var sockets []model.SocketInfo
// Use netstat to get all socket states (not just LISTEN)
// netstat -an -p tcp shows all TCP connections with states
out, err := exec.Command("netstat", "-an", "-p", "tcp").Output()
if err != nil {
return nil, fmt.Errorf("failed to get socket states: %w", err)
}
portSuffix := fmt.Sprintf(".%d", port)
portColonSuffix := fmt.Sprintf(":%d", port)
for line := range strings.Lines(string(out)) {
fields := strings.Fields(line)
if len(fields) < 6 {
continue
}
// Check if this line mentions our port
localAddr := fields[3]
if !strings.HasSuffix(localAddr, portSuffix) && !strings.HasSuffix(localAddr, portColonSuffix) {
continue
}
// Parse the state (field 5)
state := fields[5]
remoteAddr := fields[4]
// Parse local address
address, _ := parseNetstatAddr(localAddr)
info := model.SocketInfo{
Port: port,
State: state,
LocalAddr: address,
RemoteAddr: remoteAddr,
}
// Add explanation and workaround based on state
addStateExplanation(&info)
sockets = append(sockets, info)
}
return sockets, nil
}
// GetSocketStateForPort returns the most relevant socket state for a port
// Prioritizes non-LISTEN states that explain why a port might be unavailable
func GetSocketStateForPort(port int) *model.SocketInfo {
states, err := GetSocketStates(port)
if err != nil || len(states) == 0 {
return nil
}
// Prioritize problematic states
for _, s := range states {
if s.State == "TIME_WAIT" || s.State == "CLOSE_WAIT" || s.State == "FIN_WAIT_1" || s.State == "FIN_WAIT_2" {
return &s
}
}
// Return LISTEN if that's all we have
for _, s := range states {
if s.State == "LISTEN" {
return &s
}
}
// Return first state found
if len(states) > 0 {
return &states[0]
}
return nil
}
// addStateExplanation adds human-readable explanation for socket states
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 (default 60s on macOS)"
info.Workaround = "Wait for timeout to expire, or use SO_REUSEADDR in your server"
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"
}
}
// GetTIMEWAITRemaining estimates remaining TIME_WAIT duration
// macOS default MSL is 30 seconds, so TIME_WAIT is 60 seconds
func GetTIMEWAITRemaining() string {
// We can't easily determine when TIME_WAIT started without additional tracking
// Return a general estimate
return "up to 60s remaining (macOS default)"
}
// CountSocketsByState returns a count of sockets by state for a port
func CountSocketsByState(port int) map[string]int {
counts := make(map[string]int)
states, err := GetSocketStates(port)
if err != nil {
return counts
}
for _, s := range states {
counts[s.State]++
}
return counts
}
// GetMSLDuration returns the Maximum Segment Lifetime setting
// This determines TIME_WAIT duration (2 * MSL)
func GetMSLDuration() int {
// Try to read from sysctl
out, err := exec.Command("sysctl", "-n", "net.inet.tcp.msl").Output()
if err != nil {
return 30000 // Default 30 seconds in milliseconds
}
msl, err := strconv.Atoi(strings.TrimSpace(string(out)))
if err != nil {
return 30000
}
return msl
}