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