Skip to content

begin adding lneto - #16

Open
soypat wants to merge 14 commits into
netbirdio:masterfrom
soypat:nb-wg-prep
Open

begin adding lneto#16
soypat wants to merge 14 commits into
netbirdio:masterfrom
soypat:nb-wg-prep

Conversation

@soypat

@soypat soypat commented Jun 16, 2026

Copy link
Copy Markdown

WIP

Summary by CodeRabbit

Summary

  • New Features
    • Added a userspace networking layer with typed TCP/UDP dial & listen, DNS hostname resolution, and Go-style Dial/Listen APIs.
    • Added a build-enabled WireGuard-backed TUN backend with TCP/UDP support (ping exposed; may be unsupported depending on backend).
  • Refactor
    • Reworked backend wiring so transport and DNS logic flows through the shared networking layer; updated gVisor backend entry point and ping plumbing.
  • Tests
    • Added end-to-end regression coverage for constructor stability, safe close/read/write races, TCP port 0 assignment, TCP/UDP echo (IPv4/IPv6), and DNS-over-TCP.
  • Chores
    • Updated Go to 1.24, refreshed module wiring, and expanded git ignores.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a backend-agnostic netstack API, refactors the gvisor path onto it, introduces an lneto-backed implementation, wires build-tag selection and debug hooks, and expands integration coverage. It also updates module settings and one pool test assertion message.

Changes

Repository setup and test message update

Layer / File(s) Summary
Ignore and module settings
.gitignore, go.mod
Adds the *LNETO_EQUIVALENCE.md ignore pattern, bumps Go to 1.24, adds github.com/soypat/lneto, and adds a local replace to ../lneto.
Pool test assertion text
device/pools_test.go
Changes the failure message to print max.Load() for the observed maximum count.

Netstack abstraction, gvisor refactor, lneto backend, and tests

Layer / File(s) Summary
Net API and typed networking contracts
tun/netstack/net.go
Introduces Net, Stack, typed TCP/UDP/ping interfaces, PingAddr, typed dial/listen helpers, DNS delegation, generic dialing, and partial-deadline resolution.
Backend selection and packet debug hooks
tun/netstack/net_gvisor.go, tun/netstack/net_lneto.go, tun/netstack/net_debug.go, tun/netstack/net_debugoff.go
Adds build-tagged CreateNetTUN wrappers and packet-tracing helpers with debug and no-op variants.
gVisor backend refactor
tun/netstack/gvisor.go
Renames the constructor, wraps the device in Net, moves socket/DNS methods to netTun, updates ping methods, adds packet debug calls, and removes the old *Net dialer path.
lneto constructor, device I/O, and socket setup
tun/netstack/lneto.go
Implements the lneto constructor, stack state, backoff and interrupt handling, device lifecycle, packet I/O, and TCP/UDP/ping socket operations.
lneto DNS resolution and TUN counters
tun/netstack/lneto.go, tun/netstack/lneto_tuncount.go, tun/netstack/lneto_tuncount_off.go
Adds lneto DNS lookup and DNS-over-TCP helpers, plus build-tagged ingress/egress packet and byte counters.
lneto integration tests
tun/netstack/lneto_test.go
Adds wglneto tests for construction, close races, TCP/UDP echo, TCP port assignment, and DNS-over-TCP, with pump and DNS server helpers.

Estimated code review effort: 4 (Complex) | ~75 minutes

Poem

