File: ecn_test.go

package info (click to toggle)
golang-github-lucas-clemente-quic-go 0.50.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,496 kB
  • sloc: sh: 54; makefile: 7
file content (272 lines) | stat: -rw-r--r-- 12,873 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
package ackhandler

import (
	mocklogging "github.com/quic-go/quic-go/internal/mocks/logging"
	"github.com/quic-go/quic-go/internal/protocol"
	"github.com/quic-go/quic-go/internal/utils"
	"github.com/quic-go/quic-go/logging"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("ECN tracker", func() {
	var ecnTracker *ecnTracker
	var tracer *mocklogging.MockConnectionTracer

	getAckedPackets := func(pns ...protocol.PacketNumber) []*packet {
		var packets []*packet
		for _, p := range pns {
			packets = append(packets, &packet{PacketNumber: p})
		}
		return packets
	}

	BeforeEach(func() {
		var tr *logging.ConnectionTracer
		tr, tracer = mocklogging.NewMockConnectionTracer(mockCtrl)
		ecnTracker = newECNTracker(utils.DefaultLogger, tr)
	})

	It("sends exactly 10 testing packets", func() {
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateTesting, logging.ECNTriggerNoTrigger)
		for i := 0; i < 9; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
			// Do this twice to make sure only sent packets are counted
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
			ecnTracker.SentPacket(protocol.PacketNumber(10+i), protocol.ECT0)
		}
		Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateUnknown, logging.ECNTriggerNoTrigger)
		ecnTracker.SentPacket(20, protocol.ECT0)
		// In unknown state, packets shouldn't be ECN-marked.
		Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
	})

	sendAllTestingPackets := func() {
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateTesting, logging.ECNTriggerNoTrigger)
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateUnknown, logging.ECNTriggerNoTrigger)
		for i := 0; i < 10; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECT0)
		}
	}

	It("fails ECN validation if all ECN testing packets are lost", func() {
		sendAllTestingPackets()
		for i := 10; i < 20; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
		}
		for i := 0; i < 9; i++ {
			ecnTracker.LostPacket(protocol.PacketNumber(i))
		}
		// We don't care about the loss of non-testing packets
		ecnTracker.LostPacket(15)
		// Now lose the last testing packet.
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedLostAllTestingPackets)
		ecnTracker.LostPacket(9)
		Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
		// We still don't care about more non-testing packets being lost
		ecnTracker.LostPacket(16)
	})

	It("only detects ECN mangling after sending all testing packets", func() {
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateTesting, logging.ECNTriggerNoTrigger)
		for i := 0; i < 9; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECT0)
			ecnTracker.LostPacket(protocol.PacketNumber(i))
		}
		// Send the last testing packet, and receive a
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateUnknown, logging.ECNTriggerNoTrigger)
		Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
		ecnTracker.SentPacket(9, protocol.ECT0)
		// Now lose the last testing packet.
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedLostAllTestingPackets)
		ecnTracker.LostPacket(9)
	})

	It("passes ECN validation when a testing packet is acknowledged, while still in testing state", func() {
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateTesting, logging.ECNTriggerNoTrigger)
		for i := 0; i < 5; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECT0)
		}
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger)
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(3), 1, 0, 0)).To(BeFalse())
		// make sure we continue sending ECT(0) packets
		for i := 5; i < 100; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECT0)
		}
	})

	It("passes ECN validation when a testing packet is acknowledged, while in unknown state", func() {
		sendAllTestingPackets()
		for i := 10; i < 20; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
		}
		// Lose some packets to make sure this doesn't influence the outcome.
		for i := 0; i < 5; i++ {
			ecnTracker.LostPacket(protocol.PacketNumber(i))
		}
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger)
		Expect(ecnTracker.HandleNewlyAcked([]*packet{{PacketNumber: 7}}, 1, 0, 0)).To(BeFalse())
	})

	It("fails ECN validation when the ACK contains more ECN counts than we sent packets", func() {
		sendAllTestingPackets()
		for i := 10; i < 20; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
		}
		// only 10 ECT(0) packets were sent, but the ACK claims to have received 12 of them
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedMoreECNCountsThanSent)
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), 12, 0, 0)).To(BeFalse())
	})

	It("fails ECN validation when the ACK contains ECN counts for the wrong code point", func() {
		sendAllTestingPackets()
		for i := 10; i < 20; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
		}
		// We sent ECT(0), but this ACK acknowledges ECT(1).
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedMoreECNCountsThanSent)
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), 0, 1, 0)).To(BeFalse())
	})

	It("fails ECN validation when the ACK doesn't contain ECN counts", func() {
		sendAllTestingPackets()
		for i := 10; i < 20; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
		}
		// First only acknowledge packets sent without ECN marks.
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(12, 13, 14), 0, 0, 0)).To(BeFalse())
		// Now acknowledge some packets sent with ECN marks.
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedNoECNCounts)
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(1, 2, 3, 15), 0, 0, 0)).To(BeFalse())
	})

	It("fails ECN validation when an ACK decreases ECN counts", func() {
		sendAllTestingPackets()
		for i := 10; i < 20; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
		}
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger)
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(1, 2, 3, 12), 3, 0, 0)).To(BeFalse())
		// Now acknowledge some more packets, but decrease the ECN counts. Obviously, this doesn't make any sense.
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedDecreasedECNCounts)
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(4, 5, 6, 13), 2, 0, 0)).To(BeFalse())
		// make sure that new ACKs are ignored
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(7, 8, 9, 14), 5, 0, 0)).To(BeFalse())
	})

	// This can happen if ACK are lost / reordered.
	It("doesn't fail validation if the ACK contains more ECN counts than it acknowledges packets", func() {
		sendAllTestingPackets()
		for i := 10; i < 20; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
		}
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger)
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(1, 2, 3, 12), 8, 0, 0)).To(BeFalse())
	})

	It("fails ECN validation when the ACK doesn't contain enough ECN counts", func() {
		sendAllTestingPackets()
		for i := 10; i < 20; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
		}
		// First only acknowledge some packets sent with ECN marks.
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger)
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(1, 2, 3, 12), 2, 0, 1)).To(BeTrue())
		// Now acknowledge some more packets sent with ECN marks, but don't increase the counters enough.
		// This ACK acknowledges 3 more ECN-marked packets, but the counters only increase by 2.
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedTooFewECNCounts)
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(4, 5, 6, 15), 3, 0, 2)).To(BeFalse())
	})

	It("detects ECN mangling if all testing packets are marked CE", func() {
		sendAllTestingPackets()
		for i := 10; i < 20; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
		}
		// ECN capability not confirmed yet, therefore CE marks are not regarded as congestion events
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(0, 1, 2, 3), 0, 0, 4)).To(BeFalse())
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(4, 5, 6, 10, 11, 12), 0, 0, 7)).To(BeFalse())
		// With the next ACK, all testing packets will now have been marked CE.
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedManglingDetected)
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(7, 8, 9, 13), 0, 0, 10)).To(BeFalse())
	})

	It("only detects ECN mangling after sending all testing packets", func() {
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateTesting, logging.ECNTriggerNoTrigger)
		for i := 0; i < 9; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECT0)
			Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(protocol.PacketNumber(i)), 0, 0, int64(i+1))).To(BeFalse())
		}
		// Send the last testing packet, and receive a
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateUnknown, logging.ECNTriggerNoTrigger)
		Expect(ecnTracker.Mode()).To(Equal(protocol.ECT0))
		ecnTracker.SentPacket(9, protocol.ECT0)
		// This ACK now reports the last testing packets as CE as well.
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedManglingDetected)
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(9), 0, 0, 10)).To(BeFalse())
	})

	It("detects ECN mangling, if some testing packets are marked CE, and then others are lost", func() {
		sendAllTestingPackets()
		for i := 10; i < 20; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
		}
		// ECN capability not confirmed yet, therefore CE marks are not regarded as congestion events
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(0, 1, 2, 3), 0, 0, 4)).To(BeFalse())
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(6, 7, 8, 9), 0, 0, 8)).To(BeFalse())
		// Lose one of the two unacknowledged packets.
		ecnTracker.LostPacket(4)
		// By losing the last unacknowledged testing packets, we should detect the mangling.
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedManglingDetected)
		ecnTracker.LostPacket(5)
	})

	It("detects ECN mangling, if some testing packets are lost, and then others are marked CE", func() {
		sendAllTestingPackets()
		for i := 10; i < 20; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
		}
		// Lose a few packets.
		ecnTracker.LostPacket(0)
		ecnTracker.LostPacket(1)
		ecnTracker.LostPacket(2)
		// ECN capability not confirmed yet, therefore CE marks are not regarded as congestion events
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(3, 4, 5, 6, 7, 8), 0, 0, 6)).To(BeFalse())
		// By CE-marking the last unacknowledged testing packets, we should detect the mangling.
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateFailed, logging.ECNFailedManglingDetected)
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(9), 0, 0, 7)).To(BeFalse())
	})

	It("declares congestion", func() {
		sendAllTestingPackets()
		for i := 10; i < 20; i++ {
			Expect(ecnTracker.Mode()).To(Equal(protocol.ECNNon))
			ecnTracker.SentPacket(protocol.PacketNumber(i), protocol.ECNNon)
		}
		// Receive one CE count.
		tracer.EXPECT().ECNStateUpdated(logging.ECNStateCapable, logging.ECNTriggerNoTrigger)
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(1, 2, 3, 12), 2, 0, 1)).To(BeTrue())
		// No increase in CE. No congestion.
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(4, 5, 6, 13), 5, 0, 1)).To(BeFalse())
		// Increase in CE. More congestion.
		Expect(ecnTracker.HandleNewlyAcked(getAckedPackets(7, 8, 9, 14), 7, 0, 2)).To(BeTrue())
	})
})