Skip to content

fix(netstat): handle missing /proc/net/tcp6 on IPv6-disabled hosts#706

Draft
YassineElbouchaibi wants to merge 1 commit intoskevetter:mainfrom
YassineElbouchaibi:fix/705-netstat-ipv6-disabled-procfile-missing
Draft

fix(netstat): handle missing /proc/net/tcp6 on IPv6-disabled hosts#706
YassineElbouchaibi wants to merge 1 commit intoskevetter:mainfrom
YassineElbouchaibi:fix/705-netstat-ipv6-disabled-procfile-missing

Conversation

@YassineElbouchaibi
Copy link
Copy Markdown

@YassineElbouchaibi YassineElbouchaibi commented Apr 9, 2026

Summary

Fixes #705. On Linux hosts where IPv6 is disabled at boot (ipv6.disable=1) or absent from the kernel (CONFIG_IPV6=n), /proc/net/tcp6 does not exist. The devpod agent's netstat-based port watcher was returning the os.IsNotExist error verbatim from doNetstat, which propagated through TCP6Socks and caused watcher.findPorts to discard the already-collected IPv4 sockets and return an error on every tick. The net effect was that no ports were ever forwarded and IDEs like openvscode were unreachable.

Fix

pkg/netstat/netstat_util.go — in doNetstat, treat os.IsNotExist from os.Open as "zero sockets of this family" and return (nil, nil). Matches the repo's existing graceful-degradation pattern (see pkg/gitsshsigning/helper.go, pkg/workspace/provider.go, pkg/envfile/envfile.go). All other errors — permission, I/O, parse — still propagate unchanged.

Applied once in the common helper rather than per-caller so that osTCPSocks, osTCP6Socks, osUDPSocks, and osUDP6Socks all benefit uniformly.

Tests

Adds pkg/netstat/netstat_util_test.go — the first test file in this package — with four TDD-driven tests:

  1. TestDoNetstat_MissingFileReturnsEmpty — red-phase driver; fails before the fix, passes after.
  2. TestDoNetstat_ParsesValidFile — pins the happy path with a synthetic /proc/net/tcp6-format entry ([::]:22 LISTEN) so the fix cannot accidentally suppress successful parses.
  3. TestDoNetstat_PropagatesNonNotExistOpenError — proves the fix is surgical by confirming permission-denied errors still bubble up. Skipped on Windows and when running as root.
  4. TestDoNetstat_PropagatesParseError — confirms malformed content still errors.

Tests use the testify/suite pattern consistent with pkg/gitsshsigning/helper_test.go and rely only on t.TempDir(), so they are OS-agnostic.

Test plan

  • go test ./pkg/netstat/... -run TestNetstatUtilSuite -v passes (all 4 tests)
  • task cli:test equivalent (go test ./... -race -coverprofile=dist/profile.out -covermode=atomic, excluding e2e) passes
  • task cli:lint:new (golangci-lint --new-from-rev origin/main) reports 0 new issues
  • go vet ./pkg/netstat/... clean
  • Manual smoke test on a RHEL 9 VM booted with ipv6.disable=1: start a workspace, confirm openvscode is reachable and the agent log no longer shows repeated open /proc/net/tcp6: no such file or directory entries from the watcher.

Follow-ups (not in this PR)

watcher.findPorts still discards IPv4 results if TCP6Socks errors for any non-IsNotExist reason. Worth hardening, but requires injecting a socket source into Watcher — out of scope for a bug fix. Happy to open a separate issue.

Fixes #705

Summary by CodeRabbit

  • Bug Fixes

    • Improved netstat file handling: missing netstat files now return empty results gracefully instead of propagating errors, while other file access errors continue to be reported.
  • Tests

    • Added comprehensive test suite covering netstat parsing, error handling, and edge cases.