I nibble the wire with a twitchy nose,
Two backends bloom where one path goes.
DNS hums softly, packets gleam,
A lneto hop and a gvisor stream.
Hop, hop, hooray—new routes appear,
This rabbit leaves bytes neatly clear. 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is related to the change, but it is too vague and reads like a WIP note rather than a clear summary. Use a specific title such as "Add lneto netstack backend" or similar to describe the primary change.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@go.mod`:
- Line 19: Remove the replace directive for github.com/soypat/lneto from go.mod
(the line referencing ../lneto) since the relative path does not exist in the
repository and breaks builds in CI and for external contributors. Instead,
create or update a go.work file in the repository root to include this local
override, allowing developers to use it locally while keeping go.mod
reproducible and resolvable on its own.

In `@tun/netstack/lneto.go`:
- Around line 399-400: The port conversion in DialContextTCP (line 400),
ListenTCP (line 423), ListenUDP (line 438), DialUDP (line 450), and another
location (line 454) directly casts int ports to uint16 without validation,
allowing invalid ports (negative or out-of-range values) to wrap instead of
being rejected. Create a validatePort function that checks if the port value is
within the valid range (0-65535) and returns an error for invalid values, then
call this validation function before passing the port to netip.AddrPortFrom in
each of these five locations.
- Around line 239-241: The mtu variable lacks an upper bound validation before
being cast to uint16 at line 296, allowing values greater than 65535 to silently
overflow and wrap to unintended values. Add an upper bound check in the mtu
validation block (around line 239-241 where the lower bound check is performed)
to ensure mtu does not exceed 65535, capping it to a reasonable maximum value
(such as 65535) if needed, preventing overflow when the value is later narrowed
to uint16 at line 296.
- Around line 655-657: The UDP case in DialContext does not pass the caller's
context to DialUDPAddrPort on line 656, causing any deadline or cancellation
from dialCtx to be ignored. Modify the UDP case to pass dialCtx to
DialUDPAddrPort (if it accepts a context parameter), or alternatively wrap the
call to respect the context's deadline and cancellation signals. This ensures
that UDP dialing honors the caller's timeout and cancellation requirements just
like other protocol types.
- Around line 645-647: The defer cancel() call on line 647 is inside the
dial-attempt loop, which means the context cancellation is deferred until the
entire function exits rather than being cleaned up after each iteration. This
causes multiple contexts and timers to remain alive unnecessarily. Move the
cancel() call to execute immediately after each dial attempt completes (after
the candidate address is tried) rather than deferring it. This can be done
either by calling cancel() explicitly at the end of each loop iteration, or by
using a closure pattern that wraps the dial attempt and handles cleanup per
iteration. The key is ensuring the context created with context.WithDeadline is
cancelled promptly after each candidate attempt, not waiting until function
exit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 581dff07-c9ed-4836-b596-8a4600298130

📥 Commits

Reviewing files that changed from the base of the PR and between 4b4a4e3 and fd38051.

📒 Files selected for processing (4)
  • .gitignore
  • go.mod
  • tun/netstack/lneto.go
  • tun/netstack/lneto_test.go

Comment thread go.mod
golang.org/x/time v0.7.0 // indirect
)

replace github.com/soypat/lneto => ../lneto

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Checking go.mod replace directive:"
rg -n '^\s*replace\s+github\.com/soypat/lneto' go.mod

echo
echo "Checking whether target path exists from repo root:"
python - <<'PY'
from pathlib import Path
p = Path("../lneto")
print(f"path: {p}")
print(f"resolved: {p.resolve()}")
print(f"exists: {p.exists()}")
PY

Repository: netbirdio/wireguard-go

Length of output: 261


Local replace directive breaks reproducible builds outside your workstation.

Line 19 references ../lneto, which does not exist in this repository. This prevents builds in CI and for external contributors. Move this local override to a developer-local go.work file instead, keeping go.mod resolvable on its own.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go.mod` at line 19, Remove the replace directive for github.com/soypat/lneto
from go.mod (the line referencing ../lneto) since the relative path does not
exist in the repository and breaks builds in CI and for external contributors.
Instead, create or update a go.work file in the repository root to include this
local override, allowing developers to use it locally while keeping go.mod
reproducible and resolvable on its own.

Source: Linters/SAST tools

Comment thread tun/netstack/lneto.go Outdated
Comment thread tun/netstack/lneto.go Outdated
Comment thread tun/netstack/lneto.go Outdated
Comment thread tun/netstack/lneto.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tun/netstack/gvisor.go (1)

531-533: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

defer cancel() inside loop accumulates deferred calls.

Similar to the issue in net.go, the cancel function is deferred inside a for-loop. Each iteration through useUDP := []bool{true, false} creates a deferred cancel that won't execute until exchange returns.

