detached 0d9258e

test(quicnettest): assert rebind source change and upstream bind family

Extract upstreamBindAddr so each server address family is covered without host networking, and check that Rebind changes the upstream source address.
commit
0d9258e879d992aa7439a6a45920b00316b1feb7
author
brice · · 2026-10-07 07:01 UTC
parent
767263f
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +36 -11
M internal/adapters/quicnettest/listen_test.go +18 -0
@@ -24,6 +24,7 @@ func TestExplicitListenSurvivesRebind(t *testing.T) {
2424 client, err := net.ListenUDP("udp", &net.UDPAddr{IP: ip})
2525 require.NoError(t, err)
2626 defer client.Close()
27+ var sources []string
2728 for attempt := 0; attempt < 2; attempt++ {
2829 if attempt == 1 {
2930 require.NoError(t, proxy.Rebind())
@@ -35,6 +36,7 @@ func TestExplicitListenSurvivesRebind(t *testing.T) {
3536 n, from, err := server.ReadFromUDP(data)
3637 require.NoError(t, err)
3738 require.Equal(t, "fixture", string(data[:n]))
39+ sources = append(sources, from.String())
3840 _, err = server.WriteToUDP(data[:n], from)
3941 require.NoError(t, err)
4042 require.NoError(t, client.SetReadDeadline(time.Now().Add(time.Second)))
@@ -42,6 +44,22 @@ func TestExplicitListenSurvivesRebind(t *testing.T) {
4244 require.NoError(t, err)
4345 require.Equal(t, "fixture", string(data[:n]))
4446 }
47+ require.NotEqual(t, sources[0], sources[1], "rebind must change the upstream source address")
48+ })
49+ }
50+}
51+
52+func TestUpstreamBindAddrMatchesServerFamily(t *testing.T) {
53+ for _, tt := range []struct {
54+ server, want net.IP
55+ }{
56+ {net.IPv4(127, 0, 0, 1), net.IPv4(127, 0, 0, 1)},
57+ {net.IPv4(192, 0, 2, 7), net.IPv4zero},
58+ {net.IPv6loopback, net.IPv6loopback},
59+ {net.ParseIP("2001:db8::7"), net.IPv6unspecified},
60+ } {
61+ t.Run(tt.server.String(), func(t *testing.T) {
62+ require.True(t, upstreamBindAddr(tt.server).IP.Equal(tt.want))
4563 })
4664 }
4765 }
@@ -24,6 +24,7 @@ func TestExplicitListenSurvivesRebind(t *testing.T) {
24 client, err := net.ListenUDP("udp", &net.UDPAddr{IP: ip})24 client, err := net.ListenUDP("udp", &net.UDPAddr{IP: ip})
25 require.NoError(t, err)25 require.NoError(t, err)
26 defer client.Close()26 defer client.Close()
27+ var sources []string
27 for attempt := 0; attempt < 2; attempt++ {28 for attempt := 0; attempt < 2; attempt++ {
28 if attempt == 1 {29 if attempt == 1 {
29 require.NoError(t, proxy.Rebind())30 require.NoError(t, proxy.Rebind())
@@ -35,6 +36,7 @@ func TestExplicitListenSurvivesRebind(t *testing.T) {
35 n, from, err := server.ReadFromUDP(data)36 n, from, err := server.ReadFromUDP(data)
36 require.NoError(t, err)37 require.NoError(t, err)
37 require.Equal(t, "fixture", string(data[:n]))38 require.Equal(t, "fixture", string(data[:n]))
39+ sources = append(sources, from.String())
38 _, err = server.WriteToUDP(data[:n], from)40 _, err = server.WriteToUDP(data[:n], from)
39 require.NoError(t, err)41 require.NoError(t, err)
40 require.NoError(t, client.SetReadDeadline(time.Now().Add(time.Second)))42 require.NoError(t, client.SetReadDeadline(time.Now().Add(time.Second)))
@@ -42,6 +44,22 @@ func TestExplicitListenSurvivesRebind(t *testing.T) {
42 require.NoError(t, err)44 require.NoError(t, err)
43 require.Equal(t, "fixture", string(data[:n]))45 require.Equal(t, "fixture", string(data[:n]))
44 }46 }
47+ require.NotEqual(t, sources[0], sources[1], "rebind must change the upstream source address")
48+ })
49+ }
50+}
51+
52+func TestUpstreamBindAddrMatchesServerFamily(t *testing.T) {
53+ for _, tt := range []struct {
54+ server, want net.IP
55+ }{
56+ {net.IPv4(127, 0, 0, 1), net.IPv4(127, 0, 0, 1)},
57+ {net.IPv4(192, 0, 2, 7), net.IPv4zero},
58+ {net.IPv6loopback, net.IPv6loopback},
59+ {net.ParseIP("2001:db8::7"), net.IPv6unspecified},
60+ } {
61+ t.Run(tt.server.String(), func(t *testing.T) {
62+ require.True(t, upstreamBindAddr(tt.server).IP.Equal(tt.want))
45 })63 })
46 }64 }
47 }65 }
M internal/adapters/quicnettest/proxy.go +18 -11
@@ -13,8 +13,24 @@ import (
1313 // ErrClosed is returned by operations on a closed proxy.
1414 var ErrClosed = errors.New("quicnettest: proxy is closed")
1515
16+// upstreamBindAddr returns the ephemeral upstream bind address in the server's
17+// address family: loopback for a loopback server, unspecified otherwise.
18+func upstreamBindAddr(server net.IP) *net.UDPAddr {
19+ switch {
20+ case server.To4() != nil && server.IsLoopback():
21+ return &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)}
22+ case server.To4() != nil:
23+ return &net.UDPAddr{IP: net.IPv4zero}
24+ case server.IsLoopback():
25+ return &net.UDPAddr{IP: net.IPv6loopback}
26+ default:
27+ return &net.UDPAddr{IP: net.IPv6unspecified}
28+ }
29+}
30+
1631 // Proxy is a bounded, deterministic UDP relay with per-direction impairment. It
17-// listens on an ephemeral loopback port for clients and forwards to
32+// listens on Config.ListenAddr (default: ephemeral IPv4 loopback) for clients
33+// and forwards to
1834 // Config.ServerAddr from a separate upstream socket that can be rebound to a
1935 // new source address at any time.
2036 type Proxy struct {
@@ -136,16 +152,7 @@ func New(cfg Config) (*Proxy, error) {
136152 if err != nil {
137153 return nil, fmt.Errorf("quicnettest: listen client socket: %w", err)
138154 }
139- upstreamAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)}
140- if !cfg.ServerAddr.IP.IsLoopback() {
141- upstreamAddr.IP = net.IPv4zero
142- }
143- if cfg.ServerAddr.IP.To4() == nil {
144- upstreamAddr.IP = net.IPv6unspecified
145- if cfg.ServerAddr.IP.IsLoopback() {
146- upstreamAddr.IP = net.IPv6loopback
147- }
148- }
155+ upstreamAddr := upstreamBindAddr(cfg.ServerAddr.IP)
149156 upstream, err := net.ListenUDP("udp", upstreamAddr)
150157 if err != nil {
151158 _ = clientConn.Close()
@@ -13,8 +13,24 @@ import (
13 // ErrClosed is returned by operations on a closed proxy.13 // ErrClosed is returned by operations on a closed proxy.
14 var ErrClosed = errors.New("quicnettest: proxy is closed")14 var ErrClosed = errors.New("quicnettest: proxy is closed")
15 15
16+// upstreamBindAddr returns the ephemeral upstream bind address in the server's
17+// address family: loopback for a loopback server, unspecified otherwise.
18+func upstreamBindAddr(server net.IP) *net.UDPAddr {
19+ switch {
20+ case server.To4() != nil && server.IsLoopback():
21+ return &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)}
22+ case server.To4() != nil:
23+ return &net.UDPAddr{IP: net.IPv4zero}
24+ case server.IsLoopback():
25+ return &net.UDPAddr{IP: net.IPv6loopback}
26+ default:
27+ return &net.UDPAddr{IP: net.IPv6unspecified}
28+ }
29+}
30+
16 // Proxy is a bounded, deterministic UDP relay with per-direction impairment. It31 // Proxy is a bounded, deterministic UDP relay with per-direction impairment. It
17-// listens on an ephemeral loopback port for clients and forwards to32+// listens on Config.ListenAddr (default: ephemeral IPv4 loopback) for clients
33+// and forwards to
18 // Config.ServerAddr from a separate upstream socket that can be rebound to a34 // Config.ServerAddr from a separate upstream socket that can be rebound to a
19 // new source address at any time.35 // new source address at any time.
20 type Proxy struct {36 type Proxy struct {
@@ -136,16 +152,7 @@ func New(cfg Config) (*Proxy, error) {
136 if err != nil {152 if err != nil {
137 return nil, fmt.Errorf("quicnettest: listen client socket: %w", err)153 return nil, fmt.Errorf("quicnettest: listen client socket: %w", err)
138 }154 }
139- upstreamAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)}155+ upstreamAddr := upstreamBindAddr(cfg.ServerAddr.IP)
140- if !cfg.ServerAddr.IP.IsLoopback() {
141- upstreamAddr.IP = net.IPv4zero
142- }
143- if cfg.ServerAddr.IP.To4() == nil {
144- upstreamAddr.IP = net.IPv6unspecified
145- if cfg.ServerAddr.IP.IsLoopback() {
146- upstreamAddr.IP = net.IPv6loopback
147- }
148- }
149 upstream, err := net.ListenUDP("udp", upstreamAddr)156 upstream, err := net.ListenUDP("udp", upstreamAddr)
150 if err != nil {157 if err != nil {
151 _ = clientConn.Close()158 _ = clientConn.Close()

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close