On Linux hosts booted with ipv6.disable=1 or built without CONFIG_IPV6,
/proc/net/tcp6 and /proc/net/udp6 do not exist. doNetstat previously
returned the os.IsNotExist error verbatim, which propagated through
TCP6Socks and caused findPorts to discard already-collected IPv4 sockets
and fail the entire watch loop — leaving IDEs such as openvscode
unreachable because no ports were ever forwarded.

Treat a missing procfile as "zero sockets of this family" by returning
(nil, nil) only when os.IsNotExist is true. Permission errors, I/O
errors, and parse errors continue to propagate unchanged.

Adds the first unit tests for pkg/netstat, covering the missing-file
path, the happy path, non-IsNotExist open errors, and parse errors.

Fixes skevetter#705

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 9, 2026 01:48
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

The PR modifies the doNetstat function to treat missing file path errors as non-fatal by returning (nil, nil) when os.IsNotExist(err) occurs, while propagating all other errors. A comprehensive test suite is added to cover this behavior along with happy-path parsing and error propagation cases.

Changes

Cohort / File(s) Summary
Error Handling Update
pkg/netstat/netstat_util.go
Added conditional check in doNetstat to suppress os.IsNotExist errors by returning (nil, nil) instead of propagating them; other os.Open and parse errors remain unchanged.
Test Coverage
pkg/netstat/netstat_util_test.go
New test suite with four test cases: missing file returns empty result, valid file parsing succeeds, non-IsNotExist open errors propagate, and parse errors propagate.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: handling missing /proc/net/tcp6 files on IPv6-disabled hosts, which is the core fix addressed in this PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes the netstat-based port watcher on Linux hosts where IPv6 is disabled (or not built into the kernel) by treating missing /proc/net/tcp6/udp6 procfiles as “no sockets” instead of a hard error, preventing IPv4 results from being discarded.

Changes:

  • Update doNetstat to return (nil, nil) when os.Open fails with os.IsNotExist, enabling graceful degradation when IPv6 procfiles are absent.
  • Add a new test suite for doNetstat covering missing-file behavior, successful parsing, non-IsNotExist open errors, and parse errors.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
pkg/netstat/netstat_util.go Gracefully handles missing proc netstat files by treating IsNotExist as empty results.
pkg/netstat/netstat_util_test.go Introduces regression tests ensuring the new behavior is correct and surgical.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
pkg/netstat/netstat_util.go (1)

251-253: Align “empty slice” wording with actual return value.

Line 251–253 says “empty slice,” but Line 259 returns nil. Either return []SockTabEntry{} or reword to “zero entries” to avoid ambiguity.

Also applies to: 259-259

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/netstat/netstat_util.go` around lines 251 - 253, The comment claims the
function returns an “empty slice” on systems without IPv6 but the code returns
nil; change the return at the nil site (currently returning nil for
[]SockTabEntry) to an actual empty slice ([]SockTabEntry{}) and update the
comment around that block to match, referencing the SockTabEntry slice return so
callers get a real empty slice instead of nil.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/netstat/netstat_util.go`:
- Around line 251-253: The comment claims the function returns an “empty slice”
on systems without IPv6 but the code returns nil; change the return at the nil
site (currently returning nil for []SockTabEntry) to an actual empty slice
([]SockTabEntry{}) and update the comment around that block to match,
referencing the SockTabEntry slice return so callers get a real empty slice
instead of nil.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a8836361-472e-402e-b861-94072dfccede

📥 Commits

Reviewing files that changed from the base of the PR and between d56675c and 741de36.

📒 Files selected for processing (2)
  • pkg/netstat/netstat_util.go
  • pkg/netstat/netstat_util_test.go

@skevetter
Copy link
Copy Markdown
Owner

Whenever you're ready for more testing, the Desktop app and CLI artifacts are created each run (example https://github.com/skevetter/devpod/actions/runs/24168163306?pr=706)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: netstat port watcher crashes on hosts without /proc/net/tcp6 (IPv6 disabled)

3 participants