add a utility for finding all the TCP ports in listen (with the ability to exclude ports)#125
add a utility for finding all the TCP ports in listen (with the ability to exclude ports)#125
Conversation
…ty to exclude ports)
WalkthroughAdds a cross-platform exported function DetectListeningTCPPorts(exclude ...int) to the network package with Darwin and Linux implementations and a test suite; each implementation enumerates listening TCP ports, applies exclusions, and returns a sorted, unique slice of ports. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as Caller
participant N as network.DetectListeningTCPPorts
rect rgb(240,248,255)
Note right of N: Platform dispatch (build tags)
end
C->>N: DetectListeningTCPPorts(exclude...)
alt Darwin build
N->>OS: run `lsof -nP -iTCP -sTCP:LISTEN`
OS-->>N: command stdout (lines)
N->>N: regex parse ports, filter exclude, dedupe, sort
else Linux build
N->>FS: open `/proc/net/tcp` and `/proc/net/tcp6`
FS-->>N: file contents
N->>N: parse entries, filter state==0A, extract hex port, filter exclude, dedupe, sort
end
N-->>C: ([]int, error)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used🧬 Code graph analysis (2)network/port_linux.go (1)
network/port_test.go (2)
🔇 Additional comments (3)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
network/port_linux.go (1)
32-65: Consider checking scanner errors for consistency.The Darwin implementation checks
scanner.Err()after the scanning loop, but the Linux version does not. While scanning errors from/proc/net/tcp*are unlikely, checking for consistency and defensive coding would be prudent.After the loop closes the file (and before moving to the next file), add:
f.Close() if err := scanner.Err(); err != nil { // Log or handle scanner error if needed // For now, continue to next file continue } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
network/port_darwin.go(1 hunks)network/port_linux.go(1 hunks)network/port_test.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
network/port_darwin.go (1)
network/port_linux.go (1)
DetectListeningTCPPorts(16-69)
network/port_linux.go (1)
network/port_darwin.go (1)
DetectListeningTCPPorts(18-61)
network/port_test.go (2)
network/port_darwin.go (1)
DetectListeningTCPPorts(18-61)network/port_linux.go (1)
DetectListeningTCPPorts(16-69)
🔇 Additional comments (2)
network/port_darwin.go (1)
18-61: LGTM: Solid implementation with proper deduplication.The Darwin implementation correctly:
- Uses a map to deduplicate ports (multiple processes can listen on the same port)
- Checks
scanner.Err()for scanning errors- Sorts results consistently
- Handles exclusions properly
network/port_test.go (1)
11-129: LGTM: Comprehensive test coverage.The test suite provides excellent coverage:
- Basic functionality and sorting
- Single and multiple exclusions
- Non-existent exclusions (edge case)
- Actual listener detection (integration-style)
- Duplicate prevention (lines 110-119 will catch the Linux implementation bug)
- Valid port range validation
The tests appropriately use
t.Skip()when insufficient ports are available, making them resilient to different system states.
| for _, path := range files { | ||
| f, err := os.Open(path) | ||
| if err != nil { | ||
| continue // skip if file doesn't exist or can't be read | ||
| } | ||
| defer f.Close() |
There was a problem hiding this comment.
Fix resource leak: defer in loop.
The defer f.Close() executes at function exit, not at the end of each loop iteration. If both /proc/net/tcp and /proc/net/tcp6 exist, the first file remains open until the function returns.
Apply this diff to close immediately after reading:
for _, path := range files {
f, err := os.Open(path)
if err != nil {
continue // skip if file doesn't exist or can't be read
}
- defer f.Close()
scanner := bufio.NewScanner(f)
// Skip the header line
if scanner.Scan() {
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) < 4 {
continue
}
localAddr := fields[1]
state := fields[3]
if state != "0A" { // 0A means LISTEN
continue
}
// localAddr example: 0100007F:1F90
parts := strings.Split(localAddr, ":")
if len(parts) != 2 {
continue
}
portHex := parts[1]
portDec, err := strconv.ParseInt(portHex, 16, 32)
if err != nil {
continue
}
port := int(portDec)
if _, excluded := excludeSet[port]; !excluded {
ports = append(ports, port)
}
}
}
+ f.Close()
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for _, path := range files { | |
| f, err := os.Open(path) | |
| if err != nil { | |
| continue // skip if file doesn't exist or can't be read | |
| } | |
| defer f.Close() | |
| for _, path := range files { | |
| f, err := os.Open(path) | |
| if err != nil { | |
| continue // skip if file doesn't exist or can't be read | |
| } | |
| scanner := bufio.NewScanner(f) | |
| // Skip the header line | |
| if scanner.Scan() { | |
| for scanner.Scan() { | |
| fields := strings.Fields(scanner.Text()) | |
| if len(fields) < 4 { | |
| continue | |
| } | |
| localAddr := fields[1] | |
| state := fields[3] | |
| if state != "0A" { // 0A means LISTEN | |
| continue | |
| } | |
| // localAddr example: 0100007F:1F90 | |
| parts := strings.Split(localAddr, ":") | |
| if len(parts) != 2 { | |
| continue | |
| } | |
| portHex := parts[1] | |
| portDec, err := strconv.ParseInt(portHex, 16, 32) | |
| if err != nil { | |
| continue | |
| } | |
| port := int(portDec) | |
| if _, excluded := excludeSet[port]; !excluded { | |
| ports = append(ports, port) | |
| } | |
| } | |
| } | |
| f.Close() | |
| } |
🤖 Prompt for AI Agents
In network/port_linux.go around lines 25 to 30, the code uses "defer f.Close()"
inside the loop which defers closing until function exit and leaks file
descriptors when iterating multiple files; replace the defer with an explicit
close after the file is read (or wrap the read logic in a short-lived inner
function and defer f.Close() there) so each opened file is closed immediately at
the end of its iteration.
Summary by CodeRabbit
New Features
Tests