Merge branch 'prop293_squashed'

This commit is contained in:
Nick Mathewson 2018-12-05 09:24:51 -05:00
commit 1f95e80351
19 changed files with 438 additions and 15 deletions

8
changes/ticket26770 Normal file
View File

@ -0,0 +1,8 @@
o Minor features (directory authority, relay):
- Authorities now vote on a "StaleDesc" flag to indicate that a relay's
descriptor is so old that the relay should upload again soon. Relays
understand this flag, and treat it as a signal to upload a new
descriptor. This flag will eventually let us remove the 'published'
date from routerstatus entries, and save a great deal of space in our
consensus diffs. Closes ticket 26770; implements proposal 293.

View File

@ -4372,6 +4372,23 @@ clear_status_flags_on_sybil(routerstatus_t *rs)
* forget to add it to this clause. */
}
/** Space-separated list of all the flags that we will always vote on. */
const char DIRVOTE_UNIVERSAL_FLAGS[] =
"Authority "
"Exit "
"Fast "
"Guard "
"HSDir "
"Stable "
"StaleDesc "
"V2Dir "
"Valid";
/** Space-separated list of all flags that we may or may not vote on,
* depending on our configuration. */
const char DIRVOTE_OPTIONAL_FLAGS[] =
"BadExit "
"Running";
/** Return a new networkstatus_t* containing our current opinion. (For v3
* authorities) */
networkstatus_t *
@ -4620,7 +4637,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
v3_out->known_flags = smartlist_new();
smartlist_split_string(v3_out->known_flags,
"Authority Exit Fast Guard Stable V2Dir Valid HSDir",
DIRVOTE_UNIVERSAL_FLAGS,
0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
if (vote_on_reachability)
smartlist_add_strdup(v3_out->known_flags, "Running");

View File

@ -92,6 +92,9 @@
/** Maximum size of a line in a vote. */
#define MAX_BW_FILE_HEADERS_LINE_LEN 1024
extern const char DIRVOTE_UNIVERSAL_FLAGS[];
extern const char DIRVOTE_OPTIONAL_FLAGS[];
/*
* Public API. Used outside of the dirauth subsystem.
*

View File

@ -95,7 +95,7 @@ real_uptime(const routerinfo_t *router, time_t now)
*/
static int
dirserv_thinks_router_is_unreliable(time_t now,
routerinfo_t *router,
const routerinfo_t *router,
int need_uptime, int need_capacity)
{
if (need_uptime) {
@ -541,7 +541,7 @@ dirserv_set_router_is_running(routerinfo_t *router, time_t now)
void
set_routerstatus_from_routerinfo(routerstatus_t *rs,
node_t *node,
routerinfo_t *ri,
const routerinfo_t *ri,
time_t now,
int listbadexits)
{
@ -593,6 +593,10 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs,
rs->or_port = ri->or_port;
rs->dir_port = ri->dir_port;
rs->is_v2_dir = ri->supports_tunnelled_dir_requests;
rs->is_staledesc =
(ri->cache_info.published_on + DESC_IS_STALE_INTERVAL) < now;
if (options->AuthDirHasIPv6Connectivity == 1 &&
!tor_addr_is_null(&ri->ipv6_addr) &&
node->last_reachable6 >= now - REACHABLE_TIMEOUT) {

View File

@ -19,12 +19,16 @@ int running_long_enough_to_decide_unreachable(void);
void set_routerstatus_from_routerinfo(routerstatus_t *rs,
node_t *node,
routerinfo_t *ri, time_t now,
const routerinfo_t *ri,
time_t now,
int listbadexits);
void dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil);
#ifdef VOTEFLAGS_PRIVATE
/** Any descriptor older than this age causes the authorities to set the
* StaleDesc flag. */
#define DESC_IS_STALE_INTERVAL (18*60*60)
STATIC void dirserv_set_routerstatus_testing(routerstatus_t *rs);
#endif

View File

@ -434,6 +434,8 @@ routerstatus_parse_entry_from_string(memarea_t *area,
rs->is_hs_dir = 1;
} else if (!strcmp(tok->args[i], "V2Dir")) {
rs->is_v2_dir = 1;
} else if (!strcmp(tok->args[i], "StaleDesc")) {
rs->is_staledesc = 1;
}
}
/* These are implied true by having been included in a consensus made

View File

@ -135,7 +135,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version,
goto done;
smartlist_add_asprintf(chunks,
"s%s%s%s%s%s%s%s%s%s%s\n",
"s%s%s%s%s%s%s%s%s%s%s%s\n",
/* These must stay in alphabetical order. */
rs->is_authority?" Authority":"",
rs->is_bad_exit?" BadExit":"",
@ -145,6 +145,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version,
rs->is_hs_dir?" HSDir":"",
rs->is_flagged_running?" Running":"",
rs->is_stable?" Stable":"",
rs->is_staledesc?" StaleDesc":"",
rs->is_v2_dir?" V2Dir":"",
rs->is_valid?" Valid":"");

View File

@ -724,8 +724,8 @@ networkstatus_vote_find_mutable_entry(networkstatus_t *ns, const char *digest)
/** Return the entry in <b>ns</b> for the identity digest <b>digest</b>, or
* NULL if none was found. */
const routerstatus_t *
networkstatus_vote_find_entry(networkstatus_t *ns, const char *digest)
MOCK_IMPL(const routerstatus_t *,
networkstatus_vote_find_entry,(networkstatus_t *ns, const char *digest))
{
return networkstatus_vote_find_mutable_entry(ns, digest);
}

View File

@ -40,8 +40,9 @@ int compare_digest_to_routerstatus_entry(const void *_key,
const void **_member);
int compare_digest_to_vote_routerstatus_entry(const void *_key,
const void **_member);
const routerstatus_t *networkstatus_vote_find_entry(networkstatus_t *ns,
const char *digest);
MOCK_DECL(const routerstatus_t *,networkstatus_vote_find_entry,(
networkstatus_t *ns,
const char *digest));
routerstatus_t *networkstatus_vote_find_mutable_entry(networkstatus_t *ns,
const char *digest);
int networkstatus_vote_find_entry_idx(networkstatus_t *ns,

View File

@ -47,6 +47,8 @@ struct routerstatus_t {
unsigned int is_v2_dir:1; /** True iff this router publishes an open DirPort
* or it claims to accept tunnelled dir requests.
*/
unsigned int is_staledesc:1; /** True iff the authorities think this router
* should upload a new descriptor soon. */
unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */
unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */

View File

@ -59,6 +59,7 @@
#include "feature/dircommon/dir_connection_st.h"
#include "feature/nodelist/authority_cert_st.h"
#include "feature/nodelist/extrainfo_st.h"
#include "feature/nodelist/networkstatus_st.h"
#include "feature/nodelist/node_st.h"
#include "feature/nodelist/routerinfo_st.h"
#include "feature/nodelist/routerstatus_st.h"
@ -1470,9 +1471,9 @@ static extrainfo_t *desc_extrainfo = NULL;
static const char *desc_gen_reason = "uninitialized reason";
/** Since when has our descriptor been "clean"? 0 if we need to regenerate it
* now. */
static time_t desc_clean_since = 0;
STATIC time_t desc_clean_since = 0;
/** Why did we mark the descriptor dirty? */
static const char *desc_dirty_reason = "Tor just started";
STATIC const char *desc_dirty_reason = "Tor just started";
/** Boolean: do we need to regenerate the above? */
static int desc_needs_upload = 0;
@ -2134,7 +2135,9 @@ mark_my_descriptor_dirty_if_too_old(time_t now)
/* Now we see whether we want to be retrying frequently or no. The
* rule here is that we'll retry frequently if we aren't listed in the
* live consensus we have, or if the publication time of the
* descriptor listed for us in the consensus is very old. */
* descriptor listed for us in the consensus is very old, or if the
* consensus lists us as "stale" and we haven't regenerated since the
* consensus was published. */
ns = networkstatus_get_live_consensus(now);
if (ns) {
rs = networkstatus_vote_find_entry(ns, server_identitykey_digest);
@ -2142,6 +2145,8 @@ mark_my_descriptor_dirty_if_too_old(time_t now)
retry_fast_reason = "not listed in consensus";
else if (rs->published_on < slow_cutoff)
retry_fast_reason = "version listed in consensus is quite old";
else if (rs->is_staledesc && ns->valid_after > desc_clean_since)
retry_fast_reason = "listed as stale in consensus";
}
if (retry_fast_reason && desc_clean_since < fast_cutoff)

View File

@ -117,6 +117,10 @@ void router_free_all(void);
/* Used only by router.c and test.c */
STATIC void get_platform_str(char *platform, size_t len);
STATIC int router_write_fingerprint(int hashed);
#ifdef TOR_UNIT_TESTS
extern time_t desc_clean_since;
extern const char *desc_dirty_reason;
#endif
#endif
#endif /* !defined(TOR_ROUTER_H) */

View File

@ -3,6 +3,7 @@
#define NS_PARSE_PRIVATE
#define NETWORKSTATUS_PRIVATE
#include "core/or/or.h"
#include "feature/dirauth/dirvote.h"
#include "feature/dirparse/ns_parse.h"
#include "feature/dirparse/unparseable.h"
#include "lib/memarea/memarea.h"
@ -35,9 +36,12 @@ fuzz_init(void)
dummy_vote = tor_malloc_zero(sizeof(*dummy_vote));
dummy_vote->known_flags = smartlist_new();
smartlist_split_string(dummy_vote->known_flags,
"Authority BadExit Exit Fast Guard HSDir "
"NoEdConsensus Running Stable V2Dir Valid",
DIRVOTE_UNIVERSAL_FLAGS,
" ", 0, 0);
smartlist_split_string(dummy_vote->known_flags,
DIRVOTE_OPTIONAL_FLAGS,
" ", 0, 0);
smartlist_sort_strings(dummy_vote->known_flags);
return 0;
}

View File

@ -177,6 +177,7 @@ src_test_test_SOURCES += \
src/test/test_util.c \
src/test/test_util_format.c \
src/test/test_util_process.c \
src/test/test_voting_flags.c \
src/test/test_voting_schedule.c \
src/test/test_x509.c \
src/test/test_helpers.c \

View File

@ -866,7 +866,8 @@ struct testgroup_t testgroups[] = {
{ "crypto/pem/", pem_tests },
{ "dir/", dir_tests },
{ "dir/md/", microdesc_tests },
{ "dir/voting-schedule/", voting_schedule_tests },
{ "dir/voting/flags/", voting_flags_tests },
{ "dir/voting/schedule/", voting_schedule_tests },
{ "dir_handle_get/", dir_handle_get_tests },
{ "dns/", dns_tests },
{ "dos/", dos_tests },

View File

@ -265,6 +265,7 @@ extern struct testcase_t tortls_tests[];
extern struct testcase_t util_format_tests[];
extern struct testcase_t util_process_tests[];
extern struct testcase_t util_tests[];
extern struct testcase_t voting_flags_tests[];
extern struct testcase_t voting_schedule_tests[];
extern struct testcase_t x509_tests[];

View File

@ -6052,6 +6052,80 @@ test_dir_find_dl_min_delay(void* data)
mock_options = NULL;
}
static void
test_dir_matching_flags(void *arg)
{
(void) arg;
routerstatus_t *rs_noflags = NULL;
routerstatus_t *rs = NULL;
char *s = NULL;
smartlist_t *tokens = smartlist_new();
memarea_t *area = memarea_new();
int expected_val_when_unused = 0;
const char *ex_noflags =
"r example hereiswhereyouridentitygoes 2015-08-30 12:00:00 "
"192.168.0.1 9001 0\n"
"m thisoneislongerbecauseitisa256bitmddigest33\n"
"s\n";
const char *cp = ex_noflags;
rs_noflags = routerstatus_parse_entry_from_string(
area, &cp,
cp + strlen(cp),
tokens, NULL, NULL,
MAX_SUPPORTED_CONSENSUS_METHOD, FLAV_MICRODESC);
tt_assert(rs_noflags);
#define FLAG(string, field) STMT_BEGIN { \
tor_asprintf(&s,\
"r example hereiswhereyouridentitygoes 2015-08-30 12:00:00 " \
"192.168.0.1 9001 0\n" \
"m thisoneislongerbecauseitisa256bitmddigest33\n" \
"s %s\n", string); \
cp = s; \
rs = routerstatus_parse_entry_from_string( \
area, &cp, \
cp + strlen(cp), \
tokens, NULL, NULL, \
MAX_SUPPORTED_CONSENSUS_METHOD, FLAV_MICRODESC); \
/* the field should usually be 0 when no flags are listed */ \
tt_int_op(rs_noflags->field, OP_EQ, expected_val_when_unused); \
/* the field should be 1 when this flags islisted */ \
tt_int_op(rs->field, OP_EQ, 1); \
tor_free(s); \
routerstatus_free(rs); \
} STMT_END
FLAG("Authority", is_authority);
FLAG("BadExit", is_bad_exit);
FLAG("Exit", is_exit);
FLAG("Fast", is_fast);
FLAG("Guard", is_possible_guard);
FLAG("HSDir", is_hs_dir);
FLAG("Stable", is_stable);
FLAG("StaleDesc", is_staledesc);
FLAG("V2Dir", is_v2_dir);
// These flags are assumed to be set whether they're declared or not.
expected_val_when_unused = 1;
FLAG("Running", is_flagged_running);
FLAG("Valid", is_valid);
expected_val_when_unused = 0;
// These flags are no longer used, but still parsed.
FLAG("Named", is_named);
FLAG("Unnamed", is_unnamed);
done:
tor_free(s);
routerstatus_free(rs);
routerstatus_free(rs_noflags);
memarea_drop_all(area);
smartlist_free(tokens);
}
static void
test_dir_assumed_flags(void *arg)
{
@ -6377,6 +6451,7 @@ struct testcase_t dir_tests[] = {
DIR_ARG(find_dl_min_delay, TT_FORK, "cfr"),
DIR_ARG(find_dl_min_delay, TT_FORK, "car"),
DIR(assumed_flags, 0),
DIR(matching_flags, 0),
DIR(networkstatus_compute_bw_weights_v10, 0),
DIR(platform_str, 0),
DIR(networkstatus_consensus_has_ipv6, TT_FORK),

View File

@ -7,12 +7,17 @@
* \brief Unittests for code in router.c
**/
#define ROUTER_PRIVATE
#include "core/or/or.h"
#include "app/config/config.h"
#include "core/mainloop/mainloop.h"
#include "feature/hibernate/hibernate.h"
#include "feature/nodelist/networkstatus.h"
#include "feature/nodelist/networkstatus_st.h"
#include "feature/nodelist/routerinfo_st.h"
#include "feature/nodelist/routerlist.h"
#include "feature/nodelist/routerstatus_st.h"
#include "feature/relay/router.h"
#include "feature/stats/rephist.h"
#include "lib/crypt_ops/crypto_curve25519.h"
@ -231,11 +236,104 @@ test_router_check_descriptor_bandwidth_changed(void *arg)
UNMOCK(we_are_hibernating);
}
static networkstatus_t *mock_ns = NULL;
static networkstatus_t *
mock_networkstatus_get_live_consensus(time_t now)
{
(void)now;
return mock_ns;
}
static routerstatus_t *mock_rs = NULL;
static const routerstatus_t *
mock_networkstatus_vote_find_entry(networkstatus_t *ns, const char *digest)
{
(void)ns;
(void)digest;
return mock_rs;
}
static void
test_router_mark_if_too_old(void *arg)
{
(void)arg;
time_t now = approx_time();
MOCK(networkstatus_get_live_consensus,
mock_networkstatus_get_live_consensus);
MOCK(networkstatus_vote_find_entry, mock_networkstatus_vote_find_entry);
routerstatus_t rs;
networkstatus_t ns;
memset(&rs, 0, sizeof(rs));
memset(&ns, 0, sizeof(ns));
mock_ns = &ns;
mock_ns->valid_after = now-3600;
mock_rs = &rs;
mock_rs->published_on = now - 10;
// no reason to mark this time.
desc_clean_since = now-10;
desc_dirty_reason = NULL;
mark_my_descriptor_dirty_if_too_old(now);
tt_i64_op(desc_clean_since, OP_EQ, now-10);
// Doesn't appear in consensus? Still don't mark it.
mock_ns = NULL;
mark_my_descriptor_dirty_if_too_old(now);
tt_i64_op(desc_clean_since, OP_EQ, now-10);
mock_ns = &ns;
// No new descriptor in a long time? Mark it.
desc_clean_since = now - 3600 * 96;
mark_my_descriptor_dirty_if_too_old(now);
tt_i64_op(desc_clean_since, OP_EQ, 0);
tt_str_op(desc_dirty_reason, OP_EQ, "time for new descriptor");
// Version in consensus published a long time ago? We won't mark it
// if it's been clean for only a short time.
desc_clean_since = now - 10;
desc_dirty_reason = NULL;
mock_rs->published_on = now - 3600 * 96;
mark_my_descriptor_dirty_if_too_old(now);
tt_i64_op(desc_clean_since, OP_EQ, now - 10);
// ... but if it's been clean a while, we mark.
desc_clean_since = now - 2 * 3600;
mark_my_descriptor_dirty_if_too_old(now);
tt_i64_op(desc_clean_since, OP_EQ, 0);
tt_str_op(desc_dirty_reason, OP_EQ,
"version listed in consensus is quite old");
// same deal if we're marked stale.
desc_clean_since = now - 2 * 3600;
desc_dirty_reason = NULL;
mock_rs->published_on = now - 10;
mock_rs->is_staledesc = 1;
mark_my_descriptor_dirty_if_too_old(now);
tt_i64_op(desc_clean_since, OP_EQ, 0);
tt_str_op(desc_dirty_reason, OP_EQ,
"listed as stale in consensus");
// same deal if we're absent from the consensus.
desc_clean_since = now - 2 * 3600;
desc_dirty_reason = NULL;
mock_rs = NULL;
mark_my_descriptor_dirty_if_too_old(now);
tt_i64_op(desc_clean_since, OP_EQ, 0);
tt_str_op(desc_dirty_reason, OP_EQ,
"not listed in consensus");
done:
UNMOCK(networkstatus_get_live_consensus);
UNMOCK(networkstatus_vote_find_entry);
}
#define ROUTER_TEST(name, flags) \
{ #name, test_router_ ## name, flags, NULL, NULL }
struct testcase_t router_tests[] = {
ROUTER_TEST(check_descriptor_bandwidth_changed, TT_FORK),
ROUTER_TEST(dump_router_to_string_no_bridge_distribution_method, TT_FORK),
ROUTER_TEST(mark_if_too_old, TT_FORK),
END_OF_TESTCASES
};

View File

@ -0,0 +1,192 @@
/* Copyright (c) 2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#include "orconfig.h"
#define VOTEFLAGS_PRIVATE
#include "core/or/or.h"
#include "feature/dirauth/voteflags.h"
#include "feature/nodelist/node_st.h"
#include "feature/nodelist/routerstatus_st.h"
#include "feature/nodelist/routerinfo_st.h"
#include "app/config/config.h"
#include "test/test.h"
typedef struct {
time_t now;
routerinfo_t ri;
node_t node;
routerstatus_t expected;
} flag_vote_test_cfg_t;
static void
setup_cfg(flag_vote_test_cfg_t *c)
{
memset(c, 0, sizeof(*c));
c->now = approx_time();
c->ri.nickname = (char *) "testing100";
strlcpy(c->expected.nickname, "testing100", sizeof(c->expected.nickname));
memset(c->ri.cache_info.identity_digest, 0xff, DIGEST_LEN);
memset(c->ri.cache_info.signed_descriptor_digest, 0xee, DIGEST256_LEN);
c->ri.cache_info.published_on = c->now - 100;
c->expected.published_on = c->now - 100;
c->ri.addr = 0x7f010105;
c->expected.addr = 0x7f010105;
c->ri.or_port = 9090;
c->expected.or_port = 9090;
tor_addr_make_null(&c->ri.ipv6_addr, AF_INET6);
tor_addr_make_null(&c->expected.ipv6_addr, AF_INET6);
// By default we have no loaded information about stability or speed,
// so we'll default to voting "yeah sure." on these two.
c->expected.is_fast = 1;
c->expected.is_stable = 1;
}
static bool
check_result(flag_vote_test_cfg_t *c)
{
bool result = false;
routerstatus_t rs;
memset(&rs, 0, sizeof(rs));
set_routerstatus_from_routerinfo(&rs, &c->node, &c->ri, c->now, 0);
tt_i64_op(rs.published_on, OP_EQ, c->expected.published_on);
tt_str_op(rs.nickname, OP_EQ, c->expected.nickname);
// identity_digest and descriptor_digest are not set here.
tt_uint_op(rs.addr, OP_EQ, c->expected.addr);
tt_uint_op(rs.or_port, OP_EQ, c->expected.or_port);
tt_uint_op(rs.dir_port, OP_EQ, c->expected.dir_port);
tt_assert(tor_addr_eq(&rs.ipv6_addr, &c->expected.ipv6_addr));
tt_uint_op(rs.ipv6_orport, OP_EQ, c->expected.ipv6_orport);
#define FLAG(flagname) \
tt_uint_op(rs.flagname, OP_EQ, c->expected.flagname)
FLAG(is_authority);
FLAG(is_exit);
FLAG(is_stable);
FLAG(is_fast);
FLAG(is_flagged_running);
FLAG(is_named);
FLAG(is_unnamed);
FLAG(is_valid);
FLAG(is_possible_guard);
FLAG(is_bad_exit);
FLAG(is_hs_dir);
FLAG(is_v2_dir);
FLAG(is_staledesc);
FLAG(has_bandwidth);
FLAG(has_exitsummary);
FLAG(bw_is_unmeasured);
result = true;
done:
return result;
}
static void
test_voting_flags_minimal(void *arg)
{
flag_vote_test_cfg_t *cfg = arg;
check_result(cfg);
}
static void
test_voting_flags_ipv6(void *arg)
{
flag_vote_test_cfg_t *cfg = arg;
tt_assert(tor_addr_parse(&cfg->ri.ipv6_addr, "f00::b42") == AF_INET6);
cfg->ri.ipv6_orport = 9091;
// no change in expected results, since we aren't set up with ipv6
// connectivity.
if (!check_result(cfg))
goto done;
get_options_mutable()->AuthDirHasIPv6Connectivity = 1;
// no change in expected results, since last_reachable6 won't be set.
if (!check_result(cfg))
goto done;
cfg->node.last_reachable6 = cfg->now - 10;
// now that lastreachable6 is set, we expect to see the result.
tt_assert(tor_addr_parse(&cfg->expected.ipv6_addr, "f00::b42") == AF_INET6);
cfg->expected.ipv6_orport = 9091;
if (!check_result(cfg))
goto done;
done:
;
}
static void
test_voting_flags_staledesc(void *arg)
{
flag_vote_test_cfg_t *cfg = arg;
time_t now = cfg->now;
cfg->ri.cache_info.published_on = now - DESC_IS_STALE_INTERVAL + 10;
cfg->expected.published_on = now - DESC_IS_STALE_INTERVAL + 10;
// no change in expectations for is_staledesc
if (!check_result(cfg))
goto done;
cfg->ri.cache_info.published_on = now - DESC_IS_STALE_INTERVAL - 10;
cfg->expected.published_on = now - DESC_IS_STALE_INTERVAL - 10;
cfg->expected.is_staledesc = 1;
if (!check_result(cfg))
goto done;
done:
;
}
static void *
setup_voting_flags_test(const struct testcase_t *testcase)
{
(void)testcase;
flag_vote_test_cfg_t *cfg = tor_malloc_zero(sizeof(*cfg));
setup_cfg(cfg);
return cfg;
}
static int
teardown_voting_flags_test(const struct testcase_t *testcase, void *arg)
{
(void)testcase;
flag_vote_test_cfg_t *cfg = arg;
tor_free(cfg);
return 1;
}
static const struct testcase_setup_t voting_flags_setup = {
.setup_fn = setup_voting_flags_test,
.cleanup_fn = teardown_voting_flags_test,
};
#define T(name,flags) \
{ #name, test_voting_flags_##name, (flags), &voting_flags_setup, NULL }
struct testcase_t voting_flags_tests[] = {
T(minimal, 0),
T(ipv6, TT_FORK),
// TODO: Add more of these tests.
T(staledesc, TT_FORK),
END_OF_TESTCASES
};