Skip to content

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

Merged
skevetter merged 3 commits intoskevetter:mainfrom
YassineElbouchaibi:fix/705-netstat-ipv6-disabled-procfile-missing
Apr 14, 2026
Merged

fix(netstat): handle missing /proc/net/tcp6 on IPv6-disabled hosts#706
skevetter merged 3 commits 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

    • Netstat reading now treats a missing proc-like netstat file as "no entries" (returns empty results) while still reporting other file access or parsing errors.
  • Tests

    • Added tests covering missing files, successful parsing of valid netstat content, permission/open failures, and malformed-content parse errors.

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

Warning

Rate limit exceeded

@skevetter has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 48 minutes and 31 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 48 minutes and 31 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 87542ab8-d6fc-4c30-8ef8-673b8727b898

📥 Commits

Reviewing files that changed from the base of the PR and between 2515513 and 80dac5d.

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

Walkthrough

doNetstat now treats missing target files as non-fatal by returning (nil, nil) when os.Open reports os.IsNotExist; other open errors and parse errors are still returned. A new test suite exercises missing-file, valid-parse, permission-open, and parse-error cases.

Changes

Cohort / File(s) Summary
Error Handling Update
pkg/netstat/netstat_util.go
Modify doNetstat to return (nil, nil) when os.Open(path) fails with os.IsNotExist(err); all other os.Open errors and parsing behavior remain unchanged.
Test Coverage
pkg/netstat/netstat_util_test.go
Add test suite NetstatUtilTestSuite with tests for: missing file -> empty result, parsing a valid /proc/net/tcp6 entry, propagation of non-IsNotExist open errors (skipped on Windows/root), and propagation of parse errors for malformed content.

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 'fix(netstat): handle missing /proc/net/tcp6 on IPv6-disabled hosts' accurately and concisely captures the main change: treating missing IPv6 proc files as zero sockets rather than errors, enabling port forwarding on IPv6-disabled systems.

✏️ 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)

@YassineElbouchaibi
Copy link
Copy Markdown
Author

@skevetter Just tested, works fine now!

@skevetter skevetter marked this pull request as ready for review April 14, 2026 04:29
Address CodeRabbit review: comments said "empty slice" but the code
returns nil. Reword to say "(nil, nil)" for accuracy.
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_test.go (1)

23-34: Tighten the missing-file assertion to pin the exact contract.

Line 33 currently allows both nil and [] via assert.Empty, but the implementation contract here is specifically (nil, nil).

♻️ Suggested tweak
-	assert.Empty(s.T(), entries, "missing procfile must yield zero entries")
+	assert.Nil(s.T(), entries, "missing procfile should return nil entries")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/netstat/netstat_util_test.go` around lines 23 - 34, The test
TestDoNetstat_MissingFileReturnsEmpty should assert the exact contract that
doNetstat returns a nil slice (not just empty) when the proc file is missing:
replace the permissive assert.Empty on the variable entries with an assertion
that entries is nil (e.g. assert.Nil or require.Nil) while keeping the existing
assert.NoError on err so the test verifies (nil, nil) precisely for doNetstat.
🤖 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_test.go`:
- Around line 23-34: The test TestDoNetstat_MissingFileReturnsEmpty should
assert the exact contract that doNetstat returns a nil slice (not just empty)
when the proc file is missing: replace the permissive assert.Empty on the
variable entries with an assertion that entries is nil (e.g. assert.Nil or
require.Nil) while keeping the existing assert.NoError on err so the test
verifies (nil, nil) precisely for doNetstat.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5fb48594-f913-4cbe-9035-a3d6012347c5

📥 Commits

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

📒 Files selected for processing (2)
  • pkg/netstat/netstat_util.go
  • pkg/netstat/netstat_util_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/netstat/netstat_util.go

@skevetter skevetter merged commit cb27dd4 into skevetter:main Apr 14, 2026
41 checks passed
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