1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 */
8
9 #include <gmock/gmock.h>
10 #include <gtest/gtest.h>
11
12 #include <quic/api/test/Mocks.h>
13 #include <quic/common/test/TestUtils.h>
14 #include <quic/d6d/QuicD6DStateFunctions.h>
15 #include <quic/d6d/test/Mocks.h>
16 #include <quic/state/StateData.h>
17
18 using namespace testing;
19
20 namespace quic {
21 namespace test {
22
23 // timeLastNonSearchState can only increase
24 enum class TimeLastNonSearchStateEnd : uint8_t { EQ, GE };
25
26 struct D6DProbeLostTestFixture {
27 D6DMachineState stateBegin;
28 D6DMachineState stateEnd;
29 bool sendProbeBegin;
30 folly::Optional<std::chrono::milliseconds> sendProbeDelayEnd;
31 // probe loss doesn't change outstanding probes, so a begin value
32 // is enough
33 uint64_t outstandingProbes;
34 uint32_t currentProbeSizeBegin;
35 uint32_t currentProbeSizeEnd;
36 D6DProbePacket lastProbe;
37 D6DMachineState lastNonSearchStateBegin;
38 D6DMachineState lastNonSearchStateEnd;
39 TimePoint timeLastNonSearchStateBegin;
40 TimeLastNonSearchStateEnd timeLastNonSearchStateEndE;
41 };
42
makeTestShortPacket()43 RegularQuicWritePacket makeTestShortPacket() {
44 ShortHeader header(
45 ProtectionType::KeyPhaseZero, getTestConnectionId(), 2 /* packetNum */);
46 RegularQuicWritePacket packet(std::move(header));
47 return packet;
48 }
49
50 class QuicD6DStateFunctionsTest : public Test {
51 public:
runD6DProbeLostTest(QuicConnectionStateBase & conn,D6DProbeLostTestFixture fixture)52 void runD6DProbeLostTest(
53 QuicConnectionStateBase& conn,
54 D6DProbeLostTestFixture fixture) {
55 conn.d6d.state = fixture.stateBegin;
56 conn.d6d.outstandingProbes = fixture.outstandingProbes;
57 conn.d6d.currentProbeSize = fixture.currentProbeSizeBegin;
58 conn.d6d.lastProbe = fixture.lastProbe;
59 conn.pendingEvents.d6d.sendProbePacket = fixture.sendProbeBegin;
60 conn.d6d.meta.lastNonSearchState = fixture.lastNonSearchStateBegin;
61 conn.d6d.meta.timeLastNonSearchState = fixture.timeLastNonSearchStateBegin;
62 onD6DLastProbeLost(conn);
63 EXPECT_EQ(conn.d6d.state, fixture.stateEnd);
64 EXPECT_EQ(conn.d6d.currentProbeSize, fixture.currentProbeSizeEnd);
65 if (fixture.sendProbeDelayEnd.hasValue()) {
66 ASSERT_TRUE(conn.pendingEvents.d6d.sendProbeDelay.hasValue());
67 EXPECT_EQ(
68 *conn.pendingEvents.d6d.sendProbeDelay, *fixture.sendProbeDelayEnd);
69 } else {
70 ASSERT_FALSE(conn.pendingEvents.d6d.sendProbeDelay.hasValue());
71 }
72 EXPECT_EQ(conn.d6d.meta.lastNonSearchState, fixture.lastNonSearchStateEnd);
73 switch (fixture.timeLastNonSearchStateEndE) {
74 case TimeLastNonSearchStateEnd::EQ:
75 EXPECT_EQ(
76 conn.d6d.meta.timeLastNonSearchState,
77 fixture.timeLastNonSearchStateBegin);
78 break;
79 default:
80 EXPECT_GE(
81 conn.d6d.meta.timeLastNonSearchState,
82 fixture.timeLastNonSearchStateBegin);
83 }
84 }
85 };
86
TEST_F(QuicD6DStateFunctionsTest,D6DProbeTimeoutExpiredOneInBase)87 TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInBase) {
88 QuicConnectionStateBase conn(QuicNodeType::Server);
89 auto now = Clock::now();
90 // One probe lost in BASE state
91 D6DProbeLostTestFixture oneProbeLostInBase = {
92 D6DMachineState::BASE, // stateBegin
93 D6DMachineState::BASE, // stateEnd
94 false, // sendProbeBegin
95 kDefaultD6DProbeDelayWhenLost, // sendProbeEnd
96 1, // outstandingProbes
97 conn.d6d.basePMTU, // currentProbeSizeBegin
98 conn.d6d.basePMTU, // currentProbeSizeEnd
99 D6DProbePacket(0, conn.d6d.basePMTU + 10),
100 D6DMachineState::DISABLED, // lastNonSearchStateBegin
101 D6DMachineState::DISABLED, // lastNonSearchStateEnd
102 now, // timeLastNonSearchStateBegin
103 TimeLastNonSearchStateEnd::EQ // timeLastNonSearchStateEndE
104 };
105 runD6DProbeLostTest(conn, oneProbeLostInBase);
106 }
107
TEST_F(QuicD6DStateFunctionsTest,D6DProbeTimeoutExpiredMaxInBase)108 TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredMaxInBase) {
109 QuicConnectionStateBase conn(QuicNodeType::Server);
110 auto now = Clock::now();
111 // max number of probes lost in BASE state
112 D6DProbeLostTestFixture maxNumProbesLostInBase = {
113 D6DMachineState::BASE,
114 D6DMachineState::ERROR,
115 false,
116 kDefaultD6DProbeDelayWhenLost,
117 kDefaultD6DMaxOutstandingProbes,
118 conn.d6d.basePMTU,
119 kMinMaxUDPPayload,
120 D6DProbePacket(0, conn.d6d.basePMTU + 10),
121 D6DMachineState::DISABLED,
122 D6DMachineState::BASE,
123 now,
124 TimeLastNonSearchStateEnd::GE};
125 runD6DProbeLostTest(conn, maxNumProbesLostInBase);
126 }
127
TEST_F(QuicD6DStateFunctionsTest,D6DProbeTimeoutExpiredOneInSearching)128 TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInSearching) {
129 QuicConnectionStateBase conn(QuicNodeType::Server);
130 auto now = Clock::now();
131 // One probe lots in SEARCHING state
132 D6DProbeLostTestFixture oneProbeLostInSearching = {
133 D6DMachineState::SEARCHING,
134 D6DMachineState::SEARCHING,
135 false,
136 kDefaultD6DProbeDelayWhenLost,
137 1,
138 static_cast<uint32_t>(conn.d6d.basePMTU + 10),
139 static_cast<uint32_t>(conn.d6d.basePMTU + 10),
140 D6DProbePacket(0, conn.d6d.basePMTU + 10),
141 D6DMachineState::BASE,
142 D6DMachineState::BASE,
143 now,
144 TimeLastNonSearchStateEnd::EQ};
145 runD6DProbeLostTest(conn, oneProbeLostInSearching);
146 }
147
TEST_F(QuicD6DStateFunctionsTest,D6DProbeTimeoutExpiredMaxInSearching)148 TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredMaxInSearching) {
149 QuicConnectionStateBase conn(QuicNodeType::Server);
150 auto now = Clock::now();
151 // Max number of probes lost in SEARCHING state
152 D6DProbeLostTestFixture maxProbesLostInSearching = {
153 D6DMachineState::SEARCHING,
154 D6DMachineState::SEARCH_COMPLETE,
155 false,
156 folly::none,
157 kDefaultD6DMaxOutstandingProbes,
158 static_cast<uint32_t>(conn.d6d.basePMTU + 10),
159 static_cast<uint32_t>(conn.d6d.basePMTU + 10),
160 D6DProbePacket(0, conn.d6d.basePMTU + 10),
161 D6DMachineState::BASE,
162 D6DMachineState::BASE,
163 now,
164 TimeLastNonSearchStateEnd::EQ};
165 runD6DProbeLostTest(conn, maxProbesLostInSearching);
166 }
167
TEST_F(QuicD6DStateFunctionsTest,D6DProbeTimeoutExpiredOneInError)168 TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInError) {
169 QuicConnectionStateBase conn(QuicNodeType::Server);
170 auto now = Clock::now();
171 // Probe lost in ERROR state
172 D6DProbeLostTestFixture probeLostInError = {
173 D6DMachineState::ERROR,
174 D6DMachineState::ERROR,
175 false,
176 kDefaultD6DProbeDelayWhenLost,
177 kDefaultD6DMaxOutstandingProbes + 1,
178 kMinMaxUDPPayload,
179 kMinMaxUDPPayload,
180 D6DProbePacket(0, conn.d6d.basePMTU + 10),
181 D6DMachineState::BASE,
182 D6DMachineState::BASE,
183 now,
184 TimeLastNonSearchStateEnd::EQ};
185 runD6DProbeLostTest(conn, probeLostInError);
186 }
187
TEST_F(QuicD6DStateFunctionsTest,D6DProbeAckedInBase)188 TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInBase) {
189 QuicConnectionStateBase conn(QuicNodeType::Server);
190 const uint16_t expectPMTU = 1400;
191 auto& d6d = conn.d6d;
192 auto now = Clock::now();
193 Observer::Config config = {};
194 config.pmtuEvents = true;
195 auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
196 auto observers = std::make_shared<ObserverVec>();
197 observers->emplace_back(mockObserver.get());
198 conn.observers = observers;
199 d6d.state = D6DMachineState::BASE;
200 d6d.outstandingProbes = 1;
201 d6d.currentProbeSize = d6d.basePMTU;
202 d6d.meta.lastNonSearchState = D6DMachineState::DISABLED;
203 d6d.meta.timeLastNonSearchState = now;
204 auto pkt = OutstandingPacket(
205 makeTestShortPacket(),
206 Clock::now(),
207 d6d.currentProbeSize,
208 0,
209 false,
210 true,
211 d6d.currentProbeSize,
212 d6d.currentProbeSize,
213 0,
214 LossState(),
215 0);
216 d6d.lastProbe = D6DProbePacket(
217 pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
218 d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
219 auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
220 EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
221 .Times(1)
222 .WillOnce(Return(expectPMTU));
223 EXPECT_CALL(*mockObserver, pmtuUpperBoundDetected(_, _)).Times(0);
224 onD6DLastProbeAcked(conn);
225 for (auto& callback : conn.pendingCallbacks) {
226 callback(nullptr);
227 }
228 EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
229 EXPECT_EQ(d6d.currentProbeSize, expectPMTU);
230 EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
231 EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
232 EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
233 }
234
TEST_F(QuicD6DStateFunctionsTest,D6DProbeAckedInSearchingOne)235 TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInSearchingOne) {
236 QuicConnectionStateBase conn(QuicNodeType::Server);
237 const uint16_t expectPMTU = 1400;
238 auto& d6d = conn.d6d;
239 auto now = Clock::now();
240 Observer::Config config = {};
241 config.pmtuEvents = true;
242 auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
243 auto observers = std::make_shared<ObserverVec>();
244 observers->emplace_back(mockObserver.get());
245 conn.observers = observers;
246 d6d.state = D6DMachineState::SEARCHING;
247 d6d.outstandingProbes = 1;
248 conn.udpSendPacketLen = 1250;
249 d6d.currentProbeSize = 1300;
250 d6d.meta.lastNonSearchState = D6DMachineState::BASE;
251 d6d.meta.timeLastNonSearchState = now;
252 auto pkt = OutstandingPacket(
253 makeTestShortPacket(),
254 Clock::now(),
255 d6d.currentProbeSize,
256 0,
257 false,
258 true,
259 d6d.currentProbeSize,
260 d6d.currentProbeSize,
261 0,
262 LossState(),
263 0);
264 d6d.lastProbe = D6DProbePacket(
265 pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
266 d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
267 auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
268 EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
269 .Times(1)
270 .WillOnce(Return(expectPMTU));
271 EXPECT_CALL(*mockObserver, pmtuUpperBoundDetected(_, _)).Times(0);
272 onD6DLastProbeAcked(conn);
273 for (auto& callback : conn.pendingCallbacks) {
274 callback(nullptr);
275 }
276 EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
277 EXPECT_EQ(d6d.currentProbeSize, expectPMTU);
278 EXPECT_EQ(conn.udpSendPacketLen, 1300);
279 EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
280 EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
281 }
282
TEST_F(QuicD6DStateFunctionsTest,D6DProbeAckedInSearchingMax)283 TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInSearchingMax) {
284 QuicConnectionStateBase conn(QuicNodeType::Server);
285 const uint16_t oversize = 1500;
286 auto& d6d = conn.d6d;
287 auto now = Clock::now();
288 Observer::Config config = {};
289 config.pmtuEvents = true;
290 auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
291 auto observers = std::make_shared<ObserverVec>();
292 observers->emplace_back(mockObserver.get());
293 conn.observers = observers;
294 d6d.state = D6DMachineState::SEARCHING;
295 d6d.outstandingProbes = 3;
296 conn.udpSendPacketLen = 1400;
297 d6d.currentProbeSize = 1450;
298 d6d.meta.lastNonSearchState = D6DMachineState::BASE;
299 d6d.meta.timeLastNonSearchState = now;
300 d6d.meta.totalTxedProbes = 10;
301 auto pkt = OutstandingPacket(
302 makeTestShortPacket(),
303 Clock::now(),
304 d6d.currentProbeSize,
305 0,
306 false,
307 true,
308 d6d.currentProbeSize,
309 d6d.currentProbeSize,
310 0,
311 LossState(),
312 0);
313 d6d.lastProbe = D6DProbePacket(
314 pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
315 d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
316 auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
317 EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
318 .Times(1)
319 .WillOnce(Return(oversize));
320 EXPECT_CALL(*mockObserver, pmtuUpperBoundDetected(_, _))
321 .Times(1)
322 .WillOnce(Invoke([&](QuicSocket* /* qSocket */,
323 const Observer::PMTUUpperBoundEvent& event) {
324 EXPECT_LT(now, event.upperBoundTime);
325 EXPECT_LT(0us, event.timeSinceLastNonSearchState);
326 EXPECT_EQ(D6DMachineState::BASE, event.lastNonSearchState);
327 EXPECT_EQ(1450, event.upperBoundPMTU);
328 EXPECT_EQ(10, event.cumulativeProbesSent);
329 EXPECT_EQ(ProbeSizeRaiserType::ConstantStep, event.probeSizeRaiserType);
330 }));
331 onD6DLastProbeAcked(conn);
332 for (auto& callback : conn.pendingCallbacks) {
333 callback(nullptr);
334 }
335 EXPECT_EQ(d6d.state, D6DMachineState::SEARCH_COMPLETE);
336 EXPECT_EQ(d6d.currentProbeSize, 1450);
337 EXPECT_EQ(conn.udpSendPacketLen, 1450);
338 EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
339 EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
340 }
341
TEST_F(QuicD6DStateFunctionsTest,D6DProbeAckedInError)342 TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInError) {
343 QuicConnectionStateBase conn(QuicNodeType::Server);
344 auto& d6d = conn.d6d;
345 auto now = Clock::now();
346 Observer::Config config = {};
347 config.pmtuEvents = true;
348 auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
349 auto observers = std::make_shared<ObserverVec>();
350 observers->emplace_back(mockObserver.get());
351 conn.observers = observers;
352 d6d.state = D6DMachineState::ERROR;
353 d6d.outstandingProbes = 3;
354 conn.udpSendPacketLen = d6d.basePMTU;
355 d6d.currentProbeSize = d6d.basePMTU - 20;
356 d6d.meta.lastNonSearchState = D6DMachineState::BASE;
357 d6d.meta.timeLastNonSearchState = now;
358 auto pkt = OutstandingPacket(
359 makeTestShortPacket(),
360 Clock::now(),
361 d6d.currentProbeSize,
362 0,
363 false,
364 true,
365 d6d.currentProbeSize,
366 d6d.currentProbeSize,
367 0,
368 LossState(),
369 0);
370 d6d.lastProbe = D6DProbePacket(
371 pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
372 d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
373 auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
374 EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
375 .Times(1)
376 .WillOnce(Return(1300)); // Won't be used
377 EXPECT_CALL(*mockObserver, pmtuUpperBoundDetected(_, _)).Times(0);
378 onD6DLastProbeAcked(conn);
379 for (auto& callback : conn.pendingCallbacks) {
380 callback(nullptr);
381 }
382 EXPECT_EQ(d6d.state, D6DMachineState::BASE);
383 EXPECT_EQ(d6d.currentProbeSize, d6d.basePMTU);
384 EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
385 EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::ERROR);
386 EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
387 }
388
TEST_F(QuicD6DStateFunctionsTest,BlackholeInSearching)389 TEST_F(QuicD6DStateFunctionsTest, BlackholeInSearching) {
390 QuicConnectionStateBase conn(QuicNodeType::Server);
391 auto& d6d = conn.d6d;
392 auto now = Clock::now();
393 Observer::Config config = {};
394 config.pmtuEvents = true;
395 auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
396 auto observers = std::make_shared<ObserverVec>();
397 observers->emplace_back(mockObserver.get());
398 conn.observers = observers;
399 d6d.state = D6DMachineState::SEARCHING;
400 d6d.outstandingProbes = 2;
401 conn.udpSendPacketLen = d6d.basePMTU + 20;
402 d6d.currentProbeSize = d6d.basePMTU + 30;
403 d6d.meta.lastNonSearchState = D6DMachineState::BASE;
404 d6d.meta.timeLastNonSearchState = now;
405 auto pkt = OutstandingPacket(
406 makeTestShortPacket(),
407 now + 10s,
408 d6d.currentProbeSize,
409 0,
410 false,
411 true,
412 d6d.currentProbeSize,
413 d6d.currentProbeSize,
414 0,
415 LossState(),
416 0);
417 d6d.lastProbe = D6DProbePacket(
418 pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
419
420 auto lostPacket = OutstandingPacket(
421 makeTestShortPacket(),
422 now + 8s,
423 conn.udpSendPacketLen,
424 0,
425 false,
426 conn.udpSendPacketLen + d6d.currentProbeSize,
427 0,
428 conn.udpSendPacketLen + d6d.currentProbeSize,
429 0,
430 LossState(),
431 0);
432
433 d6d.thresholdCounter = std::make_unique<WindowedCounter<uint64_t, uint64_t>>(
434 std::chrono::microseconds(kDefaultD6DBlackholeDetectionWindow).count(),
435 1); // Threshold of 1 will cause window to be set to 0
436
437 EXPECT_CALL(*mockObserver, pmtuBlackholeDetected(_, _))
438 .Times(1)
439 .WillOnce(Invoke([&](QuicSocket* /* qSocket */,
440 const Observer::PMTUBlackholeEvent& event) {
441 EXPECT_LE(d6d.meta.timeLastNonSearchState, event.blackholeTime);
442 EXPECT_EQ(D6DMachineState::BASE, event.lastNonSearchState);
443 EXPECT_EQ(D6DMachineState::SEARCHING, event.currentState);
444 EXPECT_EQ(d6d.basePMTU + 20, event.udpSendPacketLen);
445 EXPECT_EQ(d6d.basePMTU + 30, event.lastProbeSize);
446 EXPECT_EQ(0, event.blackholeDetectionWindow);
447 EXPECT_EQ(1, event.blackholeDetectionThreshold);
448 EXPECT_EQ(
449 d6d.basePMTU + 20, event.triggeringPacketMetadata.encodedSize);
450 }));
451
452 detectPMTUBlackhole(conn, lostPacket);
453 for (auto& callback : conn.pendingCallbacks) {
454 callback(nullptr);
455 }
456
457 EXPECT_EQ(d6d.state, D6DMachineState::BASE);
458 EXPECT_EQ(d6d.currentProbeSize, d6d.basePMTU);
459 EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
460 EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
461 EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
462 }
463
TEST_F(QuicD6DStateFunctionsTest,BlackholeInSearchComplete)464 TEST_F(QuicD6DStateFunctionsTest, BlackholeInSearchComplete) {
465 QuicConnectionStateBase conn(QuicNodeType::Server);
466 auto& d6d = conn.d6d;
467 auto now = Clock::now();
468 Observer::Config config = {};
469 config.pmtuEvents = true;
470 auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
471 auto observers = std::make_shared<ObserverVec>();
472 observers->emplace_back(mockObserver.get());
473 conn.observers = observers;
474 d6d.state = D6DMachineState::SEARCH_COMPLETE;
475 conn.udpSendPacketLen = d6d.basePMTU + 20;
476 d6d.currentProbeSize = d6d.basePMTU + 20;
477 d6d.meta.lastNonSearchState = D6DMachineState::BASE;
478 d6d.meta.timeLastNonSearchState = now;
479 auto pkt = OutstandingPacket(
480 makeTestShortPacket(),
481 now + 10s,
482 d6d.currentProbeSize,
483 0,
484 false,
485 true,
486 d6d.currentProbeSize,
487 d6d.currentProbeSize,
488 0,
489 LossState(),
490 0);
491 d6d.lastProbe = D6DProbePacket(
492 pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
493
494 auto lostPacket = OutstandingPacket(
495 makeTestShortPacket(),
496 now + 12s,
497 conn.udpSendPacketLen,
498 0,
499 false,
500 conn.udpSendPacketLen + d6d.currentProbeSize,
501 0,
502 conn.udpSendPacketLen + d6d.currentProbeSize,
503 0,
504 LossState(),
505 0);
506
507 d6d.thresholdCounter = std::make_unique<WindowedCounter<uint64_t, uint64_t>>(
508 std::chrono::microseconds(kDefaultD6DBlackholeDetectionWindow).count(),
509 1); // Threshold of 1 will cause window to be set to 0
510
511 EXPECT_CALL(*mockObserver, pmtuBlackholeDetected(_, _))
512 .Times(1)
513 .WillOnce(Invoke([&](QuicSocket* /* qSocket */,
514 const Observer::PMTUBlackholeEvent& event) {
515 EXPECT_EQ(d6d.meta.timeLastNonSearchState, event.blackholeTime);
516 EXPECT_EQ(D6DMachineState::BASE, event.lastNonSearchState);
517 EXPECT_EQ(D6DMachineState::SEARCH_COMPLETE, event.currentState);
518 EXPECT_EQ(d6d.basePMTU + 20, event.udpSendPacketLen);
519 EXPECT_EQ(d6d.basePMTU + 20, event.lastProbeSize);
520 EXPECT_EQ(0, event.blackholeDetectionWindow);
521 EXPECT_EQ(1, event.blackholeDetectionThreshold);
522 EXPECT_EQ(
523 d6d.basePMTU + 20, event.triggeringPacketMetadata.encodedSize);
524 }));
525
526 detectPMTUBlackhole(conn, lostPacket);
527 for (auto& callback : conn.pendingCallbacks) {
528 callback(nullptr);
529 }
530
531 EXPECT_EQ(d6d.state, D6DMachineState::BASE);
532 EXPECT_EQ(d6d.currentProbeSize, d6d.basePMTU);
533 EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
534 EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::SEARCH_COMPLETE);
535 EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
536 }
537
TEST_F(QuicD6DStateFunctionsTest,ReachMaxPMTU)538 TEST_F(QuicD6DStateFunctionsTest, ReachMaxPMTU) {
539 QuicConnectionStateBase conn(QuicNodeType::Server);
540 auto& d6d = conn.d6d;
541 auto now = Clock::now();
542 Observer::Config config = {};
543 config.pmtuEvents = true;
544 auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
545 auto observers = std::make_shared<ObserverVec>();
546 observers->emplace_back(mockObserver.get());
547 conn.observers = observers;
548 d6d.state = D6DMachineState::SEARCHING;
549 d6d.maxPMTU = 1452;
550 d6d.outstandingProbes = 1;
551 conn.udpSendPacketLen = 1400;
552 d6d.currentProbeSize = 1442;
553 d6d.meta.lastNonSearchState = D6DMachineState::BASE;
554 d6d.meta.timeLastNonSearchState = now;
555 d6d.meta.totalTxedProbes = 10;
556 auto pkt = OutstandingPacket(
557 makeTestShortPacket(),
558 Clock::now(),
559 d6d.currentProbeSize,
560 0,
561 false,
562 true,
563 d6d.currentProbeSize,
564 d6d.currentProbeSize,
565 0,
566 LossState(),
567 0);
568 d6d.lastProbe = D6DProbePacket(
569 pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
570 d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
571 auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
572 EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
573 .Times(1)
574 .WillOnce(Return(1452));
575 onD6DLastProbeAcked(conn);
576 EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
577 EXPECT_EQ(d6d.currentProbeSize, 1452);
578 EXPECT_EQ(conn.udpSendPacketLen, 1442);
579 EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
580 EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
581 }
582
TEST_F(QuicD6DStateFunctionsTest,MaintainStateWhenFalsePositiveBlackholeDetected)583 TEST_F(
584 QuicD6DStateFunctionsTest,
585 MaintainStateWhenFalsePositiveBlackholeDetected) {
586 QuicConnectionStateBase conn(QuicNodeType::Server);
587 auto& d6d = conn.d6d;
588 auto now = Clock::now();
589 d6d.state = D6DMachineState::SEARCHING;
590 d6d.maxPMTU = 1452;
591 d6d.outstandingProbes = 1;
592 conn.udpSendPacketLen = 1400;
593 d6d.currentProbeSize = 1442;
594 d6d.meta.lastNonSearchState = D6DMachineState::BASE;
595 d6d.meta.timeLastNonSearchState = now;
596 d6d.meta.totalTxedProbes = 10;
597 auto pkt = OutstandingPacket(
598 makeTestShortPacket(),
599 Clock::now(),
600 d6d.currentProbeSize,
601 0,
602 false,
603 true,
604 d6d.currentProbeSize,
605 d6d.currentProbeSize,
606 0,
607 LossState(),
608 0);
609 d6d.lastProbe = D6DProbePacket(
610 pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
611 d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
612 auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
613 EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
614 .Times(1)
615 .WillOnce(Return(1452));
616 d6d.thresholdCounter = std::make_unique<WindowedCounter<uint64_t, uint64_t>>(
617 std::chrono::microseconds(kDefaultD6DBlackholeDetectionWindow).count(),
618 1); // Threshold of 1 will cause window to be set to 0
619
620 auto lostPacket = OutstandingPacket(
621 makeTestShortPacket(),
622 Clock::now(),
623 d6d.currentProbeSize,
624 0,
625 false,
626 false,
627 d6d.currentProbeSize,
628 d6d.currentProbeSize,
629 0,
630 LossState(),
631 0);
632 // Generate a false positive blackhole signal
633 detectPMTUBlackhole(conn, lostPacket);
634 EXPECT_EQ(d6d.state, D6DMachineState::BASE);
635 EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
636
637 // The ack of a non-stale probe should bring us back to SEARCHING state and
638 // correct probe size
639 onD6DLastProbeAcked(conn);
640 EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
641 EXPECT_EQ(d6d.currentProbeSize, 1452);
642 EXPECT_EQ(conn.udpSendPacketLen, 1442);
643 EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
644 EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
645 }
646
TEST_F(QuicD6DStateFunctionsTest,UpperboundIsBase)647 TEST_F(QuicD6DStateFunctionsTest, UpperboundIsBase) {
648 QuicConnectionStateBase conn(QuicNodeType::Server);
649 auto& d6d = conn.d6d;
650 auto now = Clock::now();
651 d6d.state = D6DMachineState::BASE;
652 d6d.basePMTU = 1400;
653 d6d.maxPMTU = 1400;
654 d6d.outstandingProbes = 1;
655 conn.udpSendPacketLen = 1400;
656 d6d.currentProbeSize = 1400;
657 d6d.meta.lastNonSearchState = D6DMachineState::DISABLED;
658 d6d.meta.timeLastNonSearchState = now;
659 d6d.meta.totalTxedProbes = 10;
660 auto pkt = OutstandingPacket(
661 makeTestShortPacket(),
662 Clock::now(),
663 d6d.currentProbeSize,
664 0,
665 false,
666 true,
667 d6d.currentProbeSize,
668 d6d.currentProbeSize,
669 0,
670 LossState(),
671 0);
672 d6d.lastProbe = D6DProbePacket(
673 pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
674 d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
675 auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
676 EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
677 .Times(1)
678 .WillOnce(Return(1452));
679
680 // The ack of a non-stale probe should bring us back to SEARCHING state and
681 // correct probe size
682 onD6DLastProbeAcked(conn);
683 EXPECT_EQ(d6d.state, D6DMachineState::SEARCH_COMPLETE);
684 EXPECT_EQ(d6d.currentProbeSize, 1400);
685 EXPECT_EQ(conn.udpSendPacketLen, 1400);
686 EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
687 EXPECT_GT(d6d.meta.timeLastNonSearchState, now);
688 }
689
690 } // namespace test
691 } // namespace quic
692