/* Copyright (c) 2018-2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file test_bwmgt.c * \brief tests for bandwidth management / token bucket functions */ #define CONFIG_PRIVATE #define CONNECTION_PRIVATE #define DIRAUTH_SYS_PRIVATE #define TOKEN_BUCKET_PRIVATE #include "core/or/or.h" #include "app/config/config.h" #include "core/mainloop/connection.h" #include "feature/dirauth/dirauth_sys.h" #include "feature/dircommon/directory.h" #include "feature/nodelist/microdesc.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerlist.h" #include "lib/crypt_ops/crypto_rand.h" #include "lib/evloop/token_bucket.h" #include "test/test.h" #include "test/test_helpers.h" #include "app/config/or_options_st.h" #include "core/or/connection_st.h" #include "feature/dirauth/dirauth_options_st.h" #include "feature/nodelist/microdesc_st.h" #include "feature/nodelist/networkstatus_st.h" #include "feature/nodelist/routerinfo_st.h" #include "feature/nodelist/routerstatus_st.h" // an imaginary time, in timestamp units. Chosen so it will roll over. static const uint32_t START_TS = UINT32_MAX-10; static const int32_t KB = 1024; static const uint32_t GB = (UINT64_C(1) << 30); static or_options_t mock_options; static const or_options_t * mock_get_options(void) { return &mock_options; } static networkstatus_t *dummy_ns = NULL; static networkstatus_t * mock_networkstatus_get_latest_consensus(void) { return dummy_ns; } static networkstatus_t * mock_networkstatus_get_latest_consensus_by_flavor(consensus_flavor_t f) { tor_assert(f == FLAV_MICRODESC); return dummy_ns; } /* Number of address a single node_t can have. Default to the production * value. This is to control the size of the bloom filter. */ static int addr_per_node = 2; static int mock_get_estimated_address_per_node(void) { return addr_per_node; } static void test_bwmgt_token_buf_init(void *arg) { (void)arg; token_bucket_rw_t b; token_bucket_rw_init(&b, 16*KB, 64*KB, START_TS); // Burst is correct tt_uint_op(b.cfg.burst, OP_EQ, 64*KB); // Rate is correct, within 1 percent. { uint32_t ticks_per_sec = (uint32_t) monotime_msec_to_approx_coarse_stamp_units(1000); uint32_t rate_per_sec = (b.cfg.rate * ticks_per_sec / TICKS_PER_STEP); tt_uint_op(rate_per_sec, OP_GT, 16*KB-160); tt_uint_op(rate_per_sec, OP_LT, 16*KB+160); } // Bucket starts out full: tt_uint_op(b.last_refilled_at_timestamp, OP_EQ, START_TS); tt_int_op(b.read_bucket.bucket, OP_EQ, 64*KB); done: ; } static void test_bwmgt_token_buf_adjust(void *arg) { (void)arg; token_bucket_rw_t b; token_bucket_rw_init(&b, 16*KB, 64*KB, START_TS); uint32_t rate_orig = b.cfg.rate; // Increasing burst token_bucket_rw_adjust(&b, 16*KB, 128*KB); tt_uint_op(b.cfg.rate, OP_EQ, rate_orig); tt_uint_op(b.read_bucket.bucket, OP_EQ, 64*KB); tt_uint_op(b.cfg.burst, OP_EQ, 128*KB); // Decreasing burst but staying above bucket token_bucket_rw_adjust(&b, 16*KB, 96*KB); tt_uint_op(b.cfg.rate, OP_EQ, rate_orig); tt_uint_op(b.read_bucket.bucket, OP_EQ, 64*KB); tt_uint_op(b.cfg.burst, OP_EQ, 96*KB); // Decreasing burst below bucket, token_bucket_rw_adjust(&b, 16*KB, 48*KB); tt_uint_op(b.cfg.rate, OP_EQ, rate_orig); tt_uint_op(b.read_bucket.bucket, OP_EQ, 48*KB); tt_uint_op(b.cfg.burst, OP_EQ, 48*KB); // Changing rate. token_bucket_rw_adjust(&b, 32*KB, 48*KB); tt_uint_op(b.cfg.rate, OP_GE, rate_orig*2 - 10); tt_uint_op(b.cfg.rate, OP_LE, rate_orig*2 + 10); tt_uint_op(b.read_bucket.bucket, OP_EQ, 48*KB); tt_uint_op(b.cfg.burst, OP_EQ, 48*KB); done: ; } static void test_bwmgt_token_buf_dec(void *arg) { (void)arg; token_bucket_rw_t b; token_bucket_rw_init(&b, 16*KB, 64*KB, START_TS); // full-to-not-full. tt_int_op(0, OP_EQ, token_bucket_rw_dec_read(&b, KB)); tt_int_op(b.read_bucket.bucket, OP_EQ, 63*KB); // Full to almost-not-full tt_int_op(0, OP_EQ, token_bucket_rw_dec_read(&b, 63*KB - 1)); tt_int_op(b.read_bucket.bucket, OP_EQ, 1); // almost-not-full to empty. tt_int_op(1, OP_EQ, token_bucket_rw_dec_read(&b, 1)); tt_int_op(b.read_bucket.bucket, OP_EQ, 0); // reset bucket, try full-to-empty token_bucket_rw_init(&b, 16*KB, 64*KB, START_TS); tt_int_op(1, OP_EQ, token_bucket_rw_dec_read(&b, 64*KB)); tt_int_op(b.read_bucket.bucket, OP_EQ, 0); // reset bucket, try underflow. token_bucket_rw_init(&b, 16*KB, 64*KB, START_TS); tt_int_op(1, OP_EQ, token_bucket_rw_dec_read(&b, 64*KB + 1)); tt_int_op(b.read_bucket.bucket, OP_EQ, -1); // A second underflow does not make the bucket empty. tt_int_op(0, OP_EQ, token_bucket_rw_dec_read(&b, 1000)); tt_int_op(b.read_bucket.bucket, OP_EQ, -1001); done: ; } static void test_bwmgt_token_buf_refill(void *arg) { (void)arg; token_bucket_rw_t b; const uint32_t BW_SEC = (uint32_t)monotime_msec_to_approx_coarse_stamp_units(1000); token_bucket_rw_init(&b, 16*KB, 64*KB, START_TS); /* Make the buffer much emptier, then let one second elapse. */ token_bucket_rw_dec_read(&b, 48*KB); tt_int_op(b.read_bucket.bucket, OP_EQ, 16*KB); tt_int_op(0, OP_EQ, token_bucket_rw_refill(&b, START_TS + BW_SEC)); tt_int_op(b.read_bucket.bucket, OP_GT, 32*KB - 300); tt_int_op(b.read_bucket.bucket, OP_LT, 32*KB + 300); /* Another half second. */ tt_int_op(0, OP_EQ, token_bucket_rw_refill(&b, START_TS + BW_SEC*3/2)); tt_int_op(b.read_bucket.bucket, OP_GT, 40*KB - 400); tt_int_op(b.read_bucket.bucket, OP_LT, 40*KB + 400); tt_uint_op(b.last_refilled_at_timestamp, OP_EQ, START_TS + BW_SEC*3/2); /* No time: nothing happens. */ { const uint32_t bucket_orig = b.read_bucket.bucket; tt_int_op(0, OP_EQ, token_bucket_rw_refill(&b, START_TS + BW_SEC*3/2)); tt_int_op(b.read_bucket.bucket, OP_EQ, bucket_orig); } /* Another 30 seconds: fill the bucket. */ tt_int_op(0, OP_EQ, token_bucket_rw_refill(&b, START_TS + BW_SEC*3/2 + BW_SEC*30)); tt_int_op(b.read_bucket.bucket, OP_EQ, b.cfg.burst); tt_uint_op(b.last_refilled_at_timestamp, OP_EQ, START_TS + BW_SEC*3/2 + BW_SEC*30); /* Another 30 seconds: nothing happens. */ tt_int_op(0, OP_EQ, token_bucket_rw_refill(&b, START_TS + BW_SEC*3/2 + BW_SEC*60)); tt_int_op(b.read_bucket.bucket, OP_EQ, b.cfg.burst); tt_uint_op(b.last_refilled_at_timestamp, OP_EQ, START_TS + BW_SEC*3/2 + BW_SEC*60); /* Empty the bucket, let two seconds pass, and make sure that a refill is * noticed. */ tt_int_op(1, OP_EQ, token_bucket_rw_dec_read(&b, b.cfg.burst)); tt_int_op(0, OP_EQ, b.read_bucket.bucket); tt_int_op(1, OP_EQ, token_bucket_rw_refill(&b, START_TS + BW_SEC*3/2 + BW_SEC*61)); tt_int_op(0, OP_EQ, token_bucket_rw_refill(&b, START_TS + BW_SEC*3/2 + BW_SEC*62)); tt_int_op(b.read_bucket.bucket, OP_GT, 32*KB-400); tt_int_op(b.read_bucket.bucket, OP_LT, 32*KB+400); /* Underflow the bucket, make sure we detect when it has tokens again. */ tt_int_op(1, OP_EQ, token_bucket_rw_dec_read(&b, b.read_bucket.bucket+16*KB)); tt_int_op(-16*KB, OP_EQ, b.read_bucket.bucket); // half a second passes... tt_int_op(0, OP_EQ, token_bucket_rw_refill(&b, START_TS + BW_SEC*64)); tt_int_op(b.read_bucket.bucket, OP_GT, -8*KB-300); tt_int_op(b.read_bucket.bucket, OP_LT, -8*KB+300); // a second passes tt_int_op(1, OP_EQ, token_bucket_rw_refill(&b, START_TS + BW_SEC*65)); tt_int_op(b.read_bucket.bucket, OP_GT, 8*KB-400); tt_int_op(b.read_bucket.bucket, OP_LT, 8*KB+400); // We step a second backwards, and nothing happens. tt_int_op(0, OP_EQ, token_bucket_rw_refill(&b, START_TS + BW_SEC*64)); tt_int_op(b.read_bucket.bucket, OP_GT, 8*KB-400); tt_int_op(b.read_bucket.bucket, OP_LT, 8*KB+400); // A ridiculous amount of time passes. tt_int_op(0, OP_EQ, token_bucket_rw_refill(&b, INT32_MAX)); tt_int_op(b.read_bucket.bucket, OP_EQ, b.cfg.burst); done: ; } /* Test some helper functions we use within the token bucket interface. */ static void test_bwmgt_token_buf_helpers(void *arg) { uint32_t ret; (void) arg; /* The returned value will be OS specific but in any case, it should be * greater than 1 since we are passing 1GB/sec rate. */ ret = rate_per_sec_to_rate_per_step(1 * GB); tt_u64_op(ret, OP_GT, 1); /* We default to 1 in case rate is 0. */ ret = rate_per_sec_to_rate_per_step(0); tt_u64_op(ret, OP_EQ, 1); done: ; } static void test_bwmgt_dir_conn_global_write_low(void *arg) { bool ret; int addr_family; connection_t *conn = NULL; routerstatus_t *rs = NULL; microdesc_t *md = NULL; routerinfo_t *ri = NULL; tor_addr_t relay_addr; dirauth_options_t *dirauth_opts = NULL; (void) arg; memset(&mock_options, 0, sizeof(or_options_t)); MOCK(networkstatus_get_latest_consensus, mock_networkstatus_get_latest_consensus); MOCK(networkstatus_get_latest_consensus_by_flavor, mock_networkstatus_get_latest_consensus_by_flavor); MOCK(get_estimated_address_per_node, mock_get_estimated_address_per_node); /* * The following is rather complex but that is what it takes to add a dummy * consensus with a valid routerlist which will populate our node address * set that we need to lookup to test the known relay code path. * * We MUST do that before we MOCK(get_options) else it is another world of * complexity. */ /* This will be the address of our relay. */ tor_addr_parse(&relay_addr, "1.2.3.4"); /* We'll now add a relay into our routerlist and see if we let it. */ dummy_ns = tor_malloc_zero(sizeof(*dummy_ns)); dummy_ns->flavor = FLAV_MICRODESC; dummy_ns->routerstatus_list = smartlist_new(); md = tor_malloc_zero(sizeof(*md)); ri = tor_malloc_zero(sizeof(*ri)); rs = tor_malloc_zero(sizeof(*rs)); crypto_rand(rs->identity_digest, sizeof(rs->identity_digest)); crypto_rand(md->digest, sizeof(md->digest)); memcpy(rs->descriptor_digest, md->digest, DIGEST256_LEN); /* Set IP address. */ rs->addr = tor_addr_to_ipv4h(&relay_addr); ri->addr = rs->addr; /* Add the rs to the consensus becoming a node_t. */ smartlist_add(dummy_ns->routerstatus_list, rs); /* Add all configured authorities (hardcoded) before we set the consensus so * the address set exists. */ ret = consider_adding_dir_servers(&mock_options, &mock_options); tt_int_op(ret, OP_EQ, 0); /* This will make the nodelist bloom filter very large * (the_nodelist->node_addrs) so we will fail the contain test rarely. */ addr_per_node = 1024; nodelist_set_consensus(dummy_ns); dirauth_opts = tor_malloc_zero(sizeof(dirauth_options_t)); dirauth_opts->AuthDirRejectRequestsUnderLoad = 0; dirauth_set_options(dirauth_opts); /* Ok, now time to control which options we use. */ MOCK(get_options, mock_get_options); /* Set ourselves as an authoritative dir. */ mock_options.AuthoritativeDir = 1; mock_options.V3AuthoritativeDir = 1; mock_options.UseDefaultFallbackDirs = 0; /* This will set our global bucket to 1 byte and thus we will hit the * banwdith limit in our test. */ mock_options.BandwidthRate = 1; mock_options.BandwidthBurst = 1; /* Else an IPv4 address screams. */ mock_options.ClientUseIPv4 = 1; mock_options.ClientUseIPv6 = 1; /* Initialize the global buckets. */ connection_bucket_init(); /* The address "127.0.0.1" is set with this helper. */ conn = test_conn_get_connection(DIR_CONN_STATE_MIN_, CONN_TYPE_DIR, DIR_PURPOSE_MIN_); tt_assert(conn); /* First try a non authority non relay IP thus a client but we are not * configured to reject requests under load so we should get a false value * that our limit is _not_ low. */ addr_family = tor_addr_parse(&conn->addr, "1.1.1.1"); tt_int_op(addr_family, OP_EQ, AF_INET); ret = connection_dir_is_global_write_low(conn, INT_MAX); tt_int_op(ret, OP_EQ, 0); /* Now, we will reject requests under load so try again a non authority non * relay IP thus a client. We should get a warning that our limit is too * low. */ dirauth_opts->AuthDirRejectRequestsUnderLoad = 1; addr_family = tor_addr_parse(&conn->addr, "1.1.1.1"); tt_int_op(addr_family, OP_EQ, AF_INET); ret = connection_dir_is_global_write_low(conn, INT_MAX); tt_int_op(ret, OP_EQ, 1); /* Now, lets try with a connection address from moria1. It should always * pass even though our limit is too low. */ addr_family = tor_addr_parse(&conn->addr, "128.31.0.39"); tt_int_op(addr_family, OP_EQ, AF_INET); ret = connection_dir_is_global_write_low(conn, INT_MAX); tt_int_op(ret, OP_EQ, 0); /* IPv6 testing of gabelmoo. */ addr_family = tor_addr_parse(&conn->addr, "[2001:638:a000:4140::ffff:189]"); tt_int_op(addr_family, OP_EQ, AF_INET6); ret = connection_dir_is_global_write_low(conn, INT_MAX); tt_int_op(ret, OP_EQ, 0); /* Lets retry with a known relay address. It should pass. Possible due to * our consensus setting above. */ memcpy(&conn->addr, &relay_addr, sizeof(tor_addr_t)); ret = connection_dir_is_global_write_low(conn, INT_MAX); tt_int_op(ret, OP_EQ, 0); /* Lets retry with a random IP that is not an authority nor a relay. */ addr_family = tor_addr_parse(&conn->addr, "1.2.3.4"); tt_int_op(addr_family, OP_EQ, AF_INET); ret = connection_dir_is_global_write_low(conn, INT_MAX); tt_int_op(ret, OP_EQ, 0); /* Finally, just make sure it still denies an IP if we are _not_ a v3 * directory authority. */ mock_options.V3AuthoritativeDir = 0; addr_family = tor_addr_parse(&conn->addr, "1.2.3.4"); tt_int_op(addr_family, OP_EQ, AF_INET); ret = connection_dir_is_global_write_low(conn, INT_MAX); tt_int_op(ret, OP_EQ, 1); /* Random IPv6 should not be allowed. */ addr_family = tor_addr_parse(&conn->addr, "[CAFE::ACAB]"); tt_int_op(addr_family, OP_EQ, AF_INET6); ret = connection_dir_is_global_write_low(conn, INT_MAX); tt_int_op(ret, OP_EQ, 1); done: connection_free_minimal(conn); routerstatus_free(rs); routerinfo_free(ri); microdesc_free(md); smartlist_clear(dummy_ns->routerstatus_list); networkstatus_vote_free(dummy_ns); UNMOCK(get_estimated_address_per_node); UNMOCK(networkstatus_get_latest_consensus); UNMOCK(networkstatus_get_latest_consensus_by_flavor); UNMOCK(get_options); } #define BWMGT(name) \ { #name, test_bwmgt_ ## name , TT_FORK, NULL, NULL } struct testcase_t bwmgt_tests[] = { BWMGT(token_buf_init), BWMGT(token_buf_adjust), BWMGT(token_buf_dec), BWMGT(token_buf_refill), BWMGT(token_buf_helpers), BWMGT(dir_conn_global_write_low), END_OF_TESTCASES };