• 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 base::TimeDelta current_jitter = request_jitter_ * base::RandDouble();
417     const base::TimeDelta effective_delay =
418         time_between_requests_ +
419         (base::RandInt(0, 1) ? -current_jitter : current_jitter);
420 
421     if (throttler_entry_->ImplGetTimeNow() - time_of_last_attempt_ >
422         effective_delay) {
423       if (!throttler_entry_->ShouldRejectRequest(server_->mock_request())) {
424         int status_code = server_->HandleRequest();
425         throttler_entry_->UpdateWithResponse(status_code);
426 
427         if (status_code == 200) {
428           if (results_)
429             results_->AddSuccess();
430 
431           if (last_attempt_was_failure_) {
432             last_downtime_duration_ =
433                 throttler_entry_->ImplGetTimeNow() - time_of_last_success_;
434           }
435 
436           time_of_last_success_ = throttler_entry_->ImplGetTimeNow();
437         } else if (results_) {
438           results_->AddFailure();
439         }
440         last_attempt_was_failure_ = status_code != 200;
441       } else {
442         if (results_)
443           results_->AddBlocked();
444         last_attempt_was_failure_ = true;
445       }
446 
447       time_of_last_attempt_ = throttler_entry_->ImplGetTimeNow();
448     }
449   }
450 
451   // Adds a delay until the first request, equal to a uniformly distributed
452   // value between now and now + max_delay.
SetStartupJitter(const base::TimeDelta & max_delay)453   void SetStartupJitter(const base::TimeDelta& max_delay) {
454     int delay_ms = base::RandInt(0, max_delay.InMilliseconds());
455     time_of_last_attempt_ =
456         TimeTicks() + base::Milliseconds(delay_ms) - time_between_requests_;
457   }
458 
SetRequestJitter(const base::TimeDelta & request_jitter)459   void SetRequestJitter(const base::TimeDelta& request_jitter) {
460     request_jitter_ = request_jitter;
461   }
462 
last_downtime_duration() const463   base::TimeDelta last_downtime_duration() const {
464     return last_downtime_duration_;
465   }
466 
467  private:
468   scoped_refptr<MockURLRequestThrottlerEntry> throttler_entry_;
469   const base::TimeDelta time_between_requests_;
470   base::TimeDelta request_jitter_;
471   TimeTicks time_of_last_attempt_;
472   TimeTicks time_of_last_success_;
473   bool last_attempt_was_failure_ = false;
474   base::TimeDelta last_downtime_duration_;
475   const raw_ptr<Server> server_;
476   const raw_ptr<RequesterResults> results_;  // May be NULL.
477 };
478 
SimulateAttack(Server * server,RequesterResults * attacker_results,RequesterResults * client_results,bool enable_throttling)479 void SimulateAttack(Server* server,
480                     RequesterResults* attacker_results,
481                     RequesterResults* client_results,
482                     bool enable_throttling) {
483   const size_t kNumAttackers = 50;
484   const size_t kNumClients = 50;
485   DiscreteTimeSimulation simulation;
486   URLRequestThrottlerManager manager;
487   std::vector<std::unique_ptr<Requester>> requesters;
488   for (size_t i = 0; i < kNumAttackers; ++i) {
489     // Use a tiny time_between_requests so the attackers will ping the
490     // server at every tick of the simulation.
491     auto throttler_entry =
492         base::MakeRefCounted<MockURLRequestThrottlerEntry>(&manager);
493     if (!enable_throttling)
494       throttler_entry->DisableBackoffThrottling();
495 
496     auto attacker = std::make_unique<Requester>(
497         throttler_entry.get(), base::Milliseconds(1), server, attacker_results);
498     attacker->SetStartupJitter(base::Seconds(120));
499     simulation.AddActor(attacker.get());
500     requesters.push_back(std::move(attacker));
501   }
502   for (size_t i = 0; i < kNumClients; ++i) {
503     // Normal clients only make requests every 2 minutes, plus/minus 1 minute.
504     auto throttler_entry =
505         base::MakeRefCounted<MockURLRequestThrottlerEntry>(&manager);
506     if (!enable_throttling)
507       throttler_entry->DisableBackoffThrottling();
508 
509     auto client = std::make_unique<Requester>(
510         throttler_entry.get(), base::Minutes(2), server, client_results);
511     client->SetStartupJitter(base::Seconds(120));
512     client->SetRequestJitter(base::Minutes(1));
513     simulation.AddActor(client.get());
514     requesters.push_back(std::move(client));
515   }
516   simulation.AddActor(server);
517 
518   simulation.RunSimulation(base::Minutes(6), base::Seconds(1));
519 }
520 
TEST(URLRequestThrottlerSimulation,HelpsInAttack)521 TEST(URLRequestThrottlerSimulation, HelpsInAttack) {
522   base::test::TaskEnvironment task_environment;
523 
524   Server unprotected_server(30, 1.0);
525   RequesterResults unprotected_attacker_results;
526   RequesterResults unprotected_client_results;
527   Server protected_server(30, 1.0);
528   RequesterResults protected_attacker_results;
529   RequesterResults protected_client_results;
530   SimulateAttack(&unprotected_server,
531                  &unprotected_attacker_results,
532                  &unprotected_client_results,
533                  false);
534   SimulateAttack(&protected_server,
535                  &protected_attacker_results,
536                  &protected_client_results,
537                  true);
538 
539   // These assert that the DDoS protection actually benefits the
540   // server. Manual inspection of the traffic graphs will show this
541   // even more clearly.
542   EXPECT_GT(unprotected_server.num_overloaded_ticks(),
543             protected_server.num_overloaded_ticks());
544   EXPECT_GT(unprotected_server.max_experienced_queries_per_tick(),
545             protected_server.max_experienced_queries_per_tick());
546 
547   // These assert that the DDoS protection actually benefits non-malicious
548   // (and non-degenerate/accidentally DDoSing) users.
549   EXPECT_LT(protected_client_results.GetBlockedRatio(),
550             protected_attacker_results.GetBlockedRatio());
551   EXPECT_GT(protected_client_results.GetSuccessRatio(),
552             unprotected_client_results.GetSuccessRatio());
553 
554   // The rest is just for optional manual evaluation of the results;
555   // in particular the traffic pattern is interesting.
556 
557   VerboseOut("\nUnprotected server's results:\n\n");
558   VerboseOut(unprotected_server.VisualizeASCII(132).c_str());
559   VerboseOut("\n\n");
560   VerboseOut("Protected server's results:\n\n");
561   VerboseOut(protected_server.VisualizeASCII(132).c_str());
562   VerboseOut("\n\n");
563 
564   unprotected_attacker_results.PrintResults(
565       "attackers attacking unprotected server.");
566   unprotected_client_results.PrintResults(
567       "normal clients making requests to unprotected server.");
568   protected_attacker_results.PrintResults(
569       "attackers attacking protected server.");
570   protected_client_results.PrintResults(
571       "normal clients making requests to protected server.");
572 }
573 
574 // Returns the downtime perceived by the client, as a ratio of the
575 // actual downtime.
SimulateDowntime(const base::TimeDelta & duration,const base::TimeDelta & average_client_interval,bool enable_throttling)576 double SimulateDowntime(const base::TimeDelta& duration,
577                         const base::TimeDelta& average_client_interval,
578                         bool enable_throttling) {
579   base::TimeDelta time_between_ticks = duration / 200;
580   TimeTicks start_downtime = TimeTicks() + (duration / 2);
581 
582   // A server that never rejects requests, but will go down for maintenance.
583   Server server(std::numeric_limits<int>::max(), 1.0);
584   server.SetDowntime(start_downtime, duration);
585 
586   URLRequestThrottlerManager manager;
587   auto throttler_entry =
588       base::MakeRefCounted<MockURLRequestThrottlerEntry>(&manager);
589   if (!enable_throttling)
590     throttler_entry->DisableBackoffThrottling();
591 
592   Requester requester(throttler_entry.get(), average_client_interval, &server,
593                       nullptr);
594   requester.SetStartupJitter(duration / 3);
595   requester.SetRequestJitter(average_client_interval);
596 
597   DiscreteTimeSimulation simulation;
598   simulation.AddActor(&requester);
599   simulation.AddActor(&server);
600 
601   simulation.RunSimulation(duration * 2, time_between_ticks);
602 
603   return static_cast<double>(
604       requester.last_downtime_duration().InMilliseconds()) /
605       static_cast<double>(duration.InMilliseconds());
606 }
607 
TEST(URLRequestThrottlerSimulation,PerceivedDowntimeRatio)608 TEST(URLRequestThrottlerSimulation, PerceivedDowntimeRatio) {
609   base::test::TaskEnvironment task_environment;
610 
611   struct Stats {
612     // Expected interval that we expect the ratio of downtime when anti-DDoS
613     // is enabled and downtime when anti-DDoS is not enabled to fall within.
614     //
615     // The expected interval depends on two things:  The exponential back-off
616     // policy encoded in URLRequestThrottlerEntry, and the test or set of
617     // tests that the Stats object is tracking (e.g. a test where the client
618     // retries very rapidly on a very long downtime will tend to increase the
619     // number).
620     //
621     // To determine an appropriate new interval when parameters have changed,
622     // run the test a few times (you may have to Ctrl-C out of it after a few
623     // seconds) and choose an interval that the test converges quickly and
624     // reliably to.  Then set the new interval, and run the test e.g. 20 times
625     // in succession to make sure it never takes an obscenely long time to
626     // converge to this interval.
627     double expected_min_increase;
628     double expected_max_increase;
629 
630     size_t num_runs;
631     double total_ratio_unprotected;
632     double total_ratio_protected;
633 
634     bool DidConverge(double* increase_ratio_out) {
635       double unprotected_ratio = total_ratio_unprotected / num_runs;
636       double protected_ratio = total_ratio_protected / num_runs;
637       double increase_ratio = protected_ratio / unprotected_ratio;
638       if (increase_ratio_out)
639         *increase_ratio_out = increase_ratio;
640       return expected_min_increase <= increase_ratio &&
641           increase_ratio <= expected_max_increase;
642     }
643 
644     void ReportTrialResult(double increase_ratio) {
645       VerboseOut(
646           "  Perceived downtime with throttling is %.4f times without.\n",
647           increase_ratio);
648       VerboseOut("  Test result after %d trials.\n", num_runs);
649     }
650   };
651 
652   Stats global_stats = { 1.08, 1.15 };
653 
654   struct Trial {
655     base::TimeDelta duration;
656     base::TimeDelta average_client_interval;
657     Stats stats;
658 
659     void PrintTrialDescription() {
660       const double duration_minutes = duration / base::Minutes(1);
661       const double interval_minutes =
662           average_client_interval / base::Minutes(1);
663       VerboseOut("Trial with %.2f min downtime, avg. interval %.2f min.\n",
664                  duration_minutes, interval_minutes);
665     }
666   };
667 
668   // We don't set or check expected ratio intervals on individual
669   // experiments as this might make the test too fragile, but we
670   // print them out at the end for manual evaluation (we want to be
671   // able to make claims about the expected ratios depending on the
672   // type of behavior of the client and the downtime, e.g. the difference
673   // in behavior between a client making requests every few minutes vs.
674   // one that makes a request every 15 seconds).
675   Trial trials[] = {
676       {base::Seconds(10), base::Seconds(3)},
677       {base::Seconds(30), base::Seconds(7)},
678       {base::Minutes(5), base::Seconds(30)},
679       {base::Minutes(10), base::Seconds(20)},
680       {base::Minutes(20), base::Seconds(15)},
681       {base::Minutes(20), base::Seconds(50)},
682       {base::Minutes(30), base::Minutes(2)},
683       {base::Minutes(30), base::Minutes(5)},
684       {base::Minutes(40), base::Minutes(7)},
685       {base::Minutes(40), base::Minutes(2)},
686       {base::Minutes(40), base::Seconds(15)},
687       {base::Minutes(60), base::Minutes(7)},
688       {base::Minutes(60), base::Minutes(2)},
689       {base::Minutes(60), base::Seconds(15)},
690       {base::Minutes(80), base::Minutes(20)},
691       {base::Minutes(80), base::Minutes(3)},
692       {base::Minutes(80), base::Seconds(15)},
693 
694       // Most brutal?
695       {base::Minutes(45), base::Milliseconds(500)},
696   };
697 
698   // If things don't converge by the time we've done 100K trials, then
699   // clearly one or more of the expected intervals are wrong.
700   while (global_stats.num_runs < 100000) {
701     for (auto& trial : trials) {
702       ++global_stats.num_runs;
703       ++trial.stats.num_runs;
704       double ratio_unprotected = SimulateDowntime(
705           trial.duration, trial.average_client_interval, false);
706       double ratio_protected =
707           SimulateDowntime(trial.duration, trial.average_client_interval, true);
708       global_stats.total_ratio_unprotected += ratio_unprotected;
709       global_stats.total_ratio_protected += ratio_protected;
710       trial.stats.total_ratio_unprotected += ratio_unprotected;
711       trial.stats.total_ratio_protected += ratio_protected;
712     }
713 
714     double increase_ratio;
715     if (global_stats.DidConverge(&increase_ratio))
716       break;
717 
718     if (global_stats.num_runs > 200) {
719       VerboseOut("Test has not yet converged on expected interval.\n");
720       global_stats.ReportTrialResult(increase_ratio);
721     }
722   }
723 
724   double average_increase_ratio;
725   EXPECT_TRUE(global_stats.DidConverge(&average_increase_ratio));
726 
727   // Print individual trial results for optional manual evaluation.
728   double max_increase_ratio = 0.0;
729   for (auto& trial : trials) {
730     double increase_ratio;
731     trial.stats.DidConverge(&increase_ratio);
732     max_increase_ratio = std::max(max_increase_ratio, increase_ratio);
733     trial.PrintTrialDescription();
734     trial.stats.ReportTrialResult(increase_ratio);
735   }
736 
737   VerboseOut("Average increase ratio was %.4f\n", average_increase_ratio);
738   VerboseOut("Maximum increase ratio was %.4f\n", max_increase_ratio);
739 }
740 
741 }  // namespace
742 }  // namespace net
743