Proposed fix: cancel at end of each iteration
 	for _, useUDP := range []bool{true, false} {
 		ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
-		defer cancel()

 		var c net.Conn
 		// ... dial and exchange logic ...
 		c.Close()
+		cancel()
 		if err != nil {

Or use an anonymous function to scope each iteration:

for _, useUDP := range []bool{true, false} {
    p, h, err := func() (dnsmessage.Parser, dnsmessage.Header, error) {
        ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
        defer cancel()
        // ... rest of loop body
    }()
    // handle result
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tun/netstack/gvisor.go` around lines 531 - 533, The defer cancel() call
inside the for loop that iterates over useUDP values accumulates multiple
deferred cancellations that won't execute until the exchange function returns,
causing improper context cleanup. Refactor by wrapping the loop body (starting
from context.WithDeadline through to the end of the loop iteration) inside an
anonymous function that returns the necessary values, ensuring the defer
cancel() is scoped to and executes at the end of each iteration rather than
accumulating across all iterations.
🧹 Nitpick comments (1)
tun/netstack/net.go (1)

329-343: 💤 Low value

Duplicate error message text.

errServerMisbehaving (line 334) and errServerTemporarilyMisbehaving (line 337) both have the message "server misbehaving". While they're used in different contexts (temporary vs permanent), identical messages may confuse debugging. Consider making the temporary variant distinct.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tun/netstack/net.go` around lines 329 - 343, The error variables
errServerMisbehaving and errServerTemporarilyMisbehaving both have the identical
error message text "server misbehaving", which makes it difficult to distinguish
between the temporary and permanent failure cases during debugging. Update the
error message for errServerTemporarilyMisbehaving to include a distinct message
that clearly indicates the temporary nature of the error, such as including the
word "temporarily" in the message string.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tun/netstack/net.go`:
- Around line 294-298: The `defer cancel()` statement inside the for-loop
accumulates context cancellation functions that won't execute until DialContext
returns, delaying cleanup. Instead of deferring the cancel call, invoke cancel()
directly at the end of each loop iteration after the dial attempt completes.
Remove the `defer cancel()` line and call `cancel()` explicitly after the switch
statement that handles the dial result, ensuring each context created with
`context.WithDeadline` is cancelled before moving to the next iteration.

---

Outside diff comments:
In `@tun/netstack/gvisor.go`:
- Around line 531-533: The defer cancel() call inside the for loop that iterates
over useUDP values accumulates multiple deferred cancellations that won't
execute until the exchange function returns, causing improper context cleanup.
Refactor by wrapping the loop body (starting from context.WithDeadline through
to the end of the loop iteration) inside an anonymous function that returns the
necessary values, ensuring the defer cancel() is scoped to and executes at the
end of each iteration rather than accumulating across all iterations.

---

Nitpick comments:
In `@tun/netstack/net.go`:
- Around line 329-343: The error variables errServerMisbehaving and
errServerTemporarilyMisbehaving both have the identical error message text
"server misbehaving", which makes it difficult to distinguish between the
temporary and permanent failure cases during debugging. Update the error message
for errServerTemporarilyMisbehaving to include a distinct message that clearly
indicates the temporary nature of the error, such as including the word
"temporarily" in the message string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4809ef0d-0ac5-4800-90ce-fbf0638aa968

📥 Commits

Reviewing files that changed from the base of the PR and between fd38051 and deedbc4.

📒 Files selected for processing (7)
  • device/pools_test.go
  • tun/netstack/gvisor.go
  • tun/netstack/lneto.go
  • tun/netstack/lneto_test.go
  • tun/netstack/net.go
  • tun/netstack/net_gvisor.go
  • tun/netstack/net_lneto.go
✅ Files skipped from review due to trivial changes (1)
  • tun/netstack/net_lneto.go

Comment thread tun/netstack/net.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tun/netstack/net.go (1)

337-340: 💤 Low value

Duplicate error message text may hinder debugging.

Both errServerMisbehaving and errServerTemporarilyMisbehaving have the identical message "server misbehaving". While they remain distinct error values for errors.Is() comparisons, the identical text can confuse log analysis and debugging.

🔧 Suggested fix
 	errServerMisbehaving            = errors.New("server misbehaving")
 	errInvalidDNSResponse           = errors.New("invalid DNS response")
 	errNoAnswerFromDNSServer        = errors.New("no answer from DNS server")
-	errServerTemporarilyMisbehaving = errors.New("server misbehaving")
+	errServerTemporarilyMisbehaving = errors.New("server temporarily misbehaving")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tun/netstack/net.go` around lines 337 - 340, The error constants
errServerMisbehaving and errServerTemporarilyMisbehaving have identical error
message text "server misbehaving", which makes debugging and log analysis
confusing since they should convey different situations. Change the error
message for errServerTemporarilyMisbehaving to a distinct message that reflects
its temporary nature (for example, something like "server temporarily
misbehaving" or "temporary server error") to differentiate it from the permanent
misbehavior error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tun/netstack/lneto_tuncount.go`:
- Around line 12-29: The global counter variables egressPkts, egressBytes,
ingressPkts, and ingressBytes are not protected from concurrent access, which
causes race conditions on multithreaded builds. Replace the plain int variables
with atomic types from the sync/atomic package (such as atomic.Int64 or
atomic.Uint64) and update the countEgress and countIngress functions to use
atomic operations (Add, Load) when incrementing and reading these counters
instead of direct variable assignments and reads.

---

Nitpick comments:
In `@tun/netstack/net.go`:
- Around line 337-340: The error constants errServerMisbehaving and
errServerTemporarilyMisbehaving have identical error message text "server
misbehaving", which makes debugging and log analysis confusing since they should
convey different situations. Change the error message for
errServerTemporarilyMisbehaving to a distinct message that reflects its
temporary nature (for example, something like "server temporarily misbehaving"
or "temporary server error") to differentiate it from the permanent misbehavior
error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 63a3f7ba-392e-4bfa-a943-ff1da11baa1a

📥 Commits

Reviewing files that changed from the base of the PR and between deedbc4 and 9b000ed.

📒 Files selected for processing (7)
  • tun/netstack/gvisor.go
  • tun/netstack/lneto.go
  • tun/netstack/lneto_tuncount.go
  • tun/netstack/lneto_tuncount_off.go
  • tun/netstack/net.go
  • tun/netstack/net_debug.go
  • tun/netstack/net_debugoff.go
✅ Files skipped from review due to trivial changes (3)
  • tun/netstack/lneto_tuncount_off.go
  • tun/netstack/net_debugoff.go
  • tun/netstack/net_debug.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • tun/netstack/lneto.go

Comment on lines +12 to +29
// not draining its tx. Single counters are fine: js/wasm is single-threaded.
var (
egressPkts, egressBytes int
ingressPkts, ingressBytes int
)

// countEgress records one non-empty packet pulled from the stack toward WireGuard.
func countEgress(n int) {
egressPkts++
egressBytes += n
fmt.Printf("[TUNCOUNT] egress pkts=%d bytes=%d last=%d\n", egressPkts, egressBytes, n)
}

// countIngress records one IP packet handed from WireGuard into the stack.
func countIngress(n int) {
ingressPkts++
ingressBytes += n
fmt.Printf("[TUNCOUNT] ingress pkts=%d bytes=%d last=%d\n", ingressPkts, ingressBytes, n)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Protect TUN counters from concurrent updates.

Line 12 assumes single-threaded execution, but this file is gated by debugheaplog, not js/wasm. On multithreaded builds, countEgress/countIngress can race on shared globals, producing incorrect counts and race warnings. Use atomics (or a mutex) for these counters.

Suggested fix
 import "fmt"
+import "sync/atomic"
@@
 var (
-	egressPkts, egressBytes   int
-	ingressPkts, ingressBytes int
+	egressPkts, egressBytes   atomic.Int64
+	ingressPkts, ingressBytes atomic.Int64
 )
@@
 func countEgress(n int) {
-	egressPkts++
-	egressBytes += n
-	fmt.Printf("[TUNCOUNT] egress  pkts=%d bytes=%d last=%d\n", egressPkts, egressBytes, n)
+	pkts := egressPkts.Add(1)
+	bytes := egressBytes.Add(int64(n))
+	fmt.Printf("[TUNCOUNT] egress  pkts=%d bytes=%d last=%d\n", pkts, bytes, n)
 }
@@
 func countIngress(n int) {
-	ingressPkts++
-	ingressBytes += n
-	fmt.Printf("[TUNCOUNT] ingress pkts=%d bytes=%d last=%d\n", ingressPkts, ingressBytes, n)
+	pkts := ingressPkts.Add(1)
+	bytes := ingressBytes.Add(int64(n))
+	fmt.Printf("[TUNCOUNT] ingress pkts=%d bytes=%d last=%d\n", pkts, bytes, n)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tun/netstack/lneto_tuncount.go` around lines 12 - 29, The global counter
variables egressPkts, egressBytes, ingressPkts, and ingressBytes are not
protected from concurrent access, which causes race conditions on multithreaded
builds. Replace the plain int variables with atomic types from the sync/atomic
package (such as atomic.Int64 or atomic.Uint64) and update the countEgress and
countIngress functions to use atomic operations (Add, Load) when incrementing
and reading these counters instead of direct variable assignments and reads.

Comment thread go.mod
@@ -1,8 +1,9 @@
module golang.zx2c4.com/wireguard

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

missing push of go.sum

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yes, I'm still waiting on fixes to lneto before fixing a version. we currently use replace directive for it to work

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tun/netstack/lneto.go`:
- Around line 195-196: The DNS-over-TCP lookup path is holding dnsScratch.mu
across dialing, writing, and reading, which can block other lookups past
LookupContextHost deadlines. Update the lookup flow in lneto.go so per-call
state is used instead of the shared dnsScratch lock during network I/O, or
switch to a pool and only guard buffer checkout/return with the mutex. Focus the
fix around dnsScratch and the DNS lookup helpers that perform the
dial/write/read sequence.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 41825dcb-c33a-4971-b744-5dc7d8f011a1

📥 Commits

Reviewing files that changed from the base of the PR and between b36d95b and 79f16d3.

📒 Files selected for processing (2)
  • tun/netstack/lneto.go
  • tun/netstack/lneto_test.go

Comment thread tun/netstack/lneto.go
Comment on lines +195 to +196
// dnsScratch holds the reusable buffers for the DNS-over-TCP lookup path.
dnsScratch dnsScratch

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Don’t hold the shared DNS scratch lock across network I/O.

dnsScratch.mu is held while dialing, writing, and reading, so concurrent lookups block on a non-context-aware mutex and can exceed their LookupContextHost deadline. Use per-call scratch state, or a pool, instead.

Proposed fix
-	// dnsScratch holds the reusable buffers for the DNS-over-TCP lookup path.
-	dnsScratch dnsScratch
@@
-	// The shared dnsScratch buffers are reused across the whole round trip
-	// (build → write → read → parse), so hold the lock for the entire function.
-	// This serializes DNS lookups, which is fine: A and AAAA already run sequentially.
-	s := &n.dnsScratch
-	s.mu.Lock()
-	defer s.mu.Unlock()
+	var s dnsScratch
@@
-// dnsScratch holds reusable buffers for building and parsing DNS-over-TCP
-// messages, retaining slice backing arrays across lookups. Not safe for
-// concurrent use: callers must hold mu across an entire build→read→parse
-// sequence because buf is reused for both the query and the response.
+// dnsScratch holds buffers for one DNS-over-TCP lookup.
 type dnsScratch struct {
-	mu    sync.Mutex
 	msg   dns.Message
 	buf   []byte         // length-prefixed wire buffer
 	addrs [16]netip.Addr // decode target
 }
@@
-// bytes (aliases buf; valid until the next scratch use). Caller holds mu.
+// bytes (aliases buf; valid until the next scratch use).
@@
-// the message bytes (aliases buf; valid until the next scratch use). Caller holds mu.
+// the message bytes (aliases buf; valid until the next scratch use).
@@
-// Caller holds mu. Returns the DNS rcode as error when non-zero.
+// Returns the DNS rcode as error when non-zero.

Also applies to: 522-527, 562-567, 573-576, 594-611

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tun/netstack/lneto.go` around lines 195 - 196, The DNS-over-TCP lookup path
is holding dnsScratch.mu across dialing, writing, and reading, which can block
other lookups past LookupContextHost deadlines. Update the lookup flow in
lneto.go so per-call state is used instead of the shared dnsScratch lock during
network I/O, or switch to a pool and only guard buffer checkout/return with the
mutex. Focus the fix around dnsScratch and the DNS lookup helpers that perform
the dial/write/read sequence.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants