Skip to content

refactor(intra): use the SDK's dnsintercept relay for DoH interception - #633

Draft
fortuna wants to merge 2 commits into
refactor/packetrelayfrom
feat/dnsintercept
Draft

fortuna wants to merge 2 commits into
refactor/packetrelayfrom
feat/dnsintercept

Conversation

@fortuna

@fortuna fortuna commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Warning

Do not merge yet. This depends on OutlineFoundation/outline-sdk#637, which is still
a draft. go.mod currently pins the SDK to the branch commit
v0.1.0-rc1.0.20260915234250-3f66d9c28049; it must be repointed at a released version
before this can land.

What

Intra's UDP path used to do its own DNS interception: intraPacketRelay compared every
destination against the fake DNS address, and ran the DoH query inline. The Outline SDK now
ships that behavior as a composable decorator, so this PR deletes Intra's copy and composes
the SDK one instead:

pr, err := newIntraPacketRelay(protector, eventListener)  // relays UDP, counts bytes
dnsRelay := dnsintercept.New(pr, fakeDNSAddr, dns.FuncExchanger(t.queryDNS),
    dnsintercept.WithErrorHandler(...))
lwip2transport.ConfigureDeviceWithRelay(t.sd, dnsRelay)

Android/app/src/go/intra/packet_relay.go is now nothing but byte accounting and
UDPSocketSummary reporting. The swappable DoH resolver moved from the relay to the
Tunnel, which exposes it as a dns.Exchanger (Tunnel.queryDNS) — doh.Resolver.Query
already has the wire-format signature the SDK expects.

Why

The interception logic is not Intra-specific; the only thing Intra needed that the SDK
lacked was a way to plug in its own resolver. The SDK PR adds dns.Exchanger for exactly
that, so both Intra and the Outline Client can share one implementation — and Intra gets the
SDK's ref-counted association lifetime for free.

Behavior changes

Before After
DoH query ran synchronously inside SendPacket, blocking the lwIP callback runs on its own goroutine; SendPacket returns immediately
DoH failure returned as a SendPacket error (which lwIP discards) passed to WithErrorHandlerlogging.Warnf
Query cancellation tunnel context only association context and tunnel context (context.AfterFunc)
DNS-only association reported a UDPSocketSummary with zero bytes opens no UDP socket, so reports nothing
DNS byte exclusion explicit (if isEquivalentAddrPort(...) before the counters) structural: DNS never reaches the counting relay

The dropped zero-byte summaries are not a telemetry change: IntraVpnService already
discards summaries below 10,000 bytes.

Tests

packet_relay_test.go keeps the relaying/accounting/close-semantics tests and gains
TestInterceptedDNSIsNotRelayed, which exercises the exact composition NewTunnel builds.
The new tunnel_test.go covers the exchanger adapter: it always reads the current resolver
(so SetDNS takes effect immediately) and in-flight queries are canceled on disconnect.

  • gofmt -l . — clean (except the pre-existing intra/split/retrier.go)
  • go vet ./..., go test -race ./...
  • ./gradlew :app:compileGoBackend

network.PacketProxy and NewPacketProxyFromPacketListener are deprecated in
favor of the flow-based network/packetrelay API. This migrates Intra's UDP
handling, mirroring OutlineFoundation/outline-apps#2777.

- intraPacketProxy becomes intraPacketRelay, a packetrelay.PacketRelay whose
  associations share a dohAssociation between their sender and receiver halves.
- lwip2transport.ConfigureDevice -> ConfigureDeviceWithRelay.
- packetrelay.NewPacketRelayFromPacketListener replaces
  network.NewPacketProxyFromPacketListener, keeping the same 5 minute
  write-idle timeout (now a constructor argument).
- Add unit tests for DNS interception, packet relaying, byte accounting and
  sender close semantics.

Behavior is unchanged, except that SendPacket on a closed association now
returns ErrClosed instead of issuing a DoH query whose response can no longer
be delivered.
Replace Intra's own DNS interception in the UDP path with the Outline
SDK's dnsintercept relay, composed over a relay that now only relays
packets and counts bytes. The swappable DoH resolver moves to the Tunnel,
which exposes it to the SDK as a dns.Exchanger.

DoH queries no longer block the lwIP callback, and query failures are
logged through WithErrorHandler instead of being returned (and dropped)
by SendPacket.

This pins the SDK to the branch commit of
OutlineFoundation/outline-sdk#637, and must not be merged until that PR
lands and is released.
@fortuna
fortuna added this pull request to stack #632 September 16, 2026 20:42
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.

1 participant