tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test-ratelim.c (19267B)


      1 /*
      2 * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson
      3 *
      4 * Redistribution and use in source and binary forms, with or without
      5 * modification, are permitted provided that the following conditions
      6 * are met:
      7 * 1. Redistributions of source code must retain the above copyright
      8 *    notice, this list of conditions and the following disclaimer.
      9 * 2. Redistributions in binary form must reproduce the above copyright
     10 *    notice, this list of conditions and the following disclaimer in the
     11 *    documentation and/or other materials provided with the distribution.
     12 * 3. The name of the author may not be used to endorse or promote products
     13 *    derived from this software without specific prior written permission.
     14 *
     15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     25 */
     26 #include "../util-internal.h"
     27 
     28 #include <stdio.h>
     29 #include <stdlib.h>
     30 #include <string.h>
     31 #include <assert.h>
     32 #include <math.h>
     33 
     34 #ifdef _WIN32
     35 #include <winsock2.h>
     36 #include <ws2tcpip.h>
     37 #else
     38 #include <sys/socket.h>
     39 #include <netinet/in.h>
     40 # ifdef _XOPEN_SOURCE_EXTENDED
     41 #  include <arpa/inet.h>
     42 # endif
     43 #endif
     44 #include <signal.h>
     45 
     46 #include "event2/bufferevent.h"
     47 #include "event2/buffer.h"
     48 #include "event2/event.h"
     49 #include "event2/util.h"
     50 #include "event2/listener.h"
     51 #include "event2/thread.h"
     52 
     53 #ifndef MIN
     54 #define MIN(a,b) (((a)<(b))?(a):(b))
     55 #endif
     56 
     57 static struct evutil_weakrand_state weakrand_state;
     58 
     59 static int cfg_verbose = 0;
     60 static int cfg_help = 0;
     61 
     62 static int cfg_n_connections = 30;
     63 static int cfg_duration = 5;
     64 static int cfg_connlimit = 0;
     65 static int cfg_grouplimit = 0;
     66 static int cfg_tick_msec = 1000;
     67 static int cfg_min_share = -1;
     68 static int cfg_group_drain = 0;
     69 
     70 static int cfg_connlimit_tolerance = -1;
     71 static int cfg_grouplimit_tolerance = -1;
     72 static int cfg_stddev_tolerance = -1;
     73 
     74 #ifdef _WIN32
     75 static int cfg_enable_iocp = 0;
     76 #endif
     77 
     78 static struct timeval cfg_tick = { 0, 500*1000 };
     79 
     80 static struct ev_token_bucket_cfg *conn_bucket_cfg = NULL;
     81 static struct ev_token_bucket_cfg *group_bucket_cfg = NULL;
     82 struct bufferevent_rate_limit_group *ratelim_group = NULL;
     83 static double seconds_per_tick = 0.0;
     84 
     85 struct client_state {
     86 size_t queued;
     87 ev_uint64_t received;
     88 
     89 };
     90 static const struct timeval *ms100_common=NULL;
     91 
     92 /* Timers bias for slow CPUs, affects:
     93 * - cfg_connlimit_tolerance  (--check-connlimit)
     94 * - cfg_grouplimit_tolerance (--check-grouplimit)
     95 * - cfg_stddev_tolerance     (--check-stddev)
     96 */
     97 static int timer_bias_events;
     98 static struct timeval timer_bias_start;
     99 double timer_bias_spend;
    100 /* Real cost is less (approximately ~5 usec),
    101 * this macros adjusted to make the bias less */
    102 #define TIMER_MAX_COST_USEC 10
    103 
    104 /* info from check_bucket_levels_cb */
    105 static int total_n_bev_checks = 0;
    106 static ev_int64_t total_rbucket_level=0;
    107 static ev_int64_t total_wbucket_level=0;
    108 static ev_int64_t total_max_to_read=0;
    109 static ev_int64_t total_max_to_write=0;
    110 static ev_int64_t max_bucket_level=EV_INT64_MIN;
    111 static ev_int64_t min_bucket_level=EV_INT64_MAX;
    112 
    113 /* from check_group_bucket_levels_cb */
    114 static int total_n_group_bev_checks = 0;
    115 static ev_int64_t total_group_rbucket_level = 0;
    116 static ev_int64_t total_group_wbucket_level = 0;
    117 
    118 static int n_echo_conns_open = 0;
    119 
    120 /* Info on the open connections */
    121 struct bufferevent **bevs;
    122 struct client_state *states;
    123 struct bufferevent_rate_limit_group *group = NULL;
    124 
    125 static void check_bucket_levels_cb(evutil_socket_t fd, short events, void *arg);
    126 
    127 static void
    128 loud_writecb(struct bufferevent *bev, void *ctx)
    129 {
    130 struct client_state *cs = ctx;
    131 struct evbuffer *output = bufferevent_get_output(bev);
    132 char buf[1024];
    133 int r = evutil_weakrand_(&weakrand_state);
    134 memset(buf, r, sizeof(buf));
    135 while (evbuffer_get_length(output) < 8192) {
    136 	evbuffer_add(output, buf, sizeof(buf));
    137 	cs->queued += sizeof(buf);
    138 }
    139 }
    140 
    141 static void
    142 discard_readcb(struct bufferevent *bev, void *ctx)
    143 {
    144 struct client_state *cs = ctx;
    145 struct evbuffer *input = bufferevent_get_input(bev);
    146 size_t len = evbuffer_get_length(input);
    147 evbuffer_drain(input, len);
    148 cs->received += len;
    149 }
    150 
    151 static void
    152 write_on_connectedcb(struct bufferevent *bev, short what, void *ctx)
    153 {
    154 if (what & BEV_EVENT_CONNECTED) {
    155 	loud_writecb(bev, ctx);
    156 	/* XXXX this shouldn't be needed. */
    157 	bufferevent_enable(bev, EV_READ|EV_WRITE);
    158 }
    159 }
    160 
    161 static void
    162 echo_readcb(struct bufferevent *bev, void *ctx)
    163 {
    164 struct evbuffer *input = bufferevent_get_input(bev);
    165 struct evbuffer *output = bufferevent_get_output(bev);
    166 
    167 evbuffer_add_buffer(output, input);
    168 if (evbuffer_get_length(output) > 1024000)
    169 	bufferevent_disable(bev, EV_READ);
    170 }
    171 
    172 static void
    173 echo_writecb(struct bufferevent *bev, void *ctx)
    174 {
    175 struct evbuffer *output = bufferevent_get_output(bev);
    176 if (evbuffer_get_length(output) < 512000)
    177 	bufferevent_enable(bev, EV_READ);
    178 }
    179 
    180 static void
    181 echo_eventcb(struct bufferevent *bev, short what, void *ctx)
    182 {
    183 if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) {
    184 	--n_echo_conns_open;
    185 	bufferevent_free(bev);
    186 }
    187 }
    188 
    189 static void
    190 echo_listenercb(struct evconnlistener *listener, evutil_socket_t newsock,
    191    struct sockaddr *sourceaddr, int socklen, void *ctx)
    192 {
    193 struct event_base *base = ctx;
    194 int flags = BEV_OPT_CLOSE_ON_FREE|BEV_OPT_THREADSAFE;
    195 struct bufferevent *bev;
    196 
    197 bev = bufferevent_socket_new(base, newsock, flags);
    198 bufferevent_setcb(bev, echo_readcb, echo_writecb, echo_eventcb, NULL);
    199 if (conn_bucket_cfg) {
    200 	struct event *check_event =
    201 	    event_new(base, -1, EV_PERSIST, check_bucket_levels_cb, bev);
    202 	bufferevent_set_rate_limit(bev, conn_bucket_cfg);
    203 
    204 	assert(bufferevent_get_token_bucket_cfg(bev) != NULL);
    205 	event_add(check_event, ms100_common);
    206 }
    207 if (ratelim_group)
    208 	bufferevent_add_to_rate_limit_group(bev, ratelim_group);
    209 ++n_echo_conns_open;
    210 bufferevent_enable(bev, EV_READ|EV_WRITE);
    211 }
    212 
    213 /* Called periodically to check up on how full the buckets are */
    214 static void
    215 check_bucket_levels_cb(evutil_socket_t fd, short events, void *arg)
    216 {
    217 struct bufferevent *bev = arg;
    218 
    219 ev_ssize_t r = bufferevent_get_read_limit(bev);
    220 ev_ssize_t w = bufferevent_get_write_limit(bev);
    221 ev_ssize_t rm = bufferevent_get_max_to_read(bev);
    222 ev_ssize_t wm = bufferevent_get_max_to_write(bev);
    223 /* XXXX check that no value is above the cofigured burst
    224  * limit */
    225 total_rbucket_level += r;
    226 total_wbucket_level += w;
    227 total_max_to_read += rm;
    228 total_max_to_write += wm;
    229 #define B(x) \
    230 if ((x) > max_bucket_level)		\
    231 	max_bucket_level = (x);		\
    232 if ((x) < min_bucket_level)		\
    233 	min_bucket_level = (x)
    234 B(r);
    235 B(w);
    236 #undef B
    237 
    238 total_n_bev_checks++;
    239 if (total_n_bev_checks >= .8 * ((double)cfg_duration / cfg_tick_msec) * cfg_n_connections) {
    240 	event_free(event_base_get_running_event(bufferevent_get_base(bev)));
    241 }
    242 }
    243 
    244 static void
    245 check_group_bucket_levels_cb(evutil_socket_t fd, short events, void *arg)
    246 {
    247 if (ratelim_group) {
    248 	ev_ssize_t r = bufferevent_rate_limit_group_get_read_limit(ratelim_group);
    249 	ev_ssize_t w = bufferevent_rate_limit_group_get_write_limit(ratelim_group);
    250 	total_group_rbucket_level += r;
    251 	total_group_wbucket_level += w;
    252 }
    253 ++total_n_group_bev_checks;
    254 }
    255 
    256 static void
    257 group_drain_cb(evutil_socket_t fd, short events, void *arg)
    258 {
    259 bufferevent_rate_limit_group_decrement_read(ratelim_group, cfg_group_drain);
    260 bufferevent_rate_limit_group_decrement_write(ratelim_group, cfg_group_drain);
    261 }
    262 
    263 static void
    264 timer_bias_cb(evutil_socket_t fd, short events, void *arg)
    265 {
    266 struct event *event = arg;
    267 struct timeval end;
    268 struct timeval diff;
    269 
    270 /** XXX: use rdtsc? (portability issues?) */
    271 evutil_gettimeofday(&end, NULL);
    272 evutil_timersub(&end, &timer_bias_start, &diff);
    273 timer_bias_spend += diff.tv_sec + diff.tv_usec * 1e6;
    274 timer_bias_start = end;
    275 
    276 if (++timer_bias_events == 100)
    277 	event_del(event);
    278 }
    279 static double
    280 timer_bias_calculate(void)
    281 {
    282 struct event_config *cfg = NULL;
    283 struct event_base *base = NULL;
    284 struct event *timer = NULL;
    285 struct timeval tv = { 0, 1 };
    286 int done = 0;
    287 
    288 cfg = event_config_new();
    289 if (!cfg)
    290 	goto err;
    291 if (event_config_set_flag(cfg, EVENT_BASE_FLAG_PRECISE_TIMER))
    292 	goto err;
    293 base = event_base_new_with_config(cfg);
    294 if (!base)
    295 	goto err;
    296 
    297 timer = event_new(base, -1, EV_PERSIST, timer_bias_cb, event_self_cbarg());
    298 if (!timer || event_add(timer, &tv)) {
    299 	goto err;
    300 }
    301 
    302 evutil_gettimeofday(&timer_bias_start, NULL);
    303 event_base_dispatch(base);
    304 done = 1;
    305 
    306 err:
    307 if (cfg)
    308 	event_config_free(cfg);
    309 if (timer)
    310 	event_free(timer);
    311 if (base)
    312 	event_base_free(base);
    313 
    314 if (done)
    315 	return MIN(timer_bias_spend / 1e6 / timer_bias_events / TIMER_MAX_COST_USEC, 5);
    316 
    317 fprintf(stderr, "Couldn't create event for CPU cycle counter bias\n");
    318 return -1;
    319 }
    320 
    321 static int
    322 test_ratelimiting(void)
    323 {
    324 struct event_base *base;
    325 struct sockaddr_in sin;
    326 struct evconnlistener *listener;
    327 
    328 struct sockaddr_storage ss;
    329 ev_socklen_t slen;
    330 
    331 int i;
    332 
    333 struct timeval tv;
    334 
    335 ev_uint64_t total_received;
    336 double total_sq_persec, total_persec;
    337 double variance;
    338 double expected_total_persec = -1.0, expected_avg_persec = -1.0;
    339 int ok = 1;
    340 struct event_config *base_cfg;
    341 struct event *periodic_level_check;
    342 struct event *group_drain_event=NULL;
    343 double timer_bias;
    344 
    345 memset(&sin, 0, sizeof(sin));
    346 sin.sin_family = AF_INET;
    347 sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
    348 sin.sin_port = 0; /* unspecified port */
    349 
    350 if (0)
    351 	event_enable_debug_mode();
    352 
    353 timer_bias = timer_bias_calculate();
    354 if (timer_bias > 1) {
    355 	fprintf(stderr, "CPU is slow, timers bias is %f\n", timer_bias);
    356 	cfg_connlimit_tolerance  *= timer_bias;
    357 	cfg_grouplimit_tolerance *= timer_bias;
    358 	cfg_stddev_tolerance     *= timer_bias;
    359 } else {
    360 	printf("CPU is fast enough, timers bias is %f\n", timer_bias);
    361 }
    362 
    363 base_cfg = event_config_new();
    364 
    365 #ifdef _WIN32
    366 if (cfg_enable_iocp) {
    367 #ifdef EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED
    368 	evthread_use_windows_threads();
    369 #endif
    370 	event_config_set_flag(base_cfg, EVENT_BASE_FLAG_STARTUP_IOCP);
    371 }
    372 #endif
    373 
    374 base = event_base_new_with_config(base_cfg);
    375 event_config_free(base_cfg);
    376 if (! base) {
    377 	fprintf(stderr, "Couldn't create event_base");
    378 	return 1;
    379 }
    380 
    381 listener = evconnlistener_new_bind(base, echo_listenercb, base,
    382     LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,
    383     (struct sockaddr *)&sin, sizeof(sin));
    384 if (! listener) {
    385 	fprintf(stderr, "Couldn't create listener");
    386 	return 1;
    387 }
    388 
    389 slen = sizeof(ss);
    390 if (getsockname(evconnlistener_get_fd(listener), (struct sockaddr *)&ss,
    391 	&slen) < 0) {
    392 	perror("getsockname");
    393 	return 1;
    394 }
    395 
    396 if (cfg_connlimit > 0) {
    397 	conn_bucket_cfg = ev_token_bucket_cfg_new(
    398 		cfg_connlimit, cfg_connlimit * 4,
    399 		cfg_connlimit, cfg_connlimit * 4,
    400 		&cfg_tick);
    401 	assert(conn_bucket_cfg);
    402 }
    403 
    404 if (cfg_grouplimit > 0) {
    405 	group_bucket_cfg = ev_token_bucket_cfg_new(
    406 		cfg_grouplimit, cfg_grouplimit * 4,
    407 		cfg_grouplimit, cfg_grouplimit * 4,
    408 		&cfg_tick);
    409 	group = ratelim_group = bufferevent_rate_limit_group_new(
    410 		base, group_bucket_cfg);
    411 	expected_total_persec = cfg_grouplimit - (cfg_group_drain / seconds_per_tick);
    412 	expected_avg_persec = cfg_grouplimit / cfg_n_connections;
    413 	if (cfg_connlimit > 0 && expected_avg_persec > cfg_connlimit)
    414 		expected_avg_persec = cfg_connlimit;
    415 	if (cfg_min_share >= 0)
    416 		bufferevent_rate_limit_group_set_min_share(
    417 			ratelim_group, cfg_min_share);
    418 }
    419 
    420 if (expected_avg_persec < 0 && cfg_connlimit > 0)
    421 	expected_avg_persec = cfg_connlimit;
    422 
    423 if (expected_avg_persec > 0)
    424 	expected_avg_persec /= seconds_per_tick;
    425 if (expected_total_persec > 0)
    426 	expected_total_persec /= seconds_per_tick;
    427 
    428 bevs = calloc(cfg_n_connections, sizeof(struct bufferevent *));
    429 states = calloc(cfg_n_connections, sizeof(struct client_state));
    430 
    431 for (i = 0; i < cfg_n_connections; ++i) {
    432 	bevs[i] = bufferevent_socket_new(base, -1,
    433 	    BEV_OPT_CLOSE_ON_FREE|BEV_OPT_THREADSAFE);
    434 	assert(bevs[i]);
    435 	bufferevent_setcb(bevs[i], discard_readcb, loud_writecb,
    436 	    write_on_connectedcb, &states[i]);
    437 	bufferevent_enable(bevs[i], EV_READ|EV_WRITE);
    438 	bufferevent_socket_connect(bevs[i], (struct sockaddr *)&ss,
    439 	    slen);
    440 }
    441 
    442 tv.tv_sec = cfg_duration - 1;
    443 tv.tv_usec = 995000;
    444 
    445 event_base_loopexit(base, &tv);
    446 
    447 tv.tv_sec = 0;
    448 tv.tv_usec = 100*1000;
    449 ms100_common = event_base_init_common_timeout(base, &tv);
    450 
    451 periodic_level_check = event_new(base, -1, EV_PERSIST, check_group_bucket_levels_cb, NULL);
    452 event_add(periodic_level_check, ms100_common);
    453 
    454 if (cfg_group_drain && ratelim_group) {
    455 	group_drain_event = event_new(base, -1, EV_PERSIST, group_drain_cb, NULL);
    456 	event_add(group_drain_event, &cfg_tick);
    457 }
    458 
    459 event_base_dispatch(base);
    460 
    461 ratelim_group = NULL; /* So no more responders get added */
    462 event_free(periodic_level_check);
    463 if (group_drain_event)
    464 	event_free(group_drain_event);
    465 
    466 for (i = 0; i < cfg_n_connections; ++i) {
    467 	bufferevent_free(bevs[i]);
    468 }
    469 evconnlistener_free(listener);
    470 
    471 /* Make sure no new echo_conns get added to the group. */
    472 ratelim_group = NULL;
    473 
    474 /* This should get _everybody_ freed */
    475 while (n_echo_conns_open) {
    476 	printf("waiting for %d conns\n", n_echo_conns_open);
    477 	tv.tv_sec = 0;
    478 	tv.tv_usec = 300000;
    479 	event_base_loopexit(base, &tv);
    480 	event_base_dispatch(base);
    481 }
    482 
    483 if (group)
    484 	bufferevent_rate_limit_group_free(group);
    485 
    486 if (total_n_bev_checks) {
    487 	printf("Average read bucket level: %f\n",
    488 	    (double)total_rbucket_level/total_n_bev_checks);
    489 	printf("Average write bucket level: %f\n",
    490 	    (double)total_wbucket_level/total_n_bev_checks);
    491 	printf("Highest read bucket level: %f\n",
    492 	    (double)max_bucket_level);
    493 	printf("Highest write bucket level: %f\n",
    494 	    (double)min_bucket_level);
    495 	printf("Average max-to-read: %f\n",
    496 	    ((double)total_max_to_read)/total_n_bev_checks);
    497 	printf("Average max-to-write: %f\n",
    498 	    ((double)total_max_to_write)/total_n_bev_checks);
    499 }
    500 if (total_n_group_bev_checks) {
    501 	printf("Average group read bucket level: %f\n",
    502 	    ((double)total_group_rbucket_level)/total_n_group_bev_checks);
    503 	printf("Average group write bucket level: %f\n",
    504 	    ((double)total_group_wbucket_level)/total_n_group_bev_checks);
    505 }
    506 
    507 total_received = 0;
    508 total_persec = 0.0;
    509 total_sq_persec = 0.0;
    510 for (i=0; i < cfg_n_connections; ++i) {
    511 	double persec = states[i].received;
    512 	persec /= cfg_duration;
    513 	total_received += states[i].received;
    514 	total_persec += persec;
    515 	total_sq_persec += persec*persec;
    516 	printf("%d: %f per second\n", i+1, persec);
    517 }
    518 printf("   total: %f per second\n",
    519     ((double)total_received)/cfg_duration);
    520 if (expected_total_persec > 0) {
    521 	double diff = expected_total_persec -
    522 	    ((double)total_received/cfg_duration);
    523 	printf("  [Off by %lf]\n", diff);
    524 	if (cfg_grouplimit_tolerance > 0 &&
    525 	    fabs(diff) > cfg_grouplimit_tolerance) {
    526 		fprintf(stderr, "Group bandwidth out of bounds\n");
    527 		ok = 0;
    528 	}
    529 }
    530 
    531 printf(" average: %f per second\n",
    532     (((double)total_received)/cfg_duration)/cfg_n_connections);
    533 if (expected_avg_persec > 0) {
    534 	double diff = expected_avg_persec - (((double)total_received)/cfg_duration)/cfg_n_connections;
    535 	printf("  [Off by %lf]\n", diff);
    536 	if (cfg_connlimit_tolerance > 0 &&
    537 	    fabs(diff) > cfg_connlimit_tolerance) {
    538 		fprintf(stderr, "Connection bandwidth out of bounds\n");
    539 		ok = 0;
    540 	}
    541 }
    542 
    543 variance = total_sq_persec/cfg_n_connections - total_persec*total_persec/(cfg_n_connections*cfg_n_connections);
    544 
    545 printf("  stddev: %f per second\n", sqrt(variance));
    546 if (cfg_stddev_tolerance > 0 &&
    547     sqrt(variance) > cfg_stddev_tolerance) {
    548 	fprintf(stderr, "Connection variance out of bounds\n");
    549 	ok = 0;
    550 }
    551 
    552 event_base_free(base);
    553 free(bevs);
    554 free(states);
    555 
    556 return ok ? 0 : 1;
    557 }
    558 
    559 static struct option {
    560 const char *name; int *ptr; int min; int isbool;
    561 } options[] = {
    562 { "-v", &cfg_verbose, 0, 1 },
    563 { "-h", &cfg_help, 0, 1 },
    564 { "-n", &cfg_n_connections, 1, 0 },
    565 { "-d", &cfg_duration, 1, 0 },
    566 { "-c", &cfg_connlimit, 0, 0 },
    567 { "-g", &cfg_grouplimit, 0, 0 },
    568 { "-G", &cfg_group_drain, -100000, 0 },
    569 { "-t", &cfg_tick_msec, 10, 0 },
    570 { "--min-share", &cfg_min_share, 0, 0 },
    571 { "--check-connlimit", &cfg_connlimit_tolerance, 0, 0 },
    572 { "--check-grouplimit", &cfg_grouplimit_tolerance, 0, 0 },
    573 { "--check-stddev", &cfg_stddev_tolerance, 0, 0 },
    574 #ifdef _WIN32
    575 { "--iocp", &cfg_enable_iocp, 0, 1 },
    576 #endif
    577 { NULL, NULL, -1, 0 },
    578 };
    579 
    580 static int
    581 handle_option(int argc, char **argv, int *i, const struct option *opt)
    582 {
    583 long val;
    584 char *endptr = NULL;
    585 if (opt->isbool) {
    586 	*opt->ptr = 1;
    587 	return 0;
    588 }
    589 if (*i + 1 == argc) {
    590 	fprintf(stderr, "Too few arguments to '%s'\n",argv[*i]);
    591 	return -1;
    592 }
    593 val = strtol(argv[*i+1], &endptr, 10);
    594 if (*argv[*i+1] == '\0' || !endptr || *endptr != '\0') {
    595 	fprintf(stderr, "Couldn't parse numeric value '%s'\n",
    596 	    argv[*i+1]);
    597 	return -1;
    598 }
    599 if (val < opt->min || val > 0x7fffffff) {
    600 	fprintf(stderr, "Value '%s' is out-of-range'\n",
    601 	    argv[*i+1]);
    602 	return -1;
    603 }
    604 *opt->ptr = (int)val;
    605 ++*i;
    606 return 0;
    607 }
    608 
    609 static void
    610 usage(void)
    611 {
    612 fprintf(stderr,
    613 "test-ratelim [-v] [-n INT] [-d INT] [-c INT] [-g INT] [-t INT]\n\n"
    614 "Pushes bytes through a number of possibly rate-limited connections, and\n"
    615 "displays average throughput.\n\n"
    616 "  -n INT: Number of connections to open (default: 30)\n"
    617 "  -d INT: Duration of the test in seconds (default: 5 sec)\n");
    618 fprintf(stderr,
    619 "  -c INT: Connection-rate limit applied to each connection in bytes per second\n"
    620 "	   (default: None.)\n"
    621 "  -g INT: Group-rate limit applied to sum of all usage in bytes per second\n"
    622 "	   (default: None.)\n"
    623 "  -G INT: drain INT bytes from the group limit every tick. (default: 0)\n"
    624 "  -t INT: Granularity of timing, in milliseconds (default: 1000 msec)\n");
    625 }
    626 
    627 int
    628 main(int argc, char **argv)
    629 {
    630 int i,j;
    631 double ratio;
    632 
    633 #ifdef _WIN32
    634 WORD wVersionRequested = MAKEWORD(2,2);
    635 WSADATA wsaData;
    636 
    637 (void) WSAStartup(wVersionRequested, &wsaData);
    638 #endif
    639 
    640 evutil_weakrand_seed_(&weakrand_state, 0);
    641 
    642 #ifndef _WIN32
    643 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
    644 	return 1;
    645 #endif
    646 for (i = 1; i < argc; ++i) {
    647 	for (j = 0; options[j].name; ++j) {
    648 		if (!strcmp(argv[i],options[j].name)) {
    649 			if (handle_option(argc,argv,&i,&options[j])<0)
    650 				return 1;
    651 			goto again;
    652 		}
    653 	}
    654 	fprintf(stderr, "Unknown option '%s'\n", argv[i]);
    655 	usage();
    656 	return 1;
    657 again:
    658 	;
    659 }
    660 if (cfg_help) {
    661 	usage();
    662 	return 0;
    663 }
    664 
    665 cfg_tick.tv_sec = cfg_tick_msec / 1000;
    666 cfg_tick.tv_usec = (cfg_tick_msec % 1000)*1000;
    667 
    668 seconds_per_tick = ratio = cfg_tick_msec / 1000.0;
    669 
    670 cfg_connlimit *= ratio;
    671 cfg_grouplimit *= ratio;
    672 
    673 {
    674 	struct timeval tv;
    675 	evutil_gettimeofday(&tv, NULL);
    676 #ifdef _WIN32
    677 	srand(tv.tv_usec);
    678 #else
    679 	srandom(tv.tv_usec);
    680 #endif
    681 }
    682 
    683 #ifndef EVENT__DISABLE_THREAD_SUPPORT
    684 evthread_enable_lock_debugging();
    685 #endif
    686 
    687 return test_ratelimiting();
    688 }