Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7a57ea7
refactor(client): modularize DNS interception logic and address remap…
fortuna Apr 15, 2026
01891a2
refactor(client): simplify dns interceptor lazy initialization
fortuna Apr 15, 2026
a5a5ec5
Remove helpers.go
fortuna Apr 15, 2026
6c038e9
Merge branch 'master' into fortuna/dns-resource-leak
fortuna Apr 27, 2026
a10b32d
Add notes
fortuna Apr 28, 2026
4e45a16
Update notes
fortuna Apr 28, 2026
d0cf3b3
More notes
fortuna Apr 28, 2026
8b43841
Error note
fortuna Apr 29, 2026
b783562
Shorter timeout for DNS
fortuna May 1, 2026
76af39e
Introduce lazy packet proxy
fortuna May 1, 2026
e285a46
Update TODO
fortuna May 1, 2026
1af2b8c
client/go/dnsintercept: close receiver after single response for sing…
fortuna May 1, 2026
4341285
docs(dnsintercept): update README to fix PR comments
fortuna May 1, 2026
7da7026
test(configregistry): add integration tests and benchmarks for DNS in…
fortuna May 1, 2026
e0655fd
test(configregistry): fix data race in DNS interceptor tests
fortuna May 1, 2026
d0cd4bc
docs(dnsintercept): add comments to NewSession and request sender
fortuna May 1, 2026
fa3cc17
test(configregistry): implement deadlines in mock to fix truncation test
fortuna May 1, 2026
e17452b
test(configregistry): add assertion to timeout test
fortuna May 1, 2026
a98384d
fix(configregistry): make error messages distinct in outline_dns_inte…
fortuna May 1, 2026
5b7923f
test(configregistry): fix remaining data races in DNS interceptor tests
fortuna May 1, 2026
c92d671
fix(dnsintercept): add sync.Once to prevent double-close in singleRes…
fortuna May 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 23 additions & 11 deletions client/go/outline/configregistry/outline_dns_intercept.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import (
"log/slog"
"math/rand/v2"
"net/netip"
"time"

"localhost/client/go/outline/connectivity"
"localhost/client/go/outline/dnsintercept"

"golang.getoutline.org/sdk/network"
"golang.getoutline.org/sdk/network/dnstruncate"
"golang.getoutline.org/sdk/transport"
)

Expand Down Expand Up @@ -57,33 +59,43 @@ func wrapTransportPairWithOutlineDNS(sd *Dialer[transport.StreamConn], pl *Packe
}

// Intercept DNS for PacketProxy
ppBase, err := network.NewPacketProxyFromPacketListener(pl)

// PacketProxy for connecting to remote servers.
// Uses the 5m timeout as recommended in https://www.rfc-editor.org/rfc/rfc4787.html#section-4.3
ppBase, err := network.NewPacketProxyFromPacketListener(pl, network.WithPacketListenerWriteIdleTimeout(5 * time.Minute))
if err != nil {
return nil, fmt.Errorf("failed to create PacketProxy: %w", err)
return nil, fmt.Errorf("failed to create base PacketProxy: %w", err)
}
// Forwards everything including DNS. For DNS it translates between the link-local and remote addresses for the DNS resolver.
ppForward, err := dnsintercept.NewDNSRedirectPacketProxy(ppBase, linkLocalDNS, remoteDNS)
// PacketProxy for DNS. Uses a shorter timeout, as recommended in https://www.rfc-editor.org/rfc/rfc5452.html#section-10.
ppDNSBase, err := network.NewPacketProxyFromPacketListener(pl, network.WithPacketListenerWriteIdleTimeout(10 * time.Second))
if err != nil {
return nil, fmt.Errorf("failed to create DNS redirect PacketProxy: %w", err)
return nil, fmt.Errorf("failed to create DNS PacketProxy: %w", err)
}
Comment thread
fortuna marked this conversation as resolved.
// Forwards everything except DNS. For DNS it returns a truncated response.
ppTrunc, err := dnsintercept.NewDNSTruncatePacketProxy(ppBase, linkLocalDNS)
// Returns a truncated response for DNS packets to force a retry over TCP.
ppDNSTrunc, err := dnstruncate.NewPacketProxy()
if err != nil {
return nil, fmt.Errorf("failed to create always-truncate DNS PacketProxy: %w", err)
}
ppMain, err := network.NewDelegatePacketProxy(ppTrunc)
// Delegate for DNS traffic: selects between forwarding and truncation based on connectivity.
ppDNSDelegate, err := network.NewDelegatePacketProxy(ppDNSTrunc)
if err != nil {
return nil, fmt.Errorf("failed to create indirect DNS PacketProxy: %w", err)
}
// Interceptor: Forwards everything except DNS to ppBase. DNS is redirected to ppDNS and
// translated between the link-local and remote addresses.
ppMain, err := dnsintercept.NewDNSInterceptor(ppBase, ppDNSDelegate, linkLocalDNS, remoteDNS)
if err != nil {
return nil, fmt.Errorf("failed to create indirect PacketProxy: %w", err)
return nil, fmt.Errorf("failed to create DNS interceptor PacketProxy: %w", err)
}

onNetworkChanged := func() {
go func() {
if err := connectivity.CheckUDPConnectivity(pl); err == nil {
slog.Info("remote device UDP is healthy")
ppMain.SetProxy(ppForward)
ppDNSDelegate.SetProxy(ppDNSBase)
} else {
slog.Warn("remote device UDP is not healthy", "err", err)
ppMain.SetProxy(ppTrunc)
ppDNSDelegate.SetProxy(ppDNSTrunc)
}
}()
}
Expand Down
Loading
Loading