• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // The tests in this file attempt to verify the following through simulation:
6 // a) That a server experiencing overload will actually benefit from the
7 //    anti-DDoS throttling logic, i.e. that its traffic spike will subside
8 //    and be distributed over a longer period of time;
9 // b) That "well-behaved" clients of a server under DDoS attack actually
10 //    benefit from the anti-DDoS throttling logic; and
11 // c) That the approximate increase in "perceived downtime" introduced by
12 //    anti-DDoS throttling for various different actual downtimes is what
13 //    we expect it to be.
14 
15 #include <cmath>
16 #include <limits>
17 #include <memory>
18 #include <vector>
19 
20 #include "base/environment.h"
21 #include "base/memory/raw_ptr.h"
22 #include "base/rand_util.h"
23 #include "base/test/task_environment.h"
24 #include "base/time/time.h"
25 #include "net/base/request_priority.h"
26 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
27 #include "net/url_request/url_request.h"
28 #include "net/url_request/url_request_context.h"
29 #include "net/url_request/url_request_context_builder.h"
30 #include "net/url_request/url_request_test_util.h"
31 #include "net/url_request/url_request_throttler_manager.h"
32 #include "net/url_request/url_request_throttler_test_support.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34 
35 using base::TimeTicks;
36 
37 namespace net {
38 namespace {
39 
40 // Set this variable in your environment if you want to see verbose results
41 // of the simulation tests.
42 const char kShowSimulationVariableName[] = "SHOW_SIMULATION_RESULTS";
43 
44 // Prints output only if a given environment variable is set. We use this
45 // to not print any output for human evaluation when the test is run without
46 // supervision.
VerboseOut(const char * format,...)47 void VerboseOut(const char* format, ...) {
48   static bool have_checked_environment = false;
49   static bool should_print = false;
50   if (!have_checked_environment) {
51     have_checked_environment = true;
52     std::unique_ptr<base::Environment> env(base::Environment::Create());
53     if (env->HasVar(kShowSimulationVariableName))
54       should_print = true;
55   }
56 
57   if (should_print) {
58     va_list arglist;
59     va_start(arglist, format);
60     vprintf(format, arglist);
61     va_end(arglist);
62   }
63 }
64 
65 // A simple two-phase discrete time simulation. Actors are added in the order
66 // they should take action at every tick of the clock. Ticks of the clock
67 // are two-phase:
68 // - Phase 1 advances every actor's time to a new absolute time.
69 // - Phase 2 asks each actor to perform their action.
70 class DiscreteTimeSimulation {
71  public:
72   class Actor {
73    public:
74     virtual ~Actor() = default;
75     virtual void AdvanceTime(const TimeTicks& absolute_time) = 0;
76     virtual void PerformAction() = 0;
77   };
78 
79   DiscreteTimeSimulation() = default;
80 
81   DiscreteTimeSimulation(const DiscreteTimeSimulation&) = delete;
82   DiscreteTimeSimulation& operator=(const DiscreteTimeSimulation&) = delete;
83 
84   // Adds an |actor| to the simulation. The client of the simulation maintains
85   // ownership of |actor| and must ensure its lifetime exceeds that of the
86   // simulation. Actors should be added in the order you wish for them to
87   // act at each tick of the simulation.
AddActor(Actor * actor)88   void AddActor(Actor* actor) {
89     actors_.push_back(actor);
90   }
91 
92   // Runs the simulation for, pretending |time_between_ticks| passes from one
93   // tick to the next. The start time will be the current real time. The
94   // simulation will stop when the simulated duration is equal to or greater
95   // than |maximum_simulated_duration|.
RunSimulation(const base::TimeDelta & maximum_simulated_duration,const base::TimeDelta & time_between_ticks)96   void RunSimulation(const base::TimeDelta& maximum_simulated_duration,
97                      const base::TimeDelta& time_between_ticks) {
98     TimeTicks start_time = TimeTicks();
99     TimeTicks now = start_time;
100     while ((now - start_time) <= maximum_simulated_duration) {
101       for (auto* actor : actors_) {
102         actor->AdvanceTime(now);
103       }
104 
105       for (auto* actor : actors_) {
106         actor->PerformAction();
107       }
108 
109       now += time_between_ticks;
110     }
111   }
112 
113  private:
114   std::vector<Actor*> actors_;
115 };
116 
117 // Represents a web server in a simulation of a server under attack by
118 // a lot of clients. Must be added to the simulation's list of actors
119 // after all |Requester| objects.
120 class Server : public DiscreteTimeSimulation::Actor {
121  public:
Server(int max_queries_per_tick,double request_drop_ratio)122   Server(int max_queries_per_tick, double request_drop_ratio)
123       : max_queries_per_tick_(max_queries_per_tick),
124         request_drop_ratio_(request_drop_ratio),
125         context_(CreateTestURLRequestContextBuilder()->Build()),
126         mock_request_(context_->CreateRequest(GURL(),
127                                               DEFAULT_PRIORITY,
128                                               nullptr,
129                                               TRAFFIC_ANNOTATION_FOR_TESTS)) {}
130 
131   Server(const Server&) = delete;
132   Server& operator=(const Server&) = delete;
133 
SetDowntime(const TimeTicks & start_time,const base::TimeDelta & duration)134   void SetDowntime(const TimeTicks& start_time,
135                    const base::TimeDelta& duration) {
136     start_downtime_ = start_time;
137     end_downtime_ = start_time + duration;
138   }
139 
AdvanceTime(const TimeTicks & absolute_time)140   void AdvanceTime(const TimeTicks& absolute_time) override {
141     now_ = absolute_time;
142   }
143 
PerformAction()144   void PerformAction() override {
145     // We are inserted at the end of the actor's list, so all Requester
146     // instances have already done their bit.
147     if (num_current_tick_queries_ > max_experienced_queries_per_tick_)
148       max_experienced_queries_per_tick_ = num_current_tick_queries_;
149 
150     if (num_current_tick_queries_ > max_queries_per_tick_) {
151       // We pretend the server fails for the next several ticks after it
152       // gets overloaded.
153       num_overloaded_ticks_remaining_ = 5;
154       ++num_overloaded_ticks_;
155     } else if (num_overloaded_ticks_remaining_ > 0) {
156       --num_overloaded_ticks_remaining_;
157     }
158 
159     requests_per_tick_.push_back(num_current_tick_queries_);
160     num_current_tick_queries_ = 0;
161   }
162 
163   // This is called by Requester. It returns the response code from
164   // the server.
HandleRequest()165   int HandleRequest() {
166     ++num_current_tick_queries_;
167     if (!start_downtime_.is_null() &&
168         start_downtime_ < now_ && now_ < end_downtime_) {
169       // For the simulation measuring the increase in perceived
170       // downtime, it might be interesting to count separately the
171       // queries seen by the server (assuming a front-end reverse proxy
172       // is what actually serves up the 503s in this case) so that we could
173       // visualize the traffic spike seen by the server when it comes up,
174       // which would in many situations be ameliorated by the anti-DDoS
175       // throttling.
176       return 503;
177     }
178 
179     if ((num_overloaded_ticks_remaining_ > 0 ||
180          num_current_tick_queries_ > max_queries_per_tick_) &&
181         base::RandDouble() < request_drop_ratio_) {
182       return 503;
183     }
184 
185     return 200;
186   }
187 
num_overloaded_ticks() const188   int num_overloaded_ticks() const {
189     return num_overloaded_ticks_;
190   }
191 
max_experienced_queries_per_tick() const192   int max_experienced_queries_per_tick() const {
193     return max_experienced_queries_per_tick_;
194   }
195 
mock_request() const196   const URLRequest& mock_request() const {
197     return *mock_request_.get();
198   }
199 
VisualizeASCII(int terminal_width)200   std::string VisualizeASCII(int terminal_width) {
201     // Account for | characters we place at left of graph.
202     terminal_width -= 1;
203 
204     VerboseOut("Overloaded for %d of %d ticks.\n",
205                num_overloaded_ticks_, requests_per_tick_.size());
206     VerboseOut("Got maximum of %d requests in a tick.\n\n",
207                max_experienced_queries_per_tick_);
208 
209     VerboseOut("Traffic graph:\n\n");
210 
211     // Printing the graph like this is a bit overkill, but was very useful
212     // while developing the various simulations to see if they were testing
213     // the corner cases we want to simulate.
214 
215     // Find the smallest number of whole ticks we need to group into a
216     // column that will let all ticks fit into the column width we have.
217     int num_ticks = requests_per_tick_.size();
218     double ticks_per_column_exact =
219         static_cast<double>(num_ticks) / static_cast<double>(terminal_width);
220     int ticks_per_column = std::ceil(ticks_per_column_exact);
221     DCHECK_GE(ticks_per_column * terminal_width, num_ticks);
222 
223     // Sum up the column values.
224     int num_columns = num_ticks / ticks_per_column;
225     if (num_ticks % ticks_per_column)
226       ++num_columns;
227     DCHECK_LE(num_columns, terminal_width);
228     auto columns = std::make_unique<int[]>(num_columns);
229     for (int tx = 0; tx < num_ticks; ++tx) {
230       int cx = tx / ticks_per_column;
231       if (tx % ticks_per_column == 0)
232         columns[cx] = 0;
233       columns[cx] += requests_per_tick_[tx];
234     }
235 
236     // Find the lowest integer divisor that will let the column values
237     // be represented in a graph of maximum height 50.
238     int max_value = 0;
239     for (int cx = 0; cx < num_columns; ++cx)
240       max_value = std::max(max_value, columns[cx]);
241     const int kNumRows = 50;
242     double row_divisor_exact = max_value / static_cast<double>(kNumRows);
243     int row_divisor = std::ceil(row_divisor_exact);
244     DCHECK_GE(row_divisor * kNumRows, max_value);
245 
246     // To show the overload line, we calculate the appropriate value.
247     int overload_value = max_queries_per_tick_ * ticks_per_column;
248 
249     // When num_ticks is not a whole multiple of ticks_per_column, the last
250     // column includes fewer ticks than the others. In this case, don't
251     // print it so that we don't show an inconsistent value.
252     int num_printed_columns = num_columns;
253     if (num_ticks % ticks_per_column)
254       --num_printed_columns;
255 
256     // This is a top-to-bottom traversal of rows, left-to-right per row.
257     std::string output;
258     for (int rx = 0; rx < kNumRows; ++rx) {
259       int range_min = (kNumRows - rx) * row_divisor;
260       int range_max = range_min + row_divisor;
261       if (range_min == 0)
262         range_min = -1;  // Make 0 values fit in the bottom range.
263       output.append("|");
264       for (int cx = 0; cx < num_printed_columns; ++cx) {
265         char block = ' ';
266         // Show the overload line.
267         if (range_min < overload_value && overload_value <= range_max)
268           block = '-';
269 
270         // Preferentially, show the graph line.
271         if (range_min < columns[cx] && columns[cx] <= range_max)
272           block = '#';
273 
274         output.append(1, block);
275       }
276       output.append("\n");
277     }
278     output.append("|");
279     output.append(num_printed_columns, '=');
280 
281     return output;
282   }
283 
context() const284   const URLRequestContext& context() const { return *context_; }
285 
286  private:
287   TimeTicks now_;
288   TimeTicks start_downtime_;  // Can be 0 to say "no downtime".
289   TimeTicks end_downtime_;
290   const int max_queries_per_tick_;
291   const double request_drop_ratio_;  // Ratio of requests to 503 when failing.
292   int num_overloaded_ticks_remaining_ = 0;
293   int num_current_tick_queries_ = 0;
294   int num_overloaded_ticks_ = 0;
295   int max_experienced_queries_per_tick_ = 0;
296   std::vector<int> requests_per_tick_;
297 
298   std::unique_ptr<URLRequestContext> context_;
299   std::unique_ptr<URLRequest> mock_request_;
300 };
301 
302 // Mock throttler entry used by Requester class.
303 class MockURLRequestThrottlerEntry : public URLRequestThrottlerEntry {
304  public:
MockURLRequestThrottlerEntry(URLRequestThrottlerManager * manager)305   explicit MockURLRequestThrottlerEntry(URLRequestThrottlerManager* manager)
306       : URLRequestThrottlerEntry(manager, std::string()),
307         backoff_entry_(&backoff_policy_, &fake_clock_) {}
308 
GetBackoffEntry() const309   const BackoffEntry* GetBackoffEntry() const override {
310     return &backoff_entry_;
311   }
312 
GetBackoffEntry()313   BackoffEntry* GetBackoffEntry() override { return &backoff_entry_; }
314 
ImplGetTimeNow() const315   TimeTicks ImplGetTimeNow() const override { return fake_clock_.NowTicks(); }
316 
SetFakeNow(const TimeTicks & fake_time)317   void SetFakeNow(const TimeTicks& fake_time) {
318     fake_clock_.set_now(fake_time);
319   }
320 
321  protected:
322   ~MockURLRequestThrottlerEntry() override = default;
323 
324  private:
325   mutable TestTickClock fake_clock_;
326   BackoffEntry backoff_entry_;
327 };
328 
329 // Registry of results for a class of |Requester| objects (e.g. attackers vs.
330 // regular clients).
331 class RequesterResults {
332  public:
333   RequesterResults() = default;
334 
AddSuccess()335   void AddSuccess() {
336     ++num_attempts_;
337     ++num_successful_;
338   }
339 
AddFailure()340   void AddFailure() {
341     ++num_attempts_;
342     ++num_failed_;
343   }
344 
AddBlocked()345   void AddBlocked() {
346     ++num_attempts_;
347     ++num_blocked_;
348   }
349 
num_attempts() const350   int num_attempts() const { return num_attempts_; }
num_successful() const351   int num_successful() const { return num_successful_; }
num_failed() const352   int num_failed() const { return num_failed_; }
num_blocked() const353   int num_blocked() const { return num_blocked_; }
354 
GetBlockedRatio()355   double GetBlockedRatio() {
356     DCHECK(num_attempts_);
357     return static_cast<double>(num_blocked_) /
358         static_cast<double>(num_attempts_);
359   }
360 
GetSuccessRatio()361   double GetSuccessRatio() {
362     DCHECK(num_attempts_);
363     return static_cast<double>(num_successful_) /
364         static_cast<double>(num_attempts_);
365   }
366 
PrintResults(const char * class_description)367   void PrintResults(const char* class_description) {
368     if (num_attempts_ == 0) {
369       VerboseOut("No data for %s\n", class_description);
370       return;
371     }
372 
373     VerboseOut("Requester results for %s\n", class_description);
374     VerboseOut("  %d attempts\n", num_attempts_);
375     VerboseOut("  %d successes\n", num_successful_);
376     VerboseOut("  %d 5xx responses\n", num_failed_);
377     VerboseOut("  %d requests blocked\n", num_blocked_);
378     VerboseOut("  %.2f success ratio\n", GetSuccessRatio());
379     VerboseOut("  %.2f blocked ratio\n", GetBlockedRatio());
380     VerboseOut("\n");
381   }
382 
383  private:
384   int num_attempts_ = 0;
385   int num_successful_ = 0;
386   int num_failed_ = 0;
387   int num_blocked_ = 0;
388 };
389 
390 // Represents an Requester in a simulated DDoS situation, that periodically
391 // requests a specific resource.
392 class Requester : public DiscreteTimeSimulation::Actor {
393  public:
Requester(MockURLRequestThrottlerEntry * throttler_entry,const base::TimeDelta & time_between_requests,Server * server,RequesterResults * results)394   Requester(MockURLRequestThrottlerEntry* throttler_entry,
395             const base::TimeDelta& time_between_requests,
396             Server* server,
397             RequesterResults* results)
398       : throttler_entry_(throttler_entry),
399         time_between_requests_(time_between_requests),
400         server_(server),
401         results_(results) {
402     DCHECK(server_);
403   }
404 
405   Requester(const Requester&) = delete;
406   Requester& operator=(const Requester&) = delete;
407 
AdvanceTime(const TimeTicks & absolute_time)408   void AdvanceTime(const TimeTicks& absolute_time) override {
409     if (time_of_last_success_.is_null())
410       time_of_last_success_ = absolute_time;
411 
412     throttler_entry_->SetFakeNow(absolute_time);
413   }
414 
PerformAction()415   void PerformAction() override {
416     const auto current_jitter =
417         request_jitter_.is_zero()
418             ? base::TimeDelta()
419             : base::RandTimeDelta(-request_jitter_, request_jitter_);
420     const base::TimeDelta effective_delay =
421         time_between_requests_ + current_jitter;
422 
423     if (throttler_entry_->ImplGetTimeNow() - time_of_last_attempt_ >
424         effective_delay) {
425       if (!throttler_entry_->ShouldRejectRequest(server_->mock_request())) {
426         int status_code = server_->HandleRequest();
427         throttler_entry_->UpdateWithResponse(status_code);
428 
429         if (status_code == 200) {
430           if (results_)
431             results_->AddSuccess();
432 
433           if (last_attempt_was_failure_) {
434             last_downtime_duration_ =
435                 throttler_entry_->ImplGetTimeNow() - time_of_last_success_;
436           }
437 
438           time_of_last_success_ = throttler_entry_->ImplGetTimeNow();
439         } else if (results_) {
440           results_->AddFailure();
441         }
442         last_attempt_was_failure_ = status_code != 200;
443       } else {
444         if (results_)
445           results_->AddBlocked();
446         last_attempt_was_failure_ = true;
447       }
448 
449       time_of_last_attempt_ = throttler_entry_->ImplGetTimeNow();
450     }
451   }
452 
453   // Adds a delay until the first request, equal to a uniformly distributed
454   // value between now and now + max_delay.
SetStartupJitter(const base::TimeDelta & max_delay)455   void SetStartupJitter(const base::TimeDelta& max_delay) {
456     const auto delay = max_delay.is_zero() ? base::TimeDelta()
457                                            : base::RandTimeDeltaUpTo(max_delay);
458     time_of_last_attempt_ = TimeTicks() + delay - time_between_requests_;
459   }
460 
SetRequestJitter(const base::TimeDelta & request_jitter)461   void SetRequestJitter(const base::TimeDelta& request_jitter) {
462     request_jitter_ = request_jitter;
463   }
464 
last_downtime_duration() const465   base::TimeDelta last_downtime_duration() const {
466     return last_downtime_duration_;
467   }
468 
469  private:
470   scoped_refptr<MockURLRequestThrottlerEntry> throttler_entry_;
471   const base::TimeDelta time_between_requests_;
472   base::TimeDelta request_jitter_;
473   TimeTicks time_of_last_attempt_;
474   TimeTicks time_of_last_success_;
475   bool last_attempt_was_failure_ = false;
476   base::TimeDelta last_downtime_duration_;
477   const raw_ptr<Server> server_;
478   const raw_ptr<RequesterResults> results_;  // May be NULL.
479 };
480 
SimulateAttack(Server * server,RequesterResults * attacker_results,RequesterResults * client_results,bool enable_throttling)481 void SimulateAttack(Server* server,
482                     RequesterResults* attacker_results,
483                     RequesterResults* client_results,
484                     bool enable_throttling) {
485   const size_t kNumAttackers = 50;
486   const size_t kNumClients = 50;
487   DiscreteTimeSimulation simulation;
488   URLRequestThrottlerManager manager;
489   std::vector<std::unique_ptr<Requester>> requesters;
490   for (size_t i = 0; i < kNumAttackers; ++i) {
491     // Use a tiny time_between_requests so the attackers will ping the
492     // server at every tick of the simulation.
493     auto throttler_entry =
494         base::MakeRefCounted<MockURLRequestThrottlerEntry>(&manager);
495     if (!enable_throttling)
496       throttler_entry->DisableBackoffThrottling();
497 
498     auto attacker = std::make_unique<Requester>(
499         throttler_entry.get(), base::Milliseconds(1), server, attacker_results);
500     attacker->SetStartupJitter(base::Seconds(120));
501     simulation.AddActor(attacker.get());
502     requesters.push_back(std::move(attacker));
503   }
504   for (size_t i = 0; i < kNumClients; ++i) {
505     // Normal clients only make requests every 2 minutes, plus/minus 1 minute.
506     auto throttler_entry =
507         base::MakeRefCounted<MockURLRequestThrottlerEntry>(&manager);
508     if (!enable_throttling)
509       throttler_entry->DisableBackoffThrottling();
510 
511     auto client = std::make_unique<Requester>(
512         throttler_entry.get(), base::Minutes(2), server, client_results);
513     client->SetStartupJitter(base::Seconds(120));
514     client->SetRequestJitter(base::Minutes(1));
515     simulation.AddActor(client.get());
516     requesters.push_back(std::move(client));
517   }
518   simulation.AddActor(server);
519 
520   simulation.RunSimulation(base::Minutes(6), base::Seconds(1));
521 }
522 
TEST(URLRequestThrottlerSimulation,HelpsInAttack)523 TEST(URLRequestThrottlerSimulation, HelpsInAttack) {
524   base::test::TaskEnvironment task_environment;
525 
526   Server unprotected_server(30, 1.0);
527   RequesterResults unprotected_attacker_results;
528   RequesterResults unprotected_client_results;
529   Server protected_server(30, 1.0);
530   RequesterResults protected_attacker_results;
531   RequesterResults protected_client_results;
532   SimulateAttack(&unprotected_server,
533                  &unprotected_attacker_results,
534                  &unprotected_client_results,
535                  false);
536   SimulateAttack(&protected_server,
537                  &protected_attacker_results,
538                  &protected_client_results,
539                  true);
540 
541   // These assert that the DDoS protection actually benefits the
542   // server. Manual inspection of the traffic graphs will show this
543   // even more clearly.
544   EXPECT_GT(unprotected_server.num_overloaded_ticks(),
545             protected_server.num_overloaded_ticks());
546   EXPECT_GT(unprotected_server.max_experienced_queries_per_tick(),
547             protected_server.max_experienced_queries_per_tick());
548 
549   // These assert that the DDoS protection actually benefits non-malicious
550   // (and non-degenerate/accidentally DDoSing) users.
551   EXPECT_LT(protected_client_results.GetBlockedRatio(),
552             protected_attacker_results.GetBlockedRatio());
553   EXPECT_GT(protected_client_results.GetSuccessRatio(),
554             unprotected_client_results.GetSuccessRatio());
555 
556   // The rest is just for optional manual evaluation of the results;
557   // in particular the traffic pattern is interesting.
558 
559   VerboseOut("\nUnprotected server's results:\n\n");
560   VerboseOut(unprotected_server.VisualizeASCII(132).c_str());
561   VerboseOut("\n\n");
562   VerboseOut("Protected server's results:\n\n");
563   VerboseOut(protected_server.VisualizeASCII(132).c_str());
564   VerboseOut("\n\n");
565 
566   unprotected_attacker_results.PrintResults(
567       "attackers attacking unprotected server.");
568   unprotected_client_results.PrintResults(
569       "normal clients making requests to unprotected server.");
570   protected_attacker_results.PrintResults(
571       "attackers attacking protected server.");
572   protected_client_results.PrintResults(
573       "normal clients making requests to protected server.");
574 }
575 
576 // Returns the downtime perceived by the client, as a ratio of the
577 // actual downtime.
SimulateDowntime(const base::TimeDelta & duration,const base::TimeDelta & average_client_interval,bool enable_throttling)578 double SimulateDowntime(const base::TimeDelta& duration,
579                         const base::TimeDelta& average_client_interval,
580                         bool enable_throttling) {
581   base::TimeDelta time_between_ticks = duration / 200;
582   TimeTicks start_downtime = TimeTicks() + (duration / 2);
583 
584   // A server that never rejects requests, but will go down for maintenance.
585   Server server(std::numeric_limits<int>::max(), 1.0);
586   server.SetDowntime(start_downtime, duration);
587 
588   URLRequestThrottlerManager manager;
589   auto throttler_entry =
590       base::MakeRefCounted<MockURLRequestThrottlerEntry>(&manager);
591   if (!enable_throttling)
592     throttler_entry->DisableBackoffThrottling();
593 
594   Requester requester(throttler_entry.get(), average_client_interval, &server,
595                       nullptr);
596   requester.SetStartupJitter(duration / 3);
597   requester.SetRequestJitter(average_client_interval);
598 
599   DiscreteTimeSimulation simulation;
600   simulation.AddActor(&requester);
601   simulation.AddActor(&server);
602 
603   simulation.RunSimulation(duration * 2, time_between_ticks);
604 
605   return static_cast<double>(
606       requester.last_downtime_duration().InMilliseconds()) /
607       static_cast<double>(duration.InMilliseconds());
608 }
609 
TEST(URLRequestThrottlerSimulation,PerceivedDowntimeRatio)610 TEST(URLRequestThrottlerSimulation, PerceivedDowntimeRatio) {
611   base::test::TaskEnvironment task_environment;
612 
613   struct Stats {
614     // Expected interval that we expect the ratio of downtime when anti-DDoS
615     // is enabled and downtime when anti-DDoS is not enabled to fall within.
616     //
617     // The expected interval depends on two things:  The exponential back-off
618     // policy encoded in URLRequestThrottlerEntry, and the test or set of
619     // tests that the Stats object is tracking (e.g. a test where the client
620     // retries very rapidly on a very long downtime will tend to increase the
621     // number).
622     //
623     // To determine an appropriate new interval when parameters have changed,
624     // run the test a few times (you may have to Ctrl-C out of it after a few
625     // seconds) and choose an interval that the test converges quickly and
626     // reliably to.  Then set the new interval, and run the test e.g. 20 times
627     // in succession to make sure it never takes an obscenely long time to
628     // converge to this interval.
629     double expected_min_increase;
630     double expected_max_increase;
631 
632     size_t num_runs;
633     double total_ratio_unprotected;
634     double total_ratio_protected;
635 
636     bool DidConverge(double* increase_ratio_out) {
637       double unprotected_ratio = total_ratio_unprotected / num_runs;
638       double protected_ratio = total_ratio_protected / num_runs;
639       double increase_ratio = protected_ratio / unprotected_ratio;
640       if (increase_ratio_out)
641         *increase_ratio_out = increase_ratio;
642       return expected_min_increase <= increase_ratio &&
643           increase_ratio <= expected_max_increase;
644     }
645 
646     void ReportTrialResult(double increase_ratio) {
647       VerboseOut(
648           "  Perceived downtime with throttling is %.4f times without.\n",
649           increase_ratio);
650       VerboseOut("  Test result after %d trials.\n", num_runs);
651     }
652   };
653 
654   Stats global_stats = { 1.08, 1.15 };
655 
656   struct Trial {
657     base::TimeDelta duration;
658     base::TimeDelta average_client_interval;
659     Stats stats;
660 
661     void PrintTrialDescription() {
662       const double duration_minutes = duration / base::Minutes(1);
663       const double interval_minutes =
664           average_client_interval / base::Minutes(1);
665       VerboseOut("Trial with %.2f min downtime, avg. interval %.2f min.\n",
666                  duration_minutes, interval_minutes);
667     }
668   };
669 
670   // We don't set or check expected ratio intervals on individual
671   // experiments as this might make the test too fragile, but we
672   // print them out at the end for manual evaluation (we want to be
673   // able to make claims about the expected ratios depending on the
674   // type of behavior of the client and the downtime, e.g. the difference
675   // in behavior between a client making requests every few minutes vs.
676   // one that makes a request every 15 seconds).
677   Trial trials[] = {
678       {base::Seconds(10), base::Seconds(3)},
679       {base::Seconds(30), base::Seconds(7)},
680       {base::Minutes(5), base::Seconds(30)},
681       {base::Minutes(10), base::Seconds(20)},
682       {base::Minutes(20), base::Seconds(15)},
683       {base::Minutes(20), base::Seconds(50)},
684       {base::Minutes(30), base::Minutes(2)},
685       {base::Minutes(30), base::Minutes(5)},
686       {base::Minutes(40), base::Minutes(7)},
687       {base::Minutes(40), base::Minutes(2)},
688       {base::Minutes(40), base::Seconds(15)},
689       {base::Minutes(60), base::Minutes(7)},
690       {base::Minutes(60), base::Minutes(2)},
691       {base::Minutes(60), base::Seconds(15)},
692       {base::Minutes(80), base::Minutes(20)},
693       {base::Minutes(80), base::Minutes(3)},
694       {base::Minutes(80), base::Seconds(15)},
695 
696       // Most brutal?
697       {base::Minutes(45), base::Milliseconds(500)},
698   };
699 
700   // If things don't converge by the time we've done 100K trials, then
701   // clearly one or more of the expected intervals are wrong.
702   while (global_stats.num_runs < 100000) {
703     for (auto& trial : trials) {
704       ++global_stats.num_runs;
705       ++trial.stats.num_runs;
706       double ratio_unprotected = SimulateDowntime(
707           trial.duration, trial.average_client_interval, false);
708       double ratio_protected =
709           SimulateDowntime(trial.duration, trial.average_client_interval, true);
710       global_stats.total_ratio_unprotected += ratio_unprotected;
711       global_stats.total_ratio_protected += ratio_protected;
712       trial.stats.total_ratio_unprotected += ratio_unprotected;
713       trial.stats.total_ratio_protected += ratio_protected;
714     }
715 
716     double increase_ratio;
717     if (global_stats.DidConverge(&increase_ratio))
718       break;
719 
720     if (global_stats.num_runs > 200) {
721       VerboseOut("Test has not yet converged on expected interval.\n");
722       global_stats.ReportTrialResult(increase_ratio);
723     }
724   }
725 
726   double average_increase_ratio;
727   EXPECT_TRUE(global_stats.DidConverge(&average_increase_ratio));
728 
729   // Print individual trial results for optional manual evaluation.
730   double max_increase_ratio = 0.0;
731   for (auto& trial : trials) {
732     double increase_ratio;
733     trial.stats.DidConverge(&increase_ratio);
734     max_increase_ratio = std::max(max_increase_ratio, increase_ratio);
735     trial.PrintTrialDescription();
736     trial.stats.ReportTrialResult(increase_ratio);
737   }
738 
739   VerboseOut("Average increase ratio was %.4f\n", average_increase_ratio);
740   VerboseOut("Maximum increase ratio was %.4f\n", max_increase_ratio);
741 }
742 
743 }  // namespace
744 }  // namespace net
745