v0.3.0 99dc266

udp_netem_test.go RawHistoryFolder

1//go:build linux
2
3package main
4
5import (
6	"context"
7	"net"
8	"os"
9	"path/filepath"
10	"strconv"
11	"testing"
12	"time"
13)
14
15func closeUDPFixture(t *testing.T, c interface{ Close() error }) {
16	t.Helper()
17	t.Cleanup(func() {
18		if err := c.Close(); err != nil {
19			t.Error(err)
20		}
21	})
22}
23
24func TestUDPNetemDropsWhenBoundedSchedulerIsFull(t *testing.T) {
25	r := &udpNetemRelay{
26		done:  make(chan struct{}),
27		slots: make(chan struct{}, udpNetemQueueCapacity),
28		queue: make(chan scheduledPacket, udpNetemQueueCapacity),
29	}
30	packet := scheduledPacket{packet: []byte("bounded"), to: &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 1}}
31	for range udpNetemQueueCapacity {
32		if !r.enqueue(packet) {
33			t.Fatal("enqueue() dropped packet before scheduler capacity was full")
34		}
35	}
36	if r.enqueue(packet) {
37		t.Fatal("enqueue() accepted packet beyond scheduler capacity")
38	}
39	if got := r.queueOverflowDrops.Load(); got != 1 {
40		t.Fatalf("queue overflow drops=%d, want 1", got)
41	}
42}
43
44func TestUDPNetemCloseRejectsQueueOverflow(t *testing.T) {
45	netem, err := newUDPNetem(udpNetemConfig{TargetPath: filepath.Join(t.TempDir(), "target")})
46	if err != nil {
47		t.Fatal(err)
48	}
49	relay, ok := netem.(*udpNetemRelay)
50	if !ok {
51		t.Fatalf("netem type=%T, want *udpNetemRelay", netem)
52	}
53	relay.queueOverflowDrops.Store(3)
54	if err := netem.Close(); err == nil || err.Error() != "udp netem queue overflow drops: 3" {
55		t.Fatalf("Close error=%v, want overflow rejection", err)
56	}
57}
58
59func TestUDPNetemExecutesRTTAndLossFixtures(t *testing.T) {
60	fixtures := []struct {
61		name string
62		rtt  time.Duration
63		loss int
64		sent int
65		want int
66	}{
67		{"25ms RTT", 25 * time.Millisecond, 0, 1, 1},
68		{"100ms RTT", 100 * time.Millisecond, 0, 1, 1},
69		{"zero loss", 0, 0, 10, 10},
70		{"one percent loss", 0, 1, 100, 99},
71	}
72	for _, tc := range fixtures {
73		t.Run(tc.name, func(t *testing.T) {
74			var lc net.ListenConfig
75			target, err := lc.ListenPacket(context.Background(), "udp", "127.0.0.1:0")
76			if err != nil {
77				t.Fatal(err)
78			}
79			closeUDPFixture(t, target)
80			targetAddr, ok := target.LocalAddr().(*net.UDPAddr)
81			if !ok {
82				t.Fatalf("target address type=%T, want *net.UDPAddr", target.LocalAddr())
83			}
84			path := filepath.Join(t.TempDir(), "target")
85			if err := os.WriteFile(path, []byte("VEV-UDP "+strconv.Itoa(targetAddr.Port)+" key\\n"), 0o600); err != nil {
86				t.Fatal(err)
87			}
88			netem, err := newUDPNetem(udpNetemConfig{RTT: tc.rtt, LossPercent: tc.loss, TargetPath: path})
89			if err != nil {
90				t.Fatal(err)
91			}
92			closeUDPFixture(t, netem)
93			client, err := lc.ListenPacket(context.Background(), "udp", "127.0.0.1:0")
94			if err != nil {
95				t.Fatal(err)
96			}
97			closeUDPFixture(t, client)
98			started := time.Now()
99			for i := 0; i < tc.sent; i++ {
100				if _, err := client.WriteTo([]byte{byte(i)}, &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: netem.Port()}); err != nil {
101					t.Fatal(err)
102				}
103			}
104			if err := target.SetReadDeadline(time.Now().Add(2 * time.Second)); err != nil {
105				t.Fatal(err)
106			}
107			got := 0
108			buf := make([]byte, 32)
109			var relayAddr net.Addr
110			for got < tc.want {
111				if _, relayAddr, err = target.ReadFrom(buf); err != nil {
112					t.Fatalf("received %d packets, want %d: %v", got, tc.want, err)
113				}
114				got++
115			}
116			if tc.rtt > 0 {
117				if elapsed := time.Since(started); elapsed < tc.rtt/2 {
118					t.Fatalf("client-to-target netem delay=%s, want at least %s", elapsed, tc.rtt/2)
119				}
120			}
121			if tc.loss == 0 {
122				// The relay learns the client from the first carriage packet. Replying
123				// through that same relay address verifies the target-to-client half
124				// of the configured RTT as well.
125				started = time.Now()
126				if _, err := target.WriteTo([]byte("reply"), relayAddr); err != nil {
127					t.Fatal(err)
128				}
129				if err := client.SetReadDeadline(time.Now().Add(2 * time.Second)); err != nil {
130					t.Fatal(err)
131				}
132				if _, _, err := client.ReadFrom(buf); err != nil {
133					t.Fatalf("relay reply: %v", err)
134				}
135				if tc.rtt > 0 {
136					if elapsed := time.Since(started); elapsed < tc.rtt/2 {
137						t.Fatalf("target-to-client netem delay=%s, want at least %s", elapsed, tc.rtt/2)
138					}
139				}
140			}
141			// Keep the deadline short: extra carriage would prove the exact 1%%
142			// loss cycle was not enforced.
143			if err := target.SetReadDeadline(time.Now().Add(20 * time.Millisecond)); err != nil {
144				t.Fatal(err)
145			}
146			if _, _, err := target.ReadFrom(buf); tc.want != tc.sent && err == nil {
147				t.Fatalf("received more than %d packets with %d%% loss", tc.want, tc.loss)
148			}
149		})
150	}
151}